Releases: Shopify/checkout-kit
Release list
[Swift] 4.0.0-alpha.6
Breaking changes
None.
Additive changes
Telemetry opt-out configuration
Checkout Kit now reports limited, anonymous diagnostic metrics — checkout errors, protocol decoding failures, navigation retries, and checkout navigation timing. A new telemetry configuration controls it:
ShopifyCheckoutKit.configure {
$0.telemetry.enabled = false
}Telemetry is enabled by default. Disabling it at runtime stops new collection and discards measurements that have not already been handed to the operating system for delivery. Diagnostics never include checkout URLs, message payloads, buyer data, or checkout, order, customer, or shop identifiers.
Behaviour changes
Anonymous diagnostic metrics are reported by default
Unless telemetry is disabled via the configuration above, the SDK exports bounded, anonymous diagnostic metrics to Shopify on a periodic interval. See the telemetry section of the README for exactly what is and is not collected.
Repeated preloads refresh the cached entry
Calling preload(checkout:) again for the same checkout now refreshes the cached web view instead of being ignored, without disrupting a checkout that is currently presented. A checkout being presented from the cache is preserved during the refresh.
[Android] 4.0.0-alpha.6
Breaking changes
None.
Additive changes
Telemetry opt-out configuration
Checkout Kit now reports limited, anonymous diagnostic metrics — checkout errors, protocol decoding failures, navigation retries, and checkout navigation timing. A new telemetry configuration controls it:
ShopifyCheckoutKit.configure {
it.telemetry = Telemetry(enabled = false)
}Telemetry is enabled by default. Disabling it at runtime stops new collection and discards measurements that have not already been handed to the operating system for delivery. Diagnostics never include checkout URLs, message payloads, buyer data, or checkout, order, customer, or shop identifiers.
Behaviour changes
Anonymous diagnostic metrics are reported by default
Unless telemetry is disabled via the configuration above, the SDK exports bounded, anonymous diagnostic metrics to Shopify on a periodic interval. See the telemetry section of the README for exactly what is and is not collected.
Drag handle is disabled while the sheet opens
The bottom sheet's drag handle no longer responds to touch while the initial open animation is in progress, so a buyer tapping the checkout button cannot accidentally grab the sheet and leave it at an unintended height.
[React Native] 4.0.0-alpha.4
Breaking changes
Single CheckoutException error class
onFail now always receives a CheckoutException instance. The error subclasses (CheckoutClientError, CheckoutExpiredError, CheckoutHTTPError, ConfigurationError, GenericError, InternalError) and the CheckoutNativeErrorType enum have been removed, and CheckoutException is a class rather than a union of those types. Branch on code instead of the error's class:
onFail: error => {
- if (error instanceof CheckoutExpiredError) {
+ if (error.code === CheckoutErrorCode.cartExpired) {
refreshCart();
}
},Every exception exposes code, message, name, and an optional statusCode (present only when an HTTP response caused the failure). The raw native fail payload type is now exported as CheckoutNativeError.
CheckoutErrorCode values reshaped
The codes now match the flattened error reporting of the native SDKs. clientError, sendingBridgeEventError, receivingBridgeEventError, and renderProcessGone have been removed; customerAccountRequired, networkError, sdkError, webViewNotSupported, and webContentProcessTerminated have been added. Update switches over the code:
switch (error.code) {
- case CheckoutErrorCode.renderProcessGone:
+ case CheckoutErrorCode.webContentProcessTerminated:
recreateCheckout();
break;
}ColorScheme.web renamed to ColorScheme.storefront
The wire value changes from web_default to storefront to match the native SDKs:
configuration: {
- colorScheme: ColorScheme.web,
+ colorScheme: ColorScheme.storefront,
}Additive changes
Incoming message origin validation
allowedMessageOrigins restricts which web origins the checkout WebView accepts incoming messages from:
configuration: {
+ allowedMessageOrigins: [
+ 'https://checkout.example.com',
+ 'https://*.example.com',
+ ],
}The surface stays open by default: when the list is empty, messages from any origin are accepted. The loaded checkout origin and shop.app (including its subdomains) are always trusted. Entries may be exact origins, wildcard subdomains (https://*.example.com), or '*' to explicitly disable validation. Rejected messages are never silently dropped — the native SDK logs each rejection as a warning with the origin and reason.
LogLevel.warn and LogLevel.none
LogLevel now covers the native SDKs' four ordered thresholds: debug, warn, error, and none. Omit logLevel to keep the native SDK default.
Behaviour changes
Native SDKs updated to 4.0.0-alpha.5
The wrapper now pins ShopifyCheckoutKit (iOS) and com.shopify:checkout-kit (Android) at 4.0.0-alpha.5, up from 4.0.0-alpha.2. Behaviour from the native alpha.3 through alpha.5 releases applies transitively — most visibly, links checkout opens via window.open now stay in an in-app browser surface (SFSafariViewController on iOS, Custom Tabs on Android) instead of leaving the app. See the Swift 4.0.0-alpha.5 and Android 4.0.0-alpha.5 release notes and their predecessors for the full details.
title applies on Android and updates at runtime
configuration.title previously changed the checkout sheet title only on iOS. It now sets the title on both platforms and takes effect at runtime when the provider configuration changes. When omitted, each platform uses its default localized title.
[Swift] 4.0.0-alpha.5
Breaking changes
Preload failure state includes a diagnostic message
PreloadState.failed now carries a best-effort diagnostic message alongside the failure reason. Pattern matches over the preload state must bind or ignore the new associated value:
preload?.onStateChange = { state in
- if case .failed(let reason) = state {
+ if case .failed(let reason, let message) = state {
discardPreload(reason)
}
}The message is diagnostic context only. It is not a stable, machine-readable value; use FailureReason to determine how to handle the failure.
Preload failure reasons consolidated
PreloadState.FailureReason replaces keepAliveLost and webContentProcessTerminated with a single webContentUnavailable case. Exhaustive switches over preload failures must be updated:
switch reason {
case .httpError(let statusCode):
recordHTTPFailure(statusCode)
case .navigationFailed:
recordNavigationFailure()
-case .keepAliveLost, .webContentProcessTerminated, .protocolError:
+case .webContentUnavailable, .protocolError:
discardPreload()
}Renderer termination during a background preload now reports .failed(reason: .webContentUnavailable, ...) instead of keepAliveLost. This applies only to the preload state; CheckoutErrorCode.webContentProcessTerminated for a presented checkout is unchanged.
Removed onMessageRejected from configuration
Configuration.onMessageRejected and the MessageRejection type have been removed:
ShopifyCheckoutKit.configure {
$0.allowedMessageOrigins = [
"https://checkout.example.com",
"https://*.example.com"
]
- $0.onMessageRejected = { rejection in
- logger.warning(
- "Rejected message from \(rejection.origin): \(rejection.reason)"
- )
- }
}Messages dropped by origin validation are never silently discarded: the SDK logs each rejection as a warning with the trusted origin and reason. The untrusted message body is not logged. allowedMessageOrigins is unchanged.
Additive changes
Stable Apple Pay button selector
The ShopifyAcceleratedCheckouts Apple Pay button now exposes a stable apple-pay-button accessibility identifier, giving UI automation a reliable selector that does not depend on localized button text.
Behaviour changes
window.open links stay in an in-app browser
Links checkout opens via window.open now present web URLs in an in-app SFSafariViewController sheet instead of leaving the app through UIApplication.shared.open. Non-web URLs (mailto:, tel:, app deep links) still open externally. Consumers can override this by handling windowOpen in their own protocol client.
Origin-validation drops are logged as warnings
Rejected checkout messages were previously logged at debug level, invisible at the default logLevel of .warn. They are now logged as warnings, so drops are visible without opting into debug logging. Child-frame messages are ignored at debug level as ambient noise.
[Android] 4.0.0-alpha.5
Breaking changes
Preload failure state includes a diagnostic message
PreloadState.Failed now carries a best-effort diagnostic message alongside the failure reason. This changes its generated constructor, component functions, and copy method. Code that constructs or destructures Failed positionally must account for the new property:
preload?.listener = PreloadStateListener { state ->
if (state is PreloadState.Failed) {
- discardPreload(state.reason)
+ discardPreload(state.reason, state.message)
}
}The message is diagnostic context only. It is not a stable, machine-readable value; use FailureReason to determine how to handle the failure.
Preload failure reasons consolidated
PreloadState.FailureReason replaces WebContentProcessTerminated with WebContentUnavailable. Exhaustive when expressions must be updated:
when (val reason = state.reason) {
is PreloadState.FailureReason.HttpError ->
recordHttpFailure(reason.statusCode)
PreloadState.FailureReason.NavigationFailed ->
recordNavigationFailure()
- PreloadState.FailureReason.WebContentProcessTerminated ->
+ PreloadState.FailureReason.WebContentUnavailable ->
discardPreload()
PreloadState.FailureReason.ProtocolError ->
discardPreload()
}WebContentUnavailable indicates that cached web content became unavailable before the preload could be reused, including renderer termination.
Removed onMessageRejected from configuration
Configuration.onMessageRejected and the RejectedMessage type have been removed:
ShopifyCheckoutKit.configure {
it.allowedMessageOrigins = setOf(
"https://checkout.example.com",
"https://*.example.com",
)
- it.onMessageRejected = { rejection ->
- reportRejectedOrigin(rejection.origin, rejection.reason)
- }
}Messages dropped by origin validation are never silently discarded: the SDK logs each rejection as a warning with the verified origin and reason. The untrusted message body is not logged. allowedMessageOrigins is unchanged.
Additive changes
None.
Behaviour changes
External web links open in Custom Tabs
Links that leave the checkout WebView — including window.open requests — now open web URLs in Android Custom Tabs, so buyers stay in an in-app browser surface by default. Contact links (mailto:, tel:) and custom-scheme deep links still launch an external ACTION_VIEW intent, and web links fall back to the external browser when no Custom Tabs-capable browser is installed. Consumers can override window.open handling in their own protocol client.
Preload expiry fires proactively
A cached checkout WebView is now evicted automatically when its five-minute TTL elapses, transitioning the preload to Expired at that moment instead of only when the cache is next checked. Expiry is measured with elapsedRealtime(), so wall-clock adjustments do not affect it, and the TTL is reconciled when the app returns to the foreground after device sleep.
Displaced preload handles retain their last state
Calling preload again, or presenting a checkout that consumes the cached preload, stops the earlier CheckoutPreload handle from receiving updates. The handle now retains its last observed state instead of reflecting the shared cache, so it remains meaningful to inspect after displacement.
Origin-validation drops are logged as warnings
Rejected checkout messages were previously logged at debug level, invisible at the default LogLevel.WARN. They are now logged as warnings, so drops are visible without opting into debug logging.
[Swift] 4.0.0-alpha.4
Breaking changes
New checkout failure case
Checkout now reports WebKit content-process termination as a terminal failure through checkoutDidFail(error:) or .onFail.
CheckoutErrorCode includes a new case. Exhaustive switches must handle it:
switch error.code {
case .networkError:
showRetry()
+case .webContentProcessTerminated:
+ showRetry()
case .sdkError:
showBrowserFallback()
// Other cases...
}Checkout Kit does not automatically reload after the process terminates. The buyer must explicitly retry or reopen checkout.
New preload failure case
PreloadState.FailureReason also includes a new case. Exhaustive switches over preload failures must be updated:
switch reason {
case .httpError(let statusCode):
recordHTTPFailure(statusCode)
case .navigationFailed:
recordNavigationFailure()
-case .keepAliveLost, .protocolError:
+case .keepAliveLost, .webContentProcessTerminated, .protocolError:
discardPreload()
}A terminated background WebView now transitions the preload to:
.failed(reason: .webContentProcessTerminated)This failure applies only to the preload. A subsequent checkout presentation can still load normally.
Additive changes
Incoming message origin validation
Checkout messages can now be restricted to explicitly trusted origins using allowedMessageOrigins:
ShopifyCheckoutKit.configure {
+ $0.allowedMessageOrigins = [
+ "https://checkout.example.com",
+ "https://*.example.com"
+ ]
}The loaded checkout origin and shop.app are always trusted. An empty list preserves the previous behavior and accepts messages from all origins.
Rejected messages can be observed with onMessageRejected:
ShopifyCheckoutKit.configure {
+ $0.onMessageRejected = { rejection in
+ logger.warning(
+ "Rejected message from \(rejection.origin): \(rejection.reason)"
+ )
+ }
}The MessageRejection payload is untrusted and should only be used for diagnostics.
Enumerate checkout error codes
CheckoutErrorCode now conforms to CaseIterable:
-public enum CheckoutErrorCode: String, Codable, Sendable {
+public enum CheckoutErrorCode: String, Codable, CaseIterable, Sendable {This allows applications and tests to enumerate every known error code:
for code in CheckoutErrorCode.allCases {
registerAnalyticsValue(code.rawValue)
}Behavior changes
WebKit content-process termination no longer fails silently
When WebKit terminates an active checkout's content process, Checkout Kit now reports .webContentProcessTerminated.
For a background preload, Checkout Kit evicts the cached WebView and reports:
.failed(reason: .webContentProcessTerminated)Checkout header layout changed
The checkout navigation bar now uses a transparent background, and checkout content extends behind the navigation bar:
-checkoutView.scrollView.contentInsetAdjustmentBehavior = .never
+checkoutView.scrollView.contentInsetAdjustmentBehavior = .automatic
-checkoutView.topAnchor.constraint(
- equalTo: view.safeAreaLayoutGuide.topAnchor
-)
+checkoutView.topAnchor.constraint(
+ equalTo: view.topAnchor
+)Apps that visually test or customize checkout presentation should verify their header, title, close button, and content positioning after upgrading.
What's Changed
- add multipass deprecation warning by @kiftio in #564
- Document checkout lifecycle errors by @kiftio in #538
- Handle WebView process termination by @kiftio in #539
- feat: incoming message origin validation for ios by @michaeljsXu in #476
- ci: cache Mint packages in the Swift test workflow by @kieran-osgood-shopify in #586
- [Swift]: conform CheckoutErrorCode to CaseIterable by @kieran-osgood-shopify in #571
- ci: extract Mint provisioning into a setup-mint composite action by @kieran-osgood-shopify in #591
- Readme discrepancies by @markmur in #526
- Add sample app test seams and run them in CI by @kieran-osgood-shopify in #553
- [Swift] Drop header background, anchor webview to top by @markmur in #615
- feat: setup nightly for testflight swift by @kieran-osgood-shopify in #605
- Release Swift 4.0.0-alpha.4 by @tiagocandido in #622
Full Changelog: 4.0.0-alpha.3...4.0.0-alpha.4
[Android] 4.0.0-alpha.4
Breaking changes
New preload failure cases
PreloadState.FailureReason includes two new cases. Exhaustive when expressions must handle them:
when (val reason = state.reason) {
is PreloadState.FailureReason.HttpError ->
recordHttpFailure(reason.statusCode)
PreloadState.FailureReason.NavigationFailed ->
recordNavigationFailure()
+ PreloadState.FailureReason.WebContentProcessTerminated ->
+ discardPreload()
+ PreloadState.FailureReason.ProtocolError ->
+ discardPreload()
}WebContentProcessTerminatedindicates that Android terminated or crashed the preloaded WebView renderer.ProtocolErrorindicates that checkout sent a terminal protocol error while preloading.
These failures apply only to the preload. A subsequent checkout presentation can still load normally.
Colors constructor signature changed
Colors now includes headerBorderColor. This changes its generated constructor, component functions, and copy method.
Code that constructs Colors positionally must supply the new argument:
val colors = Colors(
webViewBackground,
headerBackground,
headerFont,
progressIndicator,
closeIcon,
closeIconTint,
dragHandleColor,
+ headerBorderColor,
)Prefer named arguments or the customization builder to reduce migration work when alpha color options change:
-val colors = Colors(
- background,
- header,
- headerText,
- progress,
- null,
- null,
- handle,
- border,
-)
+val appearance = CheckoutAppearance.Storefront().customize {
+ webViewBackground = background
+ headerBackground = header
+ headerFont = headerText
+ progressIndicator = progress
+ dragHandleColor = handle
+ headerBorderColor = border
+}Additive changes
Incoming message origin validation
Checkout messages can now be restricted to explicitly trusted origins:
ShopifyCheckoutKit.configure {
+ it.allowedMessageOrigins = setOf(
+ "https://checkout.example.com",
+ "https://*.example.com",
+ )
}The checkout URL origin and shop.app are always trusted. An empty set preserves the previous behavior and accepts messages from every origin.
Rejected messages can be observed with onMessageRejected:
ShopifyCheckoutKit.configure {
+ it.onMessageRejected = { rejection ->
+ reportRejectedOrigin(
+ rejection.origin,
+ rejection.reason,
+ )
+ }
}The RejectedMessage payload is untrusted and should only be used for diagnostics.
Customize the checkout header border
The new headerBorderColor option controls the border displayed when checkout content scrolls beneath the native header:
ShopifyCheckoutKit.configure {
it.appearance = CheckoutAppearance.Storefront().customize {
headerBackground = Color.ResourceId(R.color.checkout_header)
headerFont = Color.ResourceId(R.color.checkout_header_text)
+ headerBorderColor = Color.ResourceId(R.color.checkout_header_border)
}
}Behavior changes
Renderer termination is consistently reported
Checkout now handles both WebView renderer crashes and system termination as terminal failures:
override fun onCheckoutFailed(error: CheckoutException) {
when (error.code) {
CheckoutErrorCode.WEB_CONTENT_PROCESS_TERMINATED -> showRetry()
else -> handleCheckoutFailure(error)
}
}Checkout Kit does not automatically recreate the WebView. The app must remove the failed presentation, destroy it, and create a new ShopifyCheckout for an explicit retry.
For an unconsumed background preload, the SDK reports:
PreloadState.Failed(
PreloadState.FailureReason.WebContentProcessTerminated
)It does not invoke the presentation lifecycle failure callback.
Checkout URLs must use HTTPS
Checkout creation and main-frame navigation now reject non-HTTPS checkout URLs:
-val checkout = ShopifyCheckout.create(context, checkoutUrl, listener)
+require(checkoutUrl.startsWith("https://"))
+val checkout = ShopifyCheckout.create(context, checkoutUrl, listener)Invalid URLs produce a CheckoutException with CheckoutErrorCode.SDK_ERROR. Initialization failures are delivered through the configured failure callback, and the returned checkout view remains inert.
Terminal protocol errors invalidate preloads
When a background preload receives a terminal ec.error message, it now transitions to:
PreloadState.Failed(
PreloadState.FailureReason.ProtocolError
)The error remains scoped to preload state and does not invoke the presentation failure callback.
Header border appears while scrolling
The checkout sheet now displays a subtle header border after checkout content scrolls beneath the native header. The border fades in and out as the WebView scroll position changes.
The drag handle also falls back to headerFont when no explicit dragHandleColor is provided.
What's Changed
- add multipass deprecation warning by @kiftio in #564
- Document checkout lifecycle errors by @kiftio in #538
- Encrypt Android demo customer access tokens by @kiftio in #547
- feat: incoming message origin validation for android by @michaeljsXu in #475
- Handle WebView process termination by @kiftio in #539
- chore(deps): bump com.android.application from 9.2.1 to 9.3.1 in /platforms/android by @dependabot[bot] in #522
- chore(deps): bump com.android.library from 9.2.1 to 9.3.1 in /platforms/android by @dependabot[bot] in #521
- fix: drag handle when using customize should follow headerFont by @kieran-osgood-shopify in #594
- [Android] Setup sample formatting and basic lint by @kiftio in #585
- [Android][lint] Fix android sample package name by @kiftio in #587
- Readme discrepancies by @markmur in #526
- [ReactNative] Adds title to the provider configuration for runtime changes by @kieran-osgood-shopify in #485
- Add sample app test seams and run them in CI by @kieran-osgood-shopify in #553
- Elevate sheet header with border by @markmur in #616
- Release Android 4.0.0-alpha.4 by @tiagocandido in #623
Full Changelog: android/4.0.0-alpha.3...android/4.0.0-alpha.4
[Swift] 4.0.0-alpha.3
Breaking changes
Checkout errors are now a flat value type
CheckoutError changed from an enum with associated values to a struct with stable properties:
-func checkoutDidFail(error: CheckoutError) {
- switch error {
- case .checkoutExpired(_, let code):
- recreateCart(for: code)
- case .checkoutUnavailable(_, let reason):
- handleUnavailableCheckout(reason)
- case .sdkError(let underlyingError):
- report(underlyingError)
- }
-}
+func checkoutDidFail(error: CheckoutError) {
+ switch error.code {
+ case .cartExpired, .cartCompleted, .invalidCart:
+ recreateCart(for: error.code)
+ case .httpError, .networkError:
+ showRetry()
+ case .sdkError, .unknown:
+ report(error.underlyingError)
+ case .storefrontPasswordRequired, .customerAccountRequired:
+ handleStorefrontRequirement(error.code)
+ }
+}The new properties are:
error.code
error.message
error.httpStatusCode
error.underlyingErrorThe intermediate CheckoutUnavailable type and enum cases such as .checkoutExpired, .checkoutUnavailable, and .sdkError have been removed.
Cancellation callbacks were renamed to dismissal
Buyer-initiated sheet closure is now described as dismissal:
final class CheckoutCoordinator: CheckoutDelegate {
- func checkoutDidCancel() {
+ func checkoutDidDismiss() {
closeCheckout()
}
}The SwiftUI modifier was also renamed:
ShopifyCheckout(checkout: checkoutURL)
- .onCancel {
+ .onDismiss {
isCheckoutPresented = false
}Color scheme configuration was replaced by appearance
Configuration.colorScheme and the .colorScheme(...) view modifier were replaced by appearance:
ShopifyCheckoutKit.configure {
- $0.colorScheme = .web
+ $0.appearance = .storefront
} ShopifyCheckout(checkout: checkoutURL)
- .colorScheme(.automatic)
+ .appearance(.app(.automatic))Migration mappings:
-.web
+.storefront
-.automatic
+.app(.automatic)
-.light
+.app(.light)
-.dark
+.app(.dark)The default also changed from an automatic app color scheme to storefront appearance.
Log levels were normalized
LogLevel.all was removed and LogLevel.warn was added:
ShopifyCheckoutKit.configure {
- $0.logLevel = .all
+ $0.logLevel = .debug
}Log levels now behave as ordered thresholds:
debug → warn → error → none
The default changed from .error to .warn.
The Swift protocol module was renamed
Apps that directly depend on the protocol package must rename their product and import:
.target(
name: "YourApp",
dependencies: [
"ShopifyCheckoutKit",
- "ShopifyCheckoutProtocol",
+ "EmbeddedCheckoutProtocol",
]
)-import ShopifyCheckoutProtocol
+import EmbeddedCheckoutProtocolProtocol source types also moved into the EmbeddedCheckoutProtocol module.
Direct protocol event handlers receive envelopes
The underlying protocol descriptors now expose complete JSON-RPC notification and request envelopes. Direct EmbeddedCheckoutProtocol.Client integrations must update handler inputs accordingly.
-client.on(EmbeddedCheckoutProtocol.Event.complete) { checkout in
- handleCompletion(checkout)
+client.on(EmbeddedCheckoutProtocol.Event.complete) { notification in
+ handleCompletion(notification.params.checkout)
}ShopifyCheckoutKit.CheckoutProtocol continues projecting common checkout events to their narrower payloads:
client.on(CheckoutProtocol.complete) { checkout in
handleCompletion(checkout)
}Window-open protocol types moved
WindowOpenRequest, WindowOpenResult, and the window-open descriptor moved from ShopifyCheckoutKit into EmbeddedCheckoutProtocol.
Prefer the Checkout Kit facade when connecting through Checkout Kit:
-client.on(CheckoutProtocol.windowOpen) { request in
- return .success
+client.on(CheckoutProtocol.windowOpen) { request in
+ open(URL(string: request.url)!)
+ return EmbeddedCheckoutProtocol.WindowOpenResult(
+ ucp: /* response envelope */,
+ continueURL: nil,
+ messages: nil
+ )
}Additive changes
Observable preload state
preload(checkout:) now returns an optional CheckoutPreload handle:
-ShopifyCheckoutKit.preload(checkout: checkoutURL)
+let preload = ShopifyCheckoutKit.preload(checkout: checkoutURL)
+preload?.onStateChange = { state in
+ switch state {
+ case .ready:
+ showCheckoutReady()
+ case .failed(let reason):
+ recordPreloadFailure(reason)
+ default:
+ break
+ }
+}The handle publishes its latest state through:
@Published public private(set) var state: PreloadStateRetain the handle for as long as preload changes need to be observed.
New checkout error codes
The flattened error model adds stable codes for:
.customerAccountRequired
.httpError
.networkError
.sdkErrorHTTP failures expose their response status through httpStatusCode.
Warning-level logging
OSLogger now provides:
logger.warn("Checkout will retry")Selecting .warn emits warnings and errors while suppressing debug output.
Behavior changes
Transient navigation failures are retried once
Checkout now retries the initial checkout navigation once for selected transient network failures, including connection loss, host lookup failures, timeouts, and temporary resource unavailability.
If the retry also fails, the SDK reports a terminal .networkError.
Preload failures are observable
Preload can now transition to:
.failed(reason: .httpError(statusCode: status))
.failed(reason: .navigationFailed)
.failed(reason: .keepAliveLost)
.failed(reason: .protocolError)These states describe preload availability and do not necessarily mean a later checkout presentation will fail.
Protocol decode failures are logged
Malformed or unsupported protocol messages now provide more consistent diagnostic logging across the native and web implementations.
What's Changed
- Flatten Swift checkout failure type by @kiftio in #537
- [Android][Swift] Rename oncancel to ondismiss in swift and android by @kiftio in #482
- Retry on didFailProvisionalNavigation by @markmur in #492
- [Swift] Add preload state observability by @markmur in #445
- [Swift] remove color scheme from automatic swift by @kiftio in #470
- [Swift] Canonicalize LogLevel to debug/warn/error/none by @markmur in #441
- Add decode logging parity for Web + Swift + Android by @markmur in #437
- Widen protocol events to expose full envelope by @markmur in #398
- Add Swift checkout appearance configuration by @tiagocandido in #421
- Derive Swift checkout branding from color scheme by @tiagocandido in #405
- Move windowOpen to protocol by @markmur in #386
- Add native checkout close selector for RN E2E by @kyle-schellen in #333
- Rename ShopifyCheckoutProtocol to EmbeddedCheckoutProtocol by @markmur in #354
- Make the OpenRPC spec the single source of truth for the Swift protocol layer by @markmur in #330
- Bump protocol to 2026.04.08.1-alpha.2, Swift and Android to 4.0.0-alpha.3 by @kiftio in #563
Full Changelog: 4.0.0-alpha.2...4.0.0-alpha.3
[Embedded Checkout Protocol] 2026.04.08.1-alpha.2
Breaking changes
Swift protocol module renamed
The Swift package product and module were renamed:
-import ShopifyCheckoutProtocol
+import EmbeddedCheckoutProtocolUpdate Swift Package Manager dependencies from ShopifyCheckoutProtocol to EmbeddedCheckoutProtocol.
Direct protocol handlers receive message envelopes
Direct Swift protocol handlers now receive complete JSON-RPC notification or request messages:
-client.on(EmbeddedCheckoutProtocol.Event.complete) { checkout in
- handle(checkout)
+client.on(EmbeddedCheckoutProtocol.Event.complete) { notification in
+ handle(notification.params.checkout)
}The descriptor generic signatures changed to distinguish wire payloads from projected handler values.
Generated protocol models changed
Kotlin and Swift generated model names and constructors were refreshed from the OpenRPC source. Recompile consumers and update direct references to renamed generated types.
Additive changes
Protocol-owned clients
Kotlin now exposes a typed protocol client from the standalone package:
+val client = EmbeddedCheckoutProtocol.Client()
+ .on(EmbeddedCheckoutProtocol.Event.complete) { message ->
+ handle(message.params.checkout)
+ }Window-open support
The protocol packages now provide typed window-open request and result models instead of requiring each SDK to define its own copies.
Extension preservation
Unknown extension properties are preserved when supported protocol models are decoded and re-encoded, improving compatibility with newer UCP extensions.
Behavior changes
Protocol decode failures now produce more consistent diagnostics across Swift, Kotlin, and TypeScript implementations.
What's Changed
- [Kotlin] Move Client to protocol by @markmur in #438
- Add decode logging parity for Web + Swift + Android by @markmur in #437
- Widen protocol events to expose full envelope by @markmur in #398
- Run Protocol tests in CI / Improve TS coverage by @markmur in #429
- Web consumes protocol by @markmur in #393
- [Protocol] Add support for additional properties by @markmur in #407
- Move windowOpen to protocol by @markmur in #386
- [TypeScript] Agnostic protocol by @markmur in #314
- Make Kotlin protocol agnostic by @markmur in #365
- Rename ShopifyCheckoutProtocol to EmbeddedCheckoutProtocol by @markmur in #354
- Make the OpenRPC spec the single source of truth for the Swift protocol layer by @markmur in #330
- expose fulfilment change in RN by @kiftio in #350
- chore(deps-dev): bump the vitest group in /protocol with 2 updates by @dependabot[bot] in #542
- Android toolchain bumps by @kiftio in #487
Full Changelog: embedded-checkout-protocol/2026.04.08.1-alpha.1...embedded-checkout-protocol/2026.04.08.1-alpha.2
[Android] 4.0.0-alpha.3
Breaking changes
Checkout failures use a flat error model
Exception subclasses were replaced by CheckoutException with a stable CheckoutErrorCode:
-when (error) {
- is HttpException -> retry(error.statusCode)
- is CheckoutExpiredException -> recreateCart()
-}
+when (error.code) {
+ CheckoutErrorCode.HTTP_ERROR -> retry(error.httpStatusCode)
+ CheckoutErrorCode.CART_EXPIRED -> recreateCart()
+ else -> report(error)
+}Cancellation callbacks renamed to dismissal
-override fun onCheckoutCanceled() = closeCheckout()
+override fun onCheckoutDismissed() = closeCheckout()Presentation builders similarly replace onCancel with onDismiss.
Color scheme configuration replaced by appearance
ShopifyCheckoutKit.configure {
- it.colorScheme = ColorScheme.Automatic()
+ it.appearance = CheckoutAppearance.App(ColorScheme.Automatic())
}Use CheckoutAppearance.Storefront() for storefront branding. Storefront appearance is now the default.
Direct protocol handlers receive envelopes
Direct EmbeddedCheckoutProtocol handlers must read typed payloads from request and notification envelopes. The Checkout Kit facade continues projecting common checkout events.
Additive changes
Embeddable checkout view
Hosts can now create a ShopifyCheckout view and own its presentation container:
+val checkout = ShopifyCheckout(
+ context,
+ checkoutUrl,
+ listener,
+)
+container.addView(checkout)Call destroy() when permanently removing the view.
Bottom-sheet configuration
Configuration.sheet adds snap points, maximum width, corner radius, title alignment, dismissal options, close-icon styling, scrim color, and an optional drag handle.
Observable preload state
preload now returns a CheckoutPreload handle with Idle, Loading, Ready, Expired, and Failed states.
Runtime title and logging controls
Configuration now supports a runtime title; LogLevel.NONE disables SDK logging.
Behavior changes
- Checkout is presented in a customizable bottom sheet.
- Selected transient main-frame navigation failures are retried once.
- Web messages use
WebMessageListenerwhen the installed WebView supports it. - Preloaded WebViews can be reused after dismissal.
What's Changed
- Flatten Android checkout failure type by @kiftio in #536
- [Android] Add preload state observability by @markmur in #451
- Flatten webview hierarchy by @kiftio in #497
- [Android][Swift] Rename oncancel to ondismiss in swift and android by @kiftio in #482
- Retry for certain transient network errors for main frame by @kiftio in #496
- [Android] Set max-width on sheet by @kiftio in #489
- [Android] Adds title to the configure {} method for runtime changes by @kieran-osgood-shopify in #479
- [Android] Extract and expose ShopifyCheckout view by @kiftio in #456
- [Android] remove color-scheme from automatic by @kiftio in #469
- [Android] Replace JavascriptInterface with WebMessageListener by @kiftio in #424
- [Android] Add NONE log level by @markmur in #442
- [Android] Allow sheet customization by @kiftio in #382
- [Android] Render checkout in bottom sheet by @kiftio in #381
- Derive Android checkout branding from color scheme by @tiagocandido in #406
- Ensure contrast in 3 button nav by @kiftio in #422
- Move windowOpen to protocol by @markmur in #386
- Make Kotlin protocol agnostic by @markmur in #365
Full Changelog: android/4.0.0-alpha.2...android/4.0.0-alpha.3