Learn about built-in validation and implement additional scenarios for Apple Pay for iOS.
The Apple Pay component includes comprehensive validation to ensure data integrity and compliance with Apple's requirements. All built-in validation is performed before creating payment requests and during the Apple Pay flow. If it fails, the SDK will throw an ApplePayValidationException with detailed error information.
You can also easily build custom validation, depending on your business needs.
By default, the Apple Pay component validates that:
- Fields marked as required are provided.
- Payment request data meets Apple Pay specifications.
- Currency codes, country codes, and amounts are formatted properly.
- Merchant capabilities and supported networks are valid.
- Contact fields and shipping methods are properly configured.
- Device and iOS compatibility requirements are met.
The Apple Pay component returns structured error codes for different validation failures:
| Error code | Description | Common causes |
|---|---|---|
REQUIRED_FIELD | A required field is missing. | Missing mandatory configuration. |
INVALID_AMOUNT_FORMAT | The amount format is invalid. | Non-decimal amount value. |
INVALID_CURRENCY_CODE | The currency code is invalid. | Non-ISO 4217 currency format. |
INVALID_COUNTRY_CODE | The country code is invalid. | Non-ISO 3166-1 alpha-2 format. |
INVALID_MERCHANT_CAPABILITIES | Merchant capabilities invalid. | Unsupported capability values. |
INVALID_PAYMENT_NETWORKS | Payment networks invalid. | Unsupported network values. |
MISSING_MERCHANT_ID | The merchant identifier is missing. | Apple Pay merchant ID not configured. |
INVALID_CONTACT_FIELDS | Contact fields invalid. | Unsupported contact field values. |
INVALID_SHIPPING_METHODS | Shipping methods invalid. | Malformed shipping method data. |
INVALID_LINE_ITEMS | Line items invalid. | Malformed payment summary items. |
APPLE_PAY_NOT_AVAILABLE | Apple Pay not available. | Unsupported device/iOS version. |
MISSING_ENTITLEMENTS | Apple Pay entitlements missing. | App not configured for Apple Pay. |
INVALID_MERCHANT_CERTIFICATE | Merchant certificate invalid. | Certificate configuration issue. |
// Example of handling validation errors
let applePayConfig = ApplePayButtonComponentConfig()
applePayConfig.onError = { error in
print("Apple Pay error: \(error)")
if let validationError = error as? ApplePayValidationException {
// Handle specific validation errors
switch validationError.code {
case "INVALID_MERCHANT_CAPABILITIES":
print("Invalid merchant capabilities configured")
case "INVALID_AMOUNT_FORMAT":
print("Invalid total amount format")
case "MISSING_MERCHANT_ID":
print("Apple Pay merchant ID missing or invalid")
case "APPLE_PAY_NOT_AVAILABLE":
print("Apple Pay not available on this device")
default:
print("Validation error: \(validationError.localizedDescription)")
}
// Display user-friendly error messages
DispatchQueue.main.async {
self.showValidationError(validationError)
}
}
}Run validation before the Apple Pay authorisation to catch issues early and prevent failed payments.
let applePayConfig = ApplePayButtonComponentConfig()
applePayConfig.onPreAuthorisation = { [weak self] in
do {
// 1. Business logic validation
let orderValidation = try await self?.validateOrder(
cartItems: self?.getCartItems() ?? [],
customerLocation: self?.getCustomerLocation(),
paymentAmount: self?.getOrderTotal() ?? 0
)
guard let orderValidation = orderValidation, orderValidation.valid else {
throw ValidationError.orderValidationFailed(orderValidation?.reason ?? "Unknown error")
}
// 2. Apple Pay specific validation
let applePayValidation = try await self?.validateApplePayRequirements(
merchantId: self?.getMerchantId() ?? "",
totalAmount: self?.getOrderTotal() ?? 0
)
guard let applePayValidation = applePayValidation, applePayValidation.valid else {
throw ValidationError.applePayRequirementsNotMet
}
// 3. Security validation
let securityCheck = try await self?.performSecurityValidation(
deviceID: UIDevice.current.identifierForVendor?.uuidString ?? "",
paymentHistory: self?.getCustomerPaymentHistory() ?? []
)
if let securityCheck = securityCheck, securityCheck.riskLevel == .high {
// Return additional security data for transaction
return ApplePayTransactionInitData(
threeDSecureData: ThreeDSecureData(
threeDSecureVersion: "2.1.0",
electronicCommerceIndicator: .eci5,
cardHolderAuthenticationVerificationValue: "AAABBBCCCDDDEEEFFFaaabbbcccdddeee",
directoryServerTransactionId: "8ac7ca4f-6068-4978-8b5e-c23c0c6d2260",
threeDSecureTransactionStatus: "Y"
),
riskScreeningData: RiskScreeningData(
performRiskScreening: true,
deviceSessionId: self?.generateDeviceSessionId() ?? "",
riskScore: securityCheck.score
)
)
}
// 4. Inventory validation
let inventoryCheck = try await self?.validateInventory(self?.getCartItems() ?? [])
guard let inventoryCheck = inventoryCheck, inventoryCheck.allAvailable else {
throw ValidationError.inventoryUnavailable
}
// 5. Regulatory compliance
let complianceCheck = try await self?.validateCompliance(
customerCountry: self?.getCustomerCountry() ?? "",
orderAmount: self?.getOrderTotal() ?? 0,
productTypes: self?.getProductTypes() ?? []
)
guard let complianceCheck = complianceCheck, complianceCheck.compliant else {
throw ValidationError.regulatoryComplianceFailure
}
// Return transaction initialisation data
return ApplePayTransactionInitData(
identityVerification: IdentityVerification(nameVerification: true),
addressVerification: AddressVerification(
countryCode: "US",
houseNumberOrName: "123 Main St",
postalCode: "10001"
),
riskScreeningData: RiskScreeningData(
performRiskScreening: true,
deviceSessionId: self?.generateDeviceSessionId() ?? ""
)
)
} catch {
print("Pre-authorisation validation failed: \(error)")
// Returning nil will cancel the Apple Pay flow
return nil
}
}
enum ValidationError: Error {
case orderValidationFailed(String)
case applePayRequirementsNotMet
case inventoryUnavailable
case regulatoryComplianceFailure
}Run validation after Apple Pay authorisation to ensure payment integrity before completing the transaction.
applePayConfig.onPostAuthorisation = { [weak self] result in
DispatchQueue.main.async {
do {
if let authorizedResult = result as? AuthorisedSubmitResult {
// 1. Validate transaction data
let transactionValidation = try await self?.validateTransactionData(
transactionId: authorizedResult.provider.code,
amount: self?.getOrderTotal() ?? 0
)
guard let transactionValidation = transactionValidation, transactionValidation.valid else {
throw ValidationError.transactionDataInvalid
}
// 2. Final inventory check
let finalInventoryCheck = try await self?.reserveInventory(orderId: self?.getOrderId() ?? "")
guard let finalInventoryCheck = finalInventoryCheck, finalInventoryCheck.success else {
throw ValidationError.inventoryUnavailable
}
// 3. Validate device and security
let deviceValidation = try await self?.validateDeviceSecurity(
deviceID: UIDevice.current.identifierForVendor?.uuidString ?? "",
transactionAmount: self?.getOrderTotal() ?? 0
)
guard let deviceValidation = deviceValidation, deviceValidation.valid else {
throw ValidationError.deviceSecurityFailure
}
// 4. Process successful payment
try await self?.completeOrder(
transactionId: authorizedResult.provider.code,
deviceInfo: self?.getDeviceInfo()
)
// Navigate to success screen
self?.navigateToSuccessScreen(transactionId: authorizedResult.provider.code)
} else if let failedResult = result as? FailedSubmitResult {
// Handle payment failure
print("Payment failed: \(failedResult.errorReason)")
self?.showError("Payment processing failed. Please try again.")
}
} catch {
print("Post-authorisation validation failed: \(error)")
self?.showError("Payment validation failed: \(error.localizedDescription)")
}
}
}
extension ValidationError {
case transactionDataInvalid
case deviceSecurityFailure
}Validate shipping addresses in real-time as customers select them in the Apple Pay sheet.
applePayConfig.onShippingContactSelected = { [weak self] contact in
do {
// 1. Validate address format
let addressValidation = self?.validateAddressFormat(contact.postalAddress)
guard let addressValidation = addressValidation, addressValidation.valid else {
return PKPaymentRequestShippingContactUpdate(
errors: [PKPaymentRequest.paymentShippingAddressInvalidError(
withKey: PKContactField.postalAddress,
localizedDescription: "Please enter a complete address"
)],
shippingMethods: [],
paymentSummaryItems: []
)
}
// 2. Check shipping restrictions
let shippingValidation = try await self?.validateShippingAvailability(
countryCode: contact.postalAddress?.isoCountryCode ?? "",
postalCode: contact.postalAddress?.postalCode ?? "",
cartItems: self?.getCartItems() ?? []
)
guard let shippingValidation = shippingValidation, shippingValidation.available else {
return PKPaymentRequestShippingContactUpdate(
errors: [PKPaymentRequest.paymentShippingAddressUnserviceableError(
withLocalizedDescription: "Shipping not available to this location"
)],
shippingMethods: [],
paymentSummaryItems: []
)
}
// 3. Calculate shipping costs
let shippingCost = try await self?.calculateShippingCost(
address: contact.postalAddress,
items: self?.getCartItems() ?? [],
expedited: false
) ?? 0
// 4. Calculate taxes
let tax = try await self?.calculateTax(
address: contact.postalAddress,
subtotal: self?.getSubtotal() ?? 0
) ?? 0
// Return updated payment details
let subtotal = self?.getSubtotal() ?? 0
let newTotal = subtotal + shippingCost + tax
let summaryItems = [
PKPaymentSummaryItem(label: "Subtotal", amount: NSDecimalNumber(value: subtotal)),
PKPaymentSummaryItem(label: "Shipping", amount: NSDecimalNumber(value: shippingCost)),
PKPaymentSummaryItem(label: "Tax", amount: NSDecimalNumber(value: tax)),
PKPaymentSummaryItem(label: "Total", amount: NSDecimalNumber(value: newTotal))
]
let shippingMethods = try await self?.getAvailableShippingMethods(for: contact.postalAddress) ?? []
return PKPaymentRequestShippingContactUpdate(
errors: [],
shippingMethods: shippingMethods,
paymentSummaryItems: summaryItems
)
} catch {
print("Shipping contact validation failed: \(error)")
return PKPaymentRequestShippingContactUpdate(
errors: [PKPaymentRequest.paymentShippingAddressUnserviceableError(
withLocalizedDescription: "Unable to validate shipping address. Please try again."
)],
shippingMethods: [],
paymentSummaryItems: []
)
}
}Ensure Apple Pay requirements are met before showing the component.
func validateApplePayCompatibility() -> ValidationResult {
var validation = ValidationResult(valid: true, errors: [])
// Check if Apple Pay is supported on device
if !PKPaymentAuthorizationController.canMakePayments() {
validation.valid = false
validation.errors.append(ValidationError(
code: "APPLE_PAY_NOT_AVAILABLE",
message: "Apple Pay not supported on this device"
))
}
// Check iOS version compatibility
if #available(iOS 10.3, *) {
// Apple Pay is available
} else {
validation.valid = false
validation.errors.append(ValidationError(
code: "IOS_VERSION_INCOMPATIBLE",
message: "iOS 10.3 or later required for Apple Pay"
))
}
// Check if user has cards set up
let supportedNetworks: [PKPaymentNetwork] = [.visa, .masterCard, .amex, .discover]
if !PKPaymentAuthorizationController.canMakePayments(usingNetworks: supportedNetworks) {
validation.valid = false
validation.errors.append(ValidationError(
code: "NO_CARDS_AVAILABLE",
message: "No compatible cards available in Apple Pay"
))
}
// Check app entitlements
if !hasApplePayEntitlement() {
validation.valid = false
validation.errors.append(ValidationError(
code: "MISSING_ENTITLEMENTS",
message: "Apple Pay capability not enabled in app"
))
}
return validation
}
// Use validation before initialising
func initializeApplePayIfCompatible() {
let compatibilityCheck = validateApplePayCompatibility()
if compatibilityCheck.valid {
// Initialise Apple Pay component
initializeApplePay()
} else {
// Show alternative payment methods
showAlternativePaymentMethods()
print("Apple Pay not available: \(compatibilityCheck.errors)")
}
}
struct ValidationResult {
var valid: Bool
var errors: [ValidationError]
}
struct ValidationError {
let code: String
let message: String
}Check in real-time if any cart items are restricted in the customer's country to prevent compliance violations.
func validateGeographicRestrictions(customerCountry: String, cartItems: [CartItem]) -> GeographicValidationResult {
let restrictedItems = cartItems.filter { item in
item.restrictions?.countries?.contains(customerCountry) == true ||
item.restrictions?.applePayRestricted?.contains(customerCountry) == true
}
// Check Apple Pay specific restrictions
let applePayRestrictions = getApplePayCountryRestrictions()
let applePayBlocked = !applePayRestrictions.allowedCountries.contains(customerCountry)
return GeographicValidationResult(
valid: restrictedItems.isEmpty && !applePayBlocked,
restrictedItems: restrictedItems,
applePayBlocked: applePayBlocked,
message: applePayBlocked
? "Apple Pay not available in your country"
: "Some items cannot be shipped to \(customerCountry)"
)
}
struct GeographicValidationResult {
let valid: Bool
let restrictedItems: [CartItem]
let applePayBlocked: Bool
let message: String
}
struct CartItem {
let id: String
let name: String
let price: Double
let restrictions: ItemRestrictions?
}
struct ItemRestrictions {
let countries: [String]?
let applePayRestricted: [String]?
}Calculate a comprehensive fraud risk score based on multiple behavioural and historical factors, with Apple Pay specific considerations.
func calculateFraudScore(transactionData: TransactionData, deviceInfo: DeviceInfo) async throws -> RiskAssessment {
let factors = RiskFactors(
velocityScore: await checkPaymentVelocity(customerEmail: transactionData.customerEmail),
locationScore: await validateLocation(deviceInfo: deviceInfo),
deviceScore: await analyzeDeviceFingerprint(deviceInfo: deviceInfo),
historyScore: await analyzePaymentHistory(customerId: transactionData.customerId),
applePayScore: await analyzeApplePayData(deviceInfo: deviceInfo)
)
// Apple Pay specific risk factors
let applePayFactors = ApplePayRiskFactors(
deviceAccountScore: deviceInfo.hasDeviceAccount ? 0.1 : 0.3,
biometricScore: deviceInfo.biometricAuthentication ? 0.1 : 0.4,
walletScore: await analyzeWalletHistory(deviceId: deviceInfo.deviceId)
)
// Weight Apple Pay factors (generally lower risk)
let baseScore = factors.averageScore
let applePayAdjustment = applePayFactors.averageScore
let totalScore = (baseScore * 0.7) + (applePayAdjustment * 0.3) // Apple Pay generally reduces risk
return RiskAssessment(
score: totalScore,
riskLevel: RiskLevel.from(score: totalScore),
factors: factors,
applePayFactors: applePayFactors,
applePayAdvantage: baseScore - totalScore // How much Apple Pay reduced the risk
)
}
struct RiskFactors {
let velocityScore: Double
let locationScore: Double
let deviceScore: Double
let historyScore: Double
let applePayScore: Double
var averageScore: Double {
return (velocityScore + locationScore + deviceScore + historyScore + applePayScore) / 5.0
}
}
struct ApplePayRiskFactors {
let deviceAccountScore: Double
let biometricScore: Double
let walletScore: Double
var averageScore: Double {
return (deviceAccountScore + biometricScore + walletScore) / 3.0
}
}
struct RiskAssessment {
let score: Double
let riskLevel: RiskLevel
let factors: RiskFactors
let applePayFactors: ApplePayRiskFactors
let applePayAdvantage: Double
}
enum RiskLevel {
case low, medium, high
static func from(score: Double) -> RiskLevel {
if score > 0.8 {
return .high
} else if score > 0.5 {
return .medium
} else {
return .low
}
}
}
struct TransactionData {
let customerId: String
let customerEmail: String
let amount: Double
let currency: String
}
struct DeviceInfo {
let deviceId: String
let hasDeviceAccount: Bool
let biometricAuthentication: Bool
let ipAddress: String
let location: CLLocation?
}Ensure payment amounts meet Apple Pay requirements and business rules.
func validatePaymentAmounts(paymentRequest: PaymentRequestData) -> AmountValidationResult {
var validation = AmountValidationResult(valid: true, errors: [])
// Validate total amount format
guard paymentRequest.totalAmount > 0 else {
validation.valid = false
validation.errors.append(AmountValidationError(
field: "totalAmount",
code: "INVALID_AMOUNT",
message: "Total amount must be a positive number"
))
return validation
}
// Validate line items sum up to total
if !paymentRequest.lineItems.isEmpty {
let lineItemsSum = paymentRequest.lineItems.reduce(0) { sum, item in
return sum + item.amount
}
let difference = abs(lineItemsSum - paymentRequest.totalAmount)
if difference > 0.01 { // Allow for small rounding differences
validation.valid = false
validation.errors.append(AmountValidationError(
field: "lineItems",
code: "AMOUNT_MISMATCH",
message: "Line items do not sum to total amount"
))
}
}
// Check minimum/maximum amounts
let minAmount: Double = 0.50 // Apple Pay minimum
let maxAmount: Double = 10000.00 // Business rule maximum
if paymentRequest.totalAmount < minAmount {
validation.valid = false
validation.errors.append(AmountValidationError(
field: "totalAmount",
code: "AMOUNT_TOO_LOW",
message: "Minimum amount is \(minAmount)"
))
}
if paymentRequest.totalAmount > maxAmount {
validation.valid = false
validation.errors.append(AmountValidationError(
field: "totalAmount",
code: "AMOUNT_TOO_HIGH",
message: "Maximum amount is \(maxAmount)"
))
}
// Validate currency code
if !isValidCurrencyCode(paymentRequest.currencyCode) {
validation.valid = false
validation.errors.append(AmountValidationError(
field: "currencyCode",
code: "INVALID_CURRENCY",
message: "Invalid currency code: \(paymentRequest.currencyCode)"
))
}
return validation
}
struct PaymentRequestData {
let totalAmount: Double
let currencyCode: String
let lineItems: [LineItem]
}
struct LineItem {
let label: String
let amount: Double
}
struct AmountValidationResult {
var valid: Bool
var errors: [AmountValidationError]
}
struct AmountValidationError {
let field: String
let code: String
let message: String
}
func isValidCurrencyCode(_ code: String) -> Bool {
let validCurrencies = ["USD", "EUR", "GBP", "JPY", "CAD", "AUD", "CHF", "SEK", "NOK", "DKK"]
return validCurrencies.contains(code)
}Here's a comprehensive example showing all validation working together:
import UIKit
import PXPCheckoutSDK
import PassKit
import CoreLocation
class ValidationViewController: UIViewController {
private var applePayComponent: ApplePayButtonComponent?
private var checkout: PxpCheckout?
override func viewDidLoad() {
super.viewDidLoad()
setupApplePayWithValidation()
}
private func setupApplePayWithValidation() {
// First validate compatibility
let compatibilityCheck = validateApplePayCompatibility()
guard compatibilityCheck.valid else {
showCompatibilityError(compatibilityCheck.errors)
return
}
let checkoutConfig = CheckoutConfig(
environment: .test,
session: SessionConfig(sessionId: "your-session-id"),
transactionData: TransactionData(
amount: 29.99,
currency: "USD",
entryType: .ecom,
intent: .sale,
merchantTransactionId: "order-\(Int(Date().timeIntervalSince1970))",
merchantTransactionDate: Date()
)
)
do {
checkout = try PxpCheckout.initialize(config: checkoutConfig)
let applePayConfig = createValidatedApplePayConfig()
applePayComponent = try checkout?.create(.applePayButton, componentConfig: applePayConfig)
if let componentView = applePayComponent?.render() {
view.addSubview(componentView)
setupConstraints(for: componentView)
}
} catch {
print("Failed to initialize: \(error)")
showError("Failed to setup Apple Pay: \(error.localizedDescription)")
}
}
private func createValidatedApplePayConfig() -> ApplePayButtonComponentConfig {
let config = ApplePayButtonComponentConfig()
// Basic configuration with validation
config.currencyCode = "USD"
config.countryCode = "US"
config.supportedNetworks = [.visa, .masterCard, .amex]
config.merchantCapabilities = [.threeDSecure, .emv, .credit, .debit]
config.requiredBillingContactFields = [.postalAddress, .name, .emailAddress]
config.requiredShippingContactFields = [.postalAddress, .name]
// Validate amounts before setting
let paymentData = PaymentRequestData(
totalAmount: 29.99,
currencyCode: "USD",
lineItems: [
LineItem(label: "Product", amount: 25.99),
LineItem(label: "Tax", amount: 4.00)
]
)
let amountValidation = validatePaymentAmounts(paymentRequest: paymentData)
guard amountValidation.valid else {
print("Amount validation failed: \(amountValidation.errors)")
return config
}
// Payment items
config.totalPaymentItem = ApplePayPaymentSummaryItem(
amount: 29.99,
label: "Total",
type: .final
)
config.paymentItems = [
ApplePayPaymentSummaryItem(amount: 25.99, label: "Product", type: .final),
ApplePayPaymentSummaryItem(amount: 4.00, label: "Tax", type: .final)
]
// Button appearance
config.buttonType = .buy
config.buttonStyle = .black
config.buttonRadius = 8.0
// Setup validation callbacks
setupValidationCallbacks(for: config)
return config
}
private func setupValidationCallbacks(for config: ApplePayButtonComponentConfig) {
// Pre-authorisation validation
config.onPreAuthorisation = { [weak self] in
do {
// Comprehensive pre-authorisation validation
try await self?.performPreAuthorizationValidation()
return ApplePayTransactionInitData(
identityVerification: IdentityVerification(nameVerification: true),
addressVerification: AddressVerification(
countryCode: "US",
houseNumberOrName: "123 Main St",
postalCode: "10001"
),
riskScreeningData: RiskScreeningData(
performRiskScreening: true,
deviceSessionId: self?.generateDeviceSessionId() ?? ""
)
)
} catch {
print("Pre-authorisation validation failed: \(error)")
DispatchQueue.main.async {
self?.showError("Validation failed: \(error.localizedDescription)")
}
return nil
}
}
// Post-authorisation validation
config.onPostAuthorisation = { [weak self] result in
DispatchQueue.main.async {
self?.handlePostAuthorizationWithValidation(result: result)
}
}
// Error handling with validation
config.onError = { [weak self] error in
print("Apple Pay error: \(error)")
DispatchQueue.main.async {
if let validationError = error as? ApplePayValidationException {
self?.handleValidationError(validationError)
} else {
self?.handleGeneralError(error)
}
}
}
// Shipping contact validation
config.onShippingContactSelected = { [weak self] contact in
return await self?.validateAndUpdateShippingContact(contact) ?? PKPaymentRequestShippingContactUpdate(
errors: [PKPaymentRequest.paymentShippingAddressUnserviceableError(
withLocalizedDescription: "Unable to validate shipping address"
)],
shippingMethods: [],
paymentSummaryItems: []
)
}
}
// Validation methods
private func performPreAuthorizationValidation() async throws {
// 1. Order validation
let orderValid = try await validateOrder()
guard orderValid else {
throw ValidationError.orderValidationFailed("Order validation failed")
}
// 2. Inventory validation
let inventoryValid = try await validateInventory()
guard inventoryValid else {
throw ValidationError.inventoryUnavailable
}
// 3. Security validation
let riskAssessment = try await calculateRiskScore()
if riskAssessment.riskLevel == .high {
throw ValidationError.highRiskTransaction
}
// 4. Geographic restrictions
let geoValidation = validateGeographicRestrictions(
customerCountry: getCustomerCountry(),
cartItems: getCartItems()
)
guard geoValidation.valid else {
throw ValidationError.geographicRestriction(geoValidation.message)
}
}
private func validateAndUpdateShippingContact(_ contact: PKContact) async -> PKPaymentRequestShippingContactUpdate {
do {
// Validate address format
guard let postalAddress = contact.postalAddress else {
return PKPaymentRequestShippingContactUpdate(
errors: [PKPaymentRequest.paymentShippingAddressInvalidError(
withKey: PKContactField.postalAddress,
localizedDescription: "Address is required"
)],
shippingMethods: [],
paymentSummaryItems: []
)
}
// Check shipping availability
let shippingAvailable = try await checkShippingAvailability(to: postalAddress)
guard shippingAvailable else {
return PKPaymentRequestShippingContactUpdate(
errors: [PKPaymentRequest.paymentShippingAddressUnserviceableError(
withLocalizedDescription: "Shipping not available to this location"
)],
shippingMethods: [],
paymentSummaryItems: []
)
}
// Calculate updated amounts
let shippingCost = try await calculateShippingCost(to: postalAddress)
let tax = try await calculateTax(for: postalAddress)
let total = 25.99 + shippingCost + tax
let summaryItems = [
PKPaymentSummaryItem(label: "Product", amount: NSDecimalNumber(value: 25.99)),
PKPaymentSummaryItem(label: "Shipping", amount: NSDecimalNumber(value: shippingCost)),
PKPaymentSummaryItem(label: "Tax", amount: NSDecimalNumber(value: tax)),
PKPaymentSummaryItem(label: "Total", amount: NSDecimalNumber(value: total))
]
let shippingMethods = getShippingMethods()
return PKPaymentRequestShippingContactUpdate(
errors: [],
shippingMethods: shippingMethods,
paymentSummaryItems: summaryItems
)
} catch {
return PKPaymentRequestShippingContactUpdate(
errors: [PKPaymentRequest.paymentShippingAddressUnserviceableError(
withLocalizedDescription: "Unable to validate address. Please try again."
)],
shippingMethods: [],
paymentSummaryItems: []
)
}
}
private func handlePostAuthorizationWithValidation(result: BaseSubmitResult) {
do {
if let authorizedResult = result as? AuthorisedSubmitResult {
// Validate transaction result
try validateTransactionResult(authorizedResult)
// Complete order
completeOrder(transactionId: authorizedResult.provider.code)
// Navigate to success
navigateToSuccess(transactionId: authorizedResult.provider.code)
} else if let failedResult = result as? FailedSubmitResult {
handlePaymentFailure(failedResult)
}
} catch {
showError("Post-authorisation validation failed: \(error.localizedDescription)")
}
}
private func handleValidationError(_ error: ApplePayValidationException) {
var message = "Validation failed"
switch error.code {
case "APPLE_PAY_NOT_AVAILABLE":
message = "Apple Pay is not available on this device"
showAlternativePaymentMethods()
case "MISSING_MERCHANT_ID":
message = "Apple Pay configuration error. Please contact support."
case "INVALID_AMOUNT_FORMAT":
message = "Invalid payment amount. Please refresh and try again."
default:
message = error.localizedDescription
}
showError(message)
}
// Helper methods for validation
private func validateOrder() async throws -> Bool {
// Simulate order validation
return true
}
private func validateInventory() async throws -> Bool {
// Simulate inventory check
return true
}
private func calculateRiskScore() async throws -> RiskAssessment {
// Simulate risk calculation
return RiskAssessment(
score: 0.3,
riskLevel: .low,
factors: RiskFactors(velocityScore: 0.2, locationScore: 0.1, deviceScore: 0.2, historyScore: 0.1, applePayScore: 0.1),
applePayFactors: ApplePayRiskFactors(deviceAccountScore: 0.1, biometricScore: 0.1, walletScore: 0.1),
applePayAdvantage: 0.2
)
}
private func checkShippingAvailability(to address: CNPostalAddress) async throws -> Bool {
// Simulate shipping availability check
return address.isoCountryCode == "US"
}
private func calculateShippingCost(to address: CNPostalAddress) async throws -> Double {
// Simulate shipping cost calculation
return 5.99
}
private func calculateTax(for address: CNPostalAddress) async throws -> Double {
// Simulate tax calculation
return 2.60
}
private func validateTransactionResult(_ result: AuthorisedSubmitResult) throws {
// Validate the transaction was processed correctly
guard !result.provider.code.isEmpty else {
throw ValidationError.invalidTransactionResult
}
}
// UI helper methods
private func showCompatibilityError(_ errors: [ValidationError]) {
let message = errors.map { $0.message }.joined(separator: "\n")
showError("Apple Pay not available:\n\(message)")
showAlternativePaymentMethods()
}
private func showError(_ message: String) {
let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true)
}
private func showAlternativePaymentMethods() {
// Show alternative payment options
print("Showing alternative payment methods")
}
private func navigateToSuccess(transactionId: String) {
let alert = UIAlertController(title: "Success", message: "Payment completed! ID: \(transactionId)", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true)
}
private func handlePaymentFailure(_ result: FailedSubmitResult) {
showError("Payment failed: \(result.errorReason)")
}
private func handleGeneralError(_ error: Error) {
showError("An error occurred: \(error.localizedDescription)")
}
// Data helper methods
private func getCustomerCountry() -> String { return "US" }
private func getCartItems() -> [CartItem] { return [] }
private func generateDeviceSessionId() -> String { return UUID().uuidString }
private func getShippingMethods() -> [PKShippingMethod] { return [] }
private func completeOrder(transactionId: String) { print("Order completed: \(transactionId)") }
private func setupConstraints(for componentView: UIView) {
componentView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
componentView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
componentView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
componentView.widthAnchor.constraint(equalToConstant: 280),
componentView.heightAnchor.constraint(equalToConstant: 50)
])
}
}
// Additional validation error types
extension ValidationError {
case highRiskTransaction
case geographicRestriction(String)
case invalidTransactionResult
}