Vezmo App

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
// 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

Checkout.swift
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:

App
// 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.

Your server
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

UIKit
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:

SwiftUI
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.

Subscribe
// 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.
.canceledThe 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):

.declinedThe bank said no. The buyer can try another card or way to pay.
.expiredThe session ran out. Create a new one on your server.
.alreadyPaidThe session was already paid. Treat the order as paid.
.notAvailableThis way to pay can't be used for this session.
.networkNo connection. Let the buyer try again.
.configA setup mistake in the app, for example present before prepare.
.unknownAnything 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.

Configuration
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

  1. 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.
  2. 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.
  3. 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

  1. Your server creates sessions with your live API key.
  2. 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.
  3. Your app declares its return link's scheme and passes incoming links to VezmoCheckout.handleURL.
  4. Live sessions are refused until your account is approved.
  5. 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 applePay in 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 returnURL in your configuration must be registered under Developers → Mobile apps, your app must declare that scheme, and it must pass the link to VezmoCheckout.handleURL.
  • "Couldn't load payment options": the reason is in the app's console (subsystem com.vezmo.checkout). The buyer can tap Try again.