Skip to content

Add ECP support to Android Checkout Kit#21

Merged
tiagocandido merged 1 commit into
mainfrom
05-05-add_ecp_support_to_android_checkout_kit
May 6, 2026
Merged

Add ECP support to Android Checkout Kit#21
tiagocandido merged 1 commit into
mainfrom
05-05-add_ecp_support_to_android_checkout_kit

Conversation

@tiagocandido
Copy link
Copy Markdown
Contributor

@tiagocandido tiagocandido commented May 5, 2026

What changes are you making?

Adds Embedded Checkout Protocol (ECP) support to the Android library and removes the legacy MobileCheckoutSdk bridge.


Embedded Checkout Protocol

A new EmbeddedCheckoutProtocolConsumer JS interface is registered on every CheckoutWebView. The checkout page calls window.EmbeddedCheckoutProtocolConsumer.postMessage(jsonRpcString) to send JSON-RPC 2.0 messages to native; the SDK responds via window.EmbeddedCheckoutProtocol.postMessage(responseString).

Full handling per the UCP embedded spec:

Method Type Behaviour
ec.ready Request Native ACK: {"result":{"ucp":{"version":"…","status":"success"}}} then notifies typed client
ec.auth Request Explicit JSON-RPC error (-32601) — SDK does not support identity linking
ec.payment.instruments_change_request Request Explicit JSON-RPC error (-32601) — not yet supported
ec.payment.credential_request Request Explicit JSON-RPC error (-32601) — not yet supported
ec.fulfillment.address_change_request Request Explicit JSON-RPC error (-32601) — not yet supported
ec.window.open_request Request Calls CheckoutCommunicationClient.openExternalUrl(url) if registered; falls back to CheckoutEventProcessor.onCheckoutLinkClicked(url) (default browser/app routing) if not. Always responds with {"result":{"ucp":{"version":"…","status":"success"}}} for valid http/https URLs
ec.start Notification Shows progress bar natively; bubbles raw message to client.process()
ec.error Notification Delegated to client.process()
ec.complete Notification Delegated to client.process()
ec.*.change Notification Delegated to client.process()
ep.* methods Any Ignored (out of scope for checkout bridge)
Unknown methods Any Delegated to client.process(); non-null response forwarded back to checkout

Why explicit errors instead of no-op for unsupported requests? Sending -32601 Method not supported allows web-side promise chains to resolve immediately rather than hanging until timeout.

MobileCheckoutSdk bridge removed

The old native→JS send path (window.MobileCheckoutSdk.dispatchMessage(…)) has been removed entirely:

  • CheckoutBridge.sendMessage(), SDKOperation, and dispatchMessageTemplate() are gone.
  • SdkToWebEvent, InstrumentationPayload, and InstrumentationType are gone.
  • The presented handshake (sent after both page load and dialog show) is superseded by the ec.ready handshake.
  • The checkout_finished_loading instrumentation message is no longer sent.
  • CheckoutWebView.notifyPresented() removed; the setOnShowListener in CheckoutDialog no longer calls it.

The JS→native receive path (window.android.postMessage(…) handling completed, error, webPixels, checkoutBlockingEvent) is unchanged.

User-agent simplification

BaseWebView.userAgentSuffix() now emits just ShopifyCheckoutKit/<version> android [platform] instead of the previous ShopifyCheckoutSDK/<version> (<cspSchema>;<theme>;<variant>) [platform] string.

Typed ECP client (CheckoutProtocol)

A new CheckoutProtocol object provides a fluent, type-safe way to subscribe to EC notifications:

val client = CheckoutProtocol.Client()
    .on(CheckoutProtocol.start)    { checkout -> showProgress(checkout) }
    .on(CheckoutProtocol.complete) { checkout -> navigateToConfirmation(checkout) }
    .on(CheckoutProtocol.ready)    { payload  -> println(payload.delegations) }
    .onOpenExternalUrl             { uri      -> startActivity(Intent(ACTION_VIEW, uri)); true }

ShopifyCheckoutKit.present(url, activity, eventProcessor, client)

Client implements value semantics — each .on() call returns a new instance, making it safe to share a base configuration across multiple presents. Handlers are always invoked on the main thread.

Predefined descriptors: start, complete, messagesChange, lineItemsChange, buyerChange, paymentChange, ready.

UCP type generation

Models.kt is generated from vendored UCP JSON Schemas via the shared scripts/generate_models.sh script at the repo root:

dev codegen kotlin   # or: ./scripts/generate_models.sh --lang kotlin
# requires: pnpm install -g quicktype

The script accepts --lang kotlin or --lang swift, producing @Serializable data classes for Android or Codable structs for Swift respectively. It uses quicktype --framework kotlinx and applies sed post-processing to satisfy -Xexplicit-api=strict (quicktype Kotlin has no --access-level flag). Type schemas live in ucp/schemas/shopping/ at the repo root; result schemas for request/response pairs are extracted from ucp/services/shopping/embedded.openrpc.json via jq.

New public API

  • CheckoutCommunicationClient interface:
    • fun process(message: String): String? — receives all delegated EC messages; return a JSON-RPC response string or null.
    • fun openExternalUrl(url: Uri): Boolean — called for ec.window.open_request; return true if the URL was displayed externally.
  • ShopifyCheckoutKit.present(…, communicationClient: CheckoutCommunicationClient? = null) — optional fourth parameter, backward-compatible (@JvmOverloads).
  • CheckoutProtocol — typed client entry point (see above).
  • NotificationDescriptor<P> — descriptor type for binding typed handlers.
  • ReadyPayload — payload for CheckoutProtocol.ready, carries delegations: List<String>.
  • All UCP model types (Checkout, CheckoutLineItem, BuyerClass, etc.) generated into Models.kt.

Files changed

  • CheckoutBridge.kt — removed send-side; receive-side unchanged
  • CheckoutWebView.kt — removed presented/loadComplete dispatch, notifyPresented(), initLoadTime, instrumentation send; registers EmbeddedCheckoutProtocol
  • BaseWebView.kt — simplified user-agent suffix; removed abstract val variant and abstract val cspSchema
  • FallbackWebView.kt — removed variant and cspSchema overrides
  • CheckoutDialog.kt — removed setOnShowListener / notifyPresented() call; threads communicationClient
  • ShopifyCheckoutKit.kt — adds optional communicationClient to present()
  • CheckoutCommunicationClient.kt — new public interface
  • EmbeddedCheckoutProtocol.kt — new internal JS bridge handler + EcpRequest data class
  • CheckoutProtocol.kt — new typed client (Client, NotificationDescriptor, ReadyPayload)
  • Models.kt — generated UCP Kotlin types (do not edit directly; regenerate via dev codegen kotlin)
  • scripts/generate_models.sh — shared type generation script (repo root); supports --lang kotlin and --lang swift
  • ucp/ — vendored UCP JSON Schemas and OpenRPC service spec (repo root, shared across platforms)
  • lib/api/lib.api — updated baseline

How to test

  1. Run the test suite:

    cd android
    ./gradlew clean test --console=plain
  2. Verify the public API surface:

    ./gradlew :lib:apiCheck
  3. Build the sample app:

    cd android/samples/MobileBuyIntegration
    ./gradlew assembleDebug
  4. Integration smoke test — in a host app:

    • Confirm ec.ready is acknowledged and CheckoutProtocol.ready handler fires
    • Confirm ec.start shows the progress bar and fires CheckoutProtocol.start handler
    • Confirm ec.payment.instruments_change_request receives a -32601 error response (not a hang)
    • Confirm ec.window.open_request with no onOpenExternalUrl handler opens the URL via DefaultCheckoutEventProcessor (system browser) and sends the UCP success result
    • Confirm ec.window.open_request with a registered handler calls it and sends the UCP success result on true, falls back to event processor on false
    • Confirm an unknown method reaches client.process() with the raw JSON-RPC string
  5. Java interop — the three-argument overload without communicationClient must still compile from Java:

    ShopifyCheckoutKit.present(url, activity, eventProcessor);

Before you merge

Important

  • I've added tests to support my implementation
  • I have read and agree with the Contribution Guidelines
  • I have read and agree with the Code of Conduct
  • I've updated the relevant platform README (android/README.md)

Releasing a new Android version?
  • I have bumped the versionName in android/lib/build.gradle
  • I have updated android/CHANGELOG.md
  • I have updated the Gradle/Maven version snippets in android/README.md

Tip

See the Contributing documentation for the full release process per platform.

Copy link
Copy Markdown
Contributor Author

tiagocandido commented May 5, 2026

@tiagocandido tiagocandido force-pushed the 05-05-add_ecp_support_to_android_checkout_kit branch 3 times, most recently from d62d273 to ffd83a8 Compare May 5, 2026 19:02
Comment thread android/lib/src/main/java/com/shopify/checkoutkit/BaseWebView.kt Outdated
}
}

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)


private fun handleReady(request: EcpRequest) {
log.d(LOG_TAG, "Handling $METHOD_READY, sending ACK.")
sendResult(request.id, "{}")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

shall we make the empty thing a constant so we can name it to try for a bit of documentation

@tiagocandido tiagocandido force-pushed the 05-05-add_ecp_support_to_android_checkout_kit branch from ffd83a8 to dee7e16 Compare May 5, 2026 21:03
@tiagocandido tiagocandido force-pushed the 05-05-rename_occurrences_of_csk_to_checkout_kit branch from 04ed7d5 to b07b867 Compare May 5, 2026 21:06
@tiagocandido tiagocandido force-pushed the 05-05-add_ecp_support_to_android_checkout_kit branch 4 times, most recently from d9b961e to e8a916e Compare May 6, 2026 15:35
@tiagocandido tiagocandido force-pushed the 05-05-rename_occurrences_of_csk_to_checkout_kit branch from b07b867 to 745fbbd Compare May 6, 2026 15:35
@tiagocandido tiagocandido force-pushed the 05-05-add_ecp_support_to_android_checkout_kit branch from e8a916e to 9bd2cbe Compare May 6, 2026 15:46
@tiagocandido tiagocandido force-pushed the 05-05-rename_occurrences_of_csk_to_checkout_kit branch 2 times, most recently from f7708b8 to ed6b26f Compare May 6, 2026 17:30
@tiagocandido tiagocandido force-pushed the 05-05-add_ecp_support_to_android_checkout_kit branch 2 times, most recently from 5ccb8a9 to 2ccc8fb Compare May 6, 2026 17:49
Base automatically changed from 05-05-rename_occurrences_of_csk_to_checkout_kit to main May 6, 2026 17:53
@tiagocandido tiagocandido force-pushed the 05-05-add_ecp_support_to_android_checkout_kit branch 2 times, most recently from d9e4494 to 69fb61c Compare May 6, 2026 18:40
@markmur
Copy link
Copy Markdown
Contributor

markmur commented May 6, 2026

🚀

@tiagocandido tiagocandido force-pushed the 05-05-add_ecp_support_to_android_checkout_kit branch from 69fb61c to 8f1a93f Compare May 6, 2026 18:52
@tiagocandido tiagocandido marked this pull request as ready for review May 6, 2026 18:52
@tiagocandido tiagocandido force-pushed the 05-05-add_ecp_support_to_android_checkout_kit branch from 8f1a93f to 56f8d35 Compare May 6, 2026 18:57
Copy link
Copy Markdown
Contributor

@kieran-osgood-shopify kieran-osgood-shopify left a comment

Choose a reason for hiding this comment

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

I think we are removing the apollo codegen by mistake for the swift dev.yml

Copy link
Copy Markdown
Contributor

@kieran-osgood-shopify kieran-osgood-shopify left a comment

Choose a reason for hiding this comment

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

Ignore previous review, didn't see the new dev.yml

Comment thread dev.yml
name: checkout-kit

commands:
codegen:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just a thought we had, it'd be nice if the default behaviour was to just generate for all platforms (dev codegen)building kotlin swift and typescript (just types, no schemas)

maybe as we have apollo codegen, it would be dev protocol codegen / dev protocol generate so its namespaced to the package area?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

can we do it as a follow up since I don't have the swift code in this branch?

@tiagocandido tiagocandido merged commit 6c6147c into main May 6, 2026
16 checks passed
@tiagocandido tiagocandido deleted the 05-05-add_ecp_support_to_android_checkout_kit branch May 6, 2026 20:29
@markmur markmur added the #gsd:50662 Rebase Checkout Kit on UCP label May 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

#gsd:50662 Rebase Checkout Kit on UCP

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants