Mobile
iOS SDK
Release candidate: the SDK is published to Swift Package Manager with Vezmo's mobile launch. Until then, use Checkout for apps.
VezmoCheckout adds the Vezmo payment sheet to a native iPhone app (SwiftUI or UIKit): cards with 3D Secure, Apple Pay, Klarna, Affirm, Afterpay, Amazon Pay, Cash App, UPI and Pix, in the buyer's own currency, in Vezmo's design. Your server keeps your API key; the app only ever gets a session token. The same sheet is in the React Native, Android and Flutter SDKs.
Requirements
- iOS 15.1 or later, Xcode 16 or later, Swift 5.9 or later.
- Your app added under Developers → Mobile apps, with its return link (see Take payments in your mobile app).
1. Install
Swift Package Manager: add the package and choose the VezmoCheckout library.
// Package.swift — or in Xcode: File → Add Package Dependencies…
dependencies: [
.package(url: "https://github.com/ACCEPT-GLOBAL-LIMITED/vezmo-mobile-sdk", .upToNextMinor(from: "0.1.0")),
],
targets: [
.target(name: "MyApp", dependencies: [.product(name: "VezmoCheckout", package: "vezmo-mobile-sdk")]),
]Add your return link's scheme (acme) under your target's Info → URL Types, and NSCameraUsageDescription to Info.plist so buyers can scan a card.
2. Set up
import VezmoCheckout
let vezmo = VezmoCheckout(configuration: .init(
returnURL: URL(string: "acme://vezmo"), // registered under Developers → Mobile apps
applePay: .init(merchantId: "merchant.com.acme") // Apple Pay shows only with your Merchant ID
))Hand the payment the return link when your app opens with it (after 3D Secure, a bank app or a pay-later provider). If your return link is a universal link, it arrives in scene(_:continue:) as userActivity.webpageURL:
// SwiftUI
WindowGroup { RootView().onOpenURL { VezmoCheckout.handleURL($0) } }
// UIKit (scene delegate)
func scene(_ scene: UIScene, openURLContexts contexts: Set<UIOpenURLContext>) {
contexts.forEach { VezmoCheckout.handleURL($0.url) }
}
// UIKit without scenes (app delegate)
func application(_ app: UIApplication, open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
VezmoCheckout.handleURL(url)
}
// A universal link as your return link (scene delegate)
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
if let url = userActivity.webpageURL { VezmoCheckout.handleURL(url) }
}3. Create the session on your server
The same API checkout session as the web. Your server first exchanges your API key and secret for an access token (see Authenticate); sandbox keys are under VezmoPay → Developers → API keys.
POST https://api.vezmo.com/api/v1/merchant/secure-payments
Authorization: Bearer <access token>
{
"title": "Order #1042",
"amount": 49.00,
"currency": "USD",
"client": { "name": "Ana Silva", "email": "ana@example.com", "country": "US", "postalCode": "10001" }
}Send data.securePayment.clientToken to the app. Add app.returnUrl only if you also open Checkout for apps.
4. Take the payment
func pay(orderId: String) async throws {
let clientToken = try await myAPI.createVezmoSession(orderId) // your server, your API key
if let error = await vezmo.prepare(clientToken: clientToken) {
return showError(error.message)
}
switch await vezmo.present(from: self) { // never throws
case .succeeded(let paymentId, _, _): showPaid(paymentId)
case .processing: showPaymentReceived() // UPI or Pix still settling
case .canceled: break // the buyer closed the sheet
case .failed(let error): showError(error.message)
}
}In SwiftUI, prepare the session, then present it with the modifier:
struct CheckoutButton: View {
let vezmo: VezmoCheckout
@State private var showing = false
var body: some View {
Button("Pay") {
Task {
let clientToken = try await myAPI.createVezmoSession()
if await vezmo.prepare(clientToken: clientToken) == nil { showing = true }
}
}
.vezmoCheckout(vezmo, isPresented: $showing) { result in
// .succeeded, .processing, .canceled or .failed
}
}
}Fulfil the order from the webhook (payment.success), not from the result alone.
Subscriptions
Start a subscription in the app: the first payment, or a free trial with the card saved for the renewals. Your server creates it with your API key and sends its checkoutToken to the app; prepare with subscriptionToken.
// checkoutToken from POST /merchant/subscription, via your server
if let error = await vezmo.prepare(subscriptionToken: checkoutToken) { return showError(error.message) }
switch await vezmo.present(from: self) {
case .succeeded(let paymentId, _, let subscription?):
// subscription.ref → "VS7K2P9Q4FXM"; subscription.status → .trialing or .active
// paymentId → the first payment, or nil when a free trial started
showSubscribed(subscription)
case .processing(_, let subscription):
showStarting(subscription) // settling; the webhook tells your server
case .canceled:
break
case .failed(let error):
showError(error.message)
default:
break
}The sheet shows what is due today, the plan after it (“7-day free trial, then $9.99 / month from 23 Oct 2026”), the authorization the buyer gives, and Start free trial or Subscribe. Cards and Apple Pay; Apple Pay shows the plan as a recurring payment (iOS 16 and later) and lists it in the buyer's Wallet with their page to manage it. Vezmo charges the renewals; your server hears about each one from the subscription webhooks. Digital goods used inside the app follow the app store rules.
When the buyer starts a free trial, Vezmo sends subscription.trial_started; the first charge sends subscription.activated (or subscription.payment_failed). See Subscriptions → Webhooks.
Results and errors
.succeeded(paymentId, transactionId, subscription) | Paid. Both ids match the dashboard and the webhook. subscription is set for a subscription checkout; paymentId is nil when a free trial started. |
.processing(paymentId, subscription) | Paid, money on its way (UPI or Pix still settling). Wait for the webhook. |
.canceled | The buyer closed the sheet. Not an error. |
.failed(error) | error.code is one of the codes below; error.message is written for the buyer and safe to show. |
error.code (also returned by prepare when a session can't be paid):
.declined | The bank said no. The buyer can try another card or way to pay. |
.expired | The session ran out. Create a new one on your server. |
.alreadyPaid | The session was already paid. Treat the order as paid. |
.notAvailable | This way to pay can't be used for this session. |
.network | No connection. Let the buyer try again. |
.config | A setup mistake in the app, for example present before prepare. |
.unknown | Anything else. Let the buyer try again. |
Appearance
The sheet follows Vezmo's checkout design in light and dark. The color keys are accent, onAccent, surface, surface2, field, fieldLine, line, ink, ink2, ink3, good and bad; a dynamic UIColor can differ between light and dark. Your own accent replaces the Vezmo gradient on the Pay button.
let vezmo = VezmoCheckout(configuration: .init(
returnURL: URL(string: "acme://vezmo"),
style: .automatic, // .light, .dark
appearance: .init(
colors: [
.accent: .systemBlue,
.surface: UIColor { $0.userInterfaceStyle == .dark ? UIColor(white: 0.11, alpha: 1) : .white },
],
cornerRadius: 12,
fontFamily: "Inter" // a font already in your app
)
))Apple Pay
- In your Apple Developer account, create a Merchant ID and turn on Apple Pay Payment Processing for your app's App ID with it. In Xcode, add the Apple Pay capability with that Merchant ID.
- In your Vezmo dashboard, open Developers → Mobile apps → Apple Pay in your iPhone app and add your Apple Merchant ID. We attach a request file; create the Apple Pay Payment Processing Certificate with it in your Apple Developer account and upload it on the same page. Apple Pay turns on once we've added it. Certificates last 25 months; the page reminds you to renew.
- Pass the Merchant ID as
applePay: .init(merchantId:). Without it the sheet shows no Apple Pay.
Testing
Create the session with your sandbox API key and pay with test card 4242 4242 4242 4242, any future expiry and any CVC. 3D Secure shows a test page where you can approve or fail the payment. Card 4000 0000 0000 0002 is always declined; 4000 0000 0000 3220 always asks for 3D Secure. In the sandbox, Apple Pay uses the cards in the phone's Wallet without charging them. More test cards are in Test mode. The example app (a small shop) pays against Vezmo's sandbox; how to run it is in the SDK's README on GitHub.
Going live
- Your server creates sessions with your live API key.
- Apple Pay: your Apple Merchant ID shows Active under Developers → Mobile apps → Apple Pay in your iPhone app, and the app has the Apple Pay capability.
- Your app declares its return link's scheme and passes incoming links to
VezmoCheckout.handleURL. - Live sessions are refused until your account is approved.
- The rest of Before you go live, and the app store rules if you sell anything used inside the app.
Troubleshooting
- No Apple Pay: no
applePayin the configuration, the Apple Pay capability is missing, no card in the phone's Wallet, or the Apple Pay certificate isn't Active (Developers → Mobile apps → Apple Pay in your iPhone app shows where it stands and when it ends). - The buyer doesn't come back after 3D Secure or a bank app: the
returnURLin your configuration must be registered under Developers → Mobile apps, your app must declare that scheme, and it must pass the link toVezmoCheckout.handleURL. - "Couldn't load payment options": the reason is in the app's console (subsystem
com.vezmo.checkout). The buyer can tap Try again.