Accept Apple Pay payments with automatic wallet handling, one-click checkout, and seamless iOS integration.
Apple Pay is automatically included in the drop-in when enabled in your session. The drop-in handles all Apple Pay setup, button rendering, wallet interaction, and payment processing automatically.
- Apple Pay appears automatically in the drop-in when it's enabled in your session configuration.
- The drop-in handles all Apple Pay setup, so you don't need Apple Pay-specific code.
- Apple Pay uses the same
onSuccessandonErrorcallbacks as other payment methods for a unified integration. - Customers can pay using any card or payment method saved in their Apple Wallet.
- Native iOS Apple Pay UI for familiar and trusted checkout experience.
- One-click checkout for fast and frictionless payments.
- Enhanced security with tokenised payment credentials.
When a customer selects Apple Pay:
- The customer taps "Apple Pay" in the payment options list.
- The native iOS Apple Pay sheet opens.
- The customer authenticates using Face ID, Touch ID, or passcode.
- The customer confirms the payment in the Apple Pay interface.
- The Apple Pay sheet closes automatically.
- The payment is processed through Unity.
- Your
onSuccesscallback fires.
Configure Apple Pay-specific settings for the payment button, merchant information, and transaction parameters.
The following properties are available for Apple Pay configuration through DropInApplePayConfig:
| Property | Description |
|---|---|
shippingContactConfigurationDropInApplePayShippingContactConfiguration? | Configuration for shipping contact fields. |
shippingContactConfiguration.requireShippingContactFields[ContactField]? | Which contact fields to require for shipping. Available values: .postalAddress, .emailAddress, .phoneNumber, .name, .phoneticName. |
billingContactConfigurationDropInApplePayBillingContactConfiguration? | Configuration for billing contact fields. |
billingContactConfiguration.requireBillingContactFields[ContactField]? | Which contact fields to require for billing. Available values: .postalAddress, .emailAddress, .phoneNumber, .name, .phoneticName. |
onShippingContactSelected(ApplePayContact?) -> ApplePayRequestUpdate? | Called when the customer selects or changes their shipping contact in Apple Pay. Return ApplePayRequestUpdate to update totals, shipping methods, or errors, or nil if no update is needed. |
onShippingMethodSelected(ApplePayShippingMethod?) -> ApplePayRequestUpdate? | Called when the customer selects or changes their shipping method in Apple Pay. Return ApplePayRequestUpdate to update totals or errors, or nil if no update is needed. |
onPaymentMethodSelected(ApplePayPaymentMethod?) -> ApplePayRequestUpdate? | Called when the customer selects or changes their payment method in Apple Pay. Return ApplePayRequestUpdate to update totals or errors, or nil if no update is needed. |
onCouponCodeChanged(String) -> ApplePayRequestUpdate? | Called when the customer enters or changes a coupon code in Apple Pay. Return ApplePayRequestUpdate to update totals or errors, or nil if the coupon is invalid or no update is needed. |
Apple Pay inherits the following settings from methodConfig.global:
| Property | Description |
|---|---|
acceptedCardNetworks[DropInCardNetworks]? | Which card brands to accept through Apple Pay (e.g., [.visa, .mastercard, .amex]). |
allowedCardFundingSource[DropInCardFundingSource]? | Which funding types to accept (Credit, Debit). Mapped to Apple Pay merchant capabilities. |
allowedIssuerCountryCodes[String]? | Allowed card issuer countries as ISO country codes (e.g., ["GB", "US", "FR"]). |
shippingOptions[DropInShippingOption]? | Shipping options displayed in the Apple Pay sheet. Each option includes id, label, description, and amount. |
couponInfoDropInCouponInfo? | Coupon configuration for Apple Pay. Includes couponCode which is passed to Apple Pay sheet. |
riskScreeningDataRiskScreeningData? | Risk screening data for fraud detection, passed into Apple Pay transaction initialisation. |
transactionInfoDropInTransactionInfo | Transaction information including country code and total label. |
transactionInfo.countryCodeString | Two-letter ISO country code for the merchant (e.g., "US", "GB"). |
transactionInfo.totalLabelString | Label shown for the total amount in Apple Pay (e.g., "Demo Store", "Total"). |
onGetConsent(DropInPaymentMethod) -> Bool | Provides consent value for Apple Pay vaulting when no consent component is attached. |
onCancel(DropInPaymentMethod, Any?) -> Void | Called when user cancels Apple Pay payment (closes sheet). |
This example shows a full Apple Pay configuration with shipping options and all supported Apple Pay-specific callbacks:
methodConfig: DropInMethodConfig(
// Global settings that apply to Apple Pay
global: DropInGlobalConfig(
// Card networks accepted through Apple Pay
acceptedCardNetworks: [.visa, .mastercard, .amex],
// Shipping options displayed in Apple Pay sheet
shippingOptions: [
DropInShippingOption(
id: "standard",
label: "Standard Shipping",
description: "Arrives in 3-5 business days",
amount: "10.00"
),
DropInShippingOption(
id: "express",
label: "Express Shipping",
description: "Arrives in 1-2 business days",
amount: "25.00"
)
],
// Transaction information for Apple Pay
transactionInfo: DropInTransactionInfo(
countryCode: "GB",
totalLabel: "Demo Store"
),
// Handle Apple Pay cancellation
onCancel: { paymentMethod, data in
if paymentMethod == .applePay {
print("Apple Pay payment cancelled: \(String(describing: data))")
// Update UI
Task { @MainActor in
showToast("Apple Pay payment was cancelled. Please try again.")
}
}
}
),
applePay: DropInApplePayConfig(
// Shipping contact configuration
shippingContactConfiguration: DropInApplePayShippingContactConfiguration(
requireShippingContactFields: [
.postalAddress,
.name,
.phoneNumber,
.emailAddress
]
),
// Billing contact configuration
billingContactConfiguration: DropInApplePayBillingContactConfiguration(
requireBillingContactFields: [
.postalAddress,
.name
]
),
// Shipping contact selection callback
onShippingContactSelected: { contact in
// Return nil when no update needed
// Return ApplePayRequestUpdate to update totals/shipping/errors
nil
},
// Shipping method selection callback
onShippingMethodSelected: { method in
// Update total based on selected shipping method
guard let shippingAmount = method?.amount else { return nil }
return ApplePayRequestUpdate(
summaryItems: [
ApplePaySummaryItem(
label: "Subtotal",
amount: Decimal(string: "99.99") ?? 0
),
ApplePaySummaryItem(
label: "Shipping",
amount: shippingAmount
),
ApplePaySummaryItem(
label: "Demo Store",
amount: (Decimal(string: "99.99") ?? 0) + shippingAmount
)
]
)
},
// Payment method selection callback
onPaymentMethodSelected: { method in
// Return nil when no update needed
nil
},
// Coupon code changed callback
onCouponCodeChanged: { couponCode in
// Validate coupon and return updated totals
// Return nil if coupon is invalid or no update needed
nil
}
)
)Apple Pay requires the following to function correctly:
- iOS compatibility: iOS 14.0 or higher.
- Apple Pay capability: Apple Pay must be configured in your Xcode project capabilities.
- Merchant ID: A valid Apple Pay merchant identifier registered in your Apple Developer account.
- Customer setup: The customer must have Apple Pay set up on their device with at least one payment method.
- HTTPS: Your backend endpoints must be served over HTTPS.
- Unity Portal configuration: Apple Pay must be enabled and configured in the Unity Portal.
- Entry type: Apple Pay is designed for e-commerce transactions (
entryType: .ecom).
To use Apple Pay, you must register an Apple Pay merchant identifier in your Apple Developer account and add the Apple Pay capability to your Xcode project. The merchant ID must also be configured in the Unity Portal.
Apple Pay works through the standard implementation, with no Apple Pay-specific code needed:
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 errorMessage = viewModel.errorMessage {
Text(errorMessage)
} else {
ProgressView("Loading checkout...")
}
}
.task {
await viewModel.loadDropIn()
}
}
}
@MainActor
final class CheckoutViewModel: ObservableObject {
@Published var dropIn: CheckoutDropIn?
@Published var errorMessage: String?
func loadDropIn() async {
// Fetch session from backend (with Apple Pay enabled)
guard let sessionData = await fetchSessionFromBackend() else {
errorMessage = "Failed to create session"
return
}
// Initialise Drop-in
let config = CheckoutDropInConfig(
environment: .test,
session: sessionData,
transactionData: DropInTransactionData(
amount: Decimal(string: "99.99") ?? 0,
currency: "GBP",
entryType: .ecom,
intent: DropInTransactionIntentData(
card: .purchase // Apple Pay uses card intent
),
merchantTransactionId: UUID().uuidString,
merchantTransactionDate: { Date() }
),
merchantShopperId: "shopper-123",
ownerId: "MERCHANT-1",
onGetShopper: {
TransactionShopper(id: "shopper-123")
},
onSuccess: { result in
print("Apple Pay payment successful!")
print("System transaction ID: \(result.systemTransactionId)")
print("Payment method: \(result.paymentMethod.rawValue)") // "ApplePay"
// CRITICAL: Verify on backend
Task {
await verifyPaymentOnBackend(result)
}
},
onError: { paymentMethod, error in
print("Apple Pay payment failed: \(error.errorMessage)")
Task { @MainActor in
self.errorMessage = "Payment failed: \(error.errorMessage)"
}
}
)
do {
let instance = try CheckoutDropIn(config: config)
await instance.create()
dropIn = instance
} catch {
errorMessage = error.localizedDescription
}
}
}Enable Apple Pay in your session request:
// BACKEND: Create a session with Apple Pay enabled
const sessionRequest = {
merchant: "MERCHANT-1",
site: "SITE-1",
sessionTimeout: 120,
merchantTransactionId: crypto.randomUUID(),
transactionMethod: {
intent: {
card: "Purchase" // Apple Pay uses card intent
}
},
amounts: {
currencyCode: "GBP",
transactionValue: 99.99
},
allowedFundingTypes: {
wallets: {
applePay: {
// Apple Pay configuration from the Unity Portal will be used
// The SDK SessionData expects:
merchantId: "merchant.com.example.demostore"
}
}
},
allowTransaction: true,
serviceType: "CheckoutDropIn"
};Drop-in supports two Apple Pay payment flows, configured via the intent parameter:
Immediate, single-step payment where funds are captured right away.
transactionData: DropInTransactionData(
amount: Decimal(string: "99.99") ?? 0,
currency: "GBP",
entryType: .ecom,
intent: DropInTransactionIntentData(
card: .purchase // Pay Now flow - Apple Pay uses card intent
),
merchantTransactionId: UUID().uuidString,
merchantTransactionDate: { Date() }
)Use this flow for:
- Digital products
- Simple orders with immediate fulfillment
- Subscriptions
- Donations
When an Apple Pay payment succeeds, your onSuccess callback receives the same standard result as other payment methods:
onSuccess: { result in
print("Payment details:")
print("- System transaction ID: \(result.systemTransactionId)")
print("- Merchant transaction ID: \(result.merchantTransactionId ?? "N/A")")
print("- Payment method: \(result.paymentMethod.rawValue)") // "ApplePay"
// Note: Amount, currency, and other transaction details must be retrieved from backend
// Apple Pay tokenisation is handled internally
}Handle Apple Pay-specific errors:
onError: { paymentMethod, error in
print("Error code: \(error.errorCode)")
print("Error message: \(error.errorMessage)")
// Handle specific error codes
let userMessage: String
switch error.errorCode {
case "SDK0602":
userMessage = "Apple Pay is not available on this device."
case "SDK0615":
// User cancelled - don't show error
return
case "SDK0616":
userMessage = "Apple Pay merchant validation failed. Please contact support."
case "SDK0617":
userMessage = "Apple Pay processing failed. Please try again."
case "SDK1119":
userMessage = "Apple Pay payment failed. Please try again."
default:
// Fall back to message-based detection
if error.errorMessage.localizedCaseInsensitiveContains("cancelled") ||
error.errorMessage.localizedCaseInsensitiveContains("closed") {
// User intentionally cancelled - don't show error
return
} else if error.errorMessage.localizedCaseInsensitiveContains("not available") {
userMessage = "Apple Pay isn't available on this device. Please use a different payment method."
} else if error.errorMessage.localizedCaseInsensitiveContains("not supported") {
userMessage = "Apple Pay isn't supported for this transaction. Please use a different payment method."
} else if error.errorMessage.localizedCaseInsensitiveContains("declined") {
userMessage = "Payment declined. Please try a different payment method."
} else if error.errorMessage.localizedCaseInsensitiveContains("timeout") {
userMessage = "Apple Pay timed out. Please try again."
} else {
userMessage = "Apple Pay payment failed: \(error.errorMessage)"
}
}
Task { @MainActor in
showError(userMessage)
}
}The following table describes common Apple Pay error scenarios:
| Scenario | How to detect | Recommended action |
|---|---|---|
| Apple Pay not supported | error.errorCode == "SDK0602" | Suggest using different payment method |
| User cancelled | error.errorCode == "SDK0615" or message contains "cancelled" | No alert needed - user action was intentional |
| Merchant validation failed | error.errorCode == "SDK0616" | Contact support for merchant configuration |
| Custom API processing failed | error.errorCode == "SDK0617" | Retry payment or contact support |
| Apple Pay payment failed | error.errorCode == "SDK1119" | Suggest trying again or using another payment method |
| Payment declined | error.errorMessage contains "declined" | Suggest trying different payment method |
| Timeout | error.errorMessage contains "timeout" | Suggest retrying the payment |
Apple Pay errors include both error codes (SDK0602, SDK0615, SDK0616, SDK0617, SDK1119) and descriptive messages. Use error.errorCode for programmatic handling and error.errorMessage for additional context. The onCancel callback in methodConfig.global can also be used to handle user cancellations.
Always verify Apple Pay payments on your backend to ensure payment success before fulfilling orders:
onSuccess: { result in
// Send to backend for verification
Task {
do {
let response = try await apiClient.post("/api/verify-payment", body: [
"systemTransactionId": result.systemTransactionId,
"merchantTransactionId": result.merchantTransactionId ?? ""
])
if response.success {
// Navigate to success screen
await MainActor.run {
navigateToSuccess(orderId: response.orderId)
}
} else {
await MainActor.run {
showError("Payment verification failed")
}
}
} catch {
print("Verification error: \(error.localizedDescription)")
await MainActor.run {
showError("Failed to verify payment")
}
}
}
}Use the following backend code to verify Apple Pay transactions via the PXP API:
// BACKEND: Verify Apple Pay payment
app.post('/api/verify-payment', async (req, res) => {
const { systemTransactionId, merchantTransactionId } = req.body;
try {
// Query the PXP API to get transaction details
const txnPath = `api/v1/transactions/${systemTransactionId}`;
const { authHeader, requestId } = createAuthHeader(
txnPath,
'',
process.env.PXP_TOKEN_ID,
process.env.PXP_TOKEN_VALUE
);
const transaction = await fetch(
`https://api-services.pxp.io/${txnPath}`,
{
headers: {
'X-Client-Id': process.env.PXP_CLIENT_ID,
'X-Request-Id': requestId,
'Authorization': authHeader
}
}
).then(r => r.json());
// Verify transaction state
if (transaction.state !== 'Authorised' && transaction.state !== 'Captured') {
return res.json({ success: false, error: 'Transaction not successful' });
}
// Verify merchant transaction ID matches
if (transaction.merchantTransactionId !== merchantTransactionId) {
return res.json({ success: false, error: 'Transaction ID mismatch' });
}
// Verify amount matches expected amount from your order records
const order = await getOrderByMerchantTransactionId(merchantTransactionId);
const txnAmount = transaction.amounts?.transactionValue || transaction.amount || 0;
if (Math.abs(txnAmount - order.amount) > 0.01) {
return res.json({ success: false, error: 'Amount mismatch' });
}
// Apple Pay payments show as Card funding type (processed as card transactions)
const fundingType = transaction.fundingData?.fundingType ||
transaction.fundingType ||
'Unknown';
if (fundingType !== 'Card') {
return res.json({ success: false, error: 'Invalid funding type' });
}
// Fulfill order
const orderId = await fulfillOrder(transaction);
return res.json({ success: true, orderId });
} catch (error) {
console.error('Verification error:', error);
return res.json({ success: false, error: 'Verification failed' });
}
});Configure your app for Apple Pay testing:
// Use test environment for Apple Pay testing
CheckoutDropInConfig(
environment: .test, // Test environment
// ... other config
methodConfig: DropInMethodConfig(
global: DropInGlobalConfig(
transactionInfo: DropInTransactionInfo(
countryCode: "GB",
totalLabel: "Demo Store"
)
),
applePay: DropInApplePayConfig(
shippingContactConfiguration: DropInApplePayShippingContactConfiguration(
requireShippingContactFields: [.postalAddress, .name]
),
billingContactConfiguration: DropInApplePayBillingContactConfiguration(
requireBillingContactFields: [.postalAddress]
)
)
)
)To test Apple Pay in the sandbox environment:
- Add test cards to your Apple Wallet on your test device or simulator.
- Use the test environment in your Drop-in configuration (
environment: .test). - Apple provides sandbox test cards that can be added to Wallet for testing.
- Test payments won't charge real cards or accounts.
Apple Pay sandbox testing requires a valid Apple Pay merchant identifier and proper Xcode project configuration. Ensure your merchant ID is registered in both your Apple Developer account and the Unity Portal.
To enable Apple Pay in your iOS app, you must configure your Xcode project:
- Open your project in Xcode.
- Select your target and go to the Signing & Capabilities tab.
- Click the + button to add a capability.
- Add "Apple Pay" capability.
- Select your Apple Pay merchant identifiers.
Your merchant identifier should match the format merchant.com.yourcompany.yourapp and must be registered in your Apple Developer account.