Learn how to use the Apple Pay component for iOS in your iOS application.
Every component follows the same four-step lifecycle:
- Initialise the PXP Checkout SDK with your configuration.
- Create the Apple Pay component with your specific configuration.
- Display the component in your view using
buildContent(). - Handle payment results and lifecycle events.
To use the Apple Pay component, you first need to:
- Install Components for iOS.
- Complete the Apple Pay onboarding process in the Unity Portal.
- Configure your iOS app with Apple Pay entitlements.
- Ensure your merchant certificate is properly configured.
Apple Pay for iOS has specific requirements for optimal functionality.
- iOS 14.0+ as minimum deployment target for
PXPCheckoutSDK. - iOS 15.0+ for coupon code support.
- iPhone: iPhone 6 or later with Touch ID or Face ID.
- iPad: iPad Pro, iPad Air 2, iPad (5th generation) or later, iPad mini 3 or later.
- Apple Watch: When paired with compatible iPhone.
- The customer must have a supported payment method in their Wallet app.
- The device must have Touch ID, Face ID, or passcode enabled.
- App must have proper Apple Pay entitlements configured.
First, ensure your iOS app has the proper Apple Pay entitlements configured.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.in-app-payments</key>
<array>
<string>merchant.com.yourcompany.yourapp</string>
</array>
</dict>
</plist><key>NSFaceIDUsageDescription</key>
<string>Use Face ID to authenticate Apple Pay transactions</string>
<key>NSContactsUsageDescription</key>
<string>Access contacts for shipping and billing information</string>Import the PXP Checkout SDK and initialise it with Apple Pay support.
import PXPCheckoutSDK
let sessionData = SessionData(
sessionId: "your-session-id",
hmacKey: "your-hmac-key",
encryptionKey: "your-encryption-key",
allowedFundingTypes: AllowedFundingType(
wallets: Wallets(
applePay: ApplePay(merchantId: "merchant.com.yourcompany.yourapp")
)
)
)
let checkoutConfig = CheckoutConfig(
environment: .test,
session: sessionData,
transactionData: TransactionData(
amount: 25.00,
currency: "USD",
entryType: .ecom,
intent: TransactionIntentData(card: .purchase),
merchantTransactionId: UUID().uuidString,
merchantTransactionDate: { Date() }
),
merchantShopperId: "shopper-id",
ownerType: "MerchantGroup",
ownerId: "your-owner-id",
onGetShopper: { async in
TransactionShopper(id: "shopper-id", email: "customer@example.com")
}
)
let checkout = try PxpCheckout.initialize(config: checkoutConfig)session.allowedFundingTypes.wallets.applePay.merchantId is required to create .applePayButton (validated in BasePxpCheckout.validateFundingTypeSupport).
| Property | Description |
|---|---|
environmentEnvironment required | The environment type. Possible values:
|
sessionSessionData required | Details about the checkout session. |
session.sessionIdString required | The unique session identifier. |
session.hmacKeyString required | HMAC key from your session response. |
session.encryptionKeyString required | Encryption key from your session response. |
session.allowedFundingTypes.wallets.applePay.merchantIdString required | Your Apple Pay merchant identifier. Required to create .applePayButton. |
ownerIdString required | The identifier of the owner related to the ownerType. |
ownerTypeString? | The type of owner (e.g. "MerchantGroup"). |
merchantShopperIdString required | A unique identifier for this shopper. |
transactionDataTransactionData required | Details about the transaction. |
transactionData.currencyString required | The currency code, in ISO 4217 format. |
transactionData.amountDecimal required | The transaction amount. |
transactionData.entryTypeEntryType required | The entry type. Possible values:
|
transactionData.intentTransactionIntentData required | The transaction intent. Apple Pay reads intent.card.Card intent values include:
|
transactionData.merchantTransactionIdString required | A unique identifier for this transaction. |
transactionData.merchantTransactionDate() -> Date required | A closure returning the date and time of the transaction. |
onGetShopper() async -> TransactionShopper? | Async callback returning shopper details (including email). |
restrictionsRestrictions? | Optional card restrictions for owner types and funding sources. When both session and config restrictions are provided, they are merged as a union. See Card restrictions for details. |
kountDisabledBool | Whether to disable the Kount fraud detection service. Defaults to false (fraud detection enabled). |
You can restrict which cards are accepted in Apple Pay based on owner type (corporate or consumer) and funding source (credit, debit, or prepaid). Restrictions can be set in two places:
- Session-level: returned from the backend in
SessionData.restrictions. - Config-level: passed directly to
CheckoutConfig.restrictions.
When both are provided, they're merged as a union (session values first, then config values not already present).
let restrictions = Restrictions(
card: Restrictions.Card(
ownerTypes: [.corporate, .consumer], // Optional
fundingSources: [.credit, .debit] // Optional
)
)
let checkoutConfig = CheckoutConfig(
// ... other parameters
restrictions: restrictions
)| Value | Description |
|---|---|
.corporate | Corporate/business cards |
.consumer | Consumer/personal cards |
| Value | Description |
|---|---|
.credit | Credit cards |
.debit | Debit cards |
.prepaid | Prepaid cards |
let checkoutConfig = CheckoutConfig(
environment: .test,
session: sessionData,
transactionData: transactionData,
merchantShopperId: "shopper-id",
ownerId: "your-owner-id",
restrictions: Restrictions(
card: Restrictions.Card(
ownerTypes: [.consumer],
fundingSources: [.credit, .debit]
)
)
)If ownerTypes or fundingSources is nil, no restriction is applied for that dimension. Setting both to nil means all cards are accepted.
Next, create the Apple Pay component configuration with your specific requirements. Set all callbacks on ApplePayButtonComponentConfig before checkout.create(.applePayButton, componentConfig:). Component config isn't a public post-create mutation surface.
private func createApplePayConfiguration() -> ApplePayButtonComponentConfig {
let config = ApplePayButtonComponentConfig()
// Basic configuration
config.paymentDescription = "Purchase from Your Store"
config.currencyCode = "USD"
config.countryCode = "US"
config.supportedNetworks = [.visa, .masterCard, .amex]
config.merchantCapabilities = [.threeDSecure, .emv]
// Button styling
config.buttonType = .buy
config.buttonStyle = .black
config.buttonRadius = 8.0
// Payment items
config.totalPaymentItem = ApplePayPaymentSummaryItem(
amount: 25.00,
type: .final,
label: "Your Store" // merchant-facing total label
)
config.paymentItems = [
ApplePayPaymentSummaryItem(amount: 20.00, type: .final, label: "Product"),
ApplePayPaymentSummaryItem(amount: 3.00, type: .final, label: "Tax"),
ApplePayPaymentSummaryItem(amount: 2.00, type: .final, label: "Shipping")
]
// Contact fields
config.requiredBillingContactFields = [.postalAddress, .name, .emailAddress]
config.requiredShippingContactFields = [.postalAddress, .name, .phoneNumber]
// Shipping methods
config.shippingMethods = [
ApplePayShippingMethod(
amount: 2.00,
detail: "5-7 business days",
identifier: "standard",
label: "Standard Shipping"
),
ApplePayShippingMethod(
amount: 5.00,
detail: "2-3 business days",
identifier: "express",
label: "Express Shipping"
)
]
// Event handlers — set before create()
config.onPreAuthorisation = { async in
ApplePayTransactionInitData(riskScreeningData: nil)
}
config.onPostAuthorisation = { submitResult, applePayResult in
if let success = submitResult as? MerchantSubmitResult {
_ = success.systemTransactionId
_ = applePayResult.shippingContact
} else if let failed = submitResult as? FailedSubmitResult {
_ = failed.errorReason
}
}
config.onShippingAddressChange = { contact in
ApplePayRequestUpdate(
totalPaymentItem: ApplePayPaymentSummaryItem(amount: 25.00, type: .final, label: "Your Store"),
paymentSummaryItems: [/* … */],
shippingMethods: [/* … */]
)
}
config.onError = { exception in
print(exception.errorCode, exception.errorMessage)
}
config.onCancel = { exception in
print(exception.errorMessage)
}
if #available(iOS 15.0, *) {
config.supportsCouponCode = true
config.onCouponSelected = { couponCode in
ApplePayRequestUpdate(paymentSummaryItems: [/* … */])
}
}
return config
}| Parameter | Description |
|---|---|
paymentDescriptionString (≤ 128 characters) required | A description of the payment that appears to customers. |
currencyCodeString required | The currency code in ISO 4217 format (e.g., "USD"). |
countryCodeString required | The merchant's country code in ISO 3166-1 alpha-2 format (e.g., "US"). |
supportedNetworks[PaymentNetwork] required | Supported card networks. Possible values:
|
merchantCapabilities[MerchantCapability] required | Payment processing capabilities. Possible values:
|
buttonTypeApplePaymentButtonType | The button type. Possible values:
|
buttonStyleApplePaymentButtonStyle | The button style. Possible values:
|
buttonRadiusCGFloat | The button corner radius (default: 4.0). |
totalPaymentItemApplePayPaymentSummaryItem | The total payment amount display. The label is the merchant-facing name shown in the Apple Pay sheet. |
paymentItems[ApplePayPaymentSummaryItem] | Individual line items for the payment. |
requiredBillingContactFields[ContactField] | Required billing contact fields. Possible values:
|
requiredShippingContactFields[ContactField] | Required shipping contact fields. |
shippingMethods[ApplePayShippingMethod] | Available shipping methods. |
supportsCouponCodeBool? | Enable coupon code entry in the Apple Pay sheet (iOS 15.0+). Set true when using onCouponSelected. |
couponCodeString? | Optional pre-filled coupon code (iOS 15.0+). |
| Callback | Signature |
|---|---|
onPreAuthorisation | () async -> ApplePayTransactionInitData? |
onPostAuthorisation | (BaseSubmitResult, ApplePayResult) -> Void |
onError | (BaseSdkException) -> Void |
onCancel | (BaseSdkException) -> Void |
onShippingAddressChange | (ApplePayContact?) -> ApplePayRequestUpdate? |
onShippingOptionChange | (ApplePayShippingMethod?) -> ApplePayRequestUpdate? |
onPaymentMethodChange | (ApplePayPaymentMethod?) -> ApplePayRequestUpdate? |
onCouponSelected | (String) -> ApplePayRequestUpdate? |
Create your view layout to include the Apple Pay button container.
@IBOutlet weak var paymentSummaryView: UIView!
@IBOutlet weak var applePayContainer: UIView!
@IBOutlet weak var errorMessageLabel: UILabel!
@IBOutlet weak var successMessageLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
initializeSDK()
}
private func setupUI() {
// Configure payment summary
setupPaymentSummary()
// Configure Apple Pay container
applePayContainer.layer.cornerRadius = 8
applePayContainer.backgroundColor = .systemBackground
// Hide message labels initially
errorMessageLabel.isHidden = true
successMessageLabel.isHidden = true
}
private func setupPaymentSummary() {
// Create payment summary views
let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 8
stackView.translatesAutoresizingMaskIntoConstraints = false
// Add line items
stackView.addArrangedSubview(createLineItem(label: "Premium T-Shirt", amount: "$20.00"))
stackView.addArrangedSubview(createLineItem(label: "Sales Tax", amount: "$3.00"))
stackView.addArrangedSubview(createLineItem(label: "Shipping", amount: "$2.00"))
// Add separator
let separator = UIView()
separator.backgroundColor = .separator
separator.heightAnchor.constraint(equalToConstant: 1).isActive = true
stackView.addArrangedSubview(separator)
// Add total
let totalView = createLineItem(label: "Total", amount: "$25.00", isTotal: true)
stackView.addArrangedSubview(totalView)
paymentSummaryView.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: paymentSummaryView.topAnchor, constant: 16),
stackView.leadingAnchor.constraint(equalTo: paymentSummaryView.leadingAnchor, constant: 16),
stackView.trailingAnchor.constraint(equalTo: paymentSummaryView.trailingAnchor, constant: -16),
stackView.bottomAnchor.constraint(equalTo: paymentSummaryView.bottomAnchor, constant: -16)
])
}
private func createLineItem(label: String, amount: String, isTotal: Bool = false) -> UIView {
let containerView = UIView()
let labelView = UILabel()
labelView.text = label
labelView.font = isTotal ? .systemFont(ofSize: 18, weight: .semibold) : .systemFont(ofSize: 16)
labelView.translatesAutoresizingMaskIntoConstraints = false
let amountLabel = UILabel()
amountLabel.text = amount
amountLabel.font = isTotal ? .systemFont(ofSize: 18, weight: .semibold) : .systemFont(ofSize: 16)
amountLabel.textAlignment = .right
amountLabel.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(labelView)
containerView.addSubview(amountLabel)
NSLayoutConstraint.activate([
labelView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
labelView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
labelView.topAnchor.constraint(equalTo: containerView.topAnchor),
labelView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
amountLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
amountLabel.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
amountLabel.leadingAnchor.constraint(greaterThanOrEqualTo: labelView.trailingAnchor, constant: 8)
])
return containerView
}import SwiftUI
import PXPCheckoutSDK
struct CheckoutView: View {
@State private var checkout: PxpCheckout?
@State private var applePayComponent: BaseComponent?
@State private var errorMessage: String = ""
@State private var successMessage: String = ""
@State private var showingError = false
@State private var showingSuccess = false
var body: some View {
VStack(spacing: 20) {
Text("Complete your purchase")
.font(.largeTitle)
.fontWeight(.bold)
// Payment summary
VStack(spacing: 12) {
HStack {
Text("Premium T-Shirt")
Spacer()
Text("$20.00")
}
HStack {
Text("Sales Tax")
Spacer()
Text("$3.00")
}
HStack {
Text("Shipping")
Spacer()
Text("$2.00")
}
Divider()
HStack {
Text("Total")
.fontWeight(.semibold)
Spacer()
Text("$25.00")
.fontWeight(.semibold)
}
.font(.title3)
}
.padding()
.background(Color(.systemGray6))
.cornerRadius(12)
// Error/Success Messages
if showingError {
Text(errorMessage)
.foregroundColor(.red)
.padding()
.background(Color.red.opacity(0.1))
.cornerRadius(8)
}
if showingSuccess {
Text(successMessage)
.foregroundColor(.green)
.padding()
.background(Color.green.opacity(0.1))
.cornerRadius(8)
}
// Apple Pay button
if let component = applePayComponent {
component.buildContent()
.frame(height: 50)
}
Text("Or pay with credit card")
.foregroundColor(.secondary)
.font(.caption)
Spacer()
}
.padding()
.onAppear {
initializeSDK()
}
}
private func initializeSDK() {
// Initialise SDK (same as UIKit example)
}
}Create the Apple Pay component and display it in your view. create(.applePayButton, componentConfig:) returns BaseComponent; keep it as BaseComponent or cast with as? ApplePayButtonComponent if you need the concrete type.
private func createAndMountApplePayComponent() {
guard let checkout = checkout else {
print("Checkout SDK not initialized")
return
}
do {
let config = createApplePayConfiguration()
let component = try checkout.create(.applePayButton, componentConfig: config)
applePayComponent = component // BaseComponent is sufficient for buildContent()
// SwiftUI
// component.buildContent().frame(height: 50)
print("Apple Pay component created successfully")
} catch {
print("Failed to create Apple Pay component: \(error)")
showError("Apple Pay is not available on this device")
}
}For UIKit hosts, wrap buildContent() in a UIHostingController rather than adding a UIView from render():
private func mountComponent(_ component: BaseComponent) {
let hostingController = UIHostingController(
rootView: component.buildContent().frame(height: 50)
)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
addChild(hostingController)
applePayContainer.addSubview(hostingController.view)
NSLayoutConstraint.activate([
hostingController.view.topAnchor.constraint(equalTo: applePayContainer.topAnchor),
hostingController.view.leadingAnchor.constraint(equalTo: applePayContainer.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: applePayContainer.trailingAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: applePayContainer.bottomAnchor)
])
hostingController.didMove(toParent: self)
}The SDK also ships ApplePayButtonView as a low-level PK button helper used internally by ApplePayButtonComponent.buildView().
Implement the event handlers to process payment results and user interactions.
// MARK: - Event Handlers
private func handlePreAuthorisation() async -> ApplePayTransactionInitData? {
print("Pre-authorisation started")
DispatchQueue.main.async {
self.showMessage("Processing payment...", type: .info)
}
return ApplePayTransactionInitData(
riskScreeningData: RiskScreeningData(
performRiskScreening: true,
userIp: "192.168.1.100",
account: RiskScreeningAccount(
id: "user_12345678",
creationDateTime: ISO8601DateFormatter().date(from: "2024-01-15T10:30:00Z")
),
items: [
RiskScreeningItem(
price: 99.99,
quantity: 1,
category: "Electronics",
sku: "PROD-001"
)
],
fulfillments: [
RiskScreeningFulfillment(
type: .shipped,
shipping: RiskScreeningShipping(shippingMethod: .express),
recipientPerson: RiskScreeningRecipientPerson(
phoneNumber: "+1234567890"
)
)
]
)
)
}
private func handlePostAuthorisation(_ result: BaseSubmitResult, _ applePayResult: ApplePayResult) {
DispatchQueue.main.async {
if let success = result as? MerchantSubmitResult {
self.showMessage("Payment successful! Redirecting...", type: .success)
print("Transaction ID: \(success.systemTransactionId)")
// Navigate to success screen after delay
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
self.navigateToSuccessScreen(transactionId: success.systemTransactionId)
}
} else if let failed = result as? FailedSubmitResult {
self.showMessage("Payment failed: \(failed.errorReason ?? "")", type: .error)
} else if let unknown = result as? UnknownSubmitResult {
self.showMessage("Unexpected result: \(unknown.stateData.message ?? "")", type: .error)
}
}
}
private func handleError(_ exception: BaseSdkException) {
print("Apple Pay error: \(exception.errorMessage)")
DispatchQueue.main.async {
self.showMessage("Payment error: \(exception.errorMessage)", type: .error)
}
}
private func handleCancellation(_ exception: BaseSdkException) {
print("Payment cancelled by user")
DispatchQueue.main.async {
self.showMessage("Payment was cancelled", type: .info)
}
}
// MARK: - Helper methods
private func getDeviceSessionId() async -> String {
return UIDevice.current.identifierForVendor?.uuidString ?? "unknown-device"
}
private func showMessage(_ message: String, type: MessageType) {
errorMessageLabel.isHidden = true
successMessageLabel.isHidden = true
switch type {
case .error:
errorMessageLabel.text = message
errorMessageLabel.isHidden = false
case .success:
successMessageLabel.text = message
successMessageLabel.isHidden = false
case .info:
successMessageLabel.text = message
successMessageLabel.textColor = .systemBlue
successMessageLabel.isHidden = false
}
}
private func navigateToSuccessScreen(transactionId: String) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let successVC = storyboard.instantiateViewController(withIdentifier: "SuccessViewController") as? SuccessViewController {
successVC.transactionId = transactionId
navigationController?.pushViewController(successVC, animated: true)
}
}
enum MessageType {
case error, success, info
}Properly manage the component lifecycle to prevent memory leaks and ensure clean transitions. Call unmount() on PxpCheckout, not on ApplePayButtonComponent.
class CheckoutViewController: UIViewController {
// MARK: - Lifecycle
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Clean up component if navigating away
if isMovingFromParent || isBeingDismissed {
cleanup()
}
}
deinit {
cleanup()
}
private func cleanup() {
checkout?.unmount()
applePayComponent = nil
checkout = nil
print("Apple Pay component cleaned up")
}
// MARK: - Error recovery
private func retryComponentCreation() {
cleanup()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.initializeSDK()
self.createAndMountApplePayComponent()
}
}
}You can configure the appearance and behaviour of the Apple Pay component to fit your brand. We've documented all configurable parameters in the Customisation page.
// Custom button styling
config.buttonType = .buy
config.buttonStyle = .black
config.buttonRadius = 12.0
// Custom SwiftUI content
config.customContent = {
return AnyView(
HStack {
Image(systemName: "applelogo")
.foregroundColor(.white)
Text("Buy with Apple Pay")
.foregroundColor(.white)
.fontWeight(.semibold)
}
.frame(maxWidth: .infinity, minHeight: 50)
.background(
LinearGradient(
gradient: Gradient(colors: [Color.black, Color.gray]),
startPoint: .leading,
endPoint: .trailing
)
)
.cornerRadius(12)
)
}The Apple Pay component emits events based on user interaction, shipping changes, and payment method updates. For more information about all the available events, see the Events page.
Error handling is crucial for payment components because they deal with sensitive financial data and complex validation rules. For more details about error handling, see the Data validation page.
// Comprehensive error handling
config.onError = { exception in
DispatchQueue.main.async {
print("Error [\(exception.errorCode)]: \(exception.errorMessage)")
}
}import UIKit
import SwiftUI
import PXPCheckoutSDK
import PassKit
class CompleteCheckoutViewController: UIViewController {
// MARK: - Outlets
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var paymentSummaryView: UIView!
@IBOutlet weak var applePayContainer: UIView!
@IBOutlet weak var errorMessageLabel: UILabel!
@IBOutlet weak var successMessageLabel: UILabel!
@IBOutlet weak var loadingIndicator: UIActivityIndicatorView!
// MARK: - Properties
private var checkout: PxpCheckout?
private var applePayComponent: BaseComponent?
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
initializeSDK()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if isMovingFromParent || isBeingDismissed {
cleanupComponent()
}
}
deinit {
cleanupComponent()
}
// MARK: - Setup
private func setupUI() {
titleLabel.text = "Complete your purchase"
titleLabel.font = UIFont.systemFont(ofSize: 28, weight: .bold)
setupPaymentSummary()
applePayContainer.layer.cornerRadius = 8
applePayContainer.backgroundColor = .systemBackground
errorMessageLabel.isHidden = true
successMessageLabel.isHidden = true
loadingIndicator.isHidden = true
view.backgroundColor = .systemBackground
}
private func setupPaymentSummary() {
paymentSummaryView.backgroundColor = UIColor.systemGray6
paymentSummaryView.layer.cornerRadius = 12
let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 12
stackView.translatesAutoresizingMaskIntoConstraints = false
// Add line items
stackView.addArrangedSubview(createLineItem(label: "Premium T-Shirt", amount: "$20.00"))
stackView.addArrangedSubview(createLineItem(label: "Sales Tax", amount: "$3.00"))
stackView.addArrangedSubview(createLineItem(label: "Shipping", amount: "$2.00"))
// Add separator
let separator = UIView()
separator.backgroundColor = .separator
separator.heightAnchor.constraint(equalToConstant: 1).isActive = true
stackView.addArrangedSubview(separator)
// Add total
stackView.addArrangedSubview(createLineItem(label: "Total", amount: "$25.00", isTotal: true))
paymentSummaryView.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: paymentSummaryView.topAnchor, constant: 16),
stackView.leadingAnchor.constraint(equalTo: paymentSummaryView.leadingAnchor, constant: 16),
stackView.trailingAnchor.constraint(equalTo: paymentSummaryView.trailingAnchor, constant: -16),
stackView.bottomAnchor.constraint(equalTo: paymentSummaryView.bottomAnchor, constant: -16)
])
}
private func createLineItem(label: String, amount: String, isTotal: Bool = false) -> UIView {
let containerView = UIView()
let labelView = UILabel()
labelView.text = label
labelView.font = isTotal ? .systemFont(ofSize: 18, weight: .semibold) : .systemFont(ofSize: 16)
labelView.translatesAutoresizingMaskIntoConstraints = false
let amountLabel = UILabel()
amountLabel.text = amount
amountLabel.font = isTotal ? .systemFont(ofSize: 18, weight: .semibold) : .systemFont(ofSize: 16)
amountLabel.textAlignment = .right
amountLabel.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(labelView)
containerView.addSubview(amountLabel)
NSLayoutConstraint.activate([
labelView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
labelView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
labelView.topAnchor.constraint(equalTo: containerView.topAnchor),
labelView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
amountLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
amountLabel.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
amountLabel.leadingAnchor.constraint(greaterThanOrEqualTo: labelView.trailingAnchor, constant: 8)
])
return containerView
}
// MARK: - SDK Initialisation
private func initializeSDK() {
let sessionData = SessionData(
sessionId: "your-session-id",
hmacKey: "your-hmac-key",
encryptionKey: "your-encryption-key",
allowedFundingTypes: AllowedFundingType(
wallets: Wallets(
applePay: ApplePay(merchantId: "merchant.com.yourcompany.yourapp")
)
)
)
let checkoutConfig = CheckoutConfig(
environment: .test,
session: sessionData,
transactionData: TransactionData(
amount: 25.00,
currency: "USD",
entryType: .ecom,
intent: TransactionIntentData(card: .purchase),
merchantTransactionId: UUID().uuidString,
merchantTransactionDate: { Date() }
),
merchantShopperId: "shopper-id",
ownerType: "MerchantGroup",
ownerId: "your-owner-id",
kountDisabled: false, // OPTIONAL: Set to true to disable Kount fraud detection
onGetShopper: { async in
TransactionShopper(id: "shopper-id", email: "customer@example.com")
}
)
do {
checkout = try PxpCheckout.initialize(config: checkoutConfig)
createAndMountApplePayComponent()
} catch {
print("Failed to initialise SDK: \(error)")
showMessage("Failed to initialise payment system", type: .error)
}
}
private func createAndMountApplePayComponent() {
guard let checkout = checkout else { return }
// Check if Apple Pay is available
guard PKPaymentAuthorizationController.canMakePayments() else {
showMessage("Apple Pay is not available on this device", type: .error)
return
}
do {
let config = createApplePayConfiguration()
let component = try checkout.create(.applePayButton, componentConfig: config)
applePayComponent = component
mountComponent(component)
} catch {
print("Failed to create Apple Pay component: \(error)")
showMessage("Apple Pay is not available", type: .error)
}
}
private func createApplePayConfiguration() -> ApplePayButtonComponentConfig {
let config = ApplePayButtonComponentConfig()
// Basic configuration
config.paymentDescription = "Premium T-Shirt Purchase"
config.currencyCode = "USD"
config.countryCode = "US"
config.supportedNetworks = [.visa, .masterCard, .amex, .discover]
config.merchantCapabilities = [.threeDSecure, .emv, .credit, .debit]
// Button styling
config.buttonType = .buy
config.buttonStyle = .black
config.buttonRadius = 8.0
// Payment items
config.totalPaymentItem = ApplePayPaymentSummaryItem(
amount: 25.00,
type: .final,
label: "Your Store Name"
)
config.paymentItems = [
ApplePayPaymentSummaryItem(amount: 20.00, type: .final, label: "Premium T-Shirt"),
ApplePayPaymentSummaryItem(amount: 3.00, type: .final, label: "Sales Tax"),
ApplePayPaymentSummaryItem(amount: 2.00, type: .final, label: "Shipping")
]
// Contact fields
config.requiredBillingContactFields = [.postalAddress, .name, .emailAddress]
config.requiredShippingContactFields = [.postalAddress, .name, .phoneNumber]
// Shipping methods
config.shippingMethods = [
ApplePayShippingMethod(
amount: 2.00,
detail: "5-7 business days",
identifier: "standard",
label: "Standard Shipping"
),
ApplePayShippingMethod(
amount: 5.00,
detail: "2-3 business days",
identifier: "express",
label: "Express Shipping"
)
]
// Event handlers
config.onPreAuthorisation = { [weak self] in
return await self?.handlePreAuthorisation()
}
config.onPostAuthorisation = { [weak self] result, applePayResult in
self?.handlePostAuthorisation(result, applePayResult)
}
config.onShippingAddressChange = { [weak self] contact in
return self?.handleShippingAddressChange(contact)
}
config.onShippingOptionChange = { [weak self] method in
return self?.handleShippingOptionChange(method)
}
config.onError = { [weak self] exception in
self?.handleError(exception)
}
config.onCancel = { [weak self] exception in
self?.handleCancellation(exception)
}
return config
}
private func mountComponent(_ component: BaseComponent) {
applePayContainer.subviews.forEach { $0.removeFromSuperview() }
let hostingController = UIHostingController(
rootView: component.buildContent().frame(height: 50)
)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
addChild(hostingController)
applePayContainer.addSubview(hostingController.view)
NSLayoutConstraint.activate([
hostingController.view.topAnchor.constraint(equalTo: applePayContainer.topAnchor),
hostingController.view.leadingAnchor.constraint(equalTo: applePayContainer.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: applePayContainer.trailingAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: applePayContainer.bottomAnchor)
])
hostingController.didMove(toParent: self)
}
// MARK: - Event Handlers
private func handlePreAuthorisation() async -> ApplePayTransactionInitData? {
DispatchQueue.main.async {
self.showMessage("Processing payment...", type: .info)
self.loadingIndicator.startAnimating()
self.loadingIndicator.isHidden = false
}
return ApplePayTransactionInitData(
riskScreeningData: RiskScreeningData(
performRiskScreening: true,
userIp: "192.168.1.100",
account: RiskScreeningAccount(
id: "user_12345678",
creationDateTime: ISO8601DateFormatter().date(from: "2024-01-15T10:30:00Z")
),
fulfillments: [
RiskScreeningFulfillment(
type: .shipped,
shipping: RiskScreeningShipping(shippingMethod: .express),
recipientPerson: RiskScreeningRecipientPerson(
phoneNumber: "+1234567890"
)
)
]
)
)
}
private func handlePostAuthorisation(_ result: BaseSubmitResult, _ applePayResult: ApplePayResult) {
DispatchQueue.main.async {
self.loadingIndicator.stopAnimating()
self.loadingIndicator.isHidden = true
if let success = result as? MerchantSubmitResult {
self.showMessage("Payment successful! Redirecting...", type: .success)
print("Transaction ID: \(success.systemTransactionId)")
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
self.navigateToSuccessScreen(transactionId: success.systemTransactionId)
}
} else if let failed = result as? FailedSubmitResult {
self.showMessage("Payment failed: \(failed.errorReason ?? "")", type: .error)
} else if let unknown = result as? UnknownSubmitResult {
self.showMessage("Unexpected result: \(unknown.stateData.message ?? "")", type: .error)
}
}
}
private func handleShippingAddressChange(_ contact: ApplePayContact?) -> ApplePayRequestUpdate? {
let shippingCost = calculateShippingCost(for: contact)
let tax = calculateTax(for: contact)
let newTotal = 20.00 + shippingCost + tax
return ApplePayRequestUpdate(
totalPaymentItem: ApplePayPaymentSummaryItem(amount: newTotal, type: .final, label: "Your Store Name"),
paymentSummaryItems: [
ApplePayPaymentSummaryItem(amount: 20.00, type: .final, label: "Premium T-Shirt"),
ApplePayPaymentSummaryItem(amount: tax, type: .final, label: "Sales Tax"),
ApplePayPaymentSummaryItem(amount: shippingCost, type: .final, label: "Shipping")
],
shippingMethods: [
ApplePayShippingMethod(
amount: shippingCost,
detail: "5-7 business days",
identifier: "standard",
label: "Standard Shipping"
)
]
)
}
private func handleShippingOptionChange(_ method: ApplePayShippingMethod?) -> ApplePayRequestUpdate? {
let baseAmount = 20.00
let tax = 3.00
let shippingCost = method?.amount ?? 2.00
let newTotal = baseAmount + tax + shippingCost
return ApplePayRequestUpdate(
paymentSummaryItems: [
ApplePayPaymentSummaryItem(amount: baseAmount, type: .final, label: "Premium T-Shirt"),
ApplePayPaymentSummaryItem(amount: tax, type: .final, label: "Sales Tax"),
ApplePayPaymentSummaryItem(amount: shippingCost, type: .final, label: "Shipping"),
ApplePayPaymentSummaryItem(amount: newTotal, type: .final, label: "Your Store Name")
]
)
}
private func handleError(_ exception: BaseSdkException) {
DispatchQueue.main.async {
self.loadingIndicator.stopAnimating()
self.loadingIndicator.isHidden = true
self.showMessage("Payment error: \(exception.errorMessage)", type: .error)
}
}
private func handleCancellation(_ exception: BaseSdkException) {
DispatchQueue.main.async {
self.loadingIndicator.stopAnimating()
self.loadingIndicator.isHidden = true
self.showMessage("Payment was cancelled", type: .info)
}
}
// MARK: - Helper Methods
private func showMessage(_ message: String, type: MessageType) {
errorMessageLabel.isHidden = true
successMessageLabel.isHidden = true
switch type {
case .error:
errorMessageLabel.text = message
errorMessageLabel.isHidden = false
case .success:
successMessageLabel.text = message
successMessageLabel.textColor = .systemGreen
successMessageLabel.isHidden = false
case .info:
successMessageLabel.text = message
successMessageLabel.textColor = .systemBlue
successMessageLabel.isHidden = false
}
}
private func calculateShippingCost(for contact: ApplePayContact?) -> Decimal {
guard let countryCode = contact?.countryCode else { return 5.00 }
if countryCode == "US" {
return contact?.administrativeArea == "CA" ? 7.99 : 4.99
}
return 15.99
}
private func calculateTax(for contact: ApplePayContact?) -> Decimal {
guard contact?.countryCode == "US" else { return 0.00 }
let taxRates: [String: Decimal] = ["CA": 0.0875, "NY": 0.08, "TX": 0.0625]
let rate = taxRates[contact?.administrativeArea ?? ""] ?? 0.06
return 20.00 * rate
}
private func navigateToSuccessScreen(transactionId: String) {
let alert = UIAlertController(
title: "Payment Successful",
message: "Transaction ID: \(transactionId)",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in
self.navigationController?.popViewController(animated: true)
})
present(alert, animated: true)
}
private func cleanupComponent() {
checkout?.unmount()
applePayComponent = nil
checkout = nil
}
enum MessageType {
case error, success, info
}
}