Shopify's Checkout Sheet Kit for Android is a library that enables Android apps to provide the world's highest converting, customizable, one-page checkout within an app. The presented experience is a fully-featured checkout that preserves all of the store customizations: Checkout UI extensions, Functions, Web Pixels, and more. It also provides idiomatic defaults such as support for light and dark mode, and convenient developer APIs to embed, customize and follow the lifecycle of the checkout experience. Check out our developer blog to learn how Checkout Sheet Kit is built.
- JDK 17+
- Android SDK 23+
- The SDK is not compatible with checkout.liquid. The Shopify Store must be migrated for extensibility
The SDK is an open source Android library. As a quick start, see sample projects or use one of the following ways to integrate the SDK into your project:
implementation "com.shopify:checkout-sheet-kit:3.2.2"
<dependency>
<groupId>com.shopify</groupId>
<artifactId>checkout-sheet-kit</artifactId>
<version>3.2.2</version>
</dependency>
Once the SDK has been added as a dependency, you can import the library:
import com.shopify.checkoutsheetkit.ShopifyCheckoutSheetKit
To present a checkout to the buyer, your application must first obtain a checkout URL.
The most common way is to use the Storefront GraphQL API
to assemble a cart (via cartCreate
and related update mutations) and query the
checkoutUrl.
You can use any GraphQL client to accomplish this and we recommend
Shopify's Mobile Buy SDK for Android to
simplify the development workflow:
val client = GraphClient.build(
context = applicationContext,
shopDomain = "yourshop.myshopify.com",
accessToken = "<storefront access token>"
)
val cartQuery = Storefront.query { query ->
query.cart(ID(id)) {
it.checkoutUrl()
}
}
client.queryGraph(cartQuery).enqueue {
if (it is GraphCallResult.Success) {
val checkoutUrl = it.response.data?.cart?.checkoutUrl
}
}
The checkoutUrl
object is a standard web checkout URL that can be opened in any browser.
To present a native checkout dialog in your Android application, provide
the checkoutUrl
alongside optional runtime configuration settings to the present(checkoutUrl)
function provided by the SDK:
fun presentCheckout() {
val checkoutUrl = cart.checkoutUrl
ShopifyCheckoutSheetKit.present(checkoutUrl, context, checkoutEventProcessor)
}
Tip
To help optimize and deliver the best experience the SDK also provides a preloading API that can be used to initialize the checkout session ahead of time.
The SDK provides a way to customize the presented checkout experience via
the ShopifyCheckoutSheetKit.configure
function.
By default, the SDK will match the user's device color appearance. This behavior can be customized
via the colorScheme
property:
ShopifyCheckoutSheetKit.configure {
// [Default] Automatically toggle idiomatic light and dark themes based on device preference.
it.colorScheme = ColorScheme.Automatic()
// Force idiomatic light color scheme
it.colorScheme = ColorScheme.Light()
// Force idiomatic dark color scheme
it.colorScheme = ColorScheme.Dark()
// Force web theme, as rendered by a mobile browser
it.colorScheme = ColorScheme.Web()
// Force web theme, passing colors for the modal header and background
it.colorScheme = ColorScheme.Web(
Colors(
webViewBackground = Color.ResourceId(R.color.web_view_background),
headerFont = Color.ResourceId(R.color.header_font),
headerBackground = Color.ResourceId(R.color.header_background),
progressIndicator = Color.ResourceId(R.color.progress_indicator),
)
)
}
Tip
Colors can also be specified in sRGB format (e.g. Color.SRGB(-0xff0001)
) and can also be overridden for Light/Dark/Automatic themes, (see example below)
val automatic = ColorScheme.Automatic(
lightColors = Colors(
headerBackground = Color.ResourceId(R.color.headerLight),
headerFont = Color.ResourceId(R.color.headerFontLight),
webViewBackground = Color.ResourceId(R.color.webViewBgLight),
progressIndicator = Color.ResourceId(R.color.indicatorLight),
),
darkColors = Colors(
headerBackground = Color.ResourceId(R.color.headerDark),
headerFont = Color.ResourceId(R.color.headerFontDark,
webViewBackground = Color.ResourceId(R.color.webViewBgDark),
progressIndicator = Color.ResourceId(R.color.indicatorDark),
)
)
The colors that can be modified are:
- headerBackground - Used to customize the background of the app bar on the dialog,
- headerFont - Used to customize the font color of the header text within in the app bar,
- webViewBackground - Used to customize the background color of the WebView,
- progressIndicator - Used to customize the color of the progress indicator shown when checkout is loading.
The current configuration can be obtained by calling ShopifyCheckoutSheetKit.getConfiguration()
.
To customize the title of the Dialog that the checkout WebView is displayed within, or to provide different values for the various locales your app supports, override the checkout_web_view_title
String resource in your application, e.g:
<string name="checkout_web_view_title">Buy Now!</string>
Initializing a checkout session requires communicating with Shopify servers and, depending on the network weather and the quality of the buyer's connection, can result in undesirable waiting time for the buyer. To help optimize and deliver the best experience, the SDK provides a preloading hint that allows app developers to signal and initialize the checkout session in the background and ahead of time.
Preloading is an advanced feature that can be disabled via a runtime flag:
ShopifyCheckoutSheetKit.configure {
it.preloading = Preloading(enabled = false) // defaults to true
}
Once enabled, preloading a checkout is as simple as:
ShopifyCheckoutSheetKit.preload(checkoutUrl)
Setting enabled to false
will cause all calls to the preload
function to be ignored. This allows the application to selectively toggle preloading behavior as a remote feature flag or dynamically in response to client conditions - e.g. when data saver functionality is enabled by the user.
ShopifyCheckoutSheetKit.configure {
it.preloading = Preloading(enabled = false)
}
ShopifyCheckoutSheetKit.preload(checkoutUrl) // no-op
To invalidate a preloaded checkout, call ShopifyCheckoutSheetKit.invalidate()
. This function will be a no-op if no checkout is preloaded.
You may wish to do this if the buyer changes shortly before entering checkout, e.g. by changing cart quantity on a cart view.
Preloading renders a checkout in a background webview, which is brought to foreground when ShopifyCheckoutSheetKit.present()
is called. The content of preloaded checkout reflects the state of the cart when preload()
was initially called. If the cart is mutated after preload()
is called, the application is responsible for invalidating the preloaded checkout to ensure that up-to-date checkout content is displayed to the buyer:
- To update preloaded contents: call
preload()
once again - To disable preloaded content: toggle the preload configuration setting
The library will automatically invalidate/abort preload under the following conditions:
- Request results in network error or non 2XX server response code
- The checkout has successfully completed, as indicated by the server response
- When
ShopifyCheckoutSheet.configure
is called (e.g. with theming changes).
A preloaded checkout is not automatically invalidated when checkout is closed. For example, if a buyer loads the checkout then exists, the preloaded checkout is retained and should be updated when cart contents change.
- Preloading is a hint, not a guarantee. The library may debounce or ignore
calls depending on various conditions; the preload may not complete before
present(checkoutUrl)
is called, in which case the buyer may still see a progress/loading indicator while the checkout session is finalized. - Preloading results in background network requests and additional CPU/memory utilization for the client, and should be used responsibly. For example, conditionally based on the state of the client and when there is a high likelihood that the buyer will soon request to checkout.
Extend the DefaultCheckoutEventProcessor
abstract class to register callbacks for key lifecycle events during the checkout session:
val processor = object : DefaultCheckoutEventProcessor(activity) {
override fun onCheckoutCompleted(checkoutCompletedEvent: CheckoutCompletedEvent) {
// Called when the checkout was completed successfully by the buyer.
// Use this to update UI, reset cart state, etc.
}
override fun onCheckoutCanceled() {
// Called when the checkout was canceled by the buyer.
// Note: This will also be received after closing a completed checkout
}
override fun onCheckoutFailed(error: CheckoutException) {
/**
* Called when the checkout encountered an error and has been aborted.
*/
override fun onCheckoutLinkClicked(uri: Uri) {
// Called when the buyer clicks a link within the checkout experience:
// - email address (`mailto:`)
// - telephone number (`tel:`)
// - web (http:)
// - deep link (e.g. myapp://checkout)
// and is being directed outside the application.
// Note: to support deep links on Android 11+ using the `DefaultCheckoutEventProcessor`,
// the client app should add a queries element in its manifest declaring which apps it should interact with.
// See the MobileBuyIntegration sample's manifest for an example.
// Queries reference - https://developer.android.com/guide/topics/manifest/queries-element
// If no app can be queried to deal with the link, the processor will log a warning:
// `Unrecognized scheme for link clicked in checkout` along with the uri.
}
override fun onWebPixelEvent(event: PixelEvent) {
// Called when a web pixel event is emitted in checkout.
// Use this to submit events to your analytics system, see below.
}
override fun onShowFileChooser(
webView: WebView,
filePathCallback: ValueCallback<Array<Uri>>,
fileChooserParams: FileChooserParams,
): Boolean {
// Called to tell the client to show a file chooser. This is called to handle HTML forms with 'file' input type,
// in response to the user pressing the "Select File" button.
// To cancel the request, call filePathCallback.onReceiveValue(null) and return true.
}
override fun onPermissionRequest(permissionRequest: PermissionRequest) {
// Called when a permission has been requested, e.g. to access the camera
// implement to grant/deny/request permissions.
}
}
Note
The DefaultCheckoutEventProcessor
provides default implementations for current and future callback functions (such as onLinkClicked()
), which can be overridden by clients wanting to change default behavior.
In the event of a checkout error occurring, the Checkout Sheet Kit may attempt to retry to recover from the error. Recovery will happen in the background by discarding the failed WebView and creating a new "recovery" instance. Recovery will be attempted in the following scenarios:
- The WebView receives a 5XX status code
- An internal SDK error is emitted
There are some caveats to note when this scenario occurs:
- The checkout experience may look different to buyers. Though the sheet kit will attempt to load any checkoput customizations for the storefront, there is no guarantee they will show in recovery mode.
- The
onCheckoutCompleted(checkoutCompletedEvent: CheckoutCompletedEvent)
will be emitted with partial data. Invocations will only received the order ID viacheckoutCompletedEvent.orderDetails.id
. onWebPixelEvent(event: PixelEvent)
lifecycle methods will not be emitted.
Should you wish to opt-out of this fallback experience entirely, you can do so by overriding shouldRecoverFromError
. Errors given to the onCheckoutFailed(error: CheckoutException)
lifecycle method will contain an isRecoverable
property by default indicating whether the request should be retried or not.
preRecoveryActions()
can also be overridden to execute code before a fallback takes place, for example to add logging, or clear up any potentially problematic state such as in cookies. By default this function is a no-op.
ShopifyCheckoutSheetKit.configure {
it.errorRecovery = object: ErrorRecovery {
override fun shouldRecoverFromError(checkoutException: CheckoutException): Boolean {
// To disable recovery (default = checkoutException.isRecoverable)
return false
}
override fun preRecoveryActions(exception: CheckoutException, checkoutUrl: String) {
// Perform actions prior to recovery, e.g. logging, clearing up cookies:
if (exception is HttpException) {
CookiePurger.purge(checkoutUrl)
}
}
}
}
Exception Class | Error Code | Description | Recommendation |
---|---|---|---|
ConfigurationException |
'checkout_liquid_not_migrated' | checkout.liquid is not supported. |
Upgrade to Extensibility. |
ConfigurationException |
'storefront_password_required' | Access to checkout is password protected. | We are working on ways to enable the Checkout Sheet Kit for usage with password protected stores. |
ConfigurationException |
'unknown' | Other configuration issue, see error details for more info. | Resolve the configuration issue in the error message. |
CheckoutExpiredException |
'cart_expired' | The cart or checkout is no longer available. | Create a new cart and open a new checkout URL. |
CheckoutExpiredException |
'cart_completed' | The cart associated with the checkout has completed checkout. | Create new cart and open a new checkout URL. |
CheckoutExpiredException |
'invalid_cart' | The cart associated with the checkout is invalid (e.g. empty). | Create a new cart and open a new checkout URL. |
CheckoutSheetKitException |
'error_receiving_message' | Checkout Sheet Kit failed to receive a message from checkout. | Show checkout in a fallback WebView. |
CheckoutSheetKitException |
'error_sending_message' | Checkout Sheet Kit failed to send a message to checkout. | Show checkout in a fallback WebView. |
CheckoutSheetKitException |
'render_process_gone' | The render process for the checkout WebView is gone. | Show checkout in a fallback WebView. |
CheckoutSheetKitException |
'unknown' | An error in Checkout Sheet Kit has occurred, see error details for more info. | Show checkout in a fallback WebView. |
HttpException |
'http_error' | An unexpected server error has been encountered. | Show checkout in a fallback WebView. |
ClientException |
'client_error' | An unhandled client error was encountered. | Show checkout in a fallback WebView. |
CheckoutUnavailableException |
'unknown' | Checkout is unavailable for another reason, see error details for more info. | Show checkout in a fallback WebView. |
---
title: Checkout Sheet Kit Exception Hierarchy
---
classDiagram
CheckoutException <|-- ConfigurationException
CheckoutException <|-- CheckoutExpiredException
CheckoutException <|-- CheckoutSheetKitException
CheckoutException <|-- CheckoutUnavailableException
CheckoutUnavailableException <|-- HttpException
CheckoutUnavailableException <|-- ClientException
<<Abstract>> CheckoutException
CheckoutException : +String errorDescription
CheckoutException : +String errorCode
CheckoutException : +bool isRecoverable
class ConfigurationException{
note: "Store or checkout configuration issues."
}
class CheckoutExpiredException{
note: "Expired or invalid carts/checkouts."
}
class CheckoutUnavailableException{
note: "Unexpected errors."
}
class HttpException{
note: "Unexpected Http response"
+int statusCode
}
class ClientException{
note: "Unexpected client/web error"
}
class CheckoutSheetKitException{
note: "Error in Checkout Sheet Kit code"
}
App developers can use lifecycle events to monitor and log the status of a checkout session.
For behavioral monitoring, standard and custom Web Pixel events will be relayed back to your application through the onWebPixelEvent
checkout event processor function. The responsibility then falls on the application developer to ensure adherence to local regulations like GDPR and ePrivacy directive before disseminating these events to first-party and third-party systems.
Here's how you might intercept these events:
fun onWebPixelEvent(event: PixelEvent) {
if (!hasPermissionToCaptureEvents()) {
return
}
when (event) {
is StandardPixelEvent -> processStandardEvent(event)
is CustomPixelEvent -> processCustomEvent(event)
}
}
fun processStandardEvent(event: StandardPixelEvent) {
const endpoint = "https://example.com/pixel?id=${accountID}&uid=${userId}";
val payload = AnalyticsPayload(
eventTime: event.timestamp,
action: event.name,
details: event.data.checkout
)
// Send events to third-party servers
httpClient.post(endpoint, payload)
}
// ... other functions, incl. processCustomEvent(event)
Note
You may need to augment these events with customer/session information derived from app state.
Note
The customData
attribute of CustomPixelEvent can take on any shape. As such, this attribute will be returned as a String. Client applications should define a custom data type and deserialize the customData
string into that type.
Buyer-aware checkout experience reduces friction and increases conversion. Depending on the context of the buyer (guest or signed-in), knowledge of buyer preferences, or account/identity system, the application can use on of the following methods to initialize personalized and contextualized buyer experience.
In addition to specifying the line items, the Cart can include buyer identity (name, email, address, etc.), and delivery and payment preferences: see guide. Included information will be used to present pre-filled and pre-selected choices to the buyer within checkout.
Shopify Plus merchants using Classic Customer Accounts can use Multipass to integrate an external identity system and initialize a buyer-aware checkout session.
{
"email": "<Customer's email address>",
"created_at": "<Current timestamp in ISO8601 encoding>",
"remote_ip": "<Client IP address>",
"return_to": "<Checkout URL obtained from Storefront API>",
...
}
- Follow the Multipass documentation to create a
multipass
URL and set the
'return_to'
to be the obtainedcheckoutUrl
- Provide the Multipass URL to
ShopifyCheckoutSheetKit.present()
.
Important
the above JSON omits useful customer attributes that should be provided where possible and encryption and signing should be done server-side to ensure Multipass keys are kept secret.
Note
Multipass errors are not "recoverable" (See Error Handling) due to their one-time nature. Failed requests containing multipass URLs will require re-generating new tokens.
To initialize accelerated Shop Pay checkout, the cart can set a walletPreference to 'shop_pay'. The sign-in state of the buyer is app-local and the buyer will be prompted to sign in to their Shop account on their first checkout, and their sign-in state will be remembered for future checkout sessions.
We are working on a library to provide buyer sign-in and authentication powered by the new Customer Account API —stay tuned.
We welcome code contributions, feature requests, and reporting of issues. Please see guidelines and instructions.
Shopify's Checkout Sheet Kit is provided under an MIT License.