Skip to content

Commit 5ccb8a9

Browse files
committed
Add ECP support to Android Checkout Kit
1 parent ed6b26f commit 5ccb8a9

69 files changed

Lines changed: 10377 additions & 640 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

android/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ your project:
5555
### Gradle
5656

5757
```groovy
58-
implementation "com.shopify:checkout-kit:3.6.0"
58+
implementation "com.shopify:checkout-kit:1.0.0"
5959
```
6060

6161
### Maven
@@ -65,7 +65,7 @@ implementation "com.shopify:checkout-kit:3.6.0"
6565
<dependency>
6666
<groupId>com.shopify</groupId>
6767
<artifactId>checkout-kit</artifactId>
68-
<version>3.6.0</version>
68+
<version>1.0.0</version>
6969
</dependency>
7070
```
7171

android/lib/api/lib.api

Lines changed: 3535 additions & 302 deletions
Large diffs are not rendered by default.

android/lib/build.gradle

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def resolveEnvVarValue(name, defaultValue) {
1616
return rawValue ? rawValue : defaultValue
1717
}
1818

19-
def versionName = resolveEnvVarValue("CHECKOUT_KIT_VERSION", "3.6.0")
19+
def versionName = resolveEnvVarValue("CHECKOUT_KIT_VERSION", "1.0.0")
2020

2121
ext {
2222
app_compat_version = '1.7.1'
@@ -141,6 +141,11 @@ detekt {
141141
autoCorrect = true
142142
}
143143

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

145150
project.afterEvaluate {
146151
publishing {

android/lib/src/main/java/com/shopify/checkoutkit/BaseWebView.kt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ internal abstract class BaseWebView(context: Context, attributeSet: AttributeSet
5757

5858
abstract fun getEventProcessor(): CheckoutWebViewEventProcessor
5959
abstract val recoverErrors: Boolean
60-
abstract val variant: String
61-
abstract val cspSchema: String
6260

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

127125
internal fun userAgentSuffix(): String {
128-
val theme = ShopifyCheckoutKit.configuration.colorScheme.id
129-
val version = ShopifyCheckoutKit.version.split("-").first()
130126
val platform = ShopifyCheckoutKit.configuration.platform
131-
val platformSuffix = if (platform != null) " ${platform.displayName}" else ""
132-
val suffix = "ShopifyCheckoutSDK/$version ($cspSchema;$theme;$variant)$platformSuffix"
127+
val suffix = buildString {
128+
append("ShopifyCheckoutKit/${BuildConfig.SDK_VERSION} android")
129+
if (platform != null) append(" ${platform.displayName}")
130+
}
133131
log.d(LOG_TAG, "Setting User-Agent suffix $suffix")
134132
return suffix
135133
}

android/lib/src/main/java/com/shopify/checkoutkit/CheckoutBridge.kt

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
package com.shopify.checkoutkit
2424

2525
import android.webkit.JavascriptInterface
26-
import android.webkit.WebView
2726
import com.shopify.checkoutkit.CheckoutBridge.CheckoutWebOperation.COMPLETED
2827
import com.shopify.checkoutkit.CheckoutBridge.CheckoutWebOperation.ERROR
2928
import com.shopify.checkoutkit.CheckoutBridge.CheckoutWebOperation.MODAL
@@ -33,7 +32,6 @@ import com.shopify.checkoutkit.errorevents.CheckoutErrorDecoder
3332
import com.shopify.checkoutkit.lifecycleevents.CheckoutCompletedEventDecoder
3433
import com.shopify.checkoutkit.pixelevents.PixelEventDecoder
3534
import kotlinx.serialization.Serializable
36-
import kotlinx.serialization.encodeToString
3735
import kotlinx.serialization.json.Json
3836

3937
internal class CheckoutBridge(
@@ -66,11 +64,6 @@ internal class CheckoutBridge(
6664
}
6765
}
6866

69-
sealed class SDKOperation(val key: String) {
70-
data object Presented : SDKOperation("presented")
71-
class Instrumentation(val payload: InstrumentationPayload) : SDKOperation("instrumentation")
72-
}
73-
7467
// Allows Web to postMessages back to the SDK
7568
@Suppress("SwallowedException")
7669
@JavascriptInterface
@@ -137,73 +130,11 @@ internal class CheckoutBridge(
137130
}
138131
}
139132

140-
// Send messages from SDK to Web
141-
@Suppress("SwallowedException")
142-
fun sendMessage(view: WebView, operation: SDKOperation) {
143-
val script = when (operation) {
144-
is SDKOperation.Presented -> {
145-
log.d(LOG_TAG, "Sending presented message to checkout, informing it that the sheet is now visible.")
146-
dispatchMessageTemplate("'${operation.key}'")
147-
}
148-
149-
is SDKOperation.Instrumentation -> {
150-
log.d(LOG_TAG, "Sending instrumentation message to checkout.")
151-
val body = Json.encodeToString(SdkToWebEvent(operation.payload))
152-
dispatchMessageTemplate("'${operation.key}', $body")
153-
}
154-
}
155-
try {
156-
view.evaluateJavascript(script, null)
157-
} catch (e: Exception) {
158-
log.d(LOG_TAG, "Failed to send message to checkout, invoking onCheckoutViewFailedWithError")
159-
onMainThread {
160-
eventProcessor.onCheckoutViewFailedWithError(
161-
CheckoutKitException(
162-
errorDescription = "Failed to send '${operation.key}' message to checkout, some features may not work.",
163-
errorCode = CheckoutKitException.ERROR_SENDING_MESSAGE_TO_CHECKOUT,
164-
isRecoverable = true,
165-
)
166-
)
167-
}
168-
}
169-
}
170-
171133
companion object {
172134
private const val LOG_TAG = "CheckoutBridge"
173-
const val SCHEMA_VERSION_NUMBER: String = "8.1"
174-
175-
private fun dispatchMessageTemplate(body: String) = """|
176-
|if (window.MobileCheckoutSdk && window.MobileCheckoutSdk.dispatchMessage) {
177-
| window.MobileCheckoutSdk.dispatchMessage($body);
178-
|} else {
179-
| window.addEventListener('mobileCheckoutBridgeReady', function () {
180-
| window.MobileCheckoutSdk.dispatchMessage($body);
181-
| }, {passive: true, once: true});
182-
|}
183-
|
184-
""".trimMargin()
185135
}
186136
}
187137

188-
@Serializable
189-
internal data class SdkToWebEvent<T>(
190-
val detail: T
191-
)
192-
193-
@Serializable
194-
internal data class InstrumentationPayload(
195-
val name: String,
196-
val value: Long,
197-
val type: InstrumentationType,
198-
val tags: Map<String, String>
199-
)
200-
201-
@Suppress("EnumNaming", "EnumEntryNameCase")
202-
@Serializable
203-
internal enum class InstrumentationType {
204-
histogram
205-
}
206-
207138
@Serializable
208139
internal data class WebToSdkEvent(
209140
val name: String,
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* MIT License
3+
*
4+
* Copyright 2023-present, Shopify Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22+
*/
23+
package com.shopify.checkoutkit
24+
25+
import android.net.Uri
26+
27+
/**
28+
* Implement this interface to handle Embedded Checkout Protocol (ECP) messages beyond
29+
* the built-in methods handled natively by the SDK.
30+
*
31+
* Register an implementation via [ShopifyCheckoutKit.present].
32+
*/
33+
public interface CheckoutCommunicationClient {
34+
/**
35+
* Process a JSON-RPC 2.0 ECP message from the checkout web page.
36+
*
37+
* Called for all EC notifications (ec.start, ec.error, ec.complete, ec.*.change)
38+
* and any unknown methods. For requests, return a JSON-RPC 2.0 response string;
39+
* for notifications, return null (no response is sent).
40+
*
41+
* @param message JSON-RPC 2.0 encoded message string
42+
* @return JSON-RPC 2.0 encoded response string, or null to send no response
43+
*/
44+
public fun process(message: String): String?
45+
46+
/**
47+
* Called when checkout requests that a URL be opened externally (ec.window.open_request).
48+
*
49+
* @param url the URL checkout wants opened in an external browser or app
50+
* @return true if the URL was handled and displayed externally, false otherwise
51+
*/
52+
public fun openExternalUrl(url: Uri): Boolean
53+
}

android/lib/src/main/java/com/shopify/checkoutkit/CheckoutDialog.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ internal class CheckoutDialog(
5555
private val checkoutUrl: String,
5656
private val checkoutEventProcessor: CheckoutEventProcessor,
5757
context: Context,
58+
private val communicationClient: CheckoutCommunicationClient? = null,
5859
) : ComponentDialog(context) {
5960

6061
internal var recoveryAttemptCount = 0
@@ -91,6 +92,8 @@ internal class CheckoutDialog(
9192
checkoutWebView.onResume()
9293
log.d(LOG_TAG, "Setting event processor on WebView.")
9394
checkoutWebView.setEventProcessor(eventProcessor())
95+
log.d(LOG_TAG, "Setting communication client on WebView.")
96+
checkoutWebView.setClient(communicationClient)
9497

9598
val colorScheme = ShopifyCheckoutKit.configuration.colorScheme
9699
log.d(LOG_TAG, "Configured colorScheme $colorScheme")
@@ -124,11 +127,6 @@ internal class CheckoutDialog(
124127
removeWebViewFromContainer()
125128
}
126129

127-
setOnShowListener {
128-
log.d(LOG_TAG, "On show listener invoked, calling WebView notifyPresented.")
129-
checkoutWebView.notifyPresented()
130-
}
131-
132130
log.d(LOG_TAG, "Showing dialog.")
133131
show()
134132
}

0 commit comments

Comments
 (0)