diff --git a/platforms/android/README.md b/platforms/android/README.md index 715604a7..942564bd 100644 --- a/platforms/android/README.md +++ b/platforms/android/README.md @@ -1,120 +1,93 @@ # Shopify Checkout Kit - Android -[![GitHub license](https://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat)](/LICENSE) +[![MIT License](https://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat)](../../LICENSE) ![Tests](https://github.com/Shopify/checkout-kit/actions/workflows/test.yml/badge.svg?branch=main) -[![GitHub Release](https://img.shields.io/github/release/shopify/checkout-kit.svg?style=flat)]() -gradients +Checkout Kit > [!WARNING] -> **Alpha — early preview.** This software is an early preview and is **not** -> production-ready. Stability is not guaranteed, and breaking changes may -> occur in any release. See [Getting Started](#getting-started). +> **Alpha - early preview.** This software is an early preview and is **not** +> production-ready. The first Checkout Kit for Android alpha is `4.0.0-alpha.1`. +> Stability is not guaranteed, and breaking changes may occur in any release. -**Shopify's Checkout 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, 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 Kit is built](https://www.shopify.com/partners/blog/mobile-checkout-sdks-for-ios-and-android). +**Checkout Kit for Android** lets Android apps present Shopify checkout in a native dialog while preserving store checkout customizations such as Checkout UI extensions, Shopify Functions, branding, and supported payment methods. -**Note**: This library was previously published as `com.shopify:checkout-sheet-kit`. It has been renamed to `com.shopify:checkout-kit`. Update your Gradle/Maven dependency if upgrading from an older version. +> [!NOTE] +> This package was previously published as `com.shopify:checkout-sheet-kit`. New integrations should use `com.shopify:checkout-kit`. - [Requirements](#requirements) -- [Getting Started](#getting-started) +- [Install](#install) - [Gradle](#gradle) - [Maven](#maven) -- [Basic Usage](#basic-usage) -- [Configuration](#configuration) - - [Color Scheme](#color-scheme) - - [Log Level](#log-level) - - [Checkout Dialog Title](#checkout-dialog-title) -- [Monitoring the lifecycle of a checkout session](#monitoring-the-lifecycle-of-a-checkout-session) +- [Get a checkout URL](#get-a-checkout-url) +- [Present checkout](#present-checkout) +- [Configure checkout](#configure-checkout) + - [Color schemes](#color-schemes) + - [Title localization](#title-localization) + - [Current configuration](#current-configuration) +- [Checkout lifecycle](#checkout-lifecycle) - [Error handling](#error-handling) - - [`CheckoutException`](#checkoutexception) - - [Exception Hierarchy](#exception-hierarchy) -- [Integrating identity \& customer accounts](#integrating-identity--customer-accounts) - - [Cart: buyer bag, identity, and preferences](#cart-buyer-bag-identity-and-preferences) - - [Multipass](#multipass) - - [Shop Pay](#shop-pay) - - [Customer Account API](#customer-account-api) +- [Browser and system callbacks](#browser-and-system-callbacks) +- [Authentication and buyer identity](#authentication-and-buyer-identity) +- [Offsite payments and links](#offsite-payments-and-links) +- [Troubleshooting](#troubleshooting) +- [Samples](#samples) - [Contributing](#contributing) - [License](#license) ## Requirements - JDK 17+ -- Android minSdk 23+ -- Android compileSdk 35+ -- Chrome >= 80 +- Android `minSdk` 23+ +- Android `compileSdk` 35+ for consuming apps. This repository currently builds the library with `compileSdk` 36. -## Getting Started +## Install -The SDK is an [open source Android library](https://central.sonatype.com/artifact/com.shopify/checkout-kit). As a quick start, see -[sample projects](samples/README.md) or use one of the following ways to integrate the SDK into -your project: +For alpha testing, install the exact version shown below. The first Checkout Kit for Android alpha is `4.0.0-alpha.1`. ### Gradle ```groovy -implementation "com.shopify:checkout-kit:4.0.0-alpha.1" +dependencies { + implementation "com.shopify:checkout-kit:4.0.0-alpha.1" +} ``` ### Maven ```xml - - com.shopify - checkout-kit - 4.0.0-alpha.1 + com.shopify + checkout-kit + 4.0.0-alpha.1 ``` -## Basic Usage - -Once the SDK has been added as a dependency, you can import the library: - -```kotlin -import com.shopify.checkoutkit.ShopifyCheckoutKit -``` +## Get a checkout URL -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](https://shopify.dev/docs/api/storefront) -to assemble a cart (via `cartCreate` and related update mutations) and load the -[`checkoutUrl`](https://shopify.dev/docs/api/storefront/latest/objects/Cart#field-cart-checkouturl). Alternatively, a [cart permalink](https://help.shopify.com/en/manual/products/details/cart-permalink) can be provided. -You can use any GraphQL client to obtain a checkout URL and we recommend -Shopify's [Mobile Buy SDK for Android](https://github.com/Shopify/mobile-buy-sdk-android) to -simplify the development workflow: +Checkout Kit presents a standard Shopify checkout URL. The common flow is: -```kotlin +1. Create or update a cart with the [Storefront GraphQL API](https://shopify.dev/docs/api/storefront), for example with [`cartCreate`](https://shopify.dev/docs/api/storefront/2026-04/mutations/cartCreate) and related cart mutations. +2. Read the cart's [`checkoutUrl`](https://shopify.dev/docs/api/storefront/2026-04/objects/Cart#field-cart-checkouturl). +3. Pass that URL, or a [cart permalink](https://help.shopify.com/en/manual/products/details/cart-permalink), to Checkout Kit. -val client = GraphClient.build( - context = applicationContext, - shopDomain = "yourshop.myshopify.com", - accessToken = "" -) +You can use any GraphQL client. The sample app uses Apollo Kotlin and is a complete reference for a modern Storefront API cart flow. -val cartQuery = Storefront.query { query -> - query.cart(ID(id)) { - it.checkoutUrl() - } -} +For production use, see the [Storefront API GraphiQL Explorer](https://shopify.dev/docs/storefronts/headless/building-with-the-storefront-api/getting-started) for schema exploration and the [`cartCreate`](https://shopify.dev/docs/api/storefront/2026-04/mutations/cartCreate) mutation reference for the full input shape, including buyer identity, attributes, discount codes, delivery addresses, and delivery options. -client.queryGraph(cartQuery).enqueue { - if (it is GraphCallResult.Success) { - val checkoutUrl = it.response.data?.cart?.checkoutUrl - } -} -``` +## Present checkout -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: +Use the Kotlin builder when presenting from a `ComponentActivity`: ```kotlin -fun presentCheckout() { - val checkoutUrl = cart.checkoutUrl - ShopifyCheckoutKit.present(checkoutUrl, context) { +import com.shopify.checkoutkit.ShopifyCheckoutKit + +fun presentCheckout(checkoutUrl: String, activity: ComponentActivity) { + ShopifyCheckoutKit.present(checkoutUrl, activity) { onFail { error -> handleCheckoutError(error) } + onCancel { resetCheckoutUi() } @@ -122,326 +95,252 @@ fun presentCheckout() { } ``` -> [!NOTE] -> Pass the standard `checkoutUrl` returned by Storefront API or a cart permalink. -> Checkout Kit adds the required UCP query parameters automatically when it loads checkout, -> so you do not need to rewrite the URL yourself. +Checkout Kit adds the required checkout protocol parameters when checkout loads. -If you also want typed Embedded Checkout Protocol (ECP) callbacks, connect a -`CheckoutProtocol.Client` inside the same Kotlin builder: +For Java integrations or shared listener implementations, extend `DefaultCheckoutListener`: ```kotlin -val checkoutProtocolClient = CheckoutProtocol.Client() - .on(CheckoutProtocol.start) { checkout -> - // Checkout is ready and interactive. - } - .on(CheckoutProtocol.complete) { checkout -> - // Typed checkout payload emitted when checkout completes. - navigateToConfirmation(checkout) +val listener = object : DefaultCheckoutListener() { + override fun onCheckoutFailed(error: CheckoutException) { + handleCheckoutError(error) } -ShopifyCheckoutKit.present(checkoutUrl, activity) { - connect(checkoutProtocolClient) + override fun onCheckoutCanceled() { + resetCheckoutUi() + } } + +ShopifyCheckoutKit.present(checkoutUrl, activity, listener) ``` -## Configuration +The `present` call returns a `CheckoutKitDialog?`. Keep it if you need to dismiss the dialog programmatically: + +```kotlin +val checkoutDialog = ShopifyCheckoutKit.present(checkoutUrl, activity) { + onCancel { resetCheckoutUi() } +} -The SDK provides a way to customize the presented checkout experience via -the `ShopifyCheckoutKit.configure` function. +checkoutDialog?.dismiss() +``` -### Color Scheme +## Configure checkout -By default, the SDK will match the user's device color appearance. This behavior can be customized -via the `colorScheme` property: +Configure global presentation defaults before presenting checkout: ```kotlin ShopifyCheckoutKit.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), - ) - ) + it.logLevel = LogLevel.ERROR } ``` -> [!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) - -```kotlin -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), - ) -) -``` +| Option | Default | Purpose | +| --- | --- | --- | +| `colorScheme` | `ColorScheme.Automatic()` | Use device appearance, force `Light` or `Dark`, or use `Web` to match web checkout branding. | +| `logLevel` | `LogLevel.WARN` | SDK logging verbosity. Use `LogLevel.DEBUG` during integration. | -**Close Icon Customization** - -The close icon in the checkout dialog header can be customized using the `customize` method for an ergonomic API: +### Color schemes ```kotlin ShopifyCheckoutKit.configure { - it.colorScheme = ColorScheme.Light().customize { - // Option 1: Just tint the default close icon - closeIconTint = Color.ResourceId(R.color.my_custom_tint_color) - - // Option 2: Use a completely custom drawable - closeIcon = DrawableResource(R.drawable.my_custom_close_icon) - } + it.colorScheme = ColorScheme.Light() + it.colorScheme = ColorScheme.Dark() + it.colorScheme = ColorScheme.Web() + it.colorScheme = ColorScheme.Automatic() } ``` -For automatic theme switching, you can provide different customizations for light and dark modes: +Customize native colors with resource IDs or sRGB integers: ```kotlin ShopifyCheckoutKit.configure { it.colorScheme = ColorScheme.Automatic().customize( light = { - closeIconTint = Color.ResourceId(R.color.light_tint) + 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) + closeIconTint = Color.ResourceId(R.color.checkout_close_light) }, dark = { - closeIconTint = Color.ResourceId(R.color.dark_tint) - } + 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) + closeIconTint = Color.ResourceId(R.color.checkout_close_dark) + }, ) } ``` -> [!Note] -> If both `closeIcon` and `closeIconTint` are provided, the custom drawable (`closeIcon`) takes precedence and the tint is ignored. - -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. -- closeIcon - Used to provide a completely custom close icon drawable -- closeIconTint - Used to tint the default close icon with a custom color - -The current configuration can be obtained by calling `ShopifyCheckoutKit.getConfiguration()`. - -### Log Level - -Enable additional debug logs via the `logLevel` configuration option. +If both `closeIcon` and `closeIconTint` are set, the custom drawable takes precedence: ```kotlin ShopifyCheckoutKit.configure { - it.logLevel = LogLevel.DEBUG + it.colorScheme = ColorScheme.Light().customize { + closeIcon = DrawableResource(R.drawable.ic_checkout_close) + } } ``` -### Checkout Dialog Title +### Title localization -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: +Override `checkout_web_view_title` in your app resources: ```xml -Buy Now! + + Buy now + +``` + +### Current configuration + +```kotlin +val configuration = ShopifyCheckoutKit.getConfiguration() ``` -## Monitoring the lifecycle of a checkout session +## Checkout lifecycle -For Kotlin integrations, use `CheckoutProtocol.Client` to observe typed checkout notifications -such as `ec.start`, `ec.complete`, and the incremental checkout state updates. The same -`present(...)` builder can wire fail/cancel callbacks plus optional browser/system hooks: +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](../../protocol/services/shopping/embedded.openrpc.json). ```kotlin -ShopifyCheckoutKit.present(checkoutUrl, activity) { - connect( - CheckoutProtocol.Client() - .on(CheckoutProtocol.start) { checkout -> - // Observe typed checkout notifications. - } - .on(CheckoutProtocol.complete) { checkout -> - // Handle successful checkout completion. - } - ) +import com.shopify.checkoutkit.CheckoutProtocol - onShowFileChooser { webView, filePathCallback, fileChooserParams -> - // Return true if the host app handled the chooser request. - false +val protocolClient = CheckoutProtocol.Client() + .on(CheckoutProtocol.start) { checkout -> + // Checkout is loaded and interactive. } - - onGeolocationPermissionsShowPrompt { origin, callback -> - // Called to tell the client to show a geolocation permissions prompt as a geolocation - // request has been made. Invoked for example if a customer uses `Use my location` - // for pickup points. + .on(CheckoutProtocol.complete) { checkout -> + // The order was completed. Clear or refresh the local cart. } - - onGeolocationPermissionsHidePrompt { - // Called to tell the client to hide the geolocation permissions prompt. + .on(CheckoutProtocol.totalsChange) { checkout -> + // React to updated totals. } - - onPermissionRequest { permissionRequest -> - // Called when a web permission has been requested, e.g. to access the camera. + .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() } } ``` -If you prefer a reusable object, or are integrating from Java, extend -`DefaultCheckoutListener` and pass it to the existing `present(...)` overload: +`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. + +### Error handling + +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. | + +## Browser and system callbacks + +Android apps must decide how to handle file choosers, web permissions, and geolocation prompts requested by checkout. ```kotlin -val listener = object : DefaultCheckoutListener() { - override fun onShowFileChooser( - webView: WebView, - filePathCallback: ValueCallback>, - fileChooserParams: FileChooserParams, - ): Boolean { - return activity.onShowFileChooser(filePathCallback, fileChooserParams) +ShopifyCheckoutKit.present(checkoutUrl, activity) { + onShowFileChooser { webView, filePathCallback, fileChooserParams -> + // Launch your ActivityResultContract and return true if handled. + false } - override fun onGeolocationPermissionsShowPrompt(origin: String, callback: GeolocationPermissions.Callback) { - activity.onGeolocationPermissionsShowPrompt(origin, callback) + onPermissionRequest { permissionRequest -> + // Grant, deny, or proxy web permissions such as camera access. } - override fun onGeolocationPermissionsHidePrompt() { - activity.onGeolocationPermissionsHidePrompt() + onGeolocationPermissionsShowPrompt { origin, callback -> + // Request Android location permission, then invoke callback.invoke(...). } - override fun onPermissionRequest(permissionRequest: PermissionRequest) { - // Grant, deny, or proxy requested web permissions. + onGeolocationPermissionsHidePrompt { + // Hide any visible geolocation prompt. } } +``` -ShopifyCheckoutKit.present(checkoutUrl, context, listener) +Declare location permissions if checkout uses pickup points or "Use my location": + +```xml + + ``` -> [!Note] -> The `DefaultCheckoutListener` overload remains available for reusable or Java-facing -> integrations and provides default implementations for optional browser/system callbacks. +## Authentication and buyer identity -### Error handling +Checkout Kit does not create carts or authenticate buyers. Add buyer context to the cart before presenting checkout: -Checkout failures are delivered to `onFail { ... }` or `onCheckoutFailed(...)` as -`CheckoutException` values. Inspect the exception type and error code to decide whether to -recreate the cart, retry later, or show an error state in the host app. - -#### `CheckoutException` - -| Exception Class | Error Code | Description | Recommendation | -| ------------------------------ | ------------------------------ | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | -| `CheckoutExpiredException` | 'cart_expired' | The cart or checkout is no longer available. | Create a new cart and open a new checkout URL. | -| `CheckoutKitException` | 'render_process_gone' | The render process for the checkout WebView is gone. | Handle as a checkout failure in the host app. | -| `HttpException` | 'http_error' | An unexpected server error has been encountered. | Handle as a checkout failure in the host app. | - -#### Exception Hierarchy - -```mermaid ---- -title: Checkout Kit Exception Hierarchy ---- -classDiagram - CheckoutException <|-- CheckoutExpiredException - CheckoutException <|-- CheckoutKitException - CheckoutException <|-- CheckoutUnavailableException - CheckoutUnavailableException <|-- HttpException - - <> CheckoutException - CheckoutException : +String errorDescription - CheckoutException : +String errorCode - class CheckoutExpiredException{ - note: "Expired checkouts." - } - class CheckoutUnavailableException{ - note: "Base class for availability failures." - } - class HttpException{ - note: "Unexpected Http response" - +int statusCode - } - class CheckoutKitException{ - note: "Error in Checkout Kit code" - } -``` +- 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_to` to the checkout URL. -## Integrating identity & customer accounts +Keep Multipass secrets out of client-side code. -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 one of the following methods to initialize personalized and contextualized buyer -experience. +## Offsite payments and links -### Cart: buyer bag, identity, and preferences +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. -In addition to specifying the line items, the Cart can include buyer identity (name, email, address, -etc.), and delivery and payment preferences: -see [guide](https://shopify.dev/docs/custom-storefronts/building-with-the-storefront-api/cart/manage). -Included information will be used to present pre-filled and pre-selected choices to the buyer within -checkout. +Checkout Kit opens external HTTPS links, `mailto:`, `tel:`, and custom-scheme links through Android intents. Make sure your app has: -### Multipass +- 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. -[Shopify Plus](https://help.shopify.com/en/manual/intro-to-shopify/pricing-plans/plans-features/shopify-plus-plan) -merchants -using [Classic Customer Accounts](https://help.shopify.com/en/manual/customers/customer-accounts/classic-customer-accounts) -can use [Multipass](https://shopify.dev/docs/api/multipass) to integrate an external identity system -and initialize a buyer-aware checkout session. +## Troubleshooting -```json -{ - "email": "", - "created_at": "", - "remote_ip": "", - "return_to": "", - ... -} -``` +- Use `LogLevel.DEBUG` while integrating. +- 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_required` and are not supported by Checkout Kit. -1. Follow the [Multipass documentation](https://shopify.dev/docs/api/multipass) to create a - multipass - URL and set the `'return_to'` to be the obtained `checkoutUrl` -2. Provide the Multipass URL to `ShopifyCheckoutKit.present()`. +## Samples -> [!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. +See [samples](samples/README.md). `MobileBuyIntegration` 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. -### Shop Pay +## Contributing -To initialize accelerated Shop Pay checkout, the cart can set a -[walletPreference](https://shopify.dev/docs/api/storefront/latest/mutations/cartBuyerIdentityUpdate#field-cartbuyeridentityinput-walletpreferences) -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. +See [CONTRIBUTING](../../.github/CONTRIBUTING.md). -### Customer Account API +Useful checks before opening an Android change: -The Customer Account API allows you to authenticate buyers and provide a personalized checkout experience. -For detailed implementation instructions, see our [Customer Account API Authentication Guide](https://shopify.dev/docs/storefronts/headless/mobile-apps/checkout-kit/authenticate-checkouts). +```sh +cd platforms/android +./gradlew :lib:build +./gradlew clean test --console=plain +./gradlew detekt lintRelease +``` ---- +For sample app changes, run: -## Contributing +```sh +cd platforms/android/samples/MobileBuyIntegration +./gradlew build +``` + +For public API changes, run: -We welcome code contributions, feature requests, and reporting of issues. Please -see [guidelines and instructions](.github/CONTRIBUTING.md). +```sh +cd platforms/android +./gradlew :lib:apiCheck +``` ## License -Shopify's Checkout Kit is provided under an [MIT License](LICENSE). +Checkout Kit is available under the [MIT license](../../LICENSE). diff --git a/platforms/android/samples/MobileBuyIntegration/README.md b/platforms/android/samples/MobileBuyIntegration/README.md index cf054942..6db7d032 100644 --- a/platforms/android/samples/MobileBuyIntegration/README.md +++ b/platforms/android/samples/MobileBuyIntegration/README.md @@ -1,152 +1,149 @@ # MobileBuyIntegration Sample App -A sample Android app demonstrating how to integrate [Checkout Kit](../../README.md) with the Shopify Storefront API using [Apollo Kotlin](https://github.com/apollographql/apollo-kotlin). +This sample demonstrates how to integrate Checkout Kit with the Shopify Storefront API using Apollo Kotlin. -## Checkout flow +## What it covers -The sample's cart flow demonstrates the Kotlin-first `ShopifyCheckoutKit.present(checkoutUrl, activity) { ... }` -API. It connects a typed `CheckoutProtocol.Client` to observe checkout state changes, including -completion, and uses the presentation builder for fail/cancel plus the sample's file chooser and -geolocation host callbacks so browser and system hooks stay on the Kotlin-first path. +- Product and collection browsing from the Storefront API +- Cart create, add, update, remove, and fetch operations +- `cart.checkoutUrl` presentation with `ShopifyCheckoutKit` +- Typed checkout lifecycle events through `CheckoutProtocol.Client` +- Checkout fail/cancel callbacks through the presentation builder +- File chooser, geolocation, and web permission host callbacks +- Buyer identity demo data for checkout prefill +- Customer Account API sign-in and customer access token cart identity -## Architecture +## Checkout flow -The app uses **Apollo GraphQL** for all Storefront API communication. GraphQL operations are defined as `.graphql` files, and Apollo Kotlin's code generation tool produces type-safe Kotlin data classes from them. +The sample's cart flow demonstrates the Kotlin-first `ShopifyCheckoutKit.present(checkoutUrl, activity) { ... }` API. It connects a typed `CheckoutProtocol.Client` to observe checkout state changes, including completion, and uses the presentation builder for fail/cancel plus the sample's file chooser, geolocation, and web permission host callbacks. -### Storefront API layer +## Architecture +The app uses Apollo Kotlin for Storefront API communication. GraphQL operations are defined as `.graphql` files, and Apollo Kotlin's code generation tool produces type-safe Kotlin data classes from them. + +```text +MobileBuyIntegration/ +|-- app/ +| |-- src/main/graphql/ Source of truth - edit these files +| | |-- schema.graphqls Storefront API schema, downloaded +| | |-- CartFragment.graphql Reusable cart fields +| | |-- CartCreate.graphql Create a new cart +| | |-- CartLinesAdd.graphql Add items to cart +| | |-- CartLinesUpdate.graphql Update item quantities +| | |-- CartLinesRemove.graphql Remove items from cart +| | |-- FetchProducts.graphql Product listing query +| | |-- FetchProduct.graphql Single product query +| | |-- FetchCollections.graphql Collection listing query +| | |-- FetchCollection.graphql Single collection query +| | |-- ProductFragment.graphql Reusable product fields +| | `-- ProductVariantFragment.graphql Reusable product variant fields +| |-- build/generated/source/apollo/ Apollo-generated Kotlin types +| | `-- storefront/.../graphql/ +| | |-- CartCreateMutation.kt +| | |-- FetchProductsQuery.kt +| | |-- fragment/CartFragment.kt +| | |-- type/CartInput.kt +| | `-- type/CartLineInput.kt +| `-- src/main/java/.../ +| |-- common/client/StorefrontApiClient.kt Apollo client wrapper and Storefront operations +| |-- cart/data/CartRepository.kt Cart state, buyer identity, and cart mutations +| |-- products/product/data/ Product detail repository +| |-- products/collection/data/ Collection repository +| |-- settings/authentication/ Customer Account API sign-in flow +| |-- cart/CartViewModel.kt Checkout presentation and protocol handlers +| `-- MainActivity.kt File chooser and geolocation permission callbacks +`-- .env Local store configuration, not checked in ``` -app/src/main/graphql/ # Source of truth — you edit these -├── schema.graphqls # API schema (downloaded) -├── CartFragment.graphql # Reusable cart fields -├── CartCreate.graphql # Create a new cart -├── CartLinesAdd.graphql # Add items to cart -├── CartLinesUpdate.graphql # Update item quantities -├── CartLinesRemove.graphql # Remove items from cart -├── FetchProducts.graphql # Product listing query -├── FetchProduct.graphql # Single product query -├── FetchCollections.graphql # Collection listing query -└── FetchCollection.graphql # Single collection query - -app/build/generated/source/apollo/ # Auto-generated — do not edit -└── storefront/.../graphql/ - ├── CartCreateMutation.kt - ├── FetchProductsQuery.kt - ├── type/CartInput.kt, CartLineInput.kt, ... - └── fragment/CartFragment.kt -``` - -**`app/src/main/graphql/`** contains the `.graphql` files you write and maintain. These define which fields the app fetches from the Storefront API. -**`app/build/generated/source/apollo/`** contains Kotlin code produced by Apollo's code generation tool. These files should not be edited by hand — they are regenerated from the `.graphql` files and the schema. +Do not edit files in `app/build/generated/source/apollo/` by hand. Update `.graphql` files and regenerate Apollo types instead. ### How it works -1. `StorefrontApiClient.kt` creates an `ApolloClient` that points at the store's Storefront API endpoint and attaches the access token via an HTTP header. It is provided as a singleton via Koin dependency injection. -2. Repository classes (`CartRepository`, `ProductRepository`, `ProductCollectionRepository`) call suspend functions on the client using the generated operation types (e.g. `CartCreateMutation`, `FetchProductsQuery`). -3. Responses are automatically decoded into the generated Kotlin types, giving you compile-time safety on every field access. +1. `StorefrontApiClient.kt` wraps an `ApolloClient`, points it at the configured Storefront API endpoint, and executes generated query and mutation types. +2. Repository classes such as `CartRepository`, `ProductRepository`, and `ProductCollectionRepository` map generated Storefront API responses into local UI state. +3. `CartViewModel.kt` presents checkout with `ShopifyCheckoutKit.present`, connects `CheckoutProtocol.Client`, and forwards browser/system callbacks to `MainActivity`. +4. Apollo decodes responses into generated Kotlin types, so schema or operation changes surface as compile errors. ## Setup -1. Copy the config template and fill in your store credentials: - - ```bash - cp .env.example .env - ``` - - Then edit `.env` with your values: - - ``` - STOREFRONT_DOMAIN=your-store.myshopify.com - STOREFRONT_ACCESS_TOKEN=your-token - API_VERSION=2025-07 - ``` - -2. Open the project in Android Studio and sync Gradle. - -3. Build and run. - -## Updating the Storefront API version - -When you want to target a newer Storefront API version (e.g. to access new fields or features), follow these steps: - -### 1. Update the API version +From this directory: -Edit your `.env` and change the `API_VERSION` value: - -``` -API_VERSION=2025-10 +```sh +cp .env.example .env ``` -### 2. Download the new schema +Edit `.env`: -The schema defines what types and fields are available in the API. Run from the **repo root** (`checkout-kit/`): - -```bash -dev apollo download_schema android +```text +STOREFRONT_DOMAIN=your-store.myshopify.com +STOREFRONT_ACCESS_TOKEN=your-token +API_VERSION=2025-07 ``` -This uses [`rover`](https://www.apollographql.com/docs/rover/) to introspect your store's Storefront API at the configured version and writes `schema.graphqls` into `app/src/main/graphql/`. - -### 3. Update your GraphQL operations (if needed) - -If the new API version introduces fields you want to use, or deprecates fields you currently use, edit the `.graphql` files in `app/src/main/graphql/`. - -For example, to add a new field to products: +Optional values enable Customer Account API and buyer identity demo flows: -```graphql -# app/src/main/graphql/FetchProducts.graphql -query FetchProducts(...) { - products(first: $numProducts) { - nodes { - id - title - myNewField # <-- add new fields here - ... - } - } -} +```text +CUSTOMER_ACCOUNT_API_CLIENT_ID=your-client-id +CUSTOMER_ACCOUNT_API_REDIRECT_URI=shop..app://callback +CUSTOMER_ACCOUNT_API_GRAPHQL_BASE_URL=https://shopify.com//account/customer/api//graphql +CUSTOMER_ACCOUNT_API_AUTH_BASE_URL=https://shopify.com/authentication/ +PREFILL_EMAIL=test.buyer@example.com +PREFILL_PHONE=+16135550123 ``` -### 4. Run code generation +Open the project in Android Studio, sync Gradle, then build and run. -From the **repo root**: - -```bash -dev apollo codegen android -``` +## Updating the Storefront API version -This reads the schema + your `.graphql` files and regenerates the Kotlin code in `app/build/generated/source/apollo/`. The command also runs `dev android lint` to check formatting. +1. Update `API_VERSION` in `.env`. +2. Download the schema with Rover. This introspects your store's Storefront API and writes `schema.graphqls` into `app/src/main/graphql/`. -### 5. Build and fix any issues + ```sh + rover graph introspect \ + "https://$STOREFRONT_DOMAIN/api/$API_VERSION/graphql" \ + --header="X-Shopify-Storefront-Access-Token: $STOREFRONT_ACCESS_TOKEN" \ + --output "app/src/main/graphql/schema.graphqls" + ``` -```bash -./gradlew :app:assembleDebug -``` +3. Update GraphQL operations in `app/src/main/graphql/` if the schema changed. For example, add a product field to `FetchProducts.graphql` before regenerating types: + + ```graphql + query FetchProducts(...) { + products(first: $numProducts) { + nodes { + id + title + myNewField + } + } + } + ``` -If the new schema removed or renamed fields, you'll get compile errors pointing you to the exact lines that need updating. +4. Regenerate Kotlin types with Apollo Kotlin. This reads the schema and `.graphql` files, then regenerates Kotlin code in `app/build/generated/source/apollo/`. -## Dev commands reference + ```sh + ./gradlew :app:generateApolloSources + ``` -All commands are run from the **repo root** (`checkout-kit/`): +5. Build and fix any compile errors from schema changes: -| Command | Description | -|---------|-------------| -| `dev apollo download_schema android` | Download the Storefront API schema for this sample app | -| `dev apollo codegen android` | Regenerate Kotlin types from `.graphql` files | -| `dev android lint` | Run detekt + Android lint checks | -| `dev android build` | Build the library | -| `dev android build samples` | Build all sample applications | -| `dev android test` | Run all tests | + ```sh + ./gradlew :app:assembleDebug + ``` ## Key files | File | Purpose | -|------|---------| -| `app/src/main/graphql/schema.graphqls` | Storefront API schema (downloaded, not hand-written) | -| `app/build.gradle` | Apollo plugin config + BuildConfig fields | -| `.env` | Store credentials + API version (not checked into git) | -| `StorefrontApiClient.kt` | Apollo client setup, auth header | -| `CartRepository.kt` | Cart state, create/add/update/remove operations | -| `ProductRepository.kt` | Product fetching operations | -| `ProductCollectionRepository.kt` | Collection fetching operations | +| --- | --- | +| `.env` | Store credentials, API version, Customer Account API values, and demo buyer identity. | +| `app/build.gradle` | Apollo plugin configuration and `BuildConfig` values from `.env`. | +| `app/src/main/graphql/schema.graphqls` | Storefront API schema. | +| `common/client/StorefrontApiClient.kt` | Apollo client setup and Storefront API auth header. | +| `cart/data/CartRepository.kt` | Cart state and Storefront API mutations. | +| `products/product/data/ProductRepository.kt` | Product detail Storefront API calls. | +| `products/collection/data/ProductCollectionRepository.kt` | Collection Storefront API calls. | +| `settings/authentication/data/CustomerRepository.kt` | Customer Account API token exchange and customer lookup. | +| `common/navigation/CheckoutKitNavHost.kt` | App navigation. | +| `cart/CartViewModel.kt` | Checkout presentation, fail/cancel callbacks, and protocol lifecycle handlers. | +| `MainActivity.kt` | File chooser and geolocation permission callbacks. | +| `settings/authentication/` | Customer Account API sign-in screens and WebView flow. | diff --git a/platforms/android/samples/README.md b/platforms/android/samples/README.md index d1efe330..42f62677 100644 --- a/platforms/android/samples/README.md +++ b/platforms/android/samples/README.md @@ -1,10 +1,25 @@ -# Sample Projects +# Android Samples -The project directory contains a `.env.example` file. -Simply rename it to `.env` and update the contained values to match your Shopify storefront. +This directory contains Android sample apps for Checkout Kit. ---- +| Sample | Purpose | +| --- | --- | +| `MobileBuyIntegration` | Storefront API cart flow with Apollo Kotlin, checkout presentation, typed protocol lifecycle events, file chooser handling, geolocation callbacks, buyer identity demo data, and Customer Account API sign-in. | -### MobileBuyIntegration +## Setup -This project demonstrates how to use the [Mobile Buy SDK](https://github.com/Shopify/mobile-buy-sdk-android) in conjunction with the `Shopify Checkout Kit` library. +From `platforms/android/samples/MobileBuyIntegration`: + +```sh +cp .env.example .env +``` + +Fill in: + +- `STOREFRONT_DOMAIN` +- `STOREFRONT_ACCESS_TOKEN` +- `API_VERSION` +- Optional Customer Account API values +- Optional demo buyer identity values + +Open `MobileBuyIntegration` in Android Studio, sync Gradle, then build and run the `app` target.