[Android] extract checkout protocol module#315
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
a9f6061 to
c0c0897
Compare
| compileSdk: 36, | ||
| minSdk: 23, | ||
| minCompileSdk: 35, | ||
| javaVersion: "11", |
There was a problem hiding this comment.
common versions for things that are not dependencies/plugins like kotlin & java
| androidApplicationGradlePlugin = "9.2.1" | ||
| androidLibraryGradlePlugin = "9.1.1" | ||
| apollo = "5.0.0" | ||
| apolloCache = "1.0.3" |
There was a problem hiding this comment.
centrally define dependency/plugin versions via a version catalog
we're using detekt, kotlin sdk, kotlinx serialization in both
c0c0897 to
598cd99
Compare
|
|
||
| private var listener = CheckoutWebViewListener(NoopCheckoutListener()) | ||
| private val embeddedCheckoutProtocol = EmbeddedCheckoutProtocol(this) | ||
| private val embeddedCheckoutProtocol = EmbeddedCheckoutProtocolBridge(this) |
There was a problem hiding this comment.
just differentiating between the protocol, and how we bridge to it via @JavascriptInterface
and avoiding a potential import alias or fqdn
8e9f76a to
ae4461f
Compare
| } | ||
| } | ||
|
|
||
| private fun CheckoutProtocol.Client.processForTest(message: String): String? { |
There was a problem hiding this comment.
This is quite ugly.. but it's test-only and it lets us keep process() internal and existing test coverage
| "Models.kt", | ||
| ); | ||
| await generateKotlin(specDir, target); | ||
| await run("node", [path.join(PROTOCOL_DIR, "scripts", "generate_kotlin_catalog.mjs")]); |
There was a problem hiding this comment.
auto gen the catalog
| ...commonSchemaSources(specDir), | ||
| "--package", | ||
| "com.shopify.checkoutkit", | ||
| "com.shopify.ucp.embedded.checkout", |
There was a problem hiding this comment.
thought was that this would work well with a future com.shopify.ucp.embedded.cart package
| val default = RecordingClient(response = DEFAULT_RESPONSE) | ||
| fun `always run after merchant runs both notification handlers`() { | ||
| var merchantHandled = false | ||
| var defaultHandled = false |
There was a problem hiding this comment.
We removed CheckoutCommunicationClient, which RecordingClient implemented and moved the tests over to testing the public .on() syntax
|
|
||
| @Serializable | ||
| private data class WindowOpenErrorDto( | ||
| private data class WindowOpenResultDto( |
There was a problem hiding this comment.
this change is private to kit
delegationDescriptor takes one response serializer and WindowOpenResultDto represents the common UCP response envelope for both success and rejected outcomes.
ae4461f to
8bb18b2
Compare
| @@ -10,6 +10,8 @@ import kotlinx.serialization.encoding.* | |||
| */ | |||
| @Serializable | |||
| public data class Checkout ( | |||
| public val attribution: Map<String, String>? = null, | |||
There was a problem hiding this comment.
these changes just came from running generate on kotlin
There was a problem hiding this comment.
This is expected - Westin updated the specs but hadn't merged new generations, I had the same for swift
Package Size
Measured from the PR base SHA and PR head SHA. This comment reports package artifact sizes only; it is not a final app binary-size report. |
c0f407f to
f420c55
Compare
| id 'org.jetbrains.kotlin.plugin.serialization' version '2.3.21' apply false | ||
| id 'io.gitlab.arturbosch.detekt' version '1.23.8' apply false | ||
| id 'org.jetbrains.kotlinx.binary-compatibility-validator' version '0.18.1' | ||
| alias(libs.plugins.android.library) apply false |
There was a problem hiding this comment.
use plugins defined in the version catalog
0dcfa34 to
d284c1c
Compare
|
|
||
| public val windowOpen: DelegationDescriptor<WindowOpenRequest, WindowOpenResult> = EmbeddedCheckoutProtocol.windowOpen.map( | ||
| decode = { request -> | ||
| request.url.toString().let(Uri::parse).let(::WindowOpenRequest) |
There was a problem hiding this comment.
windowOpen stays Kit-wrapped so Checkout Kit owns the Android-facing delegation shape and can convert protocol URI to Android Uri. Notification payloads such as Checkout and ErrorResponse are currently protocol generated types.
That allows us to convert to an android Uri and matches up with the current swift shape where Kit owns these types.
We may however want to just expose window.open protocol types via callbacks directly.. We can discuss separately
| if (descriptor !in supportedNotificationDescriptors) return this | ||
| val entry = NotificationHandler( | ||
| decode = { params -> descriptor.decode(params) }, | ||
| invoke = { payload -> |
There was a problem hiding this comment.
invoke -> invoking the on callback
| invoke = { payload -> (payload as? P)?.let { handler(it) } }, | ||
| if (descriptor !in supportedNotificationDescriptors) return this | ||
| val entry = NotificationHandler( | ||
| decode = { params -> descriptor.decode(params) }, |
There was a problem hiding this comment.
delegate decoding to the protocol descriptor
| ) | ||
|
|
||
| private fun encodeWindowOpenResult(result: WindowOpenResult): JsonObject = when (result) { | ||
| private fun encodeWindowOpenResult( |
There was a problem hiding this comment.
converting the kit type to a ECP type to respond.
Fully qualified domain names is a result of both protocol and kit having a WindowOpenResult
|
|
||
| /** Called by [EmbeddedCheckoutProtocol] for every delegated EC message. */ | ||
| override fun process(message: String): String? = | ||
| internal fun process(message: String): String? = |
There was a problem hiding this comment.
hide this low-level function, so on is the public API
| @@ -118,16 +117,6 @@ if (shopifySdkVersion == null || shopifySdkVersion.trim().isEmpty()) { | |||
| def shopifySdkArtifact = "com.shopify:checkout-kit:$shopifySdkVersion" | |||
|
|
|||
| repositories { | |||
There was a problem hiding this comment.
I was hitting issues here, so I've basically just kept this in the sample build.gradle
| protected Map<String, Object> getTypedExportedConstants() { | ||
| final Map<String, Object> constants = new HashMap<>(); | ||
| constants.put("version", ShopifyCheckoutKit.version); | ||
| constants.put("version", com.shopify.checkoutkit.BuildConfig.SDK_VERSION); |
There was a problem hiding this comment.
so we changed ShopifyCheckoutKit.version to ShopifyCheckoutKit.VERSIONto match constant conventions
But, the publshed version still has the old case, so CI (which builds against published) can fail.
This basically bypasses that and uses a BuildConfig field instead for resolving version
There was a problem hiding this comment.
I don't think I understand why CI would fail here
If the published version has .version then I'd expect that to work here
There was a problem hiding this comment.
Yeah, this is probably a bit circular now.. I think this was updated to ShopifyCheckoutKit.VERSION in one iteration, and CI failed.
And then this was to resolve it so that it doesn't matter either way. But for this PR we could actually undo this if preferred
| detektPlugins "io.gitlab.arturbosch.detekt:detekt-formatting:$detekt_formatting_version" | ||
| detektPlugins libs.detekt.formatting | ||
|
|
||
| api project(':embedded-checkout-protocol') |
There was a problem hiding this comment.
Importing protocol here, so it'll be a transitive depdendency of kit
d284c1c to
691cd9c
Compare
691cd9c to
aba9849
Compare
| - name: Check public Kotlin protocol API baseline | ||
| run: ../../protocol/languages/kotlin/gradlew -p ../../protocol/languages/kotlin :embedded-checkout-protocol:apiCheck --console=plain | ||
|
|
There was a problem hiding this comment.
Is this basically a version check or does it do more?
There was a problem hiding this comment.
This is a full BCV API check, so we get an api file to allow us to catch embedded-checkout-protocol API drift over time, like with the Checkout Kit one.
| public val start: NotificationDescriptor<Checkout> = EmbeddedCheckoutProtocol.start | ||
| public val complete: NotificationDescriptor<Checkout> = EmbeddedCheckoutProtocol.complete | ||
| public val messagesChange: NotificationDescriptor<Checkout> = EmbeddedCheckoutProtocol.messagesChange | ||
| public val lineItemsChange: NotificationDescriptor<Checkout> = EmbeddedCheckoutProtocol.lineItemsChange | ||
| public val totalsChange: NotificationDescriptor<Checkout> = EmbeddedCheckoutProtocol.totalsChange | ||
| public val error: NotificationDescriptor<ErrorResponse> = EmbeddedCheckoutProtocol.error |
There was a problem hiding this comment.
Reminder that we are missing ec.fulfillment.change across all 3 platforms
aba9849 to
a55876b
Compare
Assisted-By: devx/6488e3d0-f47f-4171-a1c4-d2432b2a653c
a55876b to
ac7d044
Compare

Goal
Split Android's low-level Embedded Checkout Protocol implementation out of Checkout Kit into an independently publishable Kotlin module.
This PR is architectural. It makes the protocol layer reusable and separable, while keeping Checkout Kit responsible for the curated Android app-developer API. It sits before the follow-up API-tightening branch that prevents consumers from hooking directly into lower-level communication processing.
Note
This is a breaking change while the Android SDK is still in alpha.
New Architecture
:embedded-checkout-protocolis now a plain Kotlin/JVMjava-librarymodule atprotocol/languages/kotlin/embedded-checkout-protocol.com.shopify:embedded-checkout-protocol.com.shopify.ucp.embedded.checkout, leaving a clear sibling path for future modules such ascom.shopify.ucp.embedded.cart.:libremains the Android Checkout Kit AAR, published ascom.shopify:checkout-kit.:libdepends on:embedded-checkout-protocolwithapi project(":embedded-checkout-protocol"), so normal Checkout Kit consumers still receive the protocol model/descriptor types transitively.EmbeddedCheckoutProtocol.Eventwire-name catalog, thin descriptors, JSON-RPC codec helpers, serializers, protocol tests, and its own API baseline.ec.ready/ack handling, default native behavior, supported event filtering, and the higher-levelCheckoutProtocolAPI that app developers use.This gives us a natural path to add separate deployables later:
Implementation Notes
platforms/androidand intoprotocol/languages/kotlin.:embedded-checkout-protocoland Maven artifactcom.shopify:embedded-checkout-protocolstable.com.shopify.ucp.embedded.checkout.EmbeddedCheckoutProtocol.Eventcatalog from OpenRPC methods, scoped toec.*; sibling capabilities such as cart can live in their own future modules.CheckoutProtocol.ktand WebView/default behavior inEmbeddedCheckoutProtocolBridge.kt.protocol/languages/kotlin.platforms/android/gradle/libs.versions.toml, with shared Java/Kotlin compatibility values inplatforms/android/gradle/android-library-versions.gradle; Android SDK levels stay local to the Android AAR build.embedded-checkout-protocolindependently fromcheckout-kit; React Native'snativeSdkVersions.androidremains the exact lowercase publishedcom.shopify:checkout-kitSemVer.checkoutKitAndroidfrom the version catalog.apiCheck.Most of the diff is module/file movement, generated model/API baseline movement, and Gradle wiring. Runtime behavior is intended to stay the same in this PR.
Stack Notes
This PR creates the protocol split and standalone artifact.
The next branch in the stack tightens the public Kit API by preventing consumers from hooking directly into lower-level communication processing and steering them toward typed callbacks, for example:
Validation
Latest validation after the package/module move:
One expected non-failing caveat: Android lint now sees the JVM protocol project as an external dependency from the Android build. That is intentional so the protocol artifact stays Android-free; protocol coverage comes from its tests, detekt, and API checks.