iOS SDK

Coming soon

The Paypercut iOS SDK is a drop-in native payment sheet — card, Apple Pay, and saved cards — that you present from your app with a single call. The customer pays inside a native SwiftUI sheet, and the SDK hands you back a payment method for your backend to charge.

Card details are captured directly into Paypercut's PCI-compliant vault on-device — they never pass through your app or your servers.


Why the native SDK?

  • Native UX — a SwiftUI sheet that renders card, Apple Pay, and saved cards, themed to your brand.
  • Minimal PCI scope — the PAN is captured straight into Paypercut's vault; it never touches your app or servers.
  • You stay in control of the charge — the SDK produces a payment method; your backend confirms the payment with your secret key.
  • Localized — the sheet ships in 18 languages and follows the device locale (or a locale you pass).

How It Works

The SDK is checkout-less — you supply the transaction context directly, with no checkout to create first.

  1. Your app presents the payment sheet with your publishable key and the amount / currency.
  2. The customer enters card details (or taps Apple Pay / picks a saved card) and confirms. 3-D Secure runs automatically when required.
  3. The SDK returns a payment method (pm_…) via onResult.
  4. Your backend confirms the payment with that payment method id using your secret key.

The SDK does not move money. It collects payment details and returns a payment method; charging it is a server-to-server call from your backend.


Prerequisites

  1. A Paypercut merchant accountSign up and get your publishable key (pk_test_… / pk_live_…) from API Keys.
  2. The transaction amount and currency — you pass these to the SDK directly. Optionally pass saved cards you've fetched server-to-server and a known customer email.
  3. (For Apple Pay) an Apple Pay merchant identifier registered with Paypercut — see Apple Pay setup.

Installation

Add the package in Xcode (File ▸ Add Package Dependencies…) or in your Package.swift:

dependencies: [
    .package(url: "https://github.com/paypercut/paypercut-ios-sdk.git", from: "2.0.0"),
],
targets: [
    .target(
        name: "YourApp",
        dependencies: [
            .product(name: "Paypercut", package: "paypercut-ios-sdk"),
        ]
    ),
]

The on-device tokenization dependency (Paypercut's PCI-compliant vault) is resolved transitively — you don't add it yourself.

Requirements: iOS 15+, SwiftUI.


Basic Integration

import SwiftUI
import Paypercut

struct CheckoutScreen: View {
    @State private var paypercut = Paypercut(
        publishableKey: "pk_test_…",   // your publishable key
        amount: 2500,                  // total in minor units (2500 = €25.00)
        currency: "EUR",
        applePayMerchantIdentifier: "merchant.com.yourcompany.yourapp"  // omit to hide Apple Pay
    )

    var body: some View {
        paypercut.paymentSheet { result in
            switch result {
            case let .created(pm):     confirmOnYourBackend(pm.paymentMethodId)
            case let .blik(code):      confirmBlikOnYourBackend(code)
            case .cancelled:           dismiss()
            case let .failed(message): showError(message)
            }
        }
    }
}

The sheet renders the enabled methods, collects the details, and returns a payment method. Charging it is a call from your backend.


Configuration

Paypercut — only publishableKey, amount, and currency are required:

Parameter Type Default Description
publishableKey String required Your Paypercut publishable key (pk_test_… / pk_live_…) — resolves your account
amount Int required Transaction total in minor units (e.g. 2500 = €25.00; 0 for a setup-mode card save)
currency String required ISO 4217 code (e.g. "EUR", "PLN")
currencyScale Int 2 Minor-unit exponent for the currency (2 for EUR/USD, 0 for JPY)
mode PaymentMode .payment .payment to charge now, .setup to save a card for later
paymentMethodTypes [String] ["card"] Which methods to render (e.g. ["card"], ["card", "blik"]). Wallets are gated by device + account, not this list
savedPaymentMethods [SavedPaymentMethodInput] [] Saved cards to offer for one-tap reuse (fetch them server-to-server). Rendered only when non-empty
allowSaveCard Bool true Show the "save card" checkbox on the card form. In setup mode it is always shown as required consent
collectBillingAddress Bool true Collect a billing address in the card form
billingAddress BillingAddress? nil Pre-fill the billing address. When set, the section renders collapsed to a summary (expandable); when absent, expanded
customerEmail String? nil A known buyer email. Hides the sheet's email field and feeds billing + 3-D Secure
customer String? nil A known customer id (01…), when you've selected one. Attributes the payment method to that customer
environment PaypercutEnvironment .prod .dev / .stage / .prod
locale String? device locale BCP-47 tag controlling the sheet's language (e.g. "pl", "de")
appearance Appearance default Branding overrides — see Appearance
applePayMerchantIdentifier String? nil Your Apple Pay merchant id (merchant.…). Omit to hide Apple Pay
enableLogging Bool false Verbose logging for development only — never enable in production
proceedOnThreeDSUnavailable Bool false When 3-D Secure comes back unavailable, proceed with the payment (no liability shift) instead of failing. Defaults to fail-closed

You pass the amount and currency directly; saved cards are the ones you supply. Which methods render is driven by paymentMethodTypes plus device / account support for wallets.

Appearance

Match the sheet to your brand:

let paypercut = Paypercut(
    publishableKey: "pk_test_…",
    amount: 2500,
    currency: "EUR",
    appearance: Appearance(
        theme: .system,                  // .system | .light | .dark
        brandColor: 0xFF6366F1,           // ARGB — CTA / brand colour (Pay button, save checkbox)
        brandColorContrast: 0xFF021A1B,   // ARGB — content on the brand (e.g. Pay button label)
        accentColor: 0xFF10B981,          // ARGB — interactive accent (selected radio, link, cursor)
        accentColorContrast: 0xFF021A1B,  // ARGB — content on the accent
        borderRadius: 12,                 // points — inputs & buttons
        fontFamily: "Inter"               // any font bundled in your app; nil = system font
    )
)

Re-running 3-D Secure

Confirmation is server-to-server: your backend confirms the payment against the Paypercut API with your secret key. Sometimes that confirm needs a fresh 3-D Secure authentication for the card — the API responds with payment_status: requires_action and a use_sdk next action (use_sdk.sdk == "three_d_secure"). Because the challenge runs on-device, your backend relays that action's metadata to the app, the app runs the challenge, and the resulting proof goes back for your backend to re-confirm with.

Plan for this from your first confirm — any card confirm can come back requires_action.

This step is a direct call, not an SDK event. onEvent and the paymentSheet result belong to the sheet and end once you have the pm_…. The retrigger is separate: you call handleNextAction(...), an async function that returns a ThreeDSProof (or throws) — nothing new arrives on onEvent.

1. Your backend confirms and inspects the response. On requires_action, read the use_sdk next action and forward its metadata to the app:

{
  "payment_status": "requires_action",
  "next_action": {
    "type": "use_sdk",
    "use_sdk": {
      "sdk": "three_d_secure",
      "metadata": { "vault_token_ref": "tok_…" }   // or "vault_token_intent_ref"
    }
  }
}

Trigger the app step only when next_action.type == "use_sdk" and use_sdk.sdk == "three_d_secure". (A top-level three_d_secure next action is the web browser flow — it does not apply to the native SDK.)

2. The app runs the challenge and returns a proof. Build the action straight from the forwarded metadata — the SDK resolves the vault key and card brand itself:

let action = PaypercutThreeDSAction(metadata: serverResponse.useSdkMetadata)
do {
    let proof = try await paypercut.handleNextAction(action)
    try await reConfirmOnYourBackend(proof)   // step 3
} catch {
    showError((error as? PaypercutError)?.displayMessage ?? "Authentication failed.")
}

3. Your backend re-confirms, server-to-server, passing the proof as payment_method_options.card.three_d_secure. That call returns the terminal outcome (succeeded / failed).

handleNextAction presents the issuer's challenge if required and returns a ThreeDSProof (cryptogram, electronicCommerceIndicator, transactionId, version). It uses the amount, currency, and account the instance was created with, and throws if authentication fails, is cancelled, or yields no proof. The SDK only produces the proof — the confirm and re-confirm are both server-to-server calls your backend owns.

Reuse the same Paypercut instance for the sheet and handleNextAction — the retrigger builds on context the SDK retained while the sheet tokenized the card, so call it on the instance that presented the sheet. And don't swap screens while you wait: confirming, the challenge, and re-confirming are one "we're working" window — keep a single loading spinner mounted and change its label (e.g. AuthenticatingConfirming payment), then move to your success / failure screen only on the terminal result.

Payment Methods

You control which methods appear through the config you pass:

  • Card — an inline card form, shown when "card" is in paymentMethodTypes (the default). When allowSaveCard is on, a "save card" checkbox lets the customer opt in to reuse the card later. A billing-address section appears when collectBillingAddress is on.
  • Apple Pay — the native Apple Pay button appears when the device supports it, your account has it enabled, and you pass applePayMerchantIdentifier. Apple Pay also returns the buyer's billing address.
  • Saved cards — pass one or more savedPaymentMethods (fetched server-to-server for your customer) and they're offered for one-tap selection; selecting one returns its pm_… as-is.
  • BLIK (Poland / PLN) — add "blik" to paymentMethodTypes. Returns the entered code (.blik), not a payment method. Your backend confirms the payment with the code and polls the result.

Handling the Result

onResult delivers one terminal outcome:

paypercut.paymentSheet { result in
    switch result {
    case let .created(pm):
        // pm.paymentMethodId  — pass this to your backend to confirm the payment
        // pm.type             — "card"
        // pm.wallet           — "apple_pay", or nil for a manual / saved card
        // pm.billingEmail     — buyer email captured on the sheet, if any
        // pm.saveForFutureUse — customer opted to save the card; pass as save_payment_method on confirm
        // pm.billingDetails   — name / email / billing address collected on the sheet (or from Apple Pay)
        confirmOnYourBackend(pm.paymentMethodId)
    case let .blik(code):
        confirmBlikOnYourBackend(code)   // single-use, expires ~2 min — confirm promptly
    case .cancelled:
        break                            // buyer dismissed the sheet
    case let .failed(message):
        showError(message)               // safe to show to the buyer
    }
}

When the customer ticks the save-card checkbox, pm.saveForFutureUse is true — forward it as save_payment_method when you confirm so the card is attached to the customer for future payments.

Lifecycle events (optional)

Pass onEvent for analytics / logging. It's observational — the sheet drives its own UI:

paypercut.paymentSheet(
    onEvent: { event in
        switch event {
        case .loaded:                                                            break
        case .processing:                                                        break
        case let .paymentMethodCreated(id, type, wallet, saveForFutureUse):      break
        case let .blikCodeEntered(code):                                         break
        case let .error(code, message):                                          break
        case .expired:                                                           break
        }
    }
) { result in /* … */ }

These events cover the sheet only. The 3-D Secure retrigger is a separate, direct call that does not flow through onEvent — see Re-running 3-D Secure.

Error codes

The .error event (and .failed result) carries a code and a buyer-safe message. Show the message; the code tells you which stage failed, so you can quote it when reporting an issue:

code Stage Meaning
tokenization_failed On-device tokenization The card or wallet token couldn't be secured into the vault.
threeds_authentication_failed 3-D Secure The issuer declined authentication, or the customer failed / cancelled the challenge.
card_declined, session_expired, payment_method_unavailable, … Payment-method create The Paypercut API rejected the create call; the code is the API's own error code.
nil Network / parsing A transport or decode failure — the message is generic on purpose.

The message is deliberately generic for tokenization and network failures — the SDK never surfaces vault or transport internals to the customer.


Apple Pay setup

Apple Pay needs a one-time merchant setup. Nothing certificate-related lives in your app — your app carries only your Apple Pay merchant identifier in its entitlement.

  1. Create an Apple Pay Merchant ID in the Apple Developer portal — must start with merchant. (e.g. merchant.com.yourcompany.yourapp).
  2. Enable Apple Pay on your app target (Signing & Capabilities → + Capability → Apple Pay) and tick that Merchant ID, then pass the same string to the SDK as applePayMerchantIdentifier.
  3. Register the Merchant ID with Paypercut — contact Paypercut to register your merchant.…. Paypercut generates a signing request; you upload it to your Merchant ID in Apple and return the certificate Apple issues. Registration is what lets Paypercut decrypt your Apple Pay tokens — required for live payments. (This is a native, in-app flow — there is no domain verification; the .well-known domain-association step applies only to web Apple Pay.)

In sandbox / test mode Apple Pay works before registration is finished. Registration and the certificate are required for live. A working sandbox flow does not prove live is set up.

See the Apple Pay setup guide for the full walkthrough.


Testing

Use sandbox keys (pk_test_…) and .dev / .stage environments during development. Test Apple Pay on a real device — Apple Pay does not run in the iOS Simulator.

For test cards and sandbox scenarios, see the Testing Guide.


Support & Resources


Ready to add native checkout to your iOS app? Create your free Paypercut account →