Pay By Card

Pay by Card lets customers pay with a debit or credit card in a dedicated, card-only sheet, with 3D Secure authentication handled by the SDK. Use the standalone mode when you want to offer card payments directly — for example, as your only payment method or behind your own payment method selection UI. If you want payabl. to present all payment methods in one flow instead, use the Hosted Payment Page.

Order PagePay By Card Page

Flow: user taps Pay by card → card sheet opens → user enters card details (or selects a saved card) → result is returned via PBLPaymentResult.

Prerequisites

  • The SDK is added to your project — see iOS SDK.
  • Your backend can create a session via /mobile/init — see Setup Your Server.

Integration

The SDK drives the payment flow through delegates. Your view model adopts:

  • PBLPayByCardDelegate — provides the session configuration when the user taps the button, and receives the payment result.
  • PBLErrorDelegate — receives SDK errors raised outside the payment result.

1. Fetch session configuration

Request a session from your backend and build a PBLConfiguration. Create a new session for every payment attempt.

class CartViewModel: ObservableObject {
    @Published var isLoading = false

    // In your server, this endpoint should call the /mobile/init API
    let backendCheckoutUrl = URL(string: "backend_endpoint/payment_page")!

    func prepareOrder() async -> PBLConfiguration? {
        var request = URLRequest(url: backendCheckoutUrl)
        request.httpMethod = "POST"

        return await withCheckedContinuation { continuation in
            let task = URLSession.shared.dataTask(with: request) { data, response, error in
                guard let data = data,
                      let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
                else { return }

                let sessionId = json["sessionId"] as! String
                let ephemeralKey = json["empheralKey"] as! String
                let transactionId = json["transactionId"] as! Int

                continuation.resume(
                    returning: PBLConfiguration(
                        sessionId: sessionId,
                        ephemeralKey: ephemeralKey,
                        customerId: "your_stable_customer_id",
                        environment: .sandbox, // use .production when going live
                        transactionId: transactionId,
                        appleMerchantId: "merchant.payabl.isdk" // your Apple Merchant ID
                    )
                )
            }
            task.resume()
        }
    }
}
💳

Saved cards

The SDK can securely save a customer's card and offer it for reuse on their next payment. To enable this, pass a stable, unique customerId for the customer in PBLConfiguration — the same value every time that customer pays.

Saved cards are scoped to the customerId, so the value must consistently identify the same person across sessions (for example, your internal customer ID). If you pass a different, random, or empty customerId between payments, previously saved cards won't be shown.

Never use a value that changes per session (such as the sessionId) or personally identifying data (such as an email address) as the customerId.

2. Adopt the delegates and add the button

Adopt PBLPayByCardDelegate to provide the configuration when the user taps the button and to receive the result. Present the card sheet using PBLPayByCardButton.

extension CartViewModel: PBLPayByCardDelegate {
    // Called when the user taps the button — load the session, then hand the config back to the SDK
    func payByCardPageTapped(onLoadSessionCompleted: @escaping (PBLConfiguration) -> Void) {
        Task {
            guard let config = await prepareOrder() else { return }
            onLoadSessionCompleted(config)
        }
    }

    // Called with the final result of the payment
    func payByCardPageCompleted(result: PBLPaymentResult) {
        handlePaymentResult(result)
    }
}

extension CartViewModel: PBLErrorDelegate {
    func errorOccured(error: any Error) {
        // Handle SDK errors (e.g., configuration or session issues)
        print("error from sdk: ", error)
    }
}
struct ContentView: View {
    @StateObject var cartVM = CartViewModel()

    var body: some View {
        VStack {
            PBLPayByCardButton(delegate: cartVM, errorDelegate: cartVM) {
                Text("Pay by card")
                    .foregroundStyle(.white)
                    .font(.body)
                    .frame(maxWidth: .infinity, minHeight: 45)
            }
        }
    }
}

3. Handle the result

payByCardPageCompleted returns a PBLPaymentResult. Handle all three cases.

func handlePaymentResult(result: PBLPaymentResult) {
    switch result {
    case .completed:
        // Payment successful — update order status
        break
    case .canceled:
        // User cancelled the payment
        break
    case .failed:
        // Payment failed — inspect the result for the error code
        break
    }
}

Best practices

Do:

  • Create a new session for each payment attempt.
  • Build the PBLConfiguration only when the user starts a payment.
  • Pass a stable, unique customerId per customer if you want saved cards to work.

Don't:

  • Reuse sessions across multiple payments.
  • Share sessions between users.
  • Use a changing or shared value for customerId — it breaks saved cards and can mix customers' saved cards.

Testing

Use the sandbox environment (.sandbox) throughout development. To smoke-test a payment, use this success card (expiry: any future date; CVV: any 3 digits):

Card networkCard numberExpected result
VISA4149011500000147PBLPaymentResult.completed
📘

For the complete set of test cards and decline scenarios (insufficient funds, do-not-honor, and more), see Testing.

Common issues

IssueCause & resolution
Saved card not appearing for a returning customerA different, empty, or changing customerId was passed. Use the same stable customerId for the customer on every payment.
Session error on launchThe session was reused, expired, or belongs to a different user. Create a fresh session per attempt.
Payment fails with an error codeLook up the code in the Error Code Reference — declines like insufficient funds (51) come from the card issuer and are expected in normal operation.

When something goes wrong, see Error Handling & Troubleshooting.