Skip to content

Implementation

Complete guide to integrating Checkout Drop-in into your iOS application.

Overview

Checkout Drop-in provides a complete, pre-built payment interface that automatically handles multiple payment methods — cards, PayPal, Apple Pay, and Paze (when enabled in your session).

It follows a simple three-step lifecycle:

  1. Initialise: Configure Drop-in with your session and transaction data (CheckoutDropIn(config:) validates sessionId and hmacKey).
  2. Create: Await create() to fetch site configuration, resolve the shopper via onGetShopper, and prepare payment components.
  3. Render: Display the UI using buildContent() within a SwiftUI view.

Drop-in automatically detects available payment methods, applies server-driven branding, and signs PXP API requests using your session's HMAC key.

Backend verification is mandatory. Always verify payments on your backend before fulfilling orders. Frontend callbacks can be manipulated by malicious users.

Before you start

Make sure you've activated the Checkout Drop-in service in the Unity Portal. Contact your account manager if you need access.

Step 1: Add the SDK dependency

Add the iOS SDK to your project using Swift Package Manager.

  1. In Xcode, go to File > Add Package Dependencies.
  2. Enter the package URL: https://github.com/PXP-IO/ios-components-sdk
  3. Select the latest version and click Add Package.

Alternatively, add it to your Package.swift file:

dependencies: [
    .package(url: "https://github.com/PXP-IO/ios-components-sdk", from: "1.0.0")
]

Step 2: Get your API credentials

In order to initialise Checkout Drop-in, you'll need to send authenticated requests to the PXP API.

To get your credentials:

  1. In the Unity Portal, go to Merchant setup > Merchant groups.
  2. Select a merchant group.
  3. Click the Inbound calls tab.
  4. Copy the Client ID in the top-right corner.
  5. Click New token.
  6. Choose a number of days before token expiry. For example, 30.
  7. Click Save to confirm. Your token is now created.
  8. Copy the token ID and token value. Make sure to keep these confidential to protect the integrity of your authentication process.

As best practice, we recommend regularly generating and implementing new tokens.

Step 3: Create a session on your backend

Checkout Drop-in requires a session from the PXP Sessions API. This must be done on your backend using HMAC authentication to keep your credentials secure.

Understanding HMAC authentication

Our platform uses HMAC (Hash-based Message Authentication Code) with SHA256 for authentication to ensure secure communication and data integrity. This method involves creating a signature by hashing your request data with a secret key, which must then be included in the HTTP headers of your API request.

Create an HMAC signature

To create the HMAC signature, you need to prepare a string that includes five parts separated by colons:

  • Token ID: your API token identifier (e.g., 9aac6071-38d0-4545-9d2f-15b936af6d7f)
  • Timestamp: the current time in Unix milliseconds (e.g., 1754701373845)
  • Request ID: a unique UUID for this request (e.g., ce244054-b372-42c2-9102-f0d976db69f6)
  • Request path: the API endpoint path: api/v1/sessions
  • Request body: the complete JSON request body as a minified string

Example request body to minify:

{
  "merchant": "MERCHANT-1",
  "site": "SITE-1",
  "sessionTimeout": 120,
  "merchantTransactionId": "0ce72cfd-014d-4256-a006-a56601b2ffc4",
  "transactionMethod": {
    "intent": {
      "card": "Authorisation",
      "paypal": "Purchase"
    }
  },
  "amounts": {
    "currencyCode": "USD",
    "transactionValue": 25.00
  },
  "allowTransaction": true,
  "serviceType": "CheckoutDropIn"
}

When creating the HMAC signature, the request body must be minified (no whitespace or formatting). The formatted JSON above is for readability only.

Session request parameters

The session request accepts the following parameters:

ParameterDescription
merchant
string (≤ 20 characters)
required
Your unique merchant identifier, as assigned by PXP. You can find it in the Unity Portal, by going to Merchant setup > Merchants and checking the Merchant ID column.
site
string (≤ 20 characters)
required
Your unique site identifier, as assigned by PXP. You can find it in the Unity Portal, by going to Merchant setup > Sites and checking the Site ID column.
merchantTransactionId
string (≤ 50 characters)
required
A unique identifier of your choice that represents this transaction.
sessionTimeout
number
required
The duration of the session, in minutes.
transactionMethod
object
required
Details about the transaction method, including the intent for each payment type.
transactionMethod.intent
object
required
The transaction intent for each payment method.
transactionMethod.intent.card
string
The intent for card and Apple Pay transactions.

Possible values:
  • Authorisation
  • Purchase
  • Verification
  • EstimatedAuthorisation
transactionMethod.intent.paypal
string
The intent for PayPal transactions.

Possible values:
  • Authorisation
  • Purchase
amounts
object
required
Details about the transaction amount.
amounts.currencyCode
string (3 characters)
required
The currency code associated with the transaction, in ISO 4217 format. See Supported payment currencies.
amounts.transactionValue
number
required
The transaction amount. The numbers after the decimal will be zero padded if they are less than the expected currencyCode exponent. For example, GBP 1.1 = GBP 1.10, USD 1 = USD 1.00, or BHD 1.3 = 1.300. The transaction will be rejected if numbers after the decimal are greater than the expected currencyCode exponent (e.g., GBP 1.234), or if a decimal is supplied when the currencyCode of the exponent does not require it (e.g., JPY 1.0).
allowTransaction
boolean
Whether or not to proceed with the transaction.
serviceType
string
required
The service type. This must be set to "CheckoutDropIn" for Drop-in integrations.

Session response

If your request is successful, you'll receive a 200 response containing the session data:

{
  "sessionId": "c5f0799b-0839-43ce-abc5-5b462a98f250",
  "hmacKey": "904bc42395d4af634e2fd48ee8c2c7f52955a1da97a3aa3d82957ff12980a7bb",
  "encryptionKey": "20d175a669ad3f8c195c9c283fc86155",
  "sessionExpiry": "2025-05-19T13:39:20.3843454Z",
  "allowedFundingTypes": {
    "cardSchemes": [
      "Visa",
      "Diners",
      "Mastercard",
      "AmericanExpress"
    ],
    "cards": [],
    "wallets": {
      "paypal": {
        "allowedFundingOptions": [
          "paylater", 
          "paypal"
        ],
        "merchantId": "paypal-merchant-123"
      },
      "applePay": {
        "merchantId": "merchant.com.example.store",
        "merchantName": "Your Store Name"
      }
    }
  }
}

Checkout Drop-in automatically detects available payment methods from the allowedFundingTypes in your session data. You don't need to manually configure which payment methods to show.

Step 4: Initialise Drop-in in your app

Import the necessary types and initialise Drop-in with your configuration.

import SwiftUI
import PXPCheckoutSDK

@MainActor
final class CheckoutViewModel: ObservableObject {
    @Published var dropIn: CheckoutDropIn?
    @Published var errorMessage: String?
    
    func loadDropIn() async {
        do {
            // Fetch session data from your backend
            let session = try await fetchSessionFromBackend()
            
            // Build transaction data
            let transactionData = DropInTransactionData(
                amount: Decimal(string: "49.99") ?? 0,
                currency: "USD",
                entryType: .ecom,
                intent: DropInTransactionIntentData(
                    card: .authorisation,
                    paypal: .authorisation
                ),
                merchantTransactionId: UUID().uuidString,
                merchantTransactionDate: { Date() }
            )
            
            // Create Drop-in configuration
            let config = CheckoutDropInConfig(
                environment: .test,
                session: session,
                transactionData: transactionData,
                merchantShopperId: "shopper-123",
                ownerId: "merchant-group-id",
                kountDisabled: false,
                onGetShopper: { async in
                    TransactionShopper(id: "shopper-123")
                },
                onBeforeSubmit: { paymentMethod in
                    // Validate your checkout state
                    return true
                },
                onSubmit: { paymentMethod in
                    print("Payment started: \(paymentMethod.rawValue)")
                },
                onSuccess: { result in
                    // CRITICAL: Verify on backend before fulfilling order
                    print("Payment successful: \(result.systemTransactionId)")
                },
                onError: { paymentMethod, error in
                    print("Payment failed: \(error.errorMessage)")
                }
            )
            
            // Initialise and create Drop-in
            let instance = try CheckoutDropIn(config: config)
            await instance.create()
            dropIn = instance
            
        } catch {
            errorMessage = error.localizedDescription
        }
    }
    
    private func fetchSessionFromBackend() async throws -> SessionData {
        // Implement your backend API call here
        let url = URL(string: "https://your-backend.com/api/create-session")!
        let (data, _) = try await URLSession.shared.data(from: url)
        
        // Decode directly to SessionData to preserve all fields including sessionExpiry and restrictions
        return try JSONDecoder().decode(SessionData.self, from: data)
    }
}

Configuration parameters

The CheckoutDropInConfig struct accepts the following configuration parameters:

ParameterDescription
environment
Environment
required
The environment type.

Possible values:
  • .test: For development and testing.
  • .live: For live transactions.
session
SessionData
required
Details about the checkout session returned from the Unity Sessions API. Includes sessionId, hmacKey, encryptionKey, and allowedFundingTypes.
transactionData
DropInTransactionData
required
Details about the transaction.
merchantShopperId
String
required
Your unique identifier for this shopper. Required at initialisation. Used for Card-on-File and other shopper-scoped flows.
ownerId
String (≤ 20 characters)
required
Your unique merchant or merchant group identifier, as assigned by PXP. You can find it in the Unity Portal.
localisation
Localisation?
Optional custom labels and messages for the drop-in interface. Use this to override default text.
locale
String?
Locale for built-in SDK strings (for example "en-US", "es-ES"). When omitted, Drop-in defaults to "en-US". It does not automatically follow the device language. See Configuration.
paypalConfig
PayPalConfig?
Optional PayPal configuration used by SDK-level PayPal flows.
restrictions
Restrictions?
Optional card restrictions supplied at SDK level. Can filter by card owner type (consumer/corporate) and funding source (credit/debit/prepaid).
kountDisabled
Bool?
Set to true to disable Kount fraud detection. Defaults to false.
methodConfig
DropInMethodConfig?
Optional payment-method-specific configuration for global, card, PayPal, and Apple Pay behaviour. See Configuration reference.
onGetShippingAddress
(() async -> ShippingAddress?)?
Async function to supply shipping address when Drop-in needs it.
onGetShopper
(() async -> TransactionShopper?)?
Async function to retrieve shopper information. Required for Card-on-File functionality. Returns a TransactionShopper object containing id.
analyticsEvent
((BaseAnalyticsEvent) -> Void)?
Receives SDK analytics events so merchants can forward them to their analytics platform. See Analytics.
onBeforeSubmit
((DropInPaymentMethod) async -> Bool)?
Async validation function called before payment submission. Return true to proceed or false to block submission.
onSubmit
((DropInPaymentMethod) -> Void)?
Called when payment processing starts for card and Apple Pay. Not called for PayPal — show loading after onBeforeSubmit returns true or when Drop-in enters processing during PayPal order creation.
onSuccess
((DropInSubmitResult) -> Void)?
Called when payment succeeds. Strongly recommended in production. Always verify the payment on your backend using the provided transaction identifiers before fulfilling the order.
onError
((DropInPaymentMethod?, BaseSdkException) -> Void)?
Called when initialisation or payment fails. Strongly recommended in production. The paymentMethod parameter can be nil for initialisation or configuration errors that occur before a payment method is selected. Handle errors appropriately and show user-friendly messages.

Transaction data parameters

The DropInTransactionData struct describes the payment transaction:

ParameterDescription
amount
Decimal
required
The transaction amount.
currency
String
required
The three-letter currency code in ISO 4217 format (e.g., "USD", "GBP", "EUR").
entryType
EntryType
required
The transaction entry type.

Possible values:
  • .ecom: E-commerce transaction (required for PayPal)
  • .moto: Mail order / telephone order
intent
DropInTransactionIntentData
required
The transaction intent for each payment method. Use DropInTransactionIntentData, not the generic TransactionIntentData.
merchantTransactionId
String
required
A unique identifier for this transaction. Use UUID().uuidString to generate.
merchantTransactionDate
() -> Date
required
A closure that returns the current date. Typically { Date() }.
cardAcceptorName
String?
Optional card acceptor name when required by your flow.
recurring
RecurringType?
Optional recurring payment data for subscription or installment use cases.
linkId
String?
Optional link ID when linking transactions together.

Step 5: Render the drop-in UI

Display the drop-in interface within a SwiftUI view:

import SwiftUI
import PXPCheckoutSDK

struct CheckoutView: View {
    @StateObject private var viewModel = CheckoutViewModel()
    
    var body: some View {
        Group {
            if let dropIn = viewModel.dropIn {
                dropIn.buildContent()
            } else if let error = viewModel.errorMessage {
                VStack(spacing: 16) {
                    Text("Unable to load checkout")
                        .font(.headline)
                    Text(error)
                        .font(.body)
                        .foregroundColor(.secondary)
                    Button("Retry") {
                        Task {
                            await viewModel.loadDropIn()
                        }
                    }
                }
                .padding()
            } else {
                ProgressView("Loading checkout...")
            }
        }
        .task {
            await viewModel.loadDropIn()
        }
    }
}

The buildContent() method returns AnyView containing the Drop-in UI. Call await create() before rendering — if create() has not completed, buildContent() returns an empty view. The SDK handles payment method selection and branding inside the hosted component.

Step 6: Handle payment callbacks

Drop-in provides four key callbacks to manage the payment flow.

onBeforeSubmit

Called before payment processing starts. Use this to validate your checkout state.

onBeforeSubmit: { paymentMethod in
    // Validate terms accepted, shipping address, etc.
    guard hasAcceptedTerms else {
        showError("Please accept terms and conditions")
        return false
    }
    return true
}

onSubmit

Called when payment processing starts for card and Apple Pay. PayPal doesn't invoke onSubmit. Show loading after onBeforeSubmit returns true or when Drop-in enters processing during PayPal order creation.

onSubmit: { paymentMethod in
    showLoadingIndicator()
    print("Processing \(paymentMethod.rawValue) payment...")
}

onSuccess

Called when payment succeeds. Always verify on your backend:

In Drop-in, result.paymentData is nil for card, PayPal, and Apple Pay.

onSuccess: { result in
    // result is DropInSubmitResult
    let systemTransactionId = result.systemTransactionId
    let merchantTransactionId = result.merchantTransactionId
    // Verify these identifiers on your backend.
    // paymentData is nil except for Paze (.paze(PazeCheckoutDecoded) when present)
    
    hideLoadingIndicator()
    
    // Send to backend for verification
    Task {
        do {
            try await verifyPaymentOnBackend(
                systemTransactionId: systemTransactionId,
                merchantTransactionId: merchantTransactionId
            )
            showSuccessScreen()
        } catch {
            showError("Payment verification failed")
        }
    }
}

onError

Called when initialisation or payment fails:

onError: { paymentMethod, error in
    // paymentMethod is DropInPaymentMethod?
    // error is BaseSdkException
    let method = paymentMethod?.rawValue ?? "unknown"
    let code = error.errorCode
    let message = error.errorMessage
    
    hideLoadingIndicator()
    
    switch code {
    case "CARD_DECLINED":
        showError("Your card was declined. Please try another payment method.")
    case "INSUFFICIENT_FUNDS":
        showError("Insufficient funds. Please use a different card.")
    case "SDK1122":
        showError("Paze is unavailable. Please choose another payment method.")
    case "SDK1123":
        showError("Paze payment failed. Please try again.")
    case "SDK1124":
        showError("Paze is only available for e-commerce checkout.")
    case "SDK0118":
        showError("Paze client ID is missing from the session.")
    case "SDK1202A":
        showError("Paze merchant category code is missing from the session.")
    case "SDK1203", "SDK1204", "SDK1205":
        showError("Paze merchant display configuration is too long.")
    case "SDK1209", "SDK1210", "SDK1211", "SDK1212":
        showError("Invalid shopper details for Paze.")
    case "SDK1213":
        showError("Paze only supports USD transactions.")
    default:
        showError("Payment failed. Please try again.")
    }
    
    // Log for debugging
    print("Error for \(method): \(message)")
}

Callback timeline

The callback sequence varies by payment method:

Card

  1. onBeforeSubmit(.card)
  2. onSubmit(.card)
  3. AVS session update (if required)
  4. 3DS authentication (if required)
  5. Authorization evaluation
  6. onSuccess(...) or onError(.card, ...)

Apple Pay

  1. onBeforeSubmit(.applePay) — when the customer taps the Apple Pay button, before the payment sheet opens
  2. Customer authorises in the Apple Pay sheet
  3. onSubmit(.applePay) — when pre-authorisation starts
  4. Session update with allowTransaction: true
  5. onSuccess(...) or onError(.applePay, ...)

PayPal

  1. onBeforeSubmit(.paypal) — when the customer taps the PayPal button
  2. Drop-in enters processing and creates the PayPal order (onPreAuthorisation)
  3. Buyer approval on PayPal.com
  4. onSuccess(...) or onError(.paypal, ...)

PayPal does not invoke onSubmit. onSuccess fires after the customer approves on PayPal.com, not when the button is first tapped. See Events.

Cleanup

Call destroy() to explicitly release the hosted drop-in UI when you're done:

dropIn.destroy()

The SDK automatically cleans up when the drop-in instance is deallocated, so calling destroy() is optional in most cases.

Security recommendations

  • Create checkout sessions on your backend only.
  • Never log HMAC keys, raw payment payloads, or full wallet/token payloads.
  • Verify the payment result server-side before shipment or fulfilment.
  • Use HTTPS for all backend traffic.
  • Treat transaction identifiers as references, not as final proof of settlement.

What's next?