Complete guide to integrating Checkout Drop-in into your iOS application.
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:
- Initialise: Configure Drop-in with your session and transaction data (
CheckoutDropIn(config:)validatessessionIdandhmacKey). - Create: Await
create()to fetch site configuration, resolve the shopper viaonGetShopper, and prepare payment components. - 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.
Make sure you've activated the Checkout Drop-in service in the Unity Portal. Contact your account manager if you need access.
Add the iOS SDK to your project using Swift Package Manager.
- In Xcode, go to File > Add Package Dependencies.
- Enter the package URL:
https://github.com/PXP-IO/ios-components-sdk - 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")
]In order to initialise Checkout Drop-in, you'll need to send authenticated requests to the PXP API.
To get your credentials:
- In the Unity Portal, go to Merchant setup > Merchant groups.
- Select a merchant group.
- Click the Inbound calls tab.
- Copy the Client ID in the top-right corner.
- Click New token.
- Choose a number of days before token expiry. For example,
30. - Click Save to confirm. Your token is now created.
- 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.
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.
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.
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.
The session request accepts the following parameters:
| Parameter | Description |
|---|---|
merchantstring (≤ 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. |
sitestring (≤ 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. |
merchantTransactionIdstring (≤ 50 characters) required | A unique identifier of your choice that represents this transaction. |
sessionTimeoutnumber required | The duration of the session, in minutes. |
transactionMethodobject required | Details about the transaction method, including the intent for each payment type. |
transactionMethod.intentobject required | The transaction intent for each payment method. |
transactionMethod.intent.cardstring | The intent for card and Apple Pay transactions. Possible values:
|
transactionMethod.intent.paypalstring | The intent for PayPal transactions. Possible values:
|
amountsobject required | Details about the transaction amount. |
amounts.currencyCodestring (3 characters) required | The currency code associated with the transaction, in ISO 4217 format. See Supported payment currencies. |
amounts.transactionValuenumber 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). |
allowTransactionboolean | Whether or not to proceed with the transaction. |
serviceTypestring required | The service type. This must be set to "CheckoutDropIn" for Drop-in integrations. |
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.
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)
}
}The CheckoutDropInConfig struct accepts the following configuration parameters:
| Parameter | Description |
|---|---|
environmentEnvironment required | The environment type. Possible values:
|
sessionSessionData required | Details about the checkout session returned from the Unity Sessions API. Includes sessionId, hmacKey, encryptionKey, and allowedFundingTypes. |
transactionDataDropInTransactionData required | Details about the transaction. |
merchantShopperIdString required | Your unique identifier for this shopper. Required at initialisation. Used for Card-on-File and other shopper-scoped flows. |
ownerIdString (≤ 20 characters) required | Your unique merchant or merchant group identifier, as assigned by PXP. You can find it in the Unity Portal. |
localisationLocalisation? | Optional custom labels and messages for the drop-in interface. Use this to override default text. |
localeString? | 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. |
paypalConfigPayPalConfig? | Optional PayPal configuration used by SDK-level PayPal flows. |
restrictionsRestrictions? | Optional card restrictions supplied at SDK level. Can filter by card owner type (consumer/corporate) and funding source (credit/debit/prepaid). |
kountDisabledBool? | Set to true to disable Kount fraud detection. Defaults to false. |
methodConfigDropInMethodConfig? | 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. |
The DropInTransactionData struct describes the payment transaction:
| Parameter | Description |
|---|---|
amountDecimal required | The transaction amount. |
currencyString required | The three-letter currency code in ISO 4217 format (e.g., "USD", "GBP", "EUR"). |
entryTypeEntryType required | The transaction entry type. Possible values:
|
intentDropInTransactionIntentData required | The transaction intent for each payment method. Use DropInTransactionIntentData, not the generic TransactionIntentData. |
merchantTransactionIdString required | A unique identifier for this transaction. Use UUID().uuidString to generate. |
merchantTransactionDate() -> Date required | A closure that returns the current date. Typically { Date() }. |
cardAcceptorNameString? | Optional card acceptor name when required by your flow. |
recurringRecurringType? | Optional recurring payment data for subscription or installment use cases. |
linkIdString? | Optional link ID when linking transactions together. |
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.
Drop-in provides four key callbacks to manage the payment flow.
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
}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...")
}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")
}
}
}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)")
}The callback sequence varies by payment method:
onBeforeSubmit(.card)onSubmit(.card)- AVS session update (if required)
- 3DS authentication (if required)
- Authorization evaluation
onSuccess(...)oronError(.card, ...)
onBeforeSubmit(.applePay)— when the customer taps the Apple Pay button, before the payment sheet opens- Customer authorises in the Apple Pay sheet
onSubmit(.applePay)— when pre-authorisation starts- Session update with
allowTransaction: true onSuccess(...)oronError(.applePay, ...)
onBeforeSubmit(.paypal)— when the customer taps the PayPal button- Drop-in enters processing and creates the PayPal order (
onPreAuthorisation) - Buyer approval on PayPal.com
onSuccess(...)oronError(.paypal, ...)
PayPal does not invoke onSubmit. onSuccess fires after the customer approves on PayPal.com, not when the button is first tapped. See Events.
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.
- 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.
- Configuration reference: explore all configuration options in detail.
- Payment methods: configure cards, PayPal, and Apple Pay.
- Events and callbacks: learn about all available callbacks.
- Error handling: implement robust error handling.
- Testing: test your integration with test cards and credentials.