Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions android/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ your project:
### Gradle

```groovy
implementation "com.shopify:checkout-kit:3.6.0"
implementation "com.shopify:checkout-kit:1.0.0"
```

### Maven
Expand All @@ -65,7 +65,7 @@ implementation "com.shopify:checkout-kit:3.6.0"
<dependency>
<groupId>com.shopify</groupId>
<artifactId>checkout-kit</artifactId>
<version>3.6.0</version>
<version>1.0.0</version>
</dependency>
```

Expand Down
9 changes: 0 additions & 9 deletions android/dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,6 @@ commands:
desc: Regenerate the baseline after intentional public API changes
run: ./gradlew :lib:apiDump

apollo:
subcommands:
download_schema:
desc: "Download GraphQL Schema. Usage: dev apollo download_schema"
run: ./scripts/apollo_download_schema
codegen:
desc: "Generate Apollo Kotlin models. Usage: dev apollo codegen"
run: ./scripts/apollo_codegen

check:
license-headers: ./scripts/check_license_headers.rb
detekt: ./gradlew detekt
Expand Down
3,837 changes: 3,535 additions & 302 deletions android/lib/api/lib.api

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion android/lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def resolveEnvVarValue(name, defaultValue) {
return rawValue ? rawValue : defaultValue
}

def versionName = resolveEnvVarValue("CHECKOUT_KIT_VERSION", "3.6.0")
def versionName = resolveEnvVarValue("CHECKOUT_KIT_VERSION", "1.0.0")

ext {
app_compat_version = '1.7.1'
Expand Down Expand Up @@ -141,6 +141,11 @@ detekt {
autoCorrect = true
}

// Models.kt is generated from UCP JSON Schemas — exclude it from static analysis.
tasks.withType(io.gitlab.arturbosch.detekt.Detekt).configureEach {
exclude('**/Models.kt')
}


project.afterEvaluate {
publishing {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@ internal abstract class BaseWebView(context: Context, attributeSet: AttributeSet

abstract fun getEventProcessor(): CheckoutWebViewEventProcessor
abstract val recoverErrors: Boolean
abstract val variant: String
abstract val cspSchema: String

private fun configureWebView() {
visibility = VISIBLE
Expand Down Expand Up @@ -125,11 +123,11 @@ internal abstract class BaseWebView(context: Context, attributeSet: AttributeSet
private fun isOnConfirmationPage(): Boolean = url?.let(Uri::parse).isConfirmationPage()

internal fun userAgentSuffix(): String {
val theme = ShopifyCheckoutKit.configuration.colorScheme.id
val version = ShopifyCheckoutKit.version.split("-").first()
val platform = ShopifyCheckoutKit.configuration.platform
val platformSuffix = if (platform != null) " ${platform.displayName}" else ""
val suffix = "ShopifyCheckoutSDK/$version ($cspSchema;$theme;$variant)$platformSuffix"
val suffix = buildString {
append("ShopifyCheckoutKit/${BuildConfig.SDK_VERSION} android")
if (platform != null) append(" ${platform.displayName}")
}
log.d(LOG_TAG, "Setting User-Agent suffix $suffix")
return suffix
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
package com.shopify.checkoutkit

import android.webkit.JavascriptInterface
import android.webkit.WebView
import com.shopify.checkoutkit.CheckoutBridge.CheckoutWebOperation.COMPLETED
import com.shopify.checkoutkit.CheckoutBridge.CheckoutWebOperation.ERROR
import com.shopify.checkoutkit.CheckoutBridge.CheckoutWebOperation.MODAL
Expand All @@ -33,7 +32,6 @@ import com.shopify.checkoutkit.errorevents.CheckoutErrorDecoder
import com.shopify.checkoutkit.lifecycleevents.CheckoutCompletedEventDecoder
import com.shopify.checkoutkit.pixelevents.PixelEventDecoder
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

internal class CheckoutBridge(
Expand Down Expand Up @@ -66,11 +64,6 @@ internal class CheckoutBridge(
}
}

sealed class SDKOperation(val key: String) {
Copy link
Copy Markdown
Contributor

@kiftio kiftio May 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yeah, we'll need to figure out preloading r.e. presented.

Unless we don't support preload initially.

We did say Page Visibility API was likely the way to go if browser support is good enough

(this was for delaying client side checkout_started, and emission of web pixel events)

data object Presented : SDKOperation("presented")
class Instrumentation(val payload: InstrumentationPayload) : SDKOperation("instrumentation")
}

// Allows Web to postMessages back to the SDK
@Suppress("SwallowedException")
@JavascriptInterface
Expand Down Expand Up @@ -137,73 +130,11 @@ internal class CheckoutBridge(
}
}

// Send messages from SDK to Web
@Suppress("SwallowedException")
fun sendMessage(view: WebView, operation: SDKOperation) {
val script = when (operation) {
is SDKOperation.Presented -> {
log.d(LOG_TAG, "Sending presented message to checkout, informing it that the sheet is now visible.")
dispatchMessageTemplate("'${operation.key}'")
}

is SDKOperation.Instrumentation -> {
log.d(LOG_TAG, "Sending instrumentation message to checkout.")
val body = Json.encodeToString(SdkToWebEvent(operation.payload))
dispatchMessageTemplate("'${operation.key}', $body")
}
}
try {
view.evaluateJavascript(script, null)
} catch (e: Exception) {
log.d(LOG_TAG, "Failed to send message to checkout, invoking onCheckoutViewFailedWithError")
onMainThread {
eventProcessor.onCheckoutViewFailedWithError(
CheckoutKitException(
errorDescription = "Failed to send '${operation.key}' message to checkout, some features may not work.",
errorCode = CheckoutKitException.ERROR_SENDING_MESSAGE_TO_CHECKOUT,
isRecoverable = true,
)
)
}
}
}

companion object {
private const val LOG_TAG = "CheckoutBridge"
const val SCHEMA_VERSION_NUMBER: String = "8.1"

private fun dispatchMessageTemplate(body: String) = """|
|if (window.MobileCheckoutSdk && window.MobileCheckoutSdk.dispatchMessage) {
| window.MobileCheckoutSdk.dispatchMessage($body);
|} else {
| window.addEventListener('mobileCheckoutBridgeReady', function () {
| window.MobileCheckoutSdk.dispatchMessage($body);
| }, {passive: true, once: true});
|}
|
""".trimMargin()
}
}

@Serializable
internal data class SdkToWebEvent<T>(
val detail: T
)

@Serializable
internal data class InstrumentationPayload(
val name: String,
val value: Long,
val type: InstrumentationType,
val tags: Map<String, String>
)

@Suppress("EnumNaming", "EnumEntryNameCase")
@Serializable
internal enum class InstrumentationType {
histogram
}

@Serializable
internal data class WebToSdkEvent(
val name: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* MIT License
*
* Copyright 2023-present, Shopify Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.shopify.checkoutkit

import android.net.Uri

/**
* Implement this interface to handle Embedded Checkout Protocol (ECP) messages beyond
* the built-in methods handled natively by the SDK.
*
* Register an implementation via [ShopifyCheckoutKit.present].
*/
public interface CheckoutCommunicationClient {
/**
* Process a JSON-RPC 2.0 ECP message from the checkout web page.
*
* Called for all EC notifications (ec.start, ec.error, ec.complete, ec.*.change)
* and any unknown methods. For requests, return a JSON-RPC 2.0 response string;
* for notifications, return null (no response is sent).
*
* @param message JSON-RPC 2.0 encoded message string
* @return JSON-RPC 2.0 encoded response string, or null to send no response
*/
public fun process(message: String): String?

/**
* Called when checkout requests that a URL be opened externally (ec.window.open_request).
*
* @param url the URL checkout wants opened in an external browser or app
* @return true if the URL was handled and displayed externally, false otherwise
*/
public fun openExternalUrl(url: Uri): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ internal class CheckoutDialog(
private val checkoutUrl: String,
private val checkoutEventProcessor: CheckoutEventProcessor,
context: Context,
private val communicationClient: CheckoutCommunicationClient? = null,
) : ComponentDialog(context) {

internal var recoveryAttemptCount = 0
Expand Down Expand Up @@ -91,6 +92,8 @@ internal class CheckoutDialog(
checkoutWebView.onResume()
log.d(LOG_TAG, "Setting event processor on WebView.")
checkoutWebView.setEventProcessor(eventProcessor())
log.d(LOG_TAG, "Setting communication client on WebView.")
checkoutWebView.setClient(communicationClient)

val colorScheme = ShopifyCheckoutKit.configuration.colorScheme
log.d(LOG_TAG, "Configured colorScheme $colorScheme")
Expand Down Expand Up @@ -124,11 +127,6 @@ internal class CheckoutDialog(
removeWebViewFromContainer()
}

setOnShowListener {
log.d(LOG_TAG, "On show listener invoked, calling WebView notifyPresented.")
checkoutWebView.notifyPresented()
}

log.d(LOG_TAG, "Showing dialog.")
show()
}
Expand Down
Loading
Loading