Warning
Alpha - early preview. This software is an early preview and is not
production-ready. The current Checkout Kit for Android alpha is 4.0.0-alpha.2.
Stability is not guaranteed, and breaking changes may occur in any release.
Checkout Kit for Android lets Android apps present Shopify checkout in a native bottom sheet while preserving store checkout customizations such as Checkout UI extensions, Shopify Functions, branding, and supported payment methods.
Note
This package was previously published as com.shopify:checkout-sheet-kit. New integrations should use com.shopify:checkout-kit.
- Requirements
- Install
- Get a checkout URL
- Present checkout
- Preload checkout
- Configure checkout
- Checkout lifecycle
- Browser and system callbacks
- Authentication and buyer identity
- Offsite payments and links
- Troubleshooting
- Samples
- Contributing
- License
- JDK 17+
- Android
minSdk23+ - Android
compileSdk35+ for consuming apps. This repository currently builds the library withcompileSdk36.
For alpha testing, install the exact version shown below. The current Checkout Kit for Android alpha is 4.0.0-alpha.2.
dependencies {
implementation "com.shopify:checkout-kit:4.0.0-alpha.2"
}<dependency>
<groupId>com.shopify</groupId>
<artifactId>checkout-kit</artifactId>
<version>4.0.0-alpha.2</version>
</dependency>Checkout Kit presents a standard Shopify checkout URL. The common flow is:
- Create or update a cart with the Storefront GraphQL API, for example with
cartCreateand related cart mutations. - Read the cart's
checkoutUrl. - Pass that URL, or a cart permalink, to Checkout Kit.
You can use any GraphQL client. The sample app uses Apollo Kotlin and is a complete reference for a modern Storefront API cart flow.
For production use, see the Storefront API GraphiQL Explorer for schema exploration and the cartCreate mutation reference for the full input shape, including buyer identity, attributes, discount codes, delivery addresses, and delivery options.
Use the Kotlin builder when presenting from a ComponentActivity:
import com.shopify.checkoutkit.ShopifyCheckoutKit
fun presentCheckout(checkoutUrl: String, activity: ComponentActivity) {
ShopifyCheckoutKit.present(checkoutUrl, activity) {
onFail { error ->
handleCheckoutError(error)
}
onCancel {
resetCheckoutUi()
}
}
}Checkout Kit adds the required checkout protocol parameters when checkout loads.
For Java integrations or shared listener implementations, extend DefaultCheckoutListener:
val listener = object : DefaultCheckoutListener() {
override fun onCheckoutFailed(error: CheckoutException) {
handleCheckoutError(error)
}
override fun onCheckoutCanceled() {
resetCheckoutUi()
}
}
ShopifyCheckoutKit.present(checkoutUrl, activity, listener)The present call returns a CheckoutHandle?. Keep it if you need to dismiss checkout programmatically:
val checkout = ShopifyCheckoutKit.present(checkoutUrl, activity) {
onCancel { resetCheckoutUi() }
}
checkout?.dismiss()Call preload when your app has a strong signal that the buyer is likely to check out soon, such as when they open the cart screen or move toward a checkout action:
ShopifyCheckoutKit.preload(checkoutUrl, activity)Checkout Kit can reuse a matching preloaded checkout when present is called later:
ShopifyCheckoutKit.present(checkoutUrl, activity) {
onFail { error -> handleCheckoutError(error) }
onCancel { resetCheckoutUi() }
}Preloading is a best-effort performance hint, not a guarantee. If the preload is unavailable, incomplete, or for a different checkout URL, checkout loads normally during presentation. A preloaded checkout reflects the cart represented by the URL passed to preload, so call preload again after cart changes produce a new checkout URL.
Avoid preloading on every add-to-cart or cart mutation. Preload only when buyer intent is strong enough to justify the additional client and network work.
Clear unused preloaded checkout work with invalidate:
ShopifyCheckoutKit.invalidate()Preloading is enabled by default. Disable it when appropriate, for example for data-saver modes or app-specific runtime conditions:
import com.shopify.checkoutkit.Preloading
ShopifyCheckoutKit.configure {
it.preloading = Preloading(enabled = false)
}Configure global presentation defaults before presenting checkout:
ShopifyCheckoutKit.configure {
it.appearance = CheckoutAppearance.Storefront()
it.sheet = CheckoutSheetOptions(
dismissal = CheckoutSheetDismissal(
dragToDismissEnabled = true,
tapAwayToDismissEnabled = true,
),
)
it.logLevel = LogLevel.ERROR
}| Option | Default | Purpose |
|---|---|---|
appearance |
CheckoutAppearance.Storefront() |
Use the storefront's web checkout branding with automatic color scheme, or use the Checkout Kit style with App(Automatic), App(Light), or App(Dark). |
sheet |
CheckoutSheetOptions() |
Customize native sheet presentation such as snap points, dismissal behavior, corner radius, title alignment, toolbar elevation, close icon styling, and the optional drag handle. |
logLevel |
LogLevel.WARN |
SDK logging verbosity. Use LogLevel.DEBUG during integration. |
preloading |
Preloading(enabled = true) |
Enables best-effort checkout preloading before presentation. |
ShopifyCheckoutKit.configure {
it.appearance = CheckoutAppearance.App(ColorScheme.Light())
it.appearance = CheckoutAppearance.App(ColorScheme.Dark())
it.appearance = CheckoutAppearance.App(ColorScheme.Automatic())
it.appearance = CheckoutAppearance.Storefront()
it.appearance = CheckoutAppearance.Storefront(ColorScheme.Automatic())
}Customize native colors with resource IDs or sRGB integers:
ShopifyCheckoutKit.configure {
it.appearance = CheckoutAppearance.App(ColorScheme.Automatic().customize(
light = {
headerBackground = Color.ResourceId(R.color.checkout_header_light)
headerFont = Color.ResourceId(R.color.checkout_header_text_light)
webViewBackground = Color.ResourceId(R.color.checkout_background_light)
progressIndicator = Color.ResourceId(R.color.checkout_progress_light)
dragHandleColor = Color.ResourceId(R.color.checkout_drag_handle_light)
},
dark = {
headerBackground = Color.ResourceId(R.color.checkout_header_dark)
headerFont = Color.ResourceId(R.color.checkout_header_text_dark)
webViewBackground = Color.ResourceId(R.color.checkout_background_dark)
progressIndicator = Color.ResourceId(R.color.checkout_progress_dark)
dragHandleColor = Color.ResourceId(R.color.checkout_drag_handle_dark)
},
))
}Customize native sheet presentation independently from checkout colors:
ShopifyCheckoutKit.configure {
it.sheet = CheckoutSheetOptions(
snapPoints = listOf(CheckoutSheetSnapPoint.Expanded(topMarginDp = 72f)),
cornerRadiusDp = 32f,
titleAlignment = CheckoutSheetTitleAlignment.CENTER,
toolbarElevationDp = 0f,
closeIconTint = Color.ResourceId(R.color.checkout_close),
dismissal = CheckoutSheetDismissal(
dragToDismissEnabled = true,
tapAwayToDismissEnabled = true,
),
dragHandle = CheckoutSheetDragHandle(
visible = true,
),
)
}CheckoutSheetOptions() defaults to CheckoutSheetSnapPoint.MaterialExpanded, which resolves to a 72dp top margin
from the window top, or 56dp when the window width is greater than 640dp. The SDK handles system bar insets
internally so the sheet does not overlap the status bar. Custom snap points currently support exactly one expanded
position.
Use closeIcon = DrawableResource(R.drawable.ic_checkout_close) to provide a custom close drawable. If both
closeIcon and closeIconTint are set, the custom drawable takes precedence.
Set dragHandle.visible = true to show a fixed, visual-only drag handle at the top of the sheet. The handle is hidden
when dismissal.dragToDismissEnabled = false so disabled drag gestures are not presented as available. Configure
dragHandleColor in ColorScheme to override the default header-font-derived handle color.
Override checkout_web_view_title in your app resources:
<resources>
<string name="checkout_web_view_title">Buy now</string>
</resources>val configuration = ShopifyCheckoutKit.getConfiguration()Use onFail and onCancel for checkout outcomes handled by your app. Use CheckoutProtocol.Client for typed checkout state, including completion. These descriptors wrap checkout protocol messages defined in the protocol schema.
import com.shopify.checkoutkit.CheckoutProtocol
val protocolClient = CheckoutProtocol.Client()
.on(CheckoutProtocol.start) { checkout ->
// Checkout is loaded and interactive.
}
.on(CheckoutProtocol.complete) { checkout ->
// The order was completed. Clear or refresh the local cart.
}
.on(CheckoutProtocol.totalsChange) { checkout ->
// React to updated totals.
}
.on(CheckoutProtocol.lineItemsChange) { checkout ->
// React to line item changes.
}
.on(CheckoutProtocol.messagesChange) { checkout ->
// React to checkout messages.
}
ShopifyCheckoutKit.present(checkoutUrl, activity) {
connect(protocolClient)
onFail { error -> handleCheckoutError(error) }
onCancel { resetCheckoutUi() }
}ec.window.open_request is handled by your registered CheckoutProtocol.windowOpen handler if you provide one. Otherwise, Checkout Kit falls back to its default Android intent behavior.
The public CheckoutProtocol descriptors are typed wrappers over UCP-backed checkout protocol messages.
Checkout failures are delivered as CheckoutException values. Checkout web error events are mapped to ConfigurationException, CheckoutExpiredException, or ClientException; register CheckoutProtocol.error separately if you need to observe ec.error messages.
| Exception | Common code | Meaning | Recommended handling |
|---|---|---|---|
ConfigurationException |
storefront_password_required |
Checkout is password protected or otherwise blocked by configuration. | Treat as fatal for this session. |
CheckoutExpiredException |
cart_expired |
The cart or checkout session expired. | Create a new cart and present a fresh checkoutUrl. |
CheckoutExpiredException |
cart_completed |
The cart already completed checkout. | Clear the local cart and fetch a new one. |
CheckoutExpiredException |
invalid_cart |
The cart is invalid or empty. | Rebuild the cart before presenting checkout. |
HttpException |
http_error |
Checkout returned an unexpected HTTP response. | Treat as fatal for this attempt; retry with a fresh URL if appropriate. |
ClientException |
client_error |
Checkout could not load for a client-side reason. | Show a recoverable error and log details. |
CheckoutKitException |
error_receiving_message, error_sending_message, render_process_gone, unknown |
Checkout Kit encountered an SDK or WebView issue. | Log details and open an issue if it persists. |
Android apps must decide how to handle file choosers, web permissions, and geolocation prompts requested by checkout.
ShopifyCheckoutKit.present(checkoutUrl, activity) {
onShowFileChooser { webView, filePathCallback, fileChooserParams ->
// Launch your ActivityResultContract and return true if handled.
false
}
onPermissionRequest { permissionRequest ->
// Grant, deny, or proxy web permissions such as camera access.
}
onGeolocationPermissionsShowPrompt { origin, callback ->
// Request Android location permission, then invoke callback.invoke(...).
}
onGeolocationPermissionsHidePrompt {
// Hide any visible geolocation prompt.
}
}Declare location permissions if checkout uses pickup points or "Use my location":
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />Checkout Kit does not create carts or authenticate buyers. Add buyer context to the cart before presenting checkout:
- Use the Customer Account API to obtain a customer access token and attach it through cart buyer identity.
- Use Storefront API cart buyer identity fields to prefill email, phone, country, and language.
- Use Storefront API cart delivery mutations to add delivery addresses and select delivery options.
- Use
walletPreferences: [shop_pay]when you want checkout to prefer Shop Pay. - Use Multipass for Shopify Plus stores that use Classic Customer Accounts. Generate Multipass tokens server-side and set
return_toto the checkout URL.
Keep Multipass secrets out of client-side code.
Some payment providers redirect buyers to external banking apps or web pages. Configure Android App Links or deep links so buyers can return to your app after those flows complete.
Checkout Kit opens external HTTPS links, mailto:, tel:, and custom-scheme links through Android intents. Make sure your app has:
- Intent filters for the storefront links it owns.
- Fallback behavior for links that no installed app can open.
- A routing path for checkout URLs, cart URLs, and post-checkout confirmation URLs.
-
Use
LogLevel.DEBUGwhile integrating. -
For production release builds, use Android app shrinking and optimization when appropriate. Checkout Kit does not require integration-specific R8 rules for normal usage, and app-level shrinking can remove unused dependency code and resources. For example:
android { buildTypes { release { isMinifyEnabled = true isShrinkResources = true } } } -
If checkout reports an expired, completed, or invalid cart, create a fresh cart and use its new
checkoutUrl. -
If checkout cannot access camera, file upload, or location features, check your manifest permissions and runtime permission flow.
-
If offsite payment redirects do not return to your app, verify App Links/deep link intent filters and domain association.
-
Password-protected storefronts return
storefront_password_requiredand are not supported by Checkout Kit.
See samples. CheckoutKitAndroidDemo demonstrates an Apollo Kotlin Storefront API cart flow, checkout presentation, typed protocol lifecycle events, file chooser handling, geolocation callbacks, and Customer Account API sign-in.
See CONTRIBUTING.
Useful checks before opening an Android change:
cd platforms/android
./gradlew :lib:build
./gradlew clean test --console=plain
./gradlew detekt lintReleaseFor sample app changes, run:
cd platforms/android/samples/CheckoutKitAndroidDemo
./gradlew buildFor public API changes, run:
cd platforms/android
./gradlew :lib:apiCheck
cd ../../protocol/languages/kotlin
./gradlew :embedded-checkout-protocol:apiCheckCheckout Kit is available under the MIT license.