Skip to content

Commit f939b70

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

35 files changed

Lines changed: 2937 additions & 2519 deletions

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

Lines changed: 2278 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: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package com.shopify.checkoutkit.protocol
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+
public const val READY_METHOD: String = Event.ready
23+
24+
public object Event {
25+
public const val ready: String = "ec.ready"
26+
public const val auth: String = "ec.auth"
27+
public const val error: String = "ec.error"
28+
public const val start: String = "ec.start"
29+
public const val complete: String = "ec.complete"
30+
public const val messagesChange: String = "ec.messages.change"
31+
public const val lineItemsChange: String = "ec.line_items.change"
32+
public const val buyerChange: String = "ec.buyer.change"
33+
public const val totalsChange: String = "ec.totals.change"
34+
public const val paymentChange: String = "ec.payment.change"
35+
public const val paymentInstrumentsChangeRequest: String = "ec.payment.instruments_change_request"
36+
public const val paymentCredentialRequest: String = "ec.payment.credential_request"
37+
public const val windowOpenRequest: String = "ec.window.open_request"
38+
public const val fulfillmentChange: String = "ec.fulfillment.change"
39+
public const val fulfillmentAddressChangeRequest: String = "ec.fulfillment.address_change_request"
40+
public const val cartReady: String = "ep.cart.ready"
41+
public const val cartAuth: String = "ep.cart.auth"
42+
public const val cartError: String = "ep.cart.error"
43+
public const val cartStart: String = "ep.cart.start"
44+
public const val cartComplete: String = "ep.cart.complete"
45+
public const val cartLineItemsChange: String = "ep.cart.line_items.change"
46+
public const val cartBuyerChange: String = "ep.cart.buyer.change"
47+
public const val cartMessagesChange: String = "ep.cart.messages.change"
48+
49+
public val all: Set<String> = setOf(
50+
ready,
51+
auth,
52+
error,
53+
start,
54+
complete,
55+
messagesChange,
56+
lineItemsChange,
57+
buyerChange,
58+
totalsChange,
59+
paymentChange,
60+
paymentInstrumentsChangeRequest,
61+
paymentCredentialRequest,
62+
windowOpenRequest,
63+
fulfillmentChange,
64+
fulfillmentAddressChangeRequest,
65+
cartReady,
66+
cartAuth,
67+
cartError,
68+
cartStart,
69+
cartComplete,
70+
cartLineItemsChange,
71+
cartBuyerChange,
72+
cartMessagesChange,
73+
)
74+
}
75+
}
76+
77+
internal val embeddedProtocolJson: Json = Json { ignoreUnknownKeys = true }
78+
79+
public fun decodeProtocolRequest(message: String): EcpRequest {
80+
val requestObject = embeddedProtocolJson.decodeFromString<JsonObject>(message)
81+
return embeddedProtocolJson.decodeFromJsonElement<EcpRequest>(requestObject).copy(id = requestObject["id"])
82+
}
83+
84+
public fun jsonRpcRequestId(id: JsonElement?): JsonElement? =
85+
when (id) {
86+
JsonNull -> JsonNull
87+
is JsonPrimitive -> id.takeIf {
88+
it.isString ||
89+
(!it.isString && JSON_RPC_INTEGER.matches(it.content) && it.content.toLongOrNull() != null)
90+
}
91+
else -> null
92+
}
93+
94+
private val JSON_RPC_INTEGER: Regex = Regex("-?(0|[1-9]\\d*)")
95+
96+
@Serializable
97+
public data class EcpRequest(
98+
public val jsonrpc: String = "2.0",
99+
public val method: String,
100+
public val id: JsonElement? = null,
101+
public val params: JsonElement? = null,
102+
)
103+
104+
@Serializable
105+
public data class ReadyParams(
106+
public val delegate: List<String> = emptyList(),
107+
)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.shopify.checkoutkit
1+
package com.shopify.checkoutkit.protocol
22

33
import kotlinx.serialization.KSerializer
44
import kotlinx.serialization.SerializationException
@@ -14,7 +14,7 @@ import kotlinx.serialization.json.JsonPrimitive
1414

1515
internal object ExtendsSerializer : KSerializer<Extends> {
1616
override val descriptor: SerialDescriptor =
17-
buildClassSerialDescriptor("com.shopify.checkoutkit.Extends")
17+
buildClassSerialDescriptor("com.shopify.checkoutkit.protocol.Extends")
1818

1919
override fun deserialize(decoder: Decoder): Extends {
2020
val input = decoder as? JsonDecoder

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.shopify.checkoutkit
1+
package com.shopify.checkoutkit.protocol
22

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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.shopify.checkoutkit
1+
package com.shopify.checkoutkit.protocol
22

33
import kotlinx.serialization.SerializationException
44
import kotlinx.serialization.json.Json
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
sdk=23
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Consumer-facing compatibility values shared by the published Android artifacts.
2+
// Keep these centralized so checkout-kit and checkout-protocol do not drift.
3+
ext.checkoutKitAndroidLibrary = [
4+
versionName: "4.0.0-alpha.1",
5+
compileSdk: 36,
6+
minSdk: 23,
7+
minCompileSdk: 35,
8+
javaVersion: "11",
9+
kotlinJvmTarget: "JVM_11",
10+
kotlinApiVersion: "KOTLIN_2_0",
11+
kotlinLanguageVersion: "KOTLIN_2_0",
12+
kotlinStdlibVersion: "2.0.21",
13+
kotlinSerializationVersion: "1.7.3",
14+
detektFormattingVersion: "1.23.8",
15+
]

0 commit comments

Comments
 (0)