A Swift Package for embedding Crossmint's checkout experience in iOS apps.
Add the package via Swift Package Manager:
In Xcode: File > Add Package Dependencies, paste the URL below, and select Up to Next Major Version from 1.0.0.
https://github.com/Crossmint/crossmint-checkout-swift.git
Or add it to your Package.swift:
dependencies: [
.package(url: "https://github.com/Crossmint/crossmint-checkout-swift.git", from: "1.0.0")
]Then add "CrossmintCheckout" to your target's dependencies.
Use the Crossmint Orders API to create an order from your backend. The response includes an orderId and clientSecret.
POST https://www.crossmint.com/api/2022-06-09/orders
Pass your client-side API key (ck_...) — the same key used by the Orders API's client-side counterpart. It is required; without it the hosted checkout renders an "Invalid input" configuration error.
import SwiftUI
import CrossmintCheckout
struct CheckoutView: View {
let orderId: String
let clientSecret: String
var body: some View {
CrossmintEmbeddedCheckout(
apiKey: "ck_production_...",
orderId: orderId,
clientSecret: clientSecret
)
}
}| Property | Type | Required | Description |
|---|---|---|---|
apiKey |
String |
Yes | Your client-side API key (ck_...) |
orderId |
String? |
Yes* | The order ID returned by the Orders API |
clientSecret |
String? |
Yes* | The client secret returned by the Orders API |
payment |
CheckoutPayment? |
No | Payment configuration (fiat/crypto, allowed methods) |
appearance |
CheckoutAppearance? |
No | UI customization (colors, fonts, border radius) |
lineItems |
CheckoutLineItems? |
No | Line item configuration (not yet implemented) |
recipient |
CheckoutRecipient? |
No | Recipient configuration (not yet implemented) |
identityVerificationHandling |
IdentityVerificationHandling? |
No | .external renders no KYC step inside checkout; you present it yourself (see below) |
controller |
CrossmintCheckoutController? |
No | Observable order state (order, client secret, KYC credentials) |
consoleLogLevel |
CheckoutLogLevel |
No | Minimum level of SDK messages written to the system console. Diagnostics also go to Crossmint at every level. Defaults to .error |
CrossmintEmbeddedCheckout(
apiKey: "ck_production_...",
orderId: orderId,
clientSecret: clientSecret,
payment: CheckoutPayment(
crypto: CheckoutCryptoPayment(enabled: true, defaultChain: "base"),
fiat: CheckoutFiatPayment(
enabled: true,
defaultCurrency: "usd",
allowedMethods: CheckoutAllowedMethods(applePay: true, card: true)
),
defaultMethod: .fiat
)
)CrossmintEmbeddedCheckout(
apiKey: "ck_production_...",
orderId: orderId,
clientSecret: clientSecret,
appearance: CheckoutAppearance(
fonts: [.googleFonts("Inter", weights: [.regular, .semibold])],
variables: CheckoutAppearanceVariables(
fontFamily: "Inter, sans-serif",
spacingUnit: 4,
borderRadius: 8,
colors: CheckoutVariablesColorStyle(
textPrimary: .color(.primary),
textSecondary: .color(.secondary),
backgroundPrimary: .uiColor(.systemBackground),
borderPrimary: .hex("#E0E0E0")
)
),
rules: CheckoutAppearanceRules(
primaryButton: CheckoutPrimaryButtonRule(
borderRadius: 12,
font: CheckoutFontStyle(size: .px(17), weight: .semibold),
colors: CheckoutColorStyle(text: .color(.white), background: .hex("#6C5CE7"))
)
)
)
)Load fonts with .googleFonts(_:weights:), or with .cssURL(_:) for other Google Fonts links. The checkout ignores stylesheets from other domains.
Set colors with a SwiftUI Color, a UIColor, a hex string, or a CSS color string. If a color changes between light and dark mode, the checkout uses the value for its mode when it loads.
Set lengths such as borderRadius and spacingUnit with a number of points, or with .rem(_:), .em(_:), or .custom(_:).
Note:
lineItemsandrecipientare accepted as parameters but not yet implemented. Passing either will display an error.
Pass a CrossmintCheckoutController to observe the order as the buyer progresses. The controller exposes the latest order, the orderClientSecret, and derived identityVerificationCredentials.
struct CheckoutView: View {
@StateObject private var controller = CrossmintCheckoutController()
var body: some View {
CrossmintEmbeddedCheckout(
apiKey: "ck_production_...",
orderId: orderId,
clientSecret: clientSecret,
controller: controller
)
.onReceive(controller.$order) { order in
print("Order phase: \(String(describing: order?.phase))")
}
}
}Order events are also available as chained methods: .onOrderUpdated { update in } and .onOrderCreationFailed { message in }.
Reusing a controller across checkout sessions requires calling clear() first.
Some orders require identity verification before payment. By default checkout renders that step inline and you do nothing.
To show the step in your own layout instead, pass identityVerificationHandling: .external to checkout. Then watch the controller for credentials and present CrossmintIdentityVerification with them. A sheet works well: present it when the order first has credentials, keep passing the latest ones while it is open, close it in onComplete or onCancel, and do not reopen it for that order, even after a swipe dismiss. Do not close it when the credentials go away: the order can leave requires-kyc as soon as the buyer submits, before the flow shows its result. Track the closed order by its orderId, not the credentials id, which can repeat across orders.
struct CheckoutView: View {
@StateObject private var controller = CrossmintCheckoutController()
@State private var kycCredentials: IdentityVerificationCredentials?
@State private var kycClosedOrderId: String?
var body: some View {
CrossmintEmbeddedCheckout(
apiKey: "ck_production_...",
orderId: orderId,
clientSecret: clientSecret,
identityVerificationHandling: .external,
controller: controller
)
.onChange(of: controller.identityVerificationCredentials) { credentials in
if let credentials, controller.order?.orderId != kycClosedOrderId {
kycCredentials = credentials
}
}
.sheet(item: $kycCredentials, onDismiss: { kycClosedOrderId = controller.order?.orderId }) { credentials in
CrossmintIdentityVerification(
apiKey: "ck_production_...",
credentials: credentials
)
.onComplete { status in
print("KYC finished: \(status)")
kycCredentials = nil
}
.onCancel { kycCredentials = nil }
.onError { error in print("KYC error: \(error.message)") }
}
}
}Checkout does not wait for a signal from your component. It polls the order until the backend confirms the verification, then continues on its own.
CrossmintIdentityVerification can also be used standalone, without embedded checkout, if you obtain the credentials from your backend's order response (payment.preparation.kyc).
| Property | Type | Required | Description |
|---|---|---|---|
apiKey |
String |
Yes | Your client-side API key (ck_...) |
credentials |
IdentityVerificationCredentials |
Yes | From the controller or your backend's order response |
locale |
CheckoutLocale? |
No | UI language of the verification flow |
consoleLogLevel |
CheckoutLogLevel |
No | Minimum level of SDK messages written to the system console. Diagnostics also go to Crossmint at every level. Defaults to .error |
Attach handlers with chained methods, all optional:
.onReady { }— the verification UI finished loading.onComplete { status in }— the buyer finished; the status carries the outcome.onCancel { }— the buyer dismissed the flow.onError { error in }— something failed;retriablesays whether showing the view again can work
The view fills the space you give it, and the verification content scrolls inside the view. Use onReady to drive your own loading indicator.
Note: Document capture needs camera access. Add
NSCameraUsageDescriptionto your app's Info.plist or the capture step fails.
Note:
identityVerificationHandlingrequires a current Crossmint deployment. Older deployments ignore the flag and render the verification step inline as well.
See a full working example at crossmint-swift-checkout-demo.
See LICENSE for details.