Skip to content

Commit 8f67425

Browse files
committed
Split Android checkout protocol module
Assisted-By: devx/6488e3d0-f47f-4171-a1c4-d2432b2a653c
1 parent 176e7bf commit 8f67425

35 files changed

Lines changed: 2928 additions & 2513 deletions

platforms/android/checkout-protocol/api/checkout-protocol.api

Lines changed: 2281 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import io.gitlab.arturbosch.detekt.Detekt
2+
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
3+
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
4+
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
5+
6+
plugins {
7+
id 'signing'
8+
id 'maven-publish'
9+
id 'com.android.library'
10+
id 'org.jetbrains.kotlin.android'
11+
id 'org.jetbrains.kotlin.plugin.serialization'
12+
id 'io.gitlab.arturbosch.detekt'
13+
}
14+
15+
apply from: file('../gradle/android-library-versions.gradle')
16+
17+
def androidLibrary = checkoutKitAndroidLibrary
18+
def versionName = androidLibrary.versionName
19+
20+
ext {
21+
junit_version = '4.13.2'
22+
robolectric_version = '4.16.1'
23+
assertj_version = '3.27.7'
24+
}
25+
26+
android {
27+
namespace = 'com.shopify.checkoutkit.protocol'
28+
compileSdk = androidLibrary.compileSdk
29+
30+
defaultConfig {
31+
minSdk = androidLibrary.minSdk
32+
versionCode = 1
33+
versionName
34+
35+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
36+
37+
aarMetadata {
38+
minCompileSdk = androidLibrary.minCompileSdk
39+
}
40+
}
41+
42+
lint {
43+
checkDependencies = true
44+
warningsAsErrors = true
45+
warning 'LintBaseline'
46+
informational 'AndroidGradlePluginVersion'
47+
}
48+
49+
buildTypes {
50+
release {
51+
minifyEnabled false
52+
}
53+
}
54+
compileOptions {
55+
sourceCompatibility JavaVersion.toVersion(androidLibrary.javaVersion)
56+
targetCompatibility JavaVersion.toVersion(androidLibrary.javaVersion)
57+
}
58+
testOptions {
59+
unitTests.includeAndroidResources = true
60+
unitTests.all {
61+
testLogging {
62+
events "passed", "skipped", "failed", "standardOut", "standardError"
63+
outputs.upToDateWhen { false }
64+
showStandardStreams = true
65+
}
66+
}
67+
}
68+
publishing {
69+
singleVariant("release") {
70+
withSourcesJar()
71+
withJavadocJar()
72+
}
73+
}
74+
}
75+
76+
tasks.withType(KotlinJvmCompile).configureEach {
77+
compilerOptions {
78+
jvmTarget.set(JvmTarget.valueOf(androidLibrary.kotlinJvmTarget))
79+
apiVersion.set(KotlinVersion.valueOf(androidLibrary.kotlinApiVersion))
80+
languageVersion.set(KotlinVersion.valueOf(androidLibrary.kotlinLanguageVersion))
81+
if (!name.contains("Test")) {
82+
freeCompilerArgs.add("-Xexplicit-api=strict")
83+
}
84+
}
85+
}
86+
87+
dependencies {
88+
detektPlugins "io.gitlab.arturbosch.detekt:detekt-formatting:${androidLibrary.detektFormattingVersion}"
89+
90+
testImplementation "junit:junit:$junit_version"
91+
testImplementation "org.robolectric:robolectric:$robolectric_version"
92+
testImplementation "org.assertj:assertj-core:$assertj_version"
93+
94+
implementation "org.jetbrains.kotlin:kotlin-stdlib:${androidLibrary.kotlinStdlibVersion}"
95+
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:${androidLibrary.kotlinSerializationVersion}"
96+
}
97+
98+
signing {
99+
def signingKeyId = findProperty("signingKeyId")
100+
def signingKey = findProperty("signingKey")
101+
def signingPassword = findProperty("signingPassword")
102+
if (signingKey) {
103+
useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword)
104+
sign publishing.publications
105+
}
106+
}
107+
108+
detekt {
109+
buildUponDefaultConfig = true
110+
config.setFrom('../lib/detekt.config.yml')
111+
autoCorrect = true
112+
}
113+
114+
// Models.kt is generated from UCP JSON Schemas - exclude it from static analysis.
115+
tasks.withType(Detekt).configureEach {
116+
exclude('**/Models.kt')
117+
}
118+
119+
project.afterEvaluate {
120+
publishing {
121+
publications {
122+
release(MavenPublication) {
123+
pom {
124+
name = "CheckoutProtocol"
125+
description = "Shopify Checkout Kit's Embedded Checkout Protocol models and wire helpers."
126+
url = "https://github.com/Shopify/checkout-kit"
127+
groupId = "com.shopify"
128+
artifactId = "checkout-protocol"
129+
version = versionName
130+
131+
licenses {
132+
license {
133+
name = "MIT"
134+
url = "https://opensource.org/licenses/MIT"
135+
}
136+
}
137+
138+
developers {
139+
developer {
140+
name = "Shopify Inc."
141+
}
142+
}
143+
144+
scm {
145+
connection = "https://github.com/Shopify/checkout-kit.git"
146+
developerConnection = "https://github.com/Shopify/checkout-kit.git"
147+
url = "https://github.com/Shopify/checkout-kit.git"
148+
}
149+
}
150+
151+
afterEvaluate {
152+
from components.release
153+
}
154+
}
155+
}
156+
157+
repositories {
158+
maven {
159+
name = 'ossrh-staging-api'
160+
url = "https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/"
161+
credentials {
162+
username = System.getenv("OSSRH_USERNAME")
163+
password = System.getenv("OSSRH_PASSWORD")
164+
}
165+
}
166+
}
167+
}
168+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<manifest />
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.shopify.checkoutkit
2+
3+
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
4+
public annotation class CheckoutProtocolInternalApi
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package com.shopify.checkoutkit
2+
3+
import kotlinx.serialization.Serializable
4+
import kotlinx.serialization.decodeFromString
5+
import kotlinx.serialization.json.Json
6+
import kotlinx.serialization.json.JsonElement
7+
import kotlinx.serialization.json.JsonNull
8+
import kotlinx.serialization.json.JsonObject
9+
import kotlinx.serialization.json.JsonPrimitive
10+
import kotlinx.serialization.json.decodeFromJsonElement
11+
12+
/**
13+
* Low-level Embedded Checkout Protocol constants and raw method catalog.
14+
*
15+
* This module intentionally does not decide which protocol methods Checkout Kit
16+
* exposes to app developers. Host SDKs build their curated typed APIs on top of
17+
* these wire-level names and generated payload models.
18+
*/
19+
public object EmbeddedCheckoutProtocol {
20+
public const val SPEC_VERSION: String = "2026-04-08"
21+
22+
@CheckoutProtocolInternalApi
23+
public const val READY_METHOD: String = Event.ready
24+
25+
public object Event {
26+
public const val ready: String = "ec.ready"
27+
public const val auth: String = "ec.auth"
28+
public const val error: String = "ec.error"
29+
public const val start: String = "ec.start"
30+
public const val complete: String = "ec.complete"
31+
public const val messagesChange: String = "ec.messages.change"
32+
public const val lineItemsChange: String = "ec.line_items.change"
33+
public const val buyerChange: String = "ec.buyer.change"
34+
public const val totalsChange: String = "ec.totals.change"
35+
public const val paymentChange: String = "ec.payment.change"
36+
public const val paymentInstrumentsChangeRequest: String = "ec.payment.instruments_change_request"
37+
public const val paymentCredentialRequest: String = "ec.payment.credential_request"
38+
public const val windowOpenRequest: String = "ec.window.open_request"
39+
public const val fulfillmentChange: String = "ec.fulfillment.change"
40+
public const val fulfillmentAddressChangeRequest: String = "ec.fulfillment.address_change_request"
41+
public const val cartReady: String = "ep.cart.ready"
42+
public const val cartAuth: String = "ep.cart.auth"
43+
public const val cartError: String = "ep.cart.error"
44+
public const val cartStart: String = "ep.cart.start"
45+
public const val cartComplete: String = "ep.cart.complete"
46+
public const val cartLineItemsChange: String = "ep.cart.line_items.change"
47+
public const val cartBuyerChange: String = "ep.cart.buyer.change"
48+
public const val cartMessagesChange: String = "ep.cart.messages.change"
49+
50+
public val all: Set<String> = setOf(
51+
ready,
52+
auth,
53+
error,
54+
start,
55+
complete,
56+
messagesChange,
57+
lineItemsChange,
58+
buyerChange,
59+
totalsChange,
60+
paymentChange,
61+
paymentInstrumentsChangeRequest,
62+
paymentCredentialRequest,
63+
windowOpenRequest,
64+
fulfillmentChange,
65+
fulfillmentAddressChangeRequest,
66+
cartReady,
67+
cartAuth,
68+
cartError,
69+
cartStart,
70+
cartComplete,
71+
cartLineItemsChange,
72+
cartBuyerChange,
73+
cartMessagesChange,
74+
)
75+
}
76+
}
77+
78+
internal val embeddedProtocolJson: Json = Json { ignoreUnknownKeys = true }
79+
80+
@CheckoutProtocolInternalApi
81+
public fun decodeProtocolRequest(message: String): EcpRequest {
82+
val requestObject = embeddedProtocolJson.decodeFromString<JsonObject>(message)
83+
return embeddedProtocolJson.decodeFromJsonElement<EcpRequest>(requestObject).copy(id = requestObject["id"])
84+
}
85+
86+
@CheckoutProtocolInternalApi
87+
public fun jsonRpcRequestId(id: JsonElement?): JsonElement? =
88+
when (id) {
89+
JsonNull -> JsonNull
90+
is JsonPrimitive -> id.takeIf {
91+
it.isString ||
92+
(!it.isString && JSON_RPC_INTEGER.matches(it.content) && it.content.toLongOrNull() != null)
93+
}
94+
else -> null
95+
}
96+
97+
private val JSON_RPC_INTEGER: Regex = Regex("-?(0|[1-9]\\d*)")
98+
99+
@CheckoutProtocolInternalApi
100+
@Serializable
101+
public data class EcpRequest(
102+
public val jsonrpc: String = "2.0",
103+
public val method: String,
104+
public val id: JsonElement? = null,
105+
public val params: JsonElement? = null,
106+
)
107+
108+
@CheckoutProtocolInternalApi
109+
@Serializable
110+
public data class ReadyParams(
111+
public val delegate: List<String> = emptyList(),
112+
)

platforms/android/lib/src/main/java/com/shopify/checkoutkit/ExtendsSerializer.kt renamed to platforms/android/checkout-protocol/src/main/java/com/shopify/checkoutkit/ExtendsSerializer.kt

File renamed without changes.

platforms/android/lib/src/main/java/com/shopify/checkoutkit/Models.kt renamed to platforms/android/checkout-protocol/src/main/java/com/shopify/checkoutkit/Models.kt

File renamed without changes.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
@file:OptIn(CheckoutProtocolInternalApi::class)
2+
3+
package com.shopify.checkoutkit
4+
5+
import kotlinx.serialization.SerializationException
6+
import kotlinx.serialization.json.JsonNull
7+
import kotlinx.serialization.json.JsonPrimitive
8+
import org.assertj.core.api.Assertions.assertThat
9+
import org.assertj.core.api.Assertions.assertThatThrownBy
10+
import org.junit.Test
11+
12+
class EmbeddedCheckoutProtocolTest {
13+
@Test
14+
fun `SPEC_VERSION is non-empty`() {
15+
assertThat(EmbeddedCheckoutProtocol.SPEC_VERSION).isNotEmpty()
16+
}
17+
18+
@Test
19+
fun `event catalog exposes checkout and cart methods`() {
20+
assertThat(EmbeddedCheckoutProtocol.Event.all).contains(
21+
"ec.start",
22+
"ec.complete",
23+
"ec.window.open_request",
24+
"ec.buyer.change",
25+
"ec.payment.credential_request",
26+
"ep.cart.ready",
27+
"ep.cart.messages.change",
28+
)
29+
}
30+
31+
@Test
32+
fun `event catalog methods are unique`() {
33+
assertThat(EmbeddedCheckoutProtocol.Event.all).hasSameSizeAs(EmbeddedCheckoutProtocol.Event.all.toSet())
34+
}
35+
36+
@Test
37+
fun `decode protocol request preserves null and numeric ids`() {
38+
val nullId = decodeProtocolRequest("""{"jsonrpc":"2.0","method":"ec.ready","id":null,"params":{}}""")
39+
val numericId = decodeProtocolRequest("""{"jsonrpc":"2.0","method":"ec.ready","id":7,"params":{}}""")
40+
41+
assertThat(nullId.id).isEqualTo(JsonNull)
42+
assertThat(numericId.id).isEqualTo(JsonPrimitive(7))
43+
}
44+
45+
@Test
46+
fun `decode protocol request rejects malformed messages`() {
47+
assertThatThrownBy {
48+
decodeProtocolRequest("not json")
49+
}.isInstanceOf(SerializationException::class.java)
50+
}
51+
52+
@Test
53+
fun `json rpc request id accepts string integer and null ids`() {
54+
assertThat(jsonRpcRequestId(JsonPrimitive("id-1"))).isEqualTo(JsonPrimitive("id-1"))
55+
assertThat(jsonRpcRequestId(JsonPrimitive(7))).isEqualTo(JsonPrimitive(7))
56+
assertThat(jsonRpcRequestId(JsonNull)).isEqualTo(JsonNull)
57+
}
58+
59+
@Test
60+
fun `json rpc request id rejects unsupported id shapes`() {
61+
assertThat(jsonRpcRequestId(JsonPrimitive(1.5))).isNull()
62+
assertThat(jsonRpcRequestId(JsonPrimitive(true))).isNull()
63+
}
64+
}

platforms/android/lib/src/test/java/com/shopify/checkoutkit/ExtendsSerializerTest.kt renamed to platforms/android/checkout-protocol/src/test/java/com/shopify/checkoutkit/ExtendsSerializerTest.kt

File renamed without changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
sdk=23

0 commit comments

Comments
 (0)