diff --git a/README.md b/README.md index 010d4372..4975cbb3 100644 --- a/README.md +++ b/README.md @@ -8,49 +8,85 @@ [![MIT License](https://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat)](LICENSE) -[Getting Started](https://shopify.dev/docs/storefronts/mobile/checkout-kit)  |  [Docs](https://shopify.dev/docs/storefronts/mobile)  |  [Contributing](.github/CONTRIBUTING.md)  |  [Code of Conduct](.github/CODE_OF_CONDUCT.md)  |  [License](LICENSE) +[Shopify.dev docs](https://shopify.dev/docs/storefronts/mobile/checkout-kit)  |  [Contributing](.github/CONTRIBUTING.md)  |  [Code of Conduct](.github/CODE_OF_CONDUCT.md)  |  [License](LICENSE) -Shopify's **Checkout Kit** lets native mobile apps embed Shopify's one-page checkout while preserving store customizations like Checkout UI extensions, Shopify Functions, and branding. It also keeps the experience idiomatic to the host platform with light and dark mode support, lifecycle hooks, and native APIs to embed and customize checkout. +**Shopify Checkout Kit** lets native mobile apps and web storefronts present Shopify's one-page checkout while preserving checkout customizations such as Checkout UI extensions, Shopify Functions, branding, Shop Pay, and supported payment methods. -## Documentation +## What is in this repo -This respository houses the implementation of Checkout Kit, use it to check implementation or open issues if you find a bug. -The [Shopify mobile storefront docs](https://shopify.dev/docs/storefronts/mobile) are where we maintain installation and step by step guides for common integration patterns. +This repository contains the Checkout Kit implementations, samples, and protocol bindings. Use it to inspect platform behavior, run samples, report bugs, and contribute fixes. The Shopify.dev mobile storefront docs remain the primary place for end-to-end product guidance and conceptual walkthroughs. -Feature guides: +## Packages -- [Preload checkout](https://shopify.dev/docs/storefronts/mobile/checkout-kit/preloading) - fetch checkout in the background so it's ready when buyers are. -- [Monitor the checkout lifecycle](https://shopify.dev/docs/storefronts/mobile/checkout-kit/monitor-checkout-lifecycle) - handle completion, failure, and cancellation events. -- [Authenticate checkouts](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts) - sign buyers in to prefill saved addresses and payment methods. -- [Privacy compliance](https://shopify.dev/docs/storefronts/mobile/checkout-kit/privacy-compliance) - pass GDPR, CCPA, and ATT consent through to Shopify. -- [Accelerated checkouts](https://shopify.dev/docs/storefronts/mobile/checkout-kit/accelerated-checkouts?extension=react-native) - Shop Pay and Apple Pay buttons for one-tap purchase on product and cart pages. +| Package | Checkout Kit release | Install channel | Status | Description | README | +| --- | --- | --- | --- | --- | --- | +| `ShopifyCheckoutKit` | `4.0.0-alpha.1` | Swift Package Manager, CocoaPods | Alpha | iOS checkout presentation SDK. | [Swift](platforms/swift/README.md) | +| `ShopifyAcceleratedCheckouts` | `4.0.0-alpha.1` | Swift Package Manager, CocoaPods subspec | Alpha | SwiftUI Shop Pay and Apple Pay accelerated checkout buttons for iOS 16+. | [Swift](platforms/swift/README.md#accelerated-checkouts) | +| `com.shopify:checkout-kit` | `4.0.0-alpha.1` | Maven Central | Alpha | Android checkout dialog SDK. | [Android](platforms/android/README.md) | +| `@shopify/checkout-kit-react-native` | `4.0.0-alpha.1` | npm `next` dist-tag | Alpha | React Native wrapper for the iOS and Android native SDKs. | [React Native](platforms/react-native/README.md) | +| `@shopify/checkout-kit` | `4.0.0-alpha.2` | npm `next` dist-tag | Alpha | Web component for opening Shopify checkout from a web page. | [Web](platforms/web/README.md) | +| `ShopifyCheckoutProtocol` | Source package | Swift Package Manager | Internal/supporting | Swift client for Embedded Checkout Protocol messages. | [Protocol Swift](protocol/languages/swift/README.md) | -## Packages in this repo +Swift, Android, and React Native rows show the planned first Checkout Kit alpha releases. Web is already published as `4.0.0-alpha.2`. -Checkout Kit is a monorepo containing all the platforms Checkout Kit supports together. +## Platform Support -| Package | Latest version | Install channel | Description | Readme | -| ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- | --------------------------------------------------------------- | ------------------------------------------ | -| [`ShopifyCheckoutKit` `ShopifyAcceleratedCheckouts`](platforms/swift/) | [![GitHub tag](https://img.shields.io/github/v/tag/Shopify/checkout-kit?label=SPM)](https://github.com/Shopify/checkout-kit/tags) [![CocoaPods](https://img.shields.io/cocoapods/v/ShopifyCheckoutKit.svg?label=CocoaPods)](https://cocoapods.org/pods/ShopifyCheckoutKit) | Swift Package Manager, CocoaPods | iOS checkout presentation and accelerated checkout libraries. | [Readme](platforms/swift/README.md) | -| [`com.shopify:checkout-kit`](platforms/android/) | [![Maven Central](https://img.shields.io/maven-central/v/com.shopify/checkout-kit.svg?label=Maven%20Central)](https://central.sonatype.com/artifact/com.shopify/checkout-kit) | Maven Central | Android checkout presentation and accelerated checkout support. | [Readme](platforms/android/README.md) | -| [`@shopify/checkout-kit-react-native`](platforms/react-native/) | [![npm latest](https://img.shields.io/npm/v/@shopify/checkout-kit-react-native/latest.svg?label=npm)](https://www.npmjs.com/package/@shopify/checkout-kit-react-native) | npm | React Native wrapper for Checkout Kit. | [Readme](platforms/react-native/README.md) | -| [`@shopify/checkout-kit`](platforms/web/) | [![npm latest](https://img.shields.io/npm/v/@shopify/checkout-kit/latest.svg?label=npm)](https://www.npmjs.com/package/@shopify/checkout-kit) | npm | Web component for embedding Shopify checkout in any website. | [Readme](platforms/web/README.md) | +| Capability | Swift | Android | React Native | Web | +| --- | --- | --- | --- | --- | +| Present checkout from `cart.checkoutUrl` | Yes | Yes | Yes | Yes | +| Cart permalink support | Yes | Yes | Yes | Yes | +| Light, dark, and web color schemes | Yes | Yes | Yes | Not applicable | +| Checkout cancel/fail callbacks | Yes | Yes | Yes | Close/error events | +| Typed checkout protocol events | Yes | Yes | Partial/native-dependent | Yes | +| File chooser and web permissions | iOS system behavior | Host callbacks | Android host callbacks | Browser behavior | +| Geolocation for pickup points | iOS system prompt | Host callback required | Android default helper or custom handler | Browser behavior | +| Offsite payment return routing | Universal Links | App Links/deep links | Platform-dependent | New tab/popup routing | +| Accelerated checkout buttons | iOS 16+ | No | iOS 16+ | No | + +## Integration Guides + +Start with the platform README for package installation and API details: + +- [Swift](platforms/swift/README.md) +- [Android](platforms/android/README.md) +- [React Native](platforms/react-native/README.md) +- [Web](platforms/web/README.md) + +Use the Shopify.dev guides for broader product workflows: + +- [Checkout Kit overview](https://shopify.dev/docs/storefronts/mobile/checkout-kit) +- [Authenticate checkouts](https://shopify.dev/docs/storefronts/mobile/checkout-kit/authenticate-checkouts) +- [Monitor the checkout lifecycle](https://shopify.dev/docs/storefronts/mobile/checkout-kit/monitor-checkout-lifecycle) +- [Offsite payments](https://shopify.dev/docs/storefronts/mobile/checkout-kit/offsite-payments) +- [Privacy compliance](https://shopify.dev/docs/storefronts/mobile/checkout-kit/privacy-compliance) +- [Accelerated checkouts](https://shopify.dev/docs/storefronts/mobile/checkout-kit/accelerated-checkouts) + +## Samples + +| Platform | Sample README | What it demonstrates | +| --- | --- | --- | +| Swift | [Samples](platforms/swift/Samples/README.md) | Storefront API cart flow, checkout presentation, Customer Account API, and accelerated checkout buttons. | +| Android | [Samples](platforms/android/samples/README.md) | Storefront API cart flow, checkout presentation, protocol lifecycle events, file chooser, and geolocation callbacks. | +| Web | [Sample](platforms/web/sample/README.md) | Local playground for the `` web component and `checkout:*` events. | ## Versioning -Checkout Kit is the new name for the Checkout Sheet Kit SDKs. It resets the version line to Checkout Kit v1, and all future development will be under Checkout Kit. +Checkout Kit is the current home for the SDKs that were previously published as Checkout Sheet Kit. The renamed packages use a shared `4.0.0-alpha.X` version format while the new package line settles: -### Legacy v3 +- Swift, Android, and React Native start at `4.0.0-alpha.1`. +- Web is currently `4.0.0-alpha.2` and is published under the npm `next` dist-tag. +- React Native requires React Native New Architecture. +- Stable releases will continue on the same `4.x` package line after the alpha period. -The legacy Checkout Sheet Kit lines are deprecated and remain available for apps maintaining older integrations. +The legacy standalone Checkout Sheet Kit repositories remain available for apps that have not migrated. -| Platform | Status | Package | Final release | Readme | -| ------------ | ----------------------------------------------------------------------- | ----------------------------------- | ------------- | --------------------------------------------------------------------------- | -| iOS | ![Deprecated](https://img.shields.io/badge/status-deprecated-lightgrey) | Checkout Sheet Kit for Swift | `3.8.x` | [Readme](https://github.com/Shopify/checkout-sheet-kit-swift#readme) | -| Android | ![Deprecated](https://img.shields.io/badge/status-deprecated-lightgrey) | Checkout Sheet Kit for Android | `3.5.x` | [Readme](https://github.com/Shopify/checkout-sheet-kit-android#readme) | -| React Native | ![Deprecated](https://img.shields.io/badge/status-deprecated-lightgrey) | Checkout Sheet Kit for React Native | `4.0.x` | [Readme](https://github.com/Shopify/checkout-sheet-kit-react-native#readme) | +| Platform | Legacy package | Final legacy line | +| --- | --- | --- | +| iOS | `checkout-sheet-kit-swift` | `3.8.x` | +| Android | `com.shopify:checkout-sheet-kit` | `3.5.x` | +| React Native | `@shopify/checkout-sheet-kit` | `4.0.x` | ## Contributing diff --git a/e2e/README.md b/e2e/README.md index 290248e4..0973a1c1 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -1,3 +1,12 @@ -# Checkout Kit — End-to-end tests +# Checkout Kit End-to-End Tests -Placeholder. End-to-end tests covering Swift, Android, and (later) React Native will be folded into this directory in a follow-up. +This directory is reserved for cross-platform end-to-end tests. There is no runnable e2e suite checked in yet. + +Planned coverage: + +- Swift checkout presentation and protocol lifecycle. +- Android checkout presentation and protocol lifecycle. +- React Native wrapper behavior. +- Web component open/close and `checkout:*` events. + +Until this directory contains test code, use the platform test suites and sample apps described in each platform README. diff --git a/platforms/swift/README.md b/platforms/swift/README.md index 0386d0e5..687177c1 100644 --- a/platforms/swift/README.md +++ b/platforms/swift/README.md @@ -1,615 +1,434 @@ # Shopify Checkout Kit - Swift -[![GitHub license](https://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat)](https://github.com/Shopify/checkout-kit/blob/main/LICENSE) [![Swift Package Manager compatible](https://img.shields.io/badge/Swift%20Package%20Manager-compatible-2ebb4e.svg?style=flat)](https://swift.org/package-manager/) [![GitHub Release](https://img.shields.io/github/release/shopify/checkout-kit.svg?style=flat)]() -gradients +[![MIT License](https://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat)](../../LICENSE) +[![Swift Package Manager compatible](https://img.shields.io/badge/Swift%20Package%20Manager-compatible-2ebb4e.svg?style=flat)](https://swift.org/package-manager/) -**Shopify Checkout Kit** is a Swift Package library that enables Swift apps to provide the world’s highest converting, customizable, one-page checkout within the app. The presented experience is a fully-featured checkout that preserves all of the store customizations: Checkout UI extensions, Functions, branding, and more. It also provides platform 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 blog to [learn how and why we built the Checkout Kit](https://www.shopify.com/partners/blog/mobile-checkout-sdks-for-ios-and-android). +Checkout Kit + +> [!WARNING] +> **Alpha - early preview.** The first Checkout Kit for Swift alpha is `4.0.0-alpha.1`. Stability is not guaranteed, and breaking changes can occur before the stable `4.x` release. + +**Checkout Kit for Swift** lets iOS apps present Shopify checkout in a native sheet while preserving store checkout customizations such as Checkout UI extensions, Shopify Functions, branding, and supported payment methods. The Swift package also includes `ShopifyAcceleratedCheckouts`, a SwiftUI library for rendering Shop Pay and Apple Pay buttons on iOS 16+. - [Requirements](#requirements) -- [Getting Started](#getting-started) - - [Package.swift](#packageswift) - - [Xcode](#xcode) +- [Install](#install) + - [Swift Package Manager](#swift-package-manager) - [CocoaPods](#cocoapods) -- [Programmatic Usage](#programmatic-usage) -- [SwiftUI Usage](#swiftui-usage) -- [Configuration](#configuration) - - [`colorScheme`](#colorscheme) - - [`tintColor`](#tintcolor) - - [`backgroundColor`](#backgroundcolor) - - [`title`](#title) - - [`closeButtonTintColor`](#closebuttontintcolor) - - [SwiftUI Configuration](#swiftui-configuration) -- [Monitoring the lifecycle of a checkout session](#monitoring-the-lifecycle-of-a-checkout-session) -- [Error handling](#error-handling) - - [`CheckoutError`](#checkouterror) -- [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) -- [Offsite Payments](#offsite-payments) +- [Get a checkout URL](#get-a-checkout-url) +- [Present checkout](#present-checkout) + - [UIKit](#uikit) + - [SwiftUI](#swiftui) +- [Configure checkout](#configure-checkout) + - [Current configuration](#current-configuration) +- [Checkout lifecycle](#checkout-lifecycle) + - [Error handling](#error-handling) +- [Authentication and buyer identity](#authentication-and-buyer-identity) +- [Offsite payments and links](#offsite-payments-and-links) +- [Geolocation and pickup points](#geolocation-and-pickup-points) - [Accelerated Checkouts](#accelerated-checkouts) - [Prerequisites](#prerequisites) - - [Install the package](#install-the-package) - - [Configure the integration](#configure-the-integration) - - [Render accelerated checkout buttons](#render-accelerated-checkout-buttons) - - [Customize wallet options](#customize-wallet-options) - - [Modify the Apple Pay button label](#modify-the-apple-pay-button-label) - - [Customize the Apple Pay button style](#customize-the-apple-pay-button-style) - - [Customize button corners](#customize-button-corners) - - [Handle loading, errors, and lifecycle events](#handle-loading-errors-and-lifecycle-events) - - [Troubleshooting](#troubleshooting) -- [Explore the sample apps](#explore-the-sample-apps) + - [Configure accelerated checkouts](#configure-accelerated-checkouts) + - [Render buttons](#render-buttons) +- [Troubleshooting](#troubleshooting) +- [Samples](#samples) - [Contributing](#contributing) - [License](#license) ## Requirements -- Swift 5.7+ -- iOS 13.0+ for Checkout Kit, iOS 16+ for Accelerated Checkouts +- Swift Package Manager with Swift tools 5.9+ +- iOS 13.0+ for `ShopifyCheckoutKit` +- iOS 16.0+ for `ShopifyAcceleratedCheckouts` +- A Storefront API access token and a checkout URL from `cart.checkoutUrl` or a cart permalink + +## Install + +The first Checkout Kit for Swift alpha is `4.0.0-alpha.1`. Pin the alpha version explicitly until a stable `4.x` release is available. + +### Swift Package Manager -## Getting Started +Add the repository from Xcode with **File > Add Package Dependencies...**: -The SDK is an open-source [Swift Package library](https://www.swift.org/package-manager/). As a quick start, see [sample projects](Samples/README.md) or use one of the following ways to integrate the SDK into your project: +```text +https://github.com/Shopify/checkout-kit +``` -### Package.swift +Or add it to `Package.swift`: ```swift dependencies: [ - .package(url: "https://github.com/Shopify/checkout-kit", from: "3") + .package(url: "https://github.com/Shopify/checkout-kit", exact: "4.0.0-alpha.1") ] ``` -### Xcode - -1. Open your Xcode project -2. Navigate to `File` > `Add Package Dependencies...` -3. Enter `https://github.com/Shopify/checkout-kit` into the search box -4. Click `Add Package` +Then add the products you need to your app target: -For more details on managing Swift Package dependencies in Xcode, please see [Apple's documentation](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app). +```swift +.target( + name: "YourApp", + dependencies: [ + "ShopifyCheckoutKit", + "ShopifyCheckoutProtocol", + "ShopifyAcceleratedCheckouts" // Only needed for accelerated checkout buttons. + ] +) +``` ### CocoaPods ```ruby -pod "ShopifyCheckoutKit", "~> 3" -``` - -For more information on CocoaPods, please see their [getting started guide](https://guides.cocoapods.org/using/getting-started.html). +pod "ShopifyCheckoutKit", "4.0.0-alpha.1" -## Programmatic Usage +# Optional: Shop Pay and Apple Pay accelerated checkout buttons. +pod "ShopifyCheckoutKit/AcceleratedCheckouts", "4.0.0-alpha.1" +``` -Once the SDK has been added as a dependency, you can import the library: +## Get a checkout URL -```swift -import ShopifyCheckoutKit -``` +Checkout Kit presents a standard Shopify checkout URL. The common flow is: -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/2023-10/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 iOS](https://github.com/Shopify/mobile-buy-sdk-ios) to simplify the development workflow: +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 without adding Checkout Kit query parameters yourself. ```swift import Buy let client = Graph.Client( - shopDomain: "yourshop.myshopify.com", + shopDomain: "your-shop.myshopify.com", apiKey: "" ) let query = Storefront.buildQuery { $0 - .cart(id: "myCartId") { $0 + .cart(id: "gid://shopify/Cart/...") { $0 .checkoutUrl() } } let task = client.queryGraphWith(query) { response, error in - let checkoutURL = response?.cart.checkoutUrl + let checkoutURL = response?.cart?.checkoutUrl } task.resume() ``` -The `checkoutURL` object is a standard web checkout URL that can be opened in any browser. To present a native checkout sheet in your application, provide the `checkoutURL` alongside optional runtime configuration settings to the `present(checkout:)` function provided by the SDK: +You can use any GraphQL client. The sample app uses Apollo iOS and is a better reference for a modern Storefront API integration. + +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, and delivery preferences. + +## Present checkout + +### UIKit ```swift -import UIKit import ShopifyCheckoutKit +import UIKit -class MyViewController: UIViewController { - func presentCheckout() { - let checkoutURL: URL = // from cart object - ShopifyCheckoutKit.present(checkout: checkoutURL, from: self, delegate: self) +final class CartViewController: UIViewController, CheckoutDelegate { + func presentCheckout(checkoutURL: URL) { + ShopifyCheckoutKit.present( + checkout: checkoutURL, + from: self, + delegate: self + ) + } + + func checkoutDidCancel() { + // The buyer closed checkout. + } + + func checkoutDidFail(error: CheckoutError) { + // Show an error state, retry with a fresh cart, or log the SDK error. } } ``` -## SwiftUI Usage +### SwiftUI ```swift -import SwiftUI import ShopifyCheckoutKit +import SwiftUI -struct ContentView: View { - @State var isPresented = false - @State var checkoutURL: URL? +struct CartView: View { + @State private var isPresented = false + let checkoutURL: URL var body: some View { Button("Checkout") { isPresented = true } .sheet(isPresented: $isPresented) { - if let url = checkoutURL { - ShopifyCheckout(checkout: url) - /// Configuration - .title("Checkout") - .colorScheme(.automatic) - .tintColor(.blue) - .backgroundColor(.white) - .closeButtonTintColor(.red) - - /// Lifecycle events - .onCancel { - isPresented = false - } - .onFail { error in - handleError(error) - } - .edgesIgnoringSafeArea(.all) - } + ShopifyCheckout(checkout: checkoutURL) + .title("Checkout") + .colorScheme(.automatic) + .tintColor(.systemBlue) + .backgroundColor(.systemBackground) + .closeButtonTintColor(nil) + .onCancel { + isPresented = false + } + .onFail { error in + handleCheckoutError(error) + } + .ignoresSafeArea() } } } ``` -## Configuration +Checkout Kit decorates the URL with the required Embedded Checkout Protocol parameters when checkout loads. -The SDK provides a way to customize the presented checkout experience via the `ShopifyCheckoutKit.configuration` object. +## Configure checkout -### `colorScheme` - -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: ```swift -// [Default] Automatically toggle idiomatic light and dark themes based on device preference (`UITraitCollection`) -ShopifyCheckoutKit.configuration.colorScheme = .automatic - -// Force idiomatic light color scheme -ShopifyCheckoutKit.configuration.colorScheme = .light - -// Force idiomatic dark color scheme -ShopifyCheckoutKit.configuration.colorScheme = .dark +import ShopifyCheckoutKit -// Force web theme, as rendered by a mobile browser -ShopifyCheckoutKit.configuration.colorScheme = .web +ShopifyCheckoutKit.configure { + $0.colorScheme = .automatic + $0.tintColor = .systemBlue + $0.backgroundColor = .systemBackground + $0.closeButtonTintColor = nil + $0.logLevel = .error +} ``` -### `tintColor` +| Option | Default | Purpose | +| --- | --- | --- | +| `colorScheme` | `.automatic` | Use device appearance, force `.light` or `.dark`, or use `.web` to match web checkout branding. | +| `tintColor` | Shopify blue | Progress indicator color while checkout initializes. | +| `backgroundColor` | `.systemBackground` | Background behind the web view while checkout initializes. | +| `title` | Localized `shopify_checkout_kit_title` or `Checkout` | Navigation title for the checkout sheet. | +| `closeButtonTintColor` | `nil` | Optional tint for the close button. | +| `logLevel` | `.error` | SDK logging verbosity. Use `.debug` or `.all` during integration. | -If the checkout session is not ready and being initialized, a progress bar is shown and can be customized via the `tintColor` property: +To localize the title, add `shopify_checkout_kit_title` to your app's `Localizable.xcstrings`. -```swift -// Use a custom UI color -ShopifyCheckoutKit.configuration.tintColor = UIColor(red: 0.09, green: 0.45, blue: 0.69, alpha: 1.00) - -// Use a system color -ShopifyCheckoutKit.configuration.tintColor = .systemBlue -``` - -### `backgroundColor` - -While the checkout session is being initialized, the background color of the view can be customized via the `backgroundColor` property: +### Current configuration ```swift -// Use a custom UI color -ShopifyCheckoutKit.configuration.backgroundColor = UIColor(red: 0.09, green: 0.45, blue: 0.69, alpha: 1.00) - -// Use a system color -ShopifyCheckoutKit.configuration.backgroundColor = .systemBackground +let configuration = ShopifyCheckoutKit.configuration ``` -### `title` +## Checkout lifecycle -By default, the Checkout Kit will look for a `shopify_checkout_kit_title` key in a `Localizable.xcstrings` file to set the sheet title, otherwise it will fallback to "Checkout" across all locales. +`CheckoutDelegate` reports host-level outcomes: -The title of the sheet can be customized by either setting a value for the `shopify_checkout_kit_title` key in the `Localizable.xcstrings` file for your application or by configuring the `title` property of the `ShopifyCheckoutKit.configuration` object manually. +- `checkoutDidCancel()` fires when the buyer closes the checkout sheet. +- `checkoutDidFail(error:)` fires when checkout cannot continue. + +Typed checkout state, including completion, flows through `ShopifyCheckoutProtocol`. ```swift -// Hardcoded title, applicable to all languages -ShopifyCheckoutKit.configuration.title = "Custom title" -``` +import ShopifyCheckoutKit +import ShopifyCheckoutProtocol -Here is an example of a `Localizable.xcstrings` containing translations for 2 locales - `en` and `fr`. - -```json -{ - "sourceLanguage": "en", - "strings": { - "shopify_checkout_kit_title": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Checkout" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Caisse" - } - } - } - } +let client = CheckoutProtocol.Client() + .on(CheckoutProtocol.start) { checkout in + // Checkout is loaded and interactive. + } + .on(CheckoutProtocol.complete) { checkout in + // The order was completed. Clear or refresh the local cart. + } + .on(CheckoutProtocol.totalsChange) { checkout in + // React to updated totals. + } + .on(CheckoutProtocol.lineItemsChange) { checkout in + // React to line item changes. + } + .on(CheckoutProtocol.messagesChange) { checkout in + // React to checkout messages. } -} -``` - -### `closeButtonTintColor` - -The color of the close button in the navigation bar can be customized via the `closeButtonTintColor` property. When set to a custom color, the close button will use a custom SF Symbol (`xmark.circle.fill`) with the specified tint color. When set to `nil` (default), the standard system close button appearance is used. - -```swift -// Use a custom UI color -ShopifyCheckoutKit.configuration.closeButtonTintColor = UIColor(red: 0.09, green: 0.45, blue: 0.69, alpha: 1.00) -// Use a system color -ShopifyCheckoutKit.configuration.closeButtonTintColor = .systemRed +ShopifyCheckoutKit.present( + checkout: checkoutURL, + from: viewController, + delegate: checkoutDelegate, + client: client +) ``` -### SwiftUI Configuration - -Similarly, configuration modifiers are available to set the configuration of your checkout when using SwiftUI: +For SwiftUI, attach the same client with `.connect(client)`. ```swift ShopifyCheckout(checkout: checkoutURL) - .title("Checkout") - .colorScheme(.automatic) - .tintColor(.blue) - .backgroundColor(.black) - .closeButtonTintColor(.red) + .connect(client) ``` -## Monitoring the lifecycle of a checkout session +The public `CheckoutProtocol` descriptors are typed wrappers over Embedded Checkout Protocol messages. Kit-owned link delegations such as `window.open` are handled internally by Checkout Kit and are not forwarded to your protocol client. -You can use the `CheckoutDelegate` protocol to register callbacks for lifecycle events the host app needs to react to: +### Error handling -```swift -extension MyViewController: CheckoutDelegate { - func checkoutDidCancel() { - // Called when the checkout was canceled by the buyer. - // Use this to call `dismiss(animated:)`, etc. - } +| Error | Meaning | Recommended handling | +| --- | --- | --- | +| `.checkoutExpired(code: .cartExpired)` | The cart or checkout session expired. | Create a new cart and present a fresh `checkoutUrl`. | +| `.checkoutExpired(code: .cartCompleted)` | The cart already completed checkout. | Clear the local cart and fetch a new one. | +| `.checkoutExpired(code: .invalidCart)` | The cart is invalid or empty. | Rebuild the cart before presenting checkout. | +| `.checkoutUnavailable(code: .httpError)` | Checkout returned an unexpected HTTP response. | Treat as fatal for this attempt; retry with a fresh URL if appropriate. | +| `.checkoutUnavailable(code: .clientError)` | Checkout could not load for a client-side reason. | Show a recoverable error and log details. | +| `.sdkError(underlying:)` | Checkout Kit encountered an internal SDK error. | Log the error and open an issue if it persists. | - func checkoutDidFail(error: CheckoutError) { - // Called when the checkout encountered an error and has been aborted. The callback - // provides a `CheckoutError` enum, with one of the following cases: +Password-protected storefronts return `storefront_password_required` and are not supported by Checkout Kit. - // Internal error: exception within the Checkout SDK code. - // Inspect the underlying error to identify the problem. - case sdkError(underlying: Swift.Error) +## Authentication and buyer identity - // Checkout cannot be initiated or completed, e.g. due to network or server-side error. - // The provided message describes the error and may be logged and presented to the buyer. - case checkoutUnavailable(message: String, code: CheckoutUnavailable) +Checkout Kit does not create carts or authenticate buyers. Add buyer context to the cart before presenting checkout: - // Checkout session associated with the provided checkoutURL is no longer available. - // The provided message describes the error and may be logged and presented to the buyer. - case checkoutExpired(message: String, code: CheckoutErrorCode) - } -} -``` - -Completion events and other in-checkout messages flow through `CheckoutCommunicationProtocol` (UCP) — register handlers on a `CheckoutProtocol.Client` and pass it to `present(checkout:from:delegate:client:)`. See `Samples/MobileBuyIntegration` for a full example. - -## Error handling +- Use Storefront API cart buyer identity fields to prefill email, phone, address, country, language, and delivery preferences. +- Use Multipass for Shopify Plus stores that use Classic Customer Accounts. Generate Multipass tokens server-side and set `return_to` to the checkout URL. +- Use the Customer Account API to obtain a customer access token and attach it through cart buyer identity. +- Use `walletPreferences: [shop_pay]` when you want checkout to prefer Shop Pay. -Errors are forwarded to `checkoutDidFail(error:)`. The dialog dismisses after the delegate is invoked. +Keep Storefront API tokens and Multipass secrets out of client-side code where they are not intended to be public. -### `CheckoutError` +## Offsite payments and links -| Type | Description | Recommendation | -| --------------------------------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------- | -| `.checkoutUnavailable(message: "Forbidden")` | Access to checkout is forbidden. | Treat as fatal for this session. | -| `.checkoutUnavailable(message: "Internal Server Error")` | An internal server error occurred. | Likely ephemeral — retry by opening a fresh checkout URL. | -| `.checkoutUnavailable(message: "Storefront password required")` | Access to checkout is password restricted. | We are working on ways to enable the Checkout Kit for usage with password protected stores. | -| `.checkoutExpired(message: "Checkout already completed")` | The checkout has already been completed | If this is incorrect, create a new cart and open a new checkout URL. | -| `.checkoutExpired(message: "Cart is empty")` | The cart session has expired. | Create a new cart and open a new checkout URL. | -| `.sdkError(underlying:)` | An error was thrown internally. | Please open an issue in this repo with as much detail as possible. | +Some payment providers redirect buyers to external banking apps or web pages. Configure Universal Links so buyers can return to your app after those flows complete: -## Integrating identity & customer accounts +- Use a custom storefront domain. `*.myshopify.com` domains do not serve `apple-app-site-association` files. +- Enable Associated Domains in your app entitlements. +- Configure the iOS Buy SDK / Storefront API app settings in the Shopify admin. +- Route incoming checkout URLs back to Checkout Kit and route order status or thank-you URLs to your own confirmation flow. -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 a personalized and contextualized buyer experience. +See [Universal Links](documentation/universal_links.md) for setup and testing details. -### Cart: buyer bag, identity, and preferences +Checkout Kit forwards external HTTPS links, deep links, `mailto:`, and `tel:` links to `UIApplication.shared.open(_:)`. -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. +## Geolocation and pickup points -### Multipass +iOS handles checkout geolocation permission prompts through the system prompt. If your checkout uses pickup points or "Use my location", add a location usage description to your app: -[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) ([API documentation](https://shopify.dev/docs/api/multipass)) to integrate an external identity system and initialize a buyer-aware checkout session. - -```json -{ - "email": "", - "created_at": "", - "remote_ip": "", - "return_to": "" -} +```xml +NSLocationWhenInUseUsageDescription +Your location is used to find pickup points near you. ``` -1. Follow the [Multipass documentation](https://shopify.dev/docs/api/multipass) to create a Multipass URL and set `return_to` to be the obtained `checkoutUrl` -2. Provide the Multipass URL to `present(checkout:)` - -> [!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 tokens are single-use. If a request containing a multipass URL fails, generate a fresh token before re-opening checkout. - -### Shop Pay - -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. 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. - -### Customer Account API - -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). - -## Offsite Payments - -Certain payment providers finalize transactions by redirecting customers to external banking apps. To enhance the user experience for your buyers, you can set up your storefront to support Universal Links on iOS, allowing customers to be redirected back to your app once the payment is completed. - -See the [Universal Links guide](https://github.com/Shopify/checkout-kit/blob/main/platforms/swift/documentation/universal_links.md) for information on how to get started with adding support for Offsite Payments in your app. - -External links opened from within checkout (HTTPS, deep links, `mailto:`, `tel:`) are forwarded to `UIApplication.shared.open(_:)` by the kit, so universal links and Offsite Payments redirects route back to your app automatically once the rest of the universal-links setup is in place. - ## Accelerated Checkouts -Accelerated checkout buttons surface Apple Pay and Shop Pay options earlier in the buyer journey so more orders complete without leaving your app. For an end-to-end walkthrough see the [`ShopifyAcceleratedCheckoutsApp` sample](Samples/ShopifyAcceleratedCheckoutsApp). +`ShopifyAcceleratedCheckouts` renders Shop Pay and Apple Pay buttons before the buyer opens the full checkout sheet. It supports cart-based and product-variant-based entry points on iOS 16+. ### Prerequisites -- iOS 16 or later -- The `write_cart_wallet_payments` access scope ([request access](https://www.appsheet.com/start/1ff317b6-2da1-4f39-b041-c01cfada6098)) -- Apple Pay payment processing certificates ([setup guide](https://shopify.dev/docs/storefronts/mobile/create-apple-payment-processing-certificates)) -- A device configured for Apple Pay ([Apple setup instructions](https://developer.apple.com/documentation/passkit/setting-up-apple-pay)) - -### Install the package - -Update your package manifest to import `ShopifyAcceleratedCheckouts` alongside `ShopifyCheckoutKit`. - -```swift -dependencies: [ - .package(url: "https://github.com/Shopify/checkout-kit", from: "3.8.0") -] -``` - -Then add the product to your target dependencies: - -```swift -.target( - name: "YourApp", - dependencies: ["ShopifyAcceleratedCheckouts"] -) -``` +- iOS 16.0+ +- Storefront API token with the `write_cart_wallet_payments` scope +- Apple Pay merchant identifier and payment processing certificate +- A device or simulator configuration that can display Apple Pay -### Configure the integration +### Configure accelerated checkouts -Create a configuration object that connects the accelerated checkout buttons to your storefront. Provide the domain, Storefront API access token, and optionally the current customer. +Create shared configuration objects and inject them into your SwiftUI hierarchy: ```swift import ShopifyAcceleratedCheckouts +import SwiftUI -// For authenticated customers (logged in with Shopify account) -let configuration = ShopifyAcceleratedCheckouts.Configuration( - storefrontDomain: "your-shop.myshopify.com", - storefrontAccessToken: "your-storefront-access-token", - customer: ShopifyAcceleratedCheckouts.Customer( - customerAccessToken: "customer-access-token" - ) -) - -// For guest customers (or explicit contact override) -let configuration = ShopifyAcceleratedCheckouts.Configuration( +@main +struct YourApp: App { + private let checkoutConfig = ShopifyAcceleratedCheckouts.Configuration( storefrontDomain: "your-shop.myshopify.com", - storefrontAccessToken: "your-storefront-access-token", - customer: ShopifyAcceleratedCheckouts.Customer( - email: "customer@example.com", - phoneNumber: "0123456789" - ) -) -``` - -> [!WARNING] -> Do not provide both `customerAccessToken` and `email`/`phoneNumber` together. For authenticated customers, email and phone are fetched automatically from the Shopify account. + storefrontAccessToken: "", + customer: nil + ) -> [!TIP] -> Pass `nil` for `customer` when the buyer is anonymous, and update the configuration later when their details are known. - -> [!NOTE] -> When using the cart ID flow, if customer contact information exists in both `config.customer` and the cart's `buyerIdentity`, the `config.customer` values take precedence. - -Configure Apple Pay with your merchant identifier, required contact fields, and any shipping restrictions. - -```swift -let applePayConfig = ShopifyAcceleratedCheckouts.ApplePayConfiguration( - merchantIdentifier: "merchant.com.yourcompany", - contactFields: [.email, .phone] -) -``` - -Use the `contactFields` parameter to request specific details from the buyer's Apple Pay sheet. Provide any -combination of `.email` and `.phone`. If you omit the parameter (or pass an empty array), Apple Pay still prompts for an -email address unless it already has one for the buyer (for example, when you supply `customer.email`). - -```swift -// Require only an email address -let applePayConfig = ShopifyAcceleratedCheckouts.ApplePayConfiguration( - merchantIdentifier: "merchant.com.yourcompany", - contactFields: [.email] -) - -// Require only a phone number -let applePayConfig = ShopifyAcceleratedCheckouts.ApplePayConfiguration( - merchantIdentifier: "merchant.com.yourcompany", - contactFields: [.phone] -) - -// Default behaviour: Apple Pay prompts for email unless it already has one -let applePayConfig = ShopifyAcceleratedCheckouts.ApplePayConfiguration( - merchantIdentifier: "merchant.com.yourcompany" -) -``` - -If you need to limit shipping destinations, pass ISO 3166-1 alpha-2 country codes to `supportedShippingCountries`. -Leave the parameter as `nil` (the default) to accept all countries and only restrict shipping when Apple Pay cannot -technically support a destination. - -```swift -// Allow shipping to the United States and Canada only -let applePayConfig = ShopifyAcceleratedCheckouts.ApplePayConfiguration( + private let applePayConfig = ShopifyAcceleratedCheckouts.ApplePayConfiguration( merchantIdentifier: "merchant.com.yourcompany", contactFields: [.email, .phone], supportedShippingCountries: ["US", "CA"] -) -``` - -Inject both configuration objects into your SwiftUI hierarchy so every `AcceleratedCheckoutButtons` instance can read them: + ) -```swift -@main -struct MyApp: App { - let configuration = ShopifyAcceleratedCheckouts.Configuration(...) - let applePayConfig = ShopifyAcceleratedCheckouts.ApplePayConfiguration(...) - - var body: some Scene { - WindowGroup { - ContentView() - .environmentObject(configuration) - .environmentObject(applePayConfig) - } + var body: some Scene { + WindowGroup { + ContentView() + .environmentObject(checkoutConfig) + .environmentObject(applePayConfig) } + } } ``` -If you are building a UIKit surface, wrap the SwiftUI buttons with a `UIHostingController` and provide the same environment objects before adding the view to your hierarchy. - -### Render accelerated checkout buttons - -Use `AcceleratedCheckoutButtons` to attach accelerated checkout calls-to-action to product or cart surfaces once you have a valid cart ID or product variant ID from the Storefront API. Guard the component with `#available(iOS 16.0, *)` if your app still supports older OS versions. +Use one customer mode at a time: ```swift -if #available(iOS 16.0, *) { - AcceleratedCheckoutButtons(cartID: cartID) - .wallets([..shopPay, .applePay]) -} +// Authenticated buyer. +ShopifyAcceleratedCheckouts.Customer(customerAccessToken: customerAccessToken) + +// Guest or explicit contact override. +ShopifyAcceleratedCheckouts.Customer( + email: "buyer@example.com", + phoneNumber: "15555555555" +) ``` -#### Customize wallet options +`contactFields` is required by the current Swift API. Include `.email` when the shop requires customer accounts and `.phone` when the shop requires phone numbers for shipping. -Accelerated checkout buttons display every available wallet by default. Use `.wallets(_:)` to show a subset or adjust the -order shoppers see them in. +### Render buttons ```swift -// Display only Shop Pay -AcceleratedCheckoutButtons(cartID: cartID) - .wallets([.shopPay]) +import ShopifyAcceleratedCheckouts -// Display Shop Pay first, then Apple Pay AcceleratedCheckoutButtons(cartID: cartID) - .wallets([.shopPay, .applePay]) + .wallets([.shopPay, .applePay]) + .applePayLabel(.buy) + .applePayStyle(.automatic) + .cornerRadius(8) + .onRenderStateChange { state in + // loading, rendered, or error(reason:) + } + .onFail { error in + // Handle checkout failure. + } + .onCancel { + // The buyer canceled the accelerated checkout flow. + } + .connect(client) ``` -#### Modify the Apple Pay button label - -Use `.applePayLabel(_:)` to map to the native `PayWithApplePayButtonLabel` values. The default is `.plain`. +You can also render buttons for a single product variant: ```swift -AcceleratedCheckoutButtons(cartID: cartID) - .applePayLabel(.buy) +AcceleratedCheckoutButtons( + variantID: "gid://shopify/ProductVariant/...", + quantity: 1 +) ``` -#### Customize the Apple Pay button style - -Use `.applePayStyle(_:)` to set the color style of the Apple Pay button. The modifier accepts a `PayWithApplePayButtonStyle` value. The default is `.automatic`, which adapts to the current appearance (light/dark mode). +Use `CheckoutProtocol.Client` through `.connect(client)` to observe checkout completion and state changes. Clear or refresh the cart when `CheckoutProtocol.complete` fires to avoid reusing an expired cart ID. -```swift -AcceleratedCheckoutButtons(cartID: cartID) - .applePayStyle(.whiteOutline) -``` +## Troubleshooting -#### Customize button corners +- Use `ShopifyCheckoutKit.configuration.logLevel = .debug` or `ShopifyAcceleratedCheckouts.logLevel = .debug` while integrating. +- If checkout reports an expired, completed, or invalid cart, create a fresh cart and use its new `checkoutUrl`. +- If Apple Pay dismisses immediately, verify the merchant ID, entitlements, payment processing certificate, and device wallet setup. +- If Universal Links do not open the app, verify the associated domain entitlement and the `/.well-known/apple-app-site-association` file on your custom storefront domain. -The `.cornerRadius(_:)` modifier lets you match the buttons to other calls-to-action in your app. Buttons default to an -8 pt radius. +## Samples -```swift -// Pill-shaped buttons -AcceleratedCheckoutButtons(cartID: cartID) - .cornerRadius(16) - -// Square buttons -AcceleratedCheckoutButtons(cartID: cartID) - .cornerRadius(0) -``` +See [Samples](Samples/README.md): -For custom layouts, compose the buttons inside your own SwiftUI view and reuse that view across surfaces. +- `MobileBuyIntegration` demonstrates a Storefront API cart flow, buyer identity modes, Customer Account API, checkout presentation, and protocol events. +- `ShopifyAcceleratedCheckoutsApp` demonstrates Shop Pay and Apple Pay accelerated checkout buttons. -### Handle loading, errors, and lifecycle events - -Listen for render state changes so you can display matching loading or error UI and only show the buttons when they are ready. - -```swift -@State private var renderState: RenderState = .loading +## Contributing -var body: some View { - if case .loading = renderState { - ProgressView() - } +See [CONTRIBUTING](../../.github/CONTRIBUTING.md). - if case .error = renderState { - ErrorStateView() - } +Useful checks before opening a Swift change: - AcceleratedCheckoutButtons(cartID: cartID) - .onRenderStateChange { state in - renderState = state - } -} +```sh +cd platforms/swift +./Scripts/xcode_run build ShopifyCheckoutKit +./Scripts/xcode_run build ShopifyAcceleratedCheckouts +./Scripts/xcode_run test ShopifyCheckoutKit-Package +./Scripts/lint ``` -Attach lifecycle handlers to respond when buyers finish, cancel, or encounter an error. Clearing the cart after a successful accelerated checkout prevents reuse of an expired cart ID. +For sample app changes, run: -```swift -AcceleratedCheckoutButtons(cartID: cartID) - .onComplete { _ in - cartManager.clearCart() - } - .onFail { error in - logger.error("Accelerated checkout failed: \(error)") - } - .onCancel { - analytics.track(.acceleratedCheckoutCancelled) - } - .onClickLink { url in - UIApplication.shared.open(url) - } +```sh +cd platforms/swift +./Scripts/build_samples ``` -### Troubleshooting - -- Increase verbosity during development with `ShopifyAcceleratedCheckouts.logLevel = .all` and `ShopifyCheckoutKit.configuration.logLevel = .all`. -- If the Apple Pay sheet dismisses immediately, verify your merchant ID configuration in the Apple Developer portal and Xcode signing settings. - ---- - -## Explore the sample apps - -See the [Samples](Samples) directory for a handful of sample iOS applications and a guide to get started. - -## Contributing - -We welcome code contributions, feature requests, and reporting of issues. Please see [guidelines and instructions](.github/CONTRIBUTING.md). - ## 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/swift/Samples/MobileBuyIntegration/README.md b/platforms/swift/Samples/MobileBuyIntegration/README.md index d6d58343..65b5fcee 100644 --- a/platforms/swift/Samples/MobileBuyIntegration/README.md +++ b/platforms/swift/Samples/MobileBuyIntegration/README.md @@ -1,154 +1,138 @@ # MobileBuyIntegration Sample App -A sample iOS app demonstrating how to integrate [Checkout Kit](../../README.md) with the Shopify Storefront API using [Apollo iOS](https://github.com/apollographql/apollo-ios). +This sample demonstrates how to integrate Checkout Kit with the Shopify Storefront API using Apollo iOS. -## Architecture +## What it covers + +- Product listing from the Storefront API +- Cart create, add, update, and fetch operations +- `cart.checkoutUrl` presentation with `ShopifyCheckoutKit` +- Checkout lifecycle and completion through `CheckoutProtocol.Client` +- Buyer identity demo data for checkout prefill +- Customer Account API sign-in and customer access token cart identity +- Universal Links entitlement generation for checkout/offsite-payment returns -The app uses **Apollo GraphQL** for all Storefront API communication. GraphQL operations are defined as `.graphql` files, and Apollo's code generation tool produces type-safe Swift code from them. +## Architecture -### Storefront API layer +The app uses Apollo iOS for Storefront API communication. GraphQL operations are defined as `.graphql` files, and Apollo's code generation tool produces type-safe Swift code from them. -``` +```text MobileBuyIntegration/ -├── GraphQL/ # Source of truth — you edit these -│ ├── Queries/ -│ │ ├── GetProducts.graphql # Product listing query -│ │ ├── CartQuery.graphql # Fetch cart by ID -│ │ ├── CartFragment.graphql # Reusable cart fields -│ │ ├── CartLineFragment.graphql # Cart line item fields -│ │ ├── CartDeliveryGroupFragment.graphql -│ │ └── CartUserErrorFragment.graphql -│ └── Mutations/ -│ ├── CartCreate.graphql # Create a new cart -│ ├── CartLinesAdd.graphql # Add items to cart -│ └── CartLinesUpdate.graphql # Update item quantities -│ -├── Generated/ # Auto-generated — do not edit -│ ├── Storefront.graphql.swift # Namespace & schema metadata -│ ├── Fragments/ # Swift types for each fragment -│ ├── Operations/ -│ │ ├── Mutations/ # CartCreateMutation, CartLinesAddMutation, etc. -│ │ └── Queries/ # GetProductsQuery, GetCartQuery -│ └── Schema/ -│ ├── Enums/ # CountryCode, CurrencyCode, LanguageCode, etc. -│ ├── InputObjects/ # CartInput, CartLineInput, etc. -│ ├── Objects/ # Cart, Product, MoneyV2, etc. -│ ├── Interfaces/ # BaseCartLine, Node -│ └── Unions/ # Merchandise -│ -├── Network.swift # Apollo client setup + auth interceptor -├── CartManager.swift # Cart state management (uses Apollo mutations) -└── ... +|-- MobileBuyIntegration/ +| |-- Sources/ +| | |-- Api/ Source of truth - edit these files +| | | |-- Queries/ +| | | | |-- GetProducts.graphql Product listing query +| | | | |-- CartQuery.graphql Fetch cart by ID +| | | | |-- CartFragment.graphql Reusable cart fields +| | | | |-- CartLineFragment.graphql +| | | | |-- CartDeliveryGroupFragment.graphql +| | | | `-- CartUserErrorFragment.graphql +| | | |-- Mutations/ +| | | | |-- CartCreate.graphql Create a new cart +| | | | |-- CartLinesAdd.graphql Add items to cart +| | | | `-- CartLinesUpdate.graphql +| | | |-- Network.swift Apollo client setup and auth interceptor +| | | `-- StorefrontClient.swift Cart input and buyer identity mapping +| | |-- Generated/ Apollo-generated Swift types +| | | |-- Storefront.graphql.swift Namespace and schema metadata +| | | |-- Fragments/ Swift fragment types +| | | |-- Operations/ +| | | | |-- Mutations/ CartCreateMutation, CartLinesAddMutation, etc. +| | | | `-- Queries/ GetProductsQuery, GetCartQuery +| | | `-- Schema/ Enums, input objects, objects, interfaces, unions +| | |-- App/ App configuration, cart state, and checkout coordination +| | | |-- AppConfiguration.swift Values loaded from Storefront.xcconfig +| | | |-- CartManager.swift Cart create, add, update, and fetch operations +| | | |-- CheckoutCoordinator.swift Checkout presentation +| | | `-- CartResettingCheckoutDelegate.swift +| | `-- Scenes/ SwiftUI screens +|-- Scripts/ +| `-- generate_entitlements.sh Universal Links entitlement generation +`-- Storefront.xcconfig Local store configuration, not checked in ``` -**`GraphQL/`** contains the `.graphql` files you write and maintain. These define which fields the app fetches from the Storefront API. - -**`Generated/`** contains Swift 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 `Generated/` by hand. Update `.graphql` files and regenerate Apollo types instead. ### How it works -1. `Network.swift` creates a shared `ApolloClient` that points at the store's Storefront API endpoint and attaches the access token via an interceptor. -2. `CartManager.swift` and `ProductView.swift` call `Network.shared.apollo.perform(mutation:)` / `.fetch(query:)` using the generated operation types (e.g. `Storefront.CartCreateMutation`, `Storefront.GetProductsQuery`). -3. Responses are automatically decoded into the generated Swift types, giving you compile-time safety on every field access. +1. `Network.swift` creates an `ApolloClient` that points at the configured Storefront API endpoint and attaches the Storefront access token. +2. `StorefrontClient.swift` and `CartManager.swift` call Apollo using generated operation types such as `Storefront.CartCreateMutation` and `Storefront.GetCartQuery`. +3. `CheckoutCoordinator.swift` presents `cart.checkoutUrl` with `ShopifyCheckoutKit`, while `CheckoutProtocolClient.swift` handles typed checkout lifecycle events. +4. Apollo decodes responses into generated Swift types, so schema or operation changes surface as compile errors. ## Setup -1. Copy the config template and fill in your store credentials: - - ```bash - cp Storefront.xcconfig.example Storefront.xcconfig - ``` - - Then edit `Storefront.xcconfig` with your values: - - ``` - STOREFRONT_DOMAIN = your-store.myshopify.com - STOREFRONT_ACCESS_TOKEN = your-token - API_VERSION = 2025-07 - ``` - -2. Open the project in Xcode and let SPM resolve the Apollo dependency. - -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 `platforms/swift`: -Edit your `Storefront.xcconfig` and change the `API_VERSION` value: - -``` -API_VERSION = 2025-10 +```sh +cp Samples/MobileBuyIntegration/Storefront.xcconfig.example \ + Samples/MobileBuyIntegration/Storefront.xcconfig ``` -### 2. Download the new schema +Edit `Storefront.xcconfig`: -The schema defines what types and fields are available in the API. Run from the **repo root** (`checkout-kit/`): - -```bash -dev apollo download_schema mobile-buy +```text +STOREFRONT_DOMAIN = your-store.myshopify.com +STOREFRONT_ACCESS_TOKEN = your-token +API_VERSION = 2025-07 ``` -This introspects your store's Storefront API at the configured version and writes a `schema..graphqls` file into the sample app directory. - -> If this is your first time, you may need to install the Apollo CLI first. In Xcode, right-click the project in the navigator and select **Install CLI** from the Apollo menu. Alternatively, download the binary from the [Apollo iOS releases](https://github.com/apollographql/apollo-ios/releases) page and place it at `Samples/MobileBuyIntegration/apollo-ios-cli`. - -### 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 `MobileBuyIntegration/GraphQL/`. +Optional values enable Customer Account API and buyer identity demo flows: -For example, to add a new field to products: - -```graphql -# MobileBuyIntegration/GraphQL/Queries/GetProducts.graphql -query GetProducts(...) { - products(first: $first) { - nodes { - id - title - myNewField # <-- add new fields here - ... - } - } -} +```text +CUSTOMER_ACCOUNT_API_CLIENT_ID = your-client-id +CUSTOMER_ACCOUNT_API_SHOP_ID = your-shop-id +EMAIL = test.buyer@example.com +PHONE = +16135550123 ``` -### 4. Run code generation - -From the **repo root**: +Open the project in Xcode, let Swift Package Manager resolve dependencies, then build and run. -```bash -dev apollo codegen mobile-buy -``` +## Updating the Storefront API version -This reads the schema + your `.graphql` files and regenerates the Swift code in `Generated/`. The command also runs `dev swift fix` to auto-format the output. +1. Update `API_VERSION` in `Storefront.xcconfig`. +2. Download the schema with Rover. This introspects your store's Storefront API and writes `schema..graphqls` into the sample app directory. -### 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 "schema.$API_VERSION.graphqls" + ``` -Build the project in Xcode. If the new schema removed or renamed fields, you'll get compile errors pointing you to the exact lines that need updating. +3. Update `.graphql` operations if the schema changed. For example, add a product field to `MobileBuyIntegration/Sources/Api/Queries/GetProducts.graphql` before regenerating types: + + ```graphql + query GetProducts(...) { + products(first: $first) { + nodes { + id + title + myNewField + } + } + } + ``` -## Dev commands reference +4. Regenerate Swift types with the Apollo iOS CLI and this sample's `apollo-codegen-config.json`. This reads the schema and `.graphql` files, then regenerates Swift code in `MobileBuyIntegration/Sources/Generated/`. -All commands are run from the **repo root** (`checkout-kit/`): + ```sh + ./apollo-ios-cli generate --path apollo-codegen-config.json + ``` -| Command | Description | -|---------|-------------| -| `dev apollo download_schema mobile-buy` | Download the Storefront API schema for this sample app | -| `dev apollo codegen mobile-buy` | Regenerate Swift types from `.graphql` files | -| `dev apollo codegen all` | Regenerate for all sample apps | -| `dev swift style` | Run SwiftLint + SwiftFormat checks | -| `dev swift fix` | Auto-fix lint/format issues | -| `dev swift build samples` | Build all sample apps | +5. Build in Xcode and fix any compile errors from schema changes. ## Key files | File | Purpose | -|------|---------| -| `schema.graphqls` | Storefront API schema (downloaded, not hand-written) | -| `apollo-codegen-config.json` | Apollo codegen configuration | -| `apollo-ios-cli` | Apollo CLI binary (not checked into git) | -| `Storefront.xcconfig` | Store credentials + API version (not checked into git) | -| `Network.swift` | Apollo client setup, auth interceptor | -| `CartManager.swift` | Cart state, create/add/update operations | +| --- | --- | +| `Storefront.xcconfig` | Store credentials, API version, Customer Account API values, and demo buyer identity. | +| `schema..graphqls` | Storefront API schema downloaded with the Apollo iOS CLI. | +| `apollo-codegen-config.json` | Apollo code generation configuration. | +| `MobileBuyIntegration/Sources/Api/Network.swift` | Apollo client setup and authentication interceptor. | +| `MobileBuyIntegration/Sources/Api/StorefrontClient.swift` | Cart input creation and buyer identity mapping. | +| `MobileBuyIntegration/Sources/App/CartManager.swift` | Cart state and Storefront API mutations. | +| `MobileBuyIntegration/Sources/App/CheckoutCoordinator.swift` | Checkout presentation. | +| `MobileBuyIntegration/Sources/CheckoutProtocolClient.swift` | Typed checkout lifecycle handlers. | +| `Scripts/generate_entitlements.sh` | Generates Associated Domains entitlements for checkout and offsite-payment return links. | diff --git a/platforms/swift/Samples/README.md b/platforms/swift/Samples/README.md index 0bcd1fa7..bf27c831 100644 --- a/platforms/swift/Samples/README.md +++ b/platforms/swift/Samples/README.md @@ -1,44 +1,63 @@ -# Sample Project +# Swift Samples -This directory contains a sample project that implements the `ShopifyCheckoutKit` library. +This directory contains iOS sample apps for Checkout Kit. -The project directory contains a `Storefront.xcconfig.example` file. Simply rename it to `Storefront.xcconfig` and update the contained values to match your Shopify storefront. +| Sample | Purpose | +| --- | --- | +| `MobileBuyIntegration` | Storefront API cart flow with Apollo iOS, checkout presentation, buyer identity modes, Customer Account API sign-in, and protocol lifecycle events. | +| `ShopifyAcceleratedCheckoutsApp` | SwiftUI Shop Pay and Apple Pay accelerated checkout buttons. | ---- +## Prerequisites -## MobileBuyIntegration +- Xcode with Swift Package Manager support +- A Shopify store with a Storefront API access token +- Optional Customer Account API app configuration for authenticated buyer flows +- Optional Apple Pay merchant identifier and payment processing certificate for accelerated checkout -This project demonstrates how to use the [Mobile Buy SDK](https://github.com/Shopify/mobile-buy-sdk-ios) in conjunction with the `ShopifyCheckoutKit` library. +## MobileBuyIntegration -### Getting Started +From `platforms/swift`: -1. Copy the example config file: ```sh -cp Samples/MobileBuyIntegration/Storefront.xcconfig.example Samples/MobileBuyIntegration/Storefront.xcconfig +cp Samples/MobileBuyIntegration/Storefront.xcconfig.example \ + Samples/MobileBuyIntegration/Storefront.xcconfig ``` -2. Fill in `STOREFRONT_DOMAIN` and other keys in `Storefront.xcconfig` with your store values. -3. Build & run — entitlements are auto-generated via a build PreAction (no manual script step needed). -### Troubleshooting +Fill in: -If the build PreAction fails, Xcode will show **"exited with status code 1"**. Click that line to open the build log — the script output at the bottom will indicate the issue. +- `STOREFRONT_DOMAIN` +- `STOREFRONT_ACCESS_TOKEN` +- `API_VERSION` +- Optional Customer Account API values +- Optional demo buyer identity values -| Build Log Output | Cause | Fix | -|------------------|-------|-----| -| `grep: Storefront.xcconfig: No such file or directory` | `Storefront.xcconfig` file is missing | Copy `.xcconfig.example` to `Storefront.xcconfig` and fill in values | -| `Error: STOREFRONT_DOMAIN is not set in Storefront.xcconfig` | `Storefront.xcconfig` exists but `STOREFRONT_DOMAIN` is blank | Set your store's domain in the config | -| Associated domains not working at runtime | Domain value is incorrect | Verify domain matches your Shopify store (no `https://` prefix) | +Open `Samples/Samples.xcworkspace` or `Samples/MobileBuyIntegration/MobileBuyIntegration.xcodeproj` in Xcode, then build and run the `MobileBuyIntegration` scheme. ---- +The project generates associated-domain entitlements from `Storefront.xcconfig` during the Xcode build pre-action. ## ShopifyAcceleratedCheckoutsApp -This project demonstrates integrating Shopify's Accelerated Checkouts, an all in one solution to accelerated checkouts via Apple Pay and Shop Pay. - -To get started: +From `platforms/swift`: -1. Copy the settings file: ```sh -cp Samples/ShopifyAcceleratedCheckouts/Storefront.xcconfig.example Samples/ShopifyAcceleratedCheckouts/Storefront.xcconfig +cp Samples/ShopifyAcceleratedCheckoutsApp/Storefront.xcconfig.example \ + Samples/ShopifyAcceleratedCheckoutsApp/Storefront.xcconfig ``` -2. Modify each of the keys in `Storefront.xcconfig` to match the value in your store settings. \ No newline at end of file + +Fill in: + +- `STOREFRONT_DOMAIN` +- `STOREFRONT_ACCESS_TOKEN` +- `API_VERSION` + +Open `Samples/Samples.xcworkspace` or `Samples/ShopifyAcceleratedCheckoutsApp/ShopifyAcceleratedCheckoutsApp.xcodeproj` in Xcode, then build and run the `ShopifyAcceleratedCheckoutsApp` scheme. + +## Troubleshooting + +If the build pre-action fails, Xcode usually shows `exited with status code 1`. Open the build log and check the script output. + +| Build log output | Cause | Fix | +| --- | --- | --- | +| `grep: Storefront.xcconfig: No such file or directory` | The sample config file is missing. | Copy `.xcconfig.example` to `Storefront.xcconfig`. | +| `Error: STOREFRONT_DOMAIN is not set in Storefront.xcconfig` | `STOREFRONT_DOMAIN` is blank. | Set your shop domain without `https://`. | +| Associated domains do not work at runtime | Domain or app association is wrong. | Verify your custom storefront domain, app ID, and Universal Links setup. | diff --git a/protocol/languages/swift/README.md b/protocol/languages/swift/README.md index 8eb936c8..73a7447e 100644 --- a/protocol/languages/swift/README.md +++ b/protocol/languages/swift/README.md @@ -1,86 +1,94 @@ -# ShopifyCheckoutProtocol — Swift SDK +# ShopifyCheckoutProtocol - Swift -Swift library for the Universal Commerce Protocol (UCP) embedded checkout specification. +`ShopifyCheckoutProtocol` is the Swift client for the Embedded Checkout Protocol messages emitted by Shopify checkout. Checkout Kit uses it to decode lifecycle notifications such as checkout start, completion, totals changes, line item changes, and checkout messages. -## Requirements +Most apps consume this product through the root Checkout Kit Swift package. -- Swift 6.0+ +## Requirements -## Installation +- Swift Package Manager with Swift tools 5.9+ +- iOS 13.0+ or macOS 10.15+ -### Swift Package Manager (Package.swift) +## Install -Add the dependency to your `Package.swift`: +Add the Checkout Kit repository: ```swift dependencies: [ - .package(url: "", from: "1.0.0") + .package(url: "https://github.com/Shopify/checkout-kit", exact: "4.0.0-alpha.1") ] ``` -Then add `ShopifyCheckoutProtocol` to your target's dependencies: +Then add `ShopifyCheckoutProtocol` to your target: ```swift .target( - name: "YourTarget", - dependencies: ["ShopifyCheckoutProtocol"] + name: "YourTarget", + dependencies: ["ShopifyCheckoutProtocol"] ) ``` -### Xcode - -1. Open your project in Xcode -2. Go to **File > Add Package Dependencies...** -3. Enter the repository URL: `` -4. Click **Add Package** +For local protocol development, this directory also contains a standalone `Package.swift`. ## Usage ```swift import ShopifyCheckoutProtocol + +let client = CheckoutProtocol.Client() + .on(CheckoutProtocol.start) { checkout in + print("Checkout started: \(checkout.id)") + } + .on(CheckoutProtocol.complete) { checkout in + print("Checkout completed: \(checkout.order?.id ?? "unknown")") + } + .on(CheckoutProtocol.totalsChange) { checkout in + print("Totals changed: \(checkout.totals)") + } ``` -### Bridging with checkout-sheet-kit-swift +## Connect to Checkout Kit -If you use [`checkout-sheet-kit-swift`](https://github.com/Shopify/checkout-sheet-kit-swift), add a retroactive conformance to bridge `CheckoutProtocol.Client` with the kit's `CheckoutCommunicationProtocol`: +Checkout Kit's Swift SDK accepts `CheckoutProtocol.Client` anywhere it accepts `CheckoutCommunicationProtocol`. + +### UIKit ```swift -extension CheckoutProtocol.Client: @retroactive CheckoutCommunicationProtocol {} -``` +import ShopifyCheckoutKit +import ShopifyCheckoutProtocol -### Creating a Client +ShopifyCheckoutKit.present( + checkout: checkoutURL, + from: viewController, + delegate: checkoutDelegate, + client: client +) +``` -`CheckoutProtocol.Client` uses a fluent API to register event handlers: +### SwiftUI ```swift -private let client = CheckoutProtocol.Client() - .on(CheckoutProtocol.start) { checkout in - print("Checkout started: \(checkout.id)") - } - .on(CheckoutProtocol.complete) { checkout in - print("Checkout completed: \(checkout.order?.id ?? "unknown")") - } +ShopifyCheckout(checkout: checkoutURL) + .connect(client) ``` -### Connecting to Accelerated Checkout Buttons - -Pass the client to `AcceleratedCheckoutButtons` using the `.connect()` modifier. Note that some event handlers like `.onFail` and `.onCancel` remain on the button view itself: +### Accelerated checkout buttons ```swift AcceleratedCheckoutButtons(cartID: cartID) - .onFail { error in - print("SDK error: \(error)") - } - .onCancel { - print("Sheet cancelled") - } - .connect(client) + .connect(client) ``` -### Presenting with ShopifyCheckoutSheetKit +The button-specific `onFail`, `onCancel`, and `onRenderStateChange` handlers remain on `AcceleratedCheckoutButtons`. -Pass the client when presenting a checkout sheet: +## Supported notifications -```swift -ShopifyCheckoutSheetKit.present(checkout: checkoutURL, delegate: client) -``` +Public notification descriptors include: + +- `CheckoutProtocol.start` +- `CheckoutProtocol.complete` +- `CheckoutProtocol.lineItemsChange` +- `CheckoutProtocol.messagesChange` +- `CheckoutProtocol.totalsChange` + +Use these for app behavior such as clearing local carts after completion, updating analytics, or logging checkout messages.