Loading...
Mobile SDKiOS

Integration guide

Step-by-step guide to integrating VoltCheckout into your iOS app.


Create a VoltCheckout instance

let checkout = VoltCheckout(
    configuration: .sandbox(
        customerId: "your-customer-id",
        tokenProvider: { try await YourAuth.fetchAccessToken() }
    )
)
  • customerId is your merchant identifier in the merchant portal.
  • tokenProvider is a closure the SDK calls before each API request. It expects a valid OAuth access token. Include scope=mobile in your token request to limit the token's privileges.

You are responsible for caching and refreshing the token — the SDK does not store it and calls this closure each time it needs one.

For details on authentication to Volt API see .

Create the VoltCheckout instance once and keep it alive for the duration of the session (e.g. in an @StateObject or as a property of your app's root object). Do not recreate it per payment.

Attach the sheet modifier

ContentView()
    .voltCheckoutSheet(using: checkout)

Attach this modifier to a view that is on screen when you start the flow. The SDK uses it to present its full-screen cover. You only need to attach it once — the same instance handles multiple consecutive payments.

Attach .voltCheckoutSheet to a view in the main navigation hierarchy, not inside a modal or alert that might be dismissed before the payment flow ends.

Build a PaymentIntent

PaymentIntent requires three things: an amount, payer information, and a transaction type.

The SDK ships a result builder that lets you compose a PaymentIntent declaratively. All failable initializers are handled automatically — if any component is invalid the whole expression evaluates to nil.

let intent = PaymentIntent {
    Amount(currency: .EUR, minorUnits: 7700)
    Payer(reference: "user@example.com") {
        Payer.Person(firstName: "Jane", lastName: "Doe")
    }
    TransactionType.goods
}
guard let intent else { /* validation failed */ return }

Payer accepts an optional email and phone, and its entity block can contain a Person, an Organization, or both — the correct Entity case is inferred automatically:

// Organisation payer
Payer(reference: "acme-corp") {
    Payer.Organization(name: "Acme Ltd")
}

// Both person and organisation
Payer(reference: "jane@acme.com", email: "jane@acme.com", phone: "+447700900123") {
    Payer.Person(firstName: "Jane", lastName: "Doe")
    Payer.Organization(name: "Acme Ltd")
}

Adding optional payment references works the same way — include PaymentReferences anywhere in the block:

let intent = PaymentIntent {
    Amount(currency: .EUR, minorUnits: 7700)
    Payer(reference: "user@example.com") {
        Payer.Person(firstName: "Jane", lastName: "Doe")
    }
    TransactionType.goods
    PaymentReferences(paymentReference: "ORD20250514", internalReference: "order-9f2c1")
}

TransactionType describes what the payment is for:

ValueDescription
.billUtility or recurring bill
.goodsPhysical goods
.servicesServices
.otherOther

Start the payment flow

let result = await checkout.payment(with: intent)

This suspends until the flow ends — either the payment is created, or the user dismisses the sheet. The SDK presents its UI automatically via the .voltCheckoutSheet modifier you attached in step 2.

Handle the result

switch result {
case .paymentCreated(let id, let status, let institution):
    // Payment was created. id is the Volt payment ID.
    // status is the status at the moment the user returned to your app.
    // institution is the bank the user paid from.
    print("Payment \(id) with status \(status) via \(institution.name)")

case nil:
    // User dismissed the sheet before the payment was created.
    // Treat this as a cancellation — no payment exists.
    break

default:
    break
}

status reflects the state at the moment the flow ended. Payments continue processing asynchronously after the user returns to your app. Subscribe to Volt webhooks to receive final status updates. For details on each status value see the .

nil means the user cancelled before payment was created — it is expected behaviour. Only treat .paymentCreated statuses like failed or refusedByBank as payment errors.

How is this guide?

Last updated on

On this page