Understand error types, handle payment failures, and implement proper error recovery.
Drop-in reports almost all errors through your onError callback. The only exceptions are two configuration checks that throw when you create Drop-in: empty sessionId or empty hmacKey. Wrap CheckoutDropIn(config:) in do/catch to handle those.
Failures during await dropIn.create(), rendering, and payment processing do not throw. They arrive in onError instead. Plan your integration around that callback for runtime recovery and user messaging.
Register onError on CheckoutDropInConfig alongside your other callbacks. Use it for every error that happens after you have constructed Drop-in, including render failures during create() and payment failures during checkout.
Reserve do/catch for initialisation only. It catches the two missing-session exceptions above. Do not expect payment or render errors to bubble up as Swift throws.
import PXPCheckoutSDK
do {
let config = CheckoutDropInConfig(
// ... other config
onError: { paymentMethod, error in
print("Payment failed: \(error.errorMessage)")
print("Error code: \(error.errorCode)")
// Handle the error
showErrorMessage(error.errorMessage)
}
)
let dropIn = try CheckoutDropIn(config: config)
} catch {
// Initialisation failed: sessionId or hmacKey missing
print("Failed to initialise Drop-in: \(error.localizedDescription)")
showErrorMessage("Failed to load payment form. Please contact support.")
}Each error passed to onError is a BaseSdkException. Inspect these properties:
| Property | Description |
|---|---|
errorMessageString | A human-readable description of what went wrong. |
errorCodeString | An SDK code in SDK#### format (for example SDK1114 for authentication failure or SDK0500 for a network error). Card and Paze authorisation failures may also return provider-specific codes through CheckoutDropInFailedSubmitResultSdkException. Use errorCode to branch your handling logic. |
paymentMethodDropInPaymentMethod? | The payment method involved, or nil when the error is not tied to a specific method (for example SDK1100 during create()). Raw values: Card, Paypal, ApplePay. |
Errors are grouped below by source and type.
Errors that occur during SDK initialisation or Drop-in component rendering.
These exceptions throw from CheckoutDropIn(config:) when session credentials are missing:
| Exception | Error code | Description | Solution |
|---|---|---|---|
MissingConfigSessionIdException | SDK0103 | session.sessionId is empty. | Provide a valid SessionData object from your backend. |
MissingConfigSessionHmackeyException | SDK0104 | session.hmacKey is empty. | Provide a valid HMAC key in SessionData. |
Other SDK01xx validation errors (for example missing funding types or intents for a component) surface during create() through onError, often wrapped in a render exception.
These exceptions are delivered through onError, typically during await dropIn.create() or when Drop-in updates session state. paymentMethod is set when the failure is tied to a specific method.
| Exception | Error code | Description | Solution |
|---|---|---|---|
CheckoutDropInConfigNotFoundSdkException | SDK1100 | Failed to retrieve Checkout Drop-In site configuration. | Check portal setup and network connectivity; retry create(). |
CheckoutDropInFailedToRenderCardSdkException | SDK1101 | Failed to render the card component. | Check session card configuration and funding types. |
CheckoutDropInFailedToRenderPaypalSdkException | SDK1102 | Failed to render the PayPal button. | Verify PayPal is enabled in the session. |
CheckoutDropInFailedToRenderApplePaySdkException | SDK1104 | Failed to render the Apple Pay button. | Check Apple Pay session and device configuration. |
CheckoutDropInFailedToRenderCardOnFileSdkException | SDK1105 | Failed to render saved cards. | Check token vault and shopper configuration. |
CheckoutDropInFailedToRenderBillingAddressSdkException | SDK1106 | Failed to render billing address fields. | Review billing address component configuration. |
CheckoutDropInFailedToRenderCardBrandSelectorSdkException | SDK1107 | Failed to render the card brand selector. | Check accepted card networks in session or config. |
CheckoutDropInFailedToRenderCardSubmitSdkException | SDK1108 | Failed to render the card submit button. | Check card component wiring and session. |
CheckoutDropInFailedToRenderCardCofSubmitSdkException | SDK1109 | Failed to render saved-card submit. | Check card-on-file configuration. |
CheckoutDropInFailedToRenderCardConsentSdkException | SDK1110 | Failed to render card consent UI. | Check site storeCardConsent and shopper setup. |
CheckoutDropInFailedToRenderPaypalConsentSdkException | SDK1111 | Failed to render PayPal consent UI. | Check PayPal consent configuration. |
CheckoutDropInFailedToUpdateBillingAddressSdkException | SDK1112 | Failed to update billing address on the session. | Retry or create a new session. |
CheckoutDropInFailedToUpdateAllowTransactionSdkException | SDK1113 | Failed to update allow-transaction state (Apple Pay). | Retry or create a new session. |
CheckoutDropInInvalidPaypalEntryTypeSdkException | SDK1120 | PayPal only supports entryType: .ecom. | Change entryType to .ecom. |
CheckoutDropInComponentTypeMismatchSdkException | SDK1121 | Internal component type mismatch. | Contact support with logs. |
NoPaymentMethodsAvailableException | SDK0204 | No payment methods available for the session. | Enable at least one funding type in the session response. |
Example handling:
onError: { paymentMethod, error in
if error.errorCode.hasPrefix("SDK110") {
print("Rendering error: \(error.errorMessage)")
// Log to monitoring
logger.error("Rendering error", [
"error_code": error.errorCode,
"message": error.errorMessage
])
// Show generic error to user
Task { @MainActor in
showErrorMessage(
"We're having trouble loading payment options. " +
"Please try again or contact support."
)
}
}
}These errors occur when checkout reaches payment processing but the transaction does not complete. Drop-in maps most failures to a method-specific SDK111x code on onError. When the gateway returns provider details, you may receive CheckoutDropInFailedSubmitResultSdkException with a provider-specific code instead of an SDK111x value.
| Exception | Error code | Description | User action |
|---|---|---|---|
CheckoutDropInCardPaymentFailedSdkException | SDK1116 | Card payment failed. | See card-specific patterns. |
CheckoutDropInPaypalPaymentFailedSdkException | SDK1117 | PayPal transaction failed. | Try again or use a different method. |
CheckoutDropInApplePayPaymentFailedSdkException | SDK1119 | Apple Pay transaction failed. | Try again or use a different method. |
CheckoutDropInFailedSubmitResultSdkException | Provider code or method fallback | Authorisation returned a failed submit result with provider details (card, PayPal, Apple Pay). | Log error.errorCode and error.errorMessage. Offer a retry or another payment method. |
Issuers and gateways format card decline messages differently. When you receive SDK1116, inspect error.errorMessage to choose an appropriate response:
| Scenario | Detection approach | User action |
|---|---|---|
| Card declined | error.errorMessage contains "declined" | Try a different card or contact the bank. |
| Insufficient funds | error.errorMessage contains "insufficient funds" | Use a different payment method. |
| Expired card | error.errorMessage contains "expired" | Use a different card. |
| Invalid CVV | error.errorMessage contains "CVV" or "security code" | Check the security code and retry. |
| Invalid card number | error.errorMessage contains "card number" or "invalid number" | Check the card number and retry. |
| Invalid expiry | error.errorMessage contains "expiry" or "expiration" | Check the expiry date and retry. |
Example handling:
onError: { paymentMethod, error in
// Card-specific errors (message-based detection)
if error.errorCode == "SDK1116" {
// Card payment failed - check message for specifics
let userMessage: String
if error.errorMessage.localizedCaseInsensitiveContains("declined") {
userMessage = "Your card was declined. Please try a different card or " +
"contact your bank for more information."
Task { @MainActor in
showErrorMessage(userMessage)
offerAlternativePaymentMethods()
}
} else if error.errorMessage.localizedCaseInsensitiveContains("insufficient funds") {
userMessage = "Insufficient funds. Please use a different payment method."
Task { @MainActor in
showErrorMessage(userMessage)
offerAlternativePaymentMethods()
}
} else if error.errorMessage.localizedCaseInsensitiveContains("expired") {
userMessage = "This card has expired. Please use a different card."
Task { @MainActor in
showErrorMessage(userMessage)
}
} else if error.errorMessage.localizedCaseInsensitiveContains("cvv") ||
error.errorMessage.localizedCaseInsensitiveContains("security code") {
userMessage = "Invalid security code. Please check the CVV on the back of " +
"your card and try again."
Task { @MainActor in
showErrorMessage(userMessage)
// Keep same payment method selected for retry
}
} else {
userMessage = "Card payment failed. Please check your details and try again."
Task { @MainActor in
showErrorMessage(userMessage)
}
}
}
// Wallet payment errors
else if error.errorCode == "SDK1117" {
Task { @MainActor in
showErrorMessage(
"PayPal payment failed. Please try again or use a different payment method."
)
offerAlternativePaymentMethods()
}
} else if error.errorCode == "SDK1119" {
Task { @MainActor in
showErrorMessage(
"Apple Pay payment failed. Please try again or use a different payment method."
)
offerAlternativePaymentMethods()
}
} else if error.errorCode == "SDK1123" {
Task { @MainActor in
showErrorMessage(
"Paze payment failed. Please try again or use a different payment method."
)
offerAlternativePaymentMethods()
}
} else if paymentMethod == .paze {
Task { @MainActor in
let userMessage: String
switch error.errorCode {
case "SDK1124":
userMessage = "Paze is only available for e-commerce checkout."
case "SDK1209", "SDK1210", "SDK1211", "SDK1212":
userMessage = "Invalid shopper identity: \(error.errorMessage)"
case "SDK1213":
userMessage = "Paze is only available for USD transactions."
case "SDK0118":
userMessage = "Paze client ID is missing from the session."
case "SDK1202A":
userMessage = "Paze merchant category code is missing from the session."
default:
userMessage = error.errorMessage.localizedCaseInsensitiveContains("declined")
? "Payment declined. Please try a different payment method."
: "Paze payment failed: \(error.errorMessage)"
}
showErrorMessage(userMessage)
if error.errorCode != "SDK1124" {
offerAlternativePaymentMethods()
}
}
}
}These errors relate to 3D Secure (3DS) authentication during card payments. Drop-in surfaces the primary failure as CheckoutDropInAuthenticationFailedSdkException (SDK1114). Related codes from the authentication flow may also appear in error.errorCode or error.errorMessage.
| Exception / code | Error code | Description | User action |
|---|---|---|---|
CheckoutDropInAuthenticationFailedSdkException | SDK1114 | 3DS authentication failed. | Try again or use a different card. |
| Pre-Initiate Authentication failed | SDK0502 | The pre-initiate authentication step failed. | Try again or use a different card. |
| Authentication rejected | SDK0503 | The issuer or gateway rejected authentication. | Try again or use a different card. |
| Authentication failed | SDK0505 | Authentication did not complete successfully. | Try again or use a different card. |
| Failed to retrieve result | SDK0510 | The SDK could not retrieve the authentication result. | Check the connection and try again. |
| Challenge page failed to load | SDK0511 | The 3DS challenge page failed to load. | Check the connection and try again. |
When no specific code is present, you can still detect common outcomes from error.errorMessage. Treat the error as a timeout when the message contains "timeout". Treat it as a customer cancellation when the message contains "cancel".
Example handling:
onError: { paymentMethod, error in
if error.errorCode == "SDK1114" ||
error.errorCode == "SDK0502" ||
error.errorCode == "SDK0503" ||
error.errorCode == "SDK0505" ||
error.errorCode == "SDK0510" ||
error.errorCode == "SDK0511" ||
error.errorMessage.localizedCaseInsensitiveContains("authentication") {
if error.errorMessage.localizedCaseInsensitiveContains("timeout") {
Task { @MainActor in
showErrorMessage(
"3D Secure authentication timed out. Please check your " +
"internet connection and try again."
)
// Offer retry
showRetryButton()
}
} else if error.errorMessage.localizedCaseInsensitiveContains("cancel") {
Task { @MainActor in
showErrorMessage(
"Authentication was cancelled. Please try again to complete " +
"your payment."
)
// Don't show error as prominently - user intentionally cancelled
showInfoMessage("You can retry your payment when ready.")
}
} else {
Task { @MainActor in
showErrorMessage(
"3D Secure authentication failed. Please try again or use a " +
"different card."
)
}
// Track authentication failures (pseudo-code - use your analytics provider)
analytics.track("3ds_authentication_failed", properties: [
"timestamp": Date().timeIntervalSince1970
])
}
}
}Errors that occur during payment authorisation.
| Exception | Error code | Description | Solution |
|---|---|---|---|
CheckoutDropInAuthorisationFailedSdkException | SDK1115 | Payment authorisation failed. | Check the transaction details and retry. |
Errors specific to Apple Pay configuration, availability, and processing.
| Exception | Error code | Description | Solution |
|---|---|---|---|
ApplePayPaymentRequestApiNotSupportedException | SDK0602 | Apple Pay is not available on this device or iOS version. | Use another payment method; test on a supported device with Apple Pay set up. |
ApplePaySessionCancelledException | SDK0615 | Customer cancelled the Apple Pay sheet. | No action required — allow retry if needed. |
ApplePayMerchantValidationFailedException | SDK0616 | Apple Pay merchant validation failed. | Verify Apple Pay merchant configuration in the Unity Portal. |
ApplePayCustomApiProcessingFailedException | SDK0617 | Processing failed during Apple Pay payment. | Check logs and retry. |
These errors reflect network connectivity, session state, or platform availability rather than a declined payment. They can appear for any payment method, or before a method is selected (paymentMethod may be nil).
Use error.errorCode where a code is listed. For message-based rows, inspect error.errorMessage:
| Scenario | How to detect | User action |
|---|---|---|
| Network error | error.errorCode == "SDK0500" | Check the connection and retry. |
| Session expired | error.errorMessage contains "session" or "expired" | Create a new session and reinitialise Drop-in. |
| Request timeout | error.errorMessage contains "timeout" | Check the connection and retry. |
| Service unavailable | error.errorMessage contains "unavailable" or "service" | Try again later. |
| Configuration error | error.errorCode starts with "SDK01" or "SDK02" | Review your session and config. Contact support if the issue persists. |
Example handling:
onError: { paymentMethod, error in
// Network and session errors
if error.errorCode == "SDK0500" {
Task { @MainActor in
showErrorMessage(
"Network connection issue. Please check your internet connection " +
"and try again."
)
showRetryButton()
}
} else if error.errorMessage.localizedCaseInsensitiveContains("session") ||
error.errorMessage.localizedCaseInsensitiveContains("expired") {
Task { @MainActor in
showErrorMessage(
"Your payment session has expired. Please reload checkout and " +
"try again."
)
showRefreshButton()
}
} else if error.errorMessage.localizedCaseInsensitiveContains("timeout") {
Task { @MainActor in
showErrorMessage(
"Request timed out. Please check your connection and try again."
)
showRetryButton()
}
} else if error.errorMessage.localizedCaseInsensitiveContains("unavailable") ||
error.errorMessage.localizedCaseInsensitiveContains("service") {
Task { @MainActor in
showErrorMessage(
"Payment service is temporarily unavailable. Please try again in " +
"a few minutes."
)
}
} else if error.errorCode.hasPrefix("SDK01") || error.errorCode.hasPrefix("SDK02") {
Task { @MainActor in
showErrorMessage(
"Payment configuration error. Please contact support for assistance."
)
}
// Log critical error
logger.critical("Configuration error", [
"error_code": error.errorCode,
"message": error.errorMessage
])
}
}The simplest error handling pattern shows user-friendly messages and logs errors for debugging.
import PXPCheckoutSDK
let config = CheckoutDropInConfig(
// ... other config
onError: { paymentMethod, error in
// Log error for debugging
print("Payment error: \(error.errorCode) - \(error.errorMessage)")
// Show user-friendly message
Task { @MainActor in
showErrorNotification(error.errorMessage)
// Re-enable payment button
enablePaymentButton()
}
}
)Implement retry logic, alternative payment methods, and error categorisation.
Don't automatically resubmit payments from onError without explicit user confirmation. The following retry logic is for transient network errors only. Payment failures require user action—show an error message and let them manually retry or choose an alternative payment method.
var retryCount = 0
let maxRetries = 3
let config = CheckoutDropInConfig(
// ... other config
onError: { paymentMethod, error in
// Log to monitoring service
logErrorToMonitoring([
"category": "payment_error",
"error_code": error.errorCode,
"message": error.errorMessage,
"device_model": UIDevice.current.model,
"timestamp": Date().timeIntervalSince1970
])
// Track analytics (pseudo-code - use your analytics provider)
analytics.track("payment_failed", properties: [
"error_code": error.errorCode,
"error_message": error.errorMessage,
"retry_count": retryCount
])
// Check if error is retryable (network issues, timeouts only)
let isNetworkError = error.errorCode == "SDK0500"
let isTimeout = error.errorMessage.localizedCaseInsensitiveContains("timeout")
let isRetryable = isNetworkError || isTimeout ||
error.errorMessage.localizedCaseInsensitiveContains("unavailable")
// Implement retry logic for transient errors only
if isRetryable && retryCount < maxRetries {
retryCount += 1
Task { @MainActor in
showWarningMessage(
"Connection issue (attempt \(retryCount)/\(maxRetries)). " +
"Please try your payment again."
) {
// Retry the payment
retryPayment()
}
}
return
}
// Reset retry count for non-retryable errors
retryCount = 0
// Show user-friendly error messages based on error code and message
let userMessage: String
switch error.errorCode {
case "SDK0500":
userMessage = "Network connection issue. Please check your internet connection and try again."
case "SDK1114":
userMessage = "3D Secure authentication failed. Please try again or use a different card."
case "SDK1115":
userMessage = "Payment authorisation failed. Please check your details and try again."
case "SDK1116":
// Card payment failed - check message for specifics
if error.errorMessage.localizedCaseInsensitiveContains("declined") {
userMessage = "Your card was declined. Please try a different card or contact your bank."
} else if error.errorMessage.localizedCaseInsensitiveContains("insufficient funds") {
userMessage = "Insufficient funds. Please use a different payment method."
} else if error.errorMessage.localizedCaseInsensitiveContains("expired") {
userMessage = "This card has expired. Please use a different card."
} else if error.errorMessage.localizedCaseInsensitiveContains("cvv") ||
error.errorMessage.localizedCaseInsensitiveContains("security code") {
userMessage = "Invalid security code. Please check the CVV on your card and try again."
} else if error.errorMessage.localizedCaseInsensitiveContains("card number") {
userMessage = "Invalid card number. Please check and try again."
} else if error.errorMessage.localizedCaseInsensitiveContains("expiry") ||
error.errorMessage.localizedCaseInsensitiveContains("expiration") {
userMessage = "Invalid expiry date. Please check and try again."
} else {
userMessage = "Card payment failed. Please check your details and try again."
}
case "SDK1117":
userMessage = "PayPal payment failed. Please try again or use a different payment method."
case "SDK1119":
userMessage = "Apple Pay payment failed. Please try again or use a different payment method."
case "SDK1122":
userMessage = "Paze is unavailable. Please choose another payment method."
case "SDK1123":
userMessage = "Paze payment failed. Please try again or use a different payment method."
case "SDK1124":
userMessage = "Paze is only available for e-commerce checkout."
case "SDK1209", "SDK1210", "SDK1211", "SDK1212":
userMessage = "Invalid shopper identity: \(error.errorMessage)"
case "SDK1213":
userMessage = "Paze is only available for USD transactions."
case "SDK0118":
userMessage = "Paze client ID is missing from the session."
case "SDK1202A":
userMessage = "Paze merchant category code is missing from the session."
default:
// Check by message content for scenarios without specific codes
if error.errorMessage.localizedCaseInsensitiveContains("timeout") {
userMessage = "Request timed out. Please check your internet connection and try again."
} else if error.errorMessage.localizedCaseInsensitiveContains("session") ||
error.errorMessage.localizedCaseInsensitiveContains("expired") {
userMessage = "Your payment session has expired. Please reload checkout."
} else if error.errorMessage.localizedCaseInsensitiveContains("unavailable") {
userMessage = "Payment service temporarily unavailable. Please try again in a few minutes."
} else if error.errorCode.hasPrefix("SDK01") || error.errorCode.hasPrefix("SDK02") {
userMessage = "Payment configuration error. Please contact support."
} else {
userMessage = error.errorMessage
}
}
Task { @MainActor in
showErrorMessage(userMessage)
// Offer alternative payment methods for certain errors
let shouldOfferAlternatives =
error.errorCode == "SDK1116" || // Card payment failed
error.errorCode == "SDK1117" || // PayPal failed
error.errorCode == "SDK1119" || // Apple Pay failed
error.errorCode == "SDK1122" || // Paze unavailable
error.errorCode == "SDK0118" || // Paze clientId missing at create
error.errorCode == "SDK1123" || // Paze failed
error.errorCode == "SDK1209" || error.errorCode == "SDK1210" ||
error.errorCode == "SDK1211" || error.errorCode == "SDK1212" ||
error.errorCode == "SDK1213" ||
error.errorCode == "SDK1202A" ||
error.errorMessage.localizedCaseInsensitiveContains("declined") ||
error.errorMessage.localizedCaseInsensitiveContains("insufficient funds")
if shouldOfferAlternatives {
showAlternativePaymentMethods()
}
// Show retry button for network/timeout errors
let shouldShowRetry =
error.errorCode == "SDK0500" ||
error.errorMessage.localizedCaseInsensitiveContains("timeout") ||
error.errorMessage.localizedCaseInsensitiveContains("unavailable")
if shouldShowRetry {
showRetryButton()
}
// Show refresh button for session errors
if error.errorMessage.localizedCaseInsensitiveContains("session") ||
error.errorMessage.localizedCaseInsensitiveContains("expired") {
showRefreshButton()
}
// Re-enable payment form
enablePaymentForm()
}
}
)Integrate with monitoring services like Firebase Crashlytics or custom logging.
import FirebaseCrashlytics
let config = CheckoutDropInConfig(
// ... other config
onError: { paymentMethod, error in
// Log to Firebase Crashlytics
Crashlytics.crashlytics().setCustomValue(error.errorCode, forKey: "error_code")
Crashlytics.crashlytics().setCustomValue(error.errorMessage, forKey: "error_message")
Crashlytics.crashlytics().record(
error: NSError(
domain: "CheckoutDropIn",
code: -1,
userInfo: [
NSLocalizedDescriptionKey: "Payment error: \(error.errorCode) - \(error.errorMessage)"
]
)
)
// Log to custom monitoring service
Task {
do {
try await logErrorToBackend([
"type": "payment_error",
"error_code": error.errorCode,
"message": error.errorMessage,
"device_model": UIDevice.current.model,
"os_version": UIDevice.current.systemVersion,
"timestamp": Date().timeIntervalSince1970
])
} catch {
// Silently fail - don't disrupt user experience
print("Failed to log error: \(error.localizedDescription)")
}
}
// Show error to user
Task { @MainActor in
showErrorMessage(error.errorMessage)
}
}
)Here's a production-ready error handling implementation:
import PXPCheckoutSDK
import FirebaseCrashlytics
var retryCount = 0
let maxRetries = 3
let config = CheckoutDropInConfig(
// ... other config
onError: { paymentMethod, error in
// 1. Log error for debugging
print("Payment error: \(error.errorCode) - \(error.errorMessage)")
if let method = paymentMethod {
print("Payment method: \(method)")
} else {
print("No payment method (initialisation/configuration error)")
}
// 2. Send to monitoring service
Crashlytics.crashlytics().setCustomValue(error.errorCode, forKey: "error_code")
Crashlytics.crashlytics().setCustomValue(error.errorMessage, forKey: "error_message")
Crashlytics.crashlytics().setCustomValue(retryCount, forKey: "retry_count")
Crashlytics.crashlytics().record(
error: NSError(
domain: "CheckoutDropIn",
code: -1,
userInfo: [NSLocalizedDescriptionKey: "Payment error: \(error.errorCode)"]
)
)
// 3. Track analytics (pseudo-code - use your analytics provider)
analytics.track("payment_failed", properties: [
"error_code": error.errorCode,
"error_message": error.errorMessage,
"payment_method": paymentMethod ?? "none",
"retry_count": retryCount,
"timestamp": Date().timeIntervalSince1970
])
// 4. Clear any loading state
Task { @MainActor in
hideLoadingOverlay()
}
// 5. Implement retry logic for transient errors only
let isNetworkError = error.errorCode == "SDK0500"
let isTimeout = error.errorMessage.localizedCaseInsensitiveContains("timeout")
let isUnavailable = error.errorMessage.localizedCaseInsensitiveContains("unavailable")
if (isNetworkError || isTimeout || isUnavailable) && retryCount < maxRetries {
retryCount += 1
Task { @MainActor in
showNotification(
type: .warning,
message: "Connection issue (attempt \(retryCount)/\(maxRetries)). " +
"Please try again."
) {
retryPayment()
}
}
return
}
// Reset retry count
retryCount = 0
// 6. Show user-friendly error messages
let userMessage: String
switch error.errorCode {
case "SDK0500":
userMessage = "Network error. Please check your connection and try again."
case "SDK1114":
userMessage = "3D Secure authentication failed. Please try again."
case "SDK1115":
userMessage = "Payment authorisation failed. Please check your details and try again."
case "SDK1116":
// Card payment failed - check message
if error.errorMessage.localizedCaseInsensitiveContains("declined") {
userMessage = "Your card was declined. Please try a different card or contact your bank."
} else if error.errorMessage.localizedCaseInsensitiveContains("insufficient funds") {
userMessage = "Insufficient funds. Please use a different payment method."
} else if error.errorMessage.localizedCaseInsensitiveContains("expired") {
userMessage = "This card has expired. Please use a different card."
} else if error.errorMessage.localizedCaseInsensitiveContains("cvv") {
userMessage = "Invalid security code. Please check and try again."
} else {
userMessage = "Card payment failed. Please check your details and try again."
}
case "SDK1117":
userMessage = "PayPal payment failed. Please try again or use a different method."
case "SDK1119":
userMessage = "Apple Pay payment failed. Please try again or use a different method."
case "SDK1122":
userMessage = "Paze is unavailable. Please choose another payment method."
case "SDK1123":
userMessage = "Paze payment failed. Please try again or use a different method."
case "SDK1124":
userMessage = "Paze is only available for e-commerce checkout."
case "SDK1209", "SDK1210", "SDK1211", "SDK1212":
userMessage = "Invalid shopper identity: \(error.errorMessage)"
case "SDK1213":
userMessage = "Paze is only available for USD transactions."
case "SDK0118":
userMessage = "Paze client ID is missing from the session."
case "SDK1202A":
userMessage = "Paze merchant category code is missing from the session."
default:
if error.errorMessage.localizedCaseInsensitiveContains("session") ||
error.errorMessage.localizedCaseInsensitiveContains("expired") {
userMessage = "Session expired. Please reload checkout."
} else if error.errorCode.hasPrefix("SDK01") || error.errorCode.hasPrefix("SDK02") {
userMessage = "Configuration error. Please contact support."
} else if paymentMethod == .paze && error.errorMessage.localizedCaseInsensitiveContains("declined") {
userMessage = "Payment declined. Please try a different payment method."
} else {
userMessage = error.errorMessage
}
}
// 7. Show error to user
Task { @MainActor in
showNotification(
type: .error,
title: "Payment failed",
message: userMessage,
duration: 8.0
)
// 8. Offer recovery options
let shouldOfferAlternatives =
error.errorCode == "SDK1116" ||
error.errorCode == "SDK1117" ||
error.errorCode == "SDK1119" ||
error.errorCode == "SDK1122" ||
error.errorCode == "SDK0118" ||
error.errorCode == "SDK1123" ||
error.errorCode == "SDK1209" || error.errorCode == "SDK1210" ||
error.errorCode == "SDK1211" || error.errorCode == "SDK1212" ||
error.errorCode == "SDK1213" ||
error.errorCode == "SDK1202A" ||
error.errorMessage.localizedCaseInsensitiveContains("declined") ||
error.errorMessage.localizedCaseInsensitiveContains("insufficient funds")
if shouldOfferAlternatives {
showAlternativePaymentMethods()
}
let shouldShowRetry =
error.errorCode == "SDK0500" ||
error.errorMessage.localizedCaseInsensitiveContains("timeout")
if shouldShowRetry {
showRetryButton()
}
if error.errorMessage.localizedCaseInsensitiveContains("session") ||
error.errorMessage.localizedCaseInsensitiveContains("expired") {
showRefreshButton()
}
// 9. Re-enable UI
enablePaymentForm()
enableSubmitButton()
}
}
)