Skip to content

Releases: Shopify/checkout-kit

[Swift] 4.0.0-alpha.6

[Swift] 4.0.0-alpha.6 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 02 Sep 12:39
Immutable release. Only release title and notes can be modified.
69c2c34

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

Pre-release

Choose a tag to compare

@github-actions github-actions released this 02 Sep 12:42
Immutable release. Only release title and notes can be modified.
69c2c34

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

Pre-release

Choose a tag to compare

@github-actions github-actions released this 24 Aug 08:41
Immutable release. Only release title and notes can be modified.
0fdad6c

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

[Swift] 4.0.0-alpha.5 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 17 Aug 09:24
Immutable release. Only release title and notes can be modified.
08e6494

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

Pre-release

Choose a tag to compare

@github-actions github-actions released this 17 Aug 09:33
Immutable release. Only release title and notes can be modified.
08e6494

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

[Swift] 4.0.0-alpha.4 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 11 Aug 13:40
Immutable release. Only release title and notes can be modified.
f0279c9

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

Full Changelog: 4.0.0-alpha.3...4.0.0-alpha.4

[Android] 4.0.0-alpha.4

Pre-release

Choose a tag to compare

@github-actions github-actions released this 11 Aug 13:47
Immutable release. Only release title and notes can be modified.
262b9c4

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()
 }
  • WebContentProcessTerminated indicates that Android terminated or crashed the preloaded WebView renderer.
  • ProtocolError indicates 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

Full Changelog: android/4.0.0-alpha.3...android/4.0.0-alpha.4

[Swift] 4.0.0-alpha.3

[Swift] 4.0.0-alpha.3 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 31 Jul 16:52
Immutable release. Only release title and notes can be modified.
1a4a374

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.underlyingError

The 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 EmbeddedCheckoutProtocol

Protocol 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: PreloadState

Retain 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
.sdkError

HTTP 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

Choose a tag to compare

@github-actions github-actions released this 31 Jul 16:36
Immutable release. Only release title and notes can be modified.
1a4a374

Breaking changes

Swift protocol module renamed

The Swift package product and module were renamed:

-import ShopifyCheckoutProtocol
+import EmbeddedCheckoutProtocol

Update 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

Pre-release

Choose a tag to compare

@github-actions github-actions released this 31 Jul 17:02
Immutable release. Only release title and notes can be modified.
1a4a374

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 WebMessageListener when 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