Learn how to diagnose and fix common issues with the card iOS SDK components.
The card iOS SDK throws specific exceptions for different error scenarios:
| Exception type | Error code | Description | Prevention |
|---|---|---|---|
| Network failures | SDK0500 | Network or transport failure before authorisation completes. | Check device connectivity and implement retry logic for transient failures. |
| Validation exceptions | SDK0501 | SDK-side validation failed on submit. | Validate all required fields before submission and implement field-level validation callbacks. |
| Authentication pre-initiation | SDK0502 | 3DS pre-initiation failed. | Verify provider ID and Portal 3DS configuration. Check onPreInitiateAuthentication implementation. |
| Authentication rejected | SDK0503 | Authentication rejected. Please try again or contact your bank. | Verify 3DS configuration in Portal and check authentication callbacks. |
| Challenge flow | SDK0505 | 3DS challenge flow failed. | Verify merchant configuration and challenge window settings in onPreAuthentication. |
| Authentication configuration | SDK0507 | Authentication configuration invalid. | Ensure both onPreInitiateAuthentication and onPreAuthentication are set together, or neither. |
| Token vault failures | SDK0304 | Token vault operation failed (retrieve, update, or delete). | Verify session data, ownerId, and merchantShopperId alignment. |
| Checkout initialisation | Various | PxpCheckout.initialize or create rejected configuration. | Validate CheckoutConfig, SessionData, and component type pairings. |
| Mismatched 3DS callbacks | Custom exception | Only one of the 3DS callback pair is set. | Set both onPreInitiateAuthentication and onPreAuthentication, or clear both for non-3DS flow. |
import PXPCheckoutSDK
var submitConfig = CardSubmitComponentConfig()
submitConfig.onSubmitError = { error in
// Log error for debugging
print("Card submit error: \(error.errorCode) — \(error.errorMessage)")
// Handle specific error types
switch error.errorCode {
case "SDK0500":
showUserMessage("Network connection issue. Please check your internet and try again.")
offerRetryOption()
case "SDK0501":
showUserMessage("Please check all required fields are completed correctly.")
highlightValidationErrors()
case "SDK0502":
showUserMessage("Payment system temporarily unavailable. Please try again in a few moments.")
logCriticalError("3DS pre-initiation failed", error: error)
case "SDK0503":
showUserMessage("Authentication was rejected. Please try again or contact your bank.")
logCriticalError("3DS authentication rejected", error: error)
case "SDK0505":
showUserMessage("Authentication failed. Please try again.")
trackEvent("3ds_challenge_failed")
case "SDK0507":
showUserMessage("Payment configuration error. Please contact support.")
logCriticalError("Authentication configuration invalid", error: error)
case let code where code.hasPrefix("SDK03"):
showUserMessage("Card tokenisation failed. Please check your card details.")
logTokenVaultError(error)
default:
showUserMessage("Payment failed. Please try again or contact support.")
logUnknownError(error)
}
}
submitConfig.onPostAuthorisation = { result in
switch result {
case let authorised as AuthorisedSubmitResult:
print("Payment successful: \(authorised.provider.message)")
navigateToSuccessScreen()
case let refused as RefusedSubmitResult:
print("Payment declined: \(refused.stateData?.message ?? "Unknown")")
handlePaymentDecline(refused)
case let failed as FailedSubmitResult:
print("Payment failed: HTTP \(failed.httpStatusCode ?? 0), \(failed.errorReason)")
handlePaymentFailure(failed)
default:
print("Unknown result type")
}
}
func showUserMessage(_ message: String) {
DispatchQueue.main.async {
// Show user-friendly error message in your UI
let alert = UIAlertController(title: "Payment Error", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
// Present alert to user
}
}
func logCriticalError(_ context: String, error: BaseSdkException) {
// Send to error monitoring service
print("Critical error: \(context) - \(error.errorMessage)")
}
func offerRetryOption() {
// Show retry button in UI
print("Offering retry option to user")
}The symptoms of this are:
- A blank area where the field or component should render.
createsucceeds but nothing visible in the view hierarchy.- Wrong field behaviour (e.g., card number field showing in the expiry slot).
// Step 1: Verify component creation
do {
let cardNumber = try pxpCheckout.create(
.cardNumber,
componentConfig: CardNumberComponentConfig(label: "Card number")
) as! CardNumberComponent
print("✅ Card number component created successfully")
print("Component type: \(type(of: cardNumber))")
} catch {
print("❌ Component creation failed: \(error)")
let nsError = error as NSError
print("Domain: \(nsError.domain), Code: \(nsError.code)")
print("User info: \(nsError.userInfo)")
}
// Step 2: Verify component is rendered
struct PaymentView: View {
var body: some View {
VStack {
// Make sure buildContent() is called
cardNumber.buildContent()
.onAppear {
print("Card number field appeared in view hierarchy")
}
}
}
}
// Step 3: Check component configuration
func validateComponentConfiguration() {
print("=== Component Configuration Validation ===")
// Verify correct component type and config pairing
let expectedPairings: [(ComponentType, String)] = [
(.cardNumber, "CardNumberComponentConfig"),
(.cardExpiryDate, "CardExpiryDateComponentConfig"),
(.cardCvc, "CardCvcComponentConfig"),
(.cardHolderName, "CardHolderNameComponentConfig")
]
for (type, configName) in expectedPairings {
print("\(type) → \(configName)")
}
}// Solution 1: Ensure buildContent() is called
struct CorrectImplementation: View {
let cardNumber: CardNumberComponent
let cardExpiry: CardExpiryDateComponent
let cardCvc: CardCvcComponent
var body: some View {
VStack(spacing: 16) {
cardNumber.buildContent()
cardExpiry.buildContent()
cardCvc.buildContent()
}
.padding()
}
}
// Solution 2: Use correct component type and config pairing
func createComponentsCorrectly() throws {
// Correct: .cardNumber with CardNumberComponentConfig
let cardNumber = try pxpCheckout.create(
.cardNumber,
componentConfig: CardNumberComponentConfig(label: "Card number")
) as! CardNumberComponent
// Incorrect: .cardNumber with CardExpiryDateComponentConfig
// This will fail or produce unexpected behaviour
}
// Solution 3: Verify all required components are linked
func linkComponentsToSubmit() {
var submitConfig = CardSubmitComponentConfig()
// Link all required components
submitConfig.cardNumberComponent = cardNumber
submitConfig.cardExpiryDateComponent = cardExpiry
submitConfig.cardCvcComponent = cardCvc
// Verify linkage
print("Card number linked: \(submitConfig.cardNumberComponent != nil)")
print("Card expiry linked: \(submitConfig.cardExpiryDateComponent != nil)")
print("CVC linked: \(submitConfig.cardCvcComponent != nil)")
}The symptoms of this are:
PxpCheckout.initializethrows before any UI appears.createthrows when building a field or component.- Error messages about invalid configuration or session data.
// Step 1: Detailed error logging
func initializeCheckoutWithDiagnostics() {
do {
let sessionData = SessionData(
sessionId: "your-session-id",
hmacKey: "your-hmac-key",
encryptionKey: "your-encryption-key"
)
let transactionData = TransactionData(
amount: Decimal(string: "99.99")!,
currency: "USD",
entryType: .ecom,
intent: TransactionIntentData(card: .authorisation),
merchantTransactionId: "order-\(UUID().uuidString)",
merchantTransactionDate: { Date() }
)
let checkoutConfig = CheckoutConfig(
environment: .test,
session: sessionData,
transactionData: transactionData,
merchantShopperId: "shopper-123",
ownerType: "MerchantGroup",
ownerId: "your-owner-id"
)
let pxpCheckout = try PxpCheckout.initialize(config: checkoutConfig)
print("✅ Checkout initialised successfully")
} catch {
print("❌ Checkout initialisation failed")
let nsError = error as NSError
print("Domain: \(nsError.domain)")
print("Code: \(nsError.code)")
print("User info: \(nsError.userInfo)")
print("Localized description: \(error.localizedDescription)")
}
}
// Step 2: Validate configuration before initialisation
func validateCheckoutConfiguration(_ config: CheckoutConfig) -> [String] {
var errors: [String] = []
// Validate session data
if config.session.sessionId.isEmpty {
errors.append("Session ID is empty")
}
if config.session.hmacKey.isEmpty {
errors.append("HMAC key is empty")
}
if config.session.encryptionKey.isEmpty {
errors.append("Encryption key is empty")
}
// Validate transaction data
if config.transactionData.amount <= 0 {
errors.append("Transaction amount must be greater than zero")
}
if config.transactionData.currency.isEmpty {
errors.append("Currency code is required")
}
// Validate merchant data
if config.merchantShopperId.isEmpty {
errors.append("Merchant shopper ID is required")
}
if config.ownerId.isEmpty {
errors.append("Owner ID is required")
}
return errors
}
// Step 3: Test with minimal configuration
func testMinimalConfiguration() {
let minimalConfig = CheckoutConfig(
environment: .test,
session: SessionData(
sessionId: "test-session",
hmacKey: "test-hmac-key",
encryptionKey: "test-encryption-key"
),
transactionData: TransactionData(
amount: Decimal(string: "1.00")!,
currency: "USD",
entryType: .ecom,
intent: TransactionIntentData(card: .authorisation),
merchantTransactionId: "test-\(UUID().uuidString)",
merchantTransactionDate: { Date() }
),
merchantShopperId: "test-shopper",
ownerType: "MerchantGroup",
ownerId: "test-owner"
)
do {
let pxpCheckout = try PxpCheckout.initialize(config: minimalConfig)
print("✅ Minimal configuration works")
} catch {
print("❌ Even minimal configuration fails: \(error)")
}
}// Solution 1: Refresh session data from backend
func refreshSessionData() async throws -> SessionData {
// Fetch fresh session data from your backend
let response = try await fetchSessionFromBackend()
return SessionData(
sessionId: response.sessionId,
hmacKey: response.hmacKey,
encryptionKey: response.encryptionKey
)
}
// Solution 2: Validate configuration before initialisation
func initializeWithValidation() async {
do {
// Get fresh session data
let sessionData = try await refreshSessionData()
let checkoutConfig = CheckoutConfig(
environment: .test,
session: sessionData,
transactionData: createTransactionData(),
merchantShopperId: "shopper-123",
ownerType: "MerchantGroup",
ownerId: "your-owner-id"
)
// Validate before initialising
let validationErrors = validateCheckoutConfiguration(checkoutConfig)
guard validationErrors.isEmpty else {
print("Configuration validation failed: \(validationErrors)")
return
}
let pxpCheckout = try PxpCheckout.initialize(config: checkoutConfig)
print("Checkout initialised successfully")
} catch {
print("Failed to initialise: \(error)")
handleInitialisationError(error)
}
}
// Solution 3: Implement proper error recovery
func handleInitialisationError(_ error: Error) {
if let nsError = error as NSError {
switch nsError.code {
case 401:
// Unauthorised - refresh session
Task {
try? await refreshSessionData()
// Retry initialisation
}
case 403:
// Forbidden - check bundle ID in Portal
showConfigurationError("App not authorised. Please check Unity Portal configuration.")
default:
showConfigurationError("Initialisation failed: \(error.localizedDescription)")
}
}
}The symptoms of this are:
- The customer taps pay but nothing happens.
- No logs from
onPostAuthorisation. - Submit appears to hang or do nothing.
// Step 1: Verify all required callbacks are implemented
func verifySubmitCallbacks(_ config: CardSubmitComponentConfig) {
print("=== Submit Callback Verification ===")
// Check required callbacks
print("onPreAuthorisation set: \(config.onPreAuthorisation != nil ? "✅" : "❌ REQUIRED")")
print("onPostAuthorisation set: \(config.onPostAuthorisation != nil ? "✅" : "⚠️ Recommended")")
print("onSubmitError set: \(config.onSubmitError != nil ? "✅" : "⚠️ Recommended")")
// Check component linkage
print("cardNumberComponent linked: \(config.cardNumberComponent != nil ? "✅" : "❌")")
print("cardExpiryDateComponent linked: \(config.cardExpiryDateComponent != nil ? "✅" : "❌")")
print("cardCvcComponent linked: \(config.cardCvcComponent != nil ? "✅" : "❌")")
}
// Step 2: Add comprehensive logging to all callbacks
var submitConfig = CardSubmitComponentConfig()
submitConfig.onStartSubmit = {
print("🔵 Submit started")
}
submitConfig.onPreTokenisation = {
print("🔵 Pre-tokenisation callback")
return true
}
submitConfig.onPostTokenisation = { result in
print("🔵 Post-tokenisation: \(result)")
}
submitConfig.onPreAuthorisation = { preAuth in
print("🔵 Pre-authorisation callback")
print("Gateway token: \(preAuth?.gatewayTokenId ?? "nil")")
// This is an async closure - you CAN use await here
return TransactionInitiationData()
}
submitConfig.onPostAuthorisation = { result in
print("🔵 Post-authorisation callback")
print("Result type: \(type(of: result))")
}
submitConfig.onSubmitError = { error in
print("🔴 Submit error: \(error.errorCode) - \(error.errorMessage)")
}
// Step 3: Verify field validation states
func checkFieldValidation() {
// Note: getValue() is internal and not accessible
// Validation state is managed internally by the SDK
print("=== Field Validation States ===")
print("Submit button will be enabled when all fields are valid")
print("Use onValidation callback to monitor validation state")
}// Solution 1: Implement required callbacks
var submitConfig = CardSubmitComponentConfig()
// REQUIRED: onPreAuthorisation must be implemented
submitConfig.onPreAuthorisation = { preAuth in
print("Pre-authorisation data: \(String(describing: preAuth))")
// This is an async closure - you CAN use await here
// Return transaction initiation data
return TransactionInitiationData(
psd2Data: PSD2Data(scaExemption: .lowValue)
)
}
// REQUIRED: onPostAuthorisation handles final result
submitConfig.onPostAuthorisation = { result in
switch result {
case is AuthorisedSubmitResult, is CapturedSubmitResult:
print("Payment successful")
navigateToSuccessScreen()
case let refused as RefusedSubmitResult:
print("Payment declined: \(refused.stateData?.message ?? "Unknown")")
showDeclineMessage(refused)
case let failed as FailedSubmitResult:
print("Payment failed: \(failed.errorReason)")
showFailureMessage(failed)
default:
print("Unknown result type")
}
}
// Solution 2: Verify component linkage
func createSubmitWithProperLinkage() throws -> CardSubmitComponent {
// Create field components first
let cardNumber = try pxpCheckout.create(
.cardNumber,
componentConfig: CardNumberComponentConfig(label: "Card number")
) as! CardNumberComponent
let cardExpiry = try pxpCheckout.create(
.cardExpiryDate,
componentConfig: CardExpiryDateComponentConfig(label: "Expiry")
) as! CardExpiryDateComponent
let cardCvc = try pxpCheckout.create(
.cardCvc,
componentConfig: CardCvcComponentConfig(label: "CVC")
) as! CardCvcComponent
// Link components to submit
var submitConfig = CardSubmitComponentConfig()
submitConfig.cardNumberComponent = cardNumber
submitConfig.cardExpiryDateComponent = cardExpiry
submitConfig.cardCvcComponent = cardCvc
// Implement callbacks
submitConfig.onPreAuthorisation = { _ in
// This is an async closure - you CAN use await here
return TransactionInitiationData()
}
submitConfig.onPostAuthorisation = { _ in }
// Create submit component
return try pxpCheckout.create(.cardSubmit, componentConfig: submitConfig) as! CardSubmitComponent
}
// Solution 3: Handle async operations correctly
submitConfig.onPreAuthorisation = { preAuth in
print("Pre-authorisation callback")
// This is an async closure - you CAN use await here for async operations
return TransactionInitiationData()
}
submitConfig.onPostAuthorisation = { result in
// Move heavy work off main thread
Task {
await processPaymentResult(result)
}
// Update UI on main thread
DispatchQueue.main.async {
updateUIForResult(result)
}
}The symptoms of this are:
- Submit button stays disabled when
disableUntilValidatedis used. onSubmitErrorfires withSDK0501after tapping pay.- Fields appear filled but validation fails.
// Step 1: Add validation callback logging
submitConfig.onValidation = { results in
print("=== Validation Results ===")
for (index, result) in results.enumerated() {
print("Field \(index):")
print(" Valid: \(result.valid ? "✅" : "❌")")
if let errors = result.errors, !errors.isEmpty {
print(" Errors:")
for (code, message) in errors {
print(" [\(code)] \(message)")
}
}
}
}
// Step 2: Check individual field validation
var cardNumberConfig = CardNumberComponentConfig(label: "Card number")
cardNumberConfig.onValidationPassed = { _ in
print("✅ Card number validation passed")
}
cardNumberConfig.onValidationFailed = { _ in
print("❌ Card number validation failed")
}
var cardExpiryConfig = CardExpiryDateComponentConfig(label: "Expiry")
cardExpiryConfig.onValidationPassed = { _ in
print("✅ Expiry validation passed")
}
cardExpiryConfig.onValidationFailed = { _ in
print("❌ Expiry validation failed")
}
// Step 3: Manually trigger validation
func testFieldValidation() {
print("=== Manual Validation Test ===")
// Note: getValue() is internal and not accessible
// Use onValidation callback to inspect validation results
print("Monitor validation via onValidation callback")
print("Enable validationOnChange for real-time validation")
}// Solution 1: Enable real-time validation
var cardNumberConfig = CardNumberComponentConfig(
label: "Card number",
validationOnChange: true, // Enable real-time validation
onValidationPassed: { _ in
print("Card number valid")
},
onValidationFailed: { _ in
print("Card number invalid")
}
)
var cardExpiryConfig = CardExpiryDateComponentConfig(
label: "Expiry",
validationOnChange: true
)
var cardCvcConfig = CardCvcComponentConfig(
label: "CVC",
validationOnChange: true
)
// Solution 2: Handle validation with disableUntilValidated
var submitConfig = CardSubmitComponentConfig()
submitConfig.disableUntilValidated = true
submitConfig.onValidation = { results in
let allValid = results.allSatisfy { $0.valid }
print("All fields valid: \(allValid ? "✅" : "❌")")
if !allValid {
// Show which fields are invalid
for (index, result) in results.enumerated() where !result.valid {
print("Field \(index) is invalid")
if let errors = result.errors {
for (code, message) in errors {
print(" Error [\(code)]: \(message)")
}
}
}
}
}
// Solution 3: Include billing address validation when using AVS
if submitConfig.avsRequest == true {
print("⚠️ AVS enabled - billing address fields must also be valid")
// Ensure billing address components are created and linked
let billing = try pxpCheckout.create(
.billingAddress,
componentConfig: BillingAddressComponentConfig()
) as! BillingAddressComponent
submitConfig.billingAddressComponents = BillingAddressComponents(
billingAddressComponent: billing
)
}The symptoms of this are:
- Challenge UI never appears.
- Flow aborts with
SDK0502toSDK0511errors. - Exception mentioning authentication configuration.
// Step 1: Verify 3DS configuration
func verify3DSConfiguration(_ config: CardSubmitComponentConfig) {
print("=== 3DS Configuration Verification ===")
let preInitiateSet = config.onPreInitiateAuthentication != nil
let preAuthSet = config.onPreAuthentication != nil
print("onPreInitiateAuthentication set: \(preInitiateSet ? "✅" : "❌")")
print("onPreAuthentication set: \(preAuthSet ? "✅" : "❌")")
if preInitiateSet != preAuthSet {
print("⚠️ WARNING: 3DS callbacks must be set together or not at all")
}
if preInitiateSet && preAuthSet {
print("✅ 3DS callbacks properly configured")
} else if !preInitiateSet && !preAuthSet {
print("ℹ️ Non-3DS flow (no authentication callbacks set)")
}
}
// Step 2: Add detailed 3DS logging
var submitConfig = CardSubmitComponentConfig()
submitConfig.onPreInitiateAuthentication = {
print("🔐 3DS Pre-initiation started")
let authData = PreInitiateIntegratedAuthenticationData(
providerId: "your_provider_id",
requestorAuthenticationIndicator: .paymentTransaction,
timeout: 120
)
print("Provider ID: \(authData.providerId)")
print("Timeout: \(authData.timeout)")
// This is an async closure - you CAN use await here
return authData
}
submitConfig.onPostInitiateAuthentication = { result in
print("🔐 3DS Pre-initiation completed")
if let failed = result as? FailedAuthenticationResult {
print("❌ Pre-initiation failed: \(failed.errorReason)")
print("Error code: \(failed.errorCode)")
} else {
print("✅ Pre-initiation successful")
}
}
submitConfig.onPreAuthentication = {
print("🔐 3DS Authentication started")
let authData = InitiateIntegratedAuthenticationData(
merchantCountryNumericCode: "840",
merchantLegalName: "Your Company Ltd",
challengeWindowSize: .size5,
requestorChallengeIndicator: .noPreference,
timeout: 300
)
print("Merchant country: \(authData.merchantCountryNumericCode)")
print("Challenge window: \(authData.challengeWindowSize)")
// This is an async closure - you CAN use await here
return authData
}
submitConfig.onPostAuthentication = { result in
print("🔐 3DS Authentication completed")
if result is FailedAuthenticationResult {
print("❌ Authentication failed")
} else {
print("✅ Authentication successful")
}
}// Solution 1: Verify Unity Portal 3DS configuration
// 1. Log in to Unity Portal
// 2. Go to Merchant setup > Merchant groups
// 3. Select your merchant group
// 4. Click Services tab
// 5. Edit Card service
// 6. Click Configure modules
// 7. Enable "ThreeD secure service"
// Solution 2: Implement complete 3DS callback set
var submitConfig = CardSubmitComponentConfig()
// Both 3DS callbacks must be set together
submitConfig.onPreInitiateAuthentication = {
// This is an async closure - you CAN use await here
return PreInitiateIntegratedAuthenticationData(
providerId: "your_3ds_provider_id",
requestorAuthenticationIndicator: .paymentTransaction,
timeout: 120
)
}
submitConfig.onPreAuthentication = {
// This is an async closure - you CAN use await here
return InitiateIntegratedAuthenticationData(
merchantCountryNumericCode: "840",
merchantLegalName: "Your Company Ltd",
challengeWindowSize: .size5,
requestorChallengeIndicator: .noPreference,
timeout: 300
)
}
// Optional but recommended
submitConfig.onPostInitiateAuthentication = { result in
if let failed = result as? FailedAuthenticationResult {
print("3DS pre-initiation failed: \(failed.errorReason)")
}
}
submitConfig.onPostAuthentication = { result in
if result is FailedAuthenticationResult {
print("3DS authentication failed")
}
}
// Required for all flows
submitConfig.onPreAuthorisation = { _ in
// This is an async closure - you CAN use await here
return TransactionInitiationData()
}
submitConfig.onPostAuthorisation = { result in
// Handle final result
}
// Solution 3: Implement conditional 3DS
let transactionAmount: Decimal = 150.00
let requiresStrong3DS = transactionAmount > 100.00
submitConfig.onPreInitiateAuthentication = {
// Return nil to skip 3DS for low amounts
guard requiresStrong3DS else {
print("Skipping 3DS for low-value transaction")
return nil
}
// This is an async closure - you CAN use await here
return PreInitiateIntegratedAuthenticationData(
providerId: "your_provider_id",
requestorAuthenticationIndicator: .paymentTransaction,
timeout: 120
)
}
// If conditional, still set both callbacks
submitConfig.onPreAuthentication = {
// This is an async closure - you CAN use await here
return InitiateIntegratedAuthenticationData(
merchantCountryNumericCode: "840",
merchantLegalName: "Your Company Ltd",
challengeWindowSize: .size5,
requestorChallengeIndicator: .noPreference,
timeout: 300
)
}To get more detailed information about errors, use this debugging setup:
// Enable comprehensive logging
class CardPaymentDebugger {
static func enableDebugMode(_ config: inout CardSubmitComponentConfig) {
print("🔧 Card Payment Debug Mode Enabled")
logEnvironmentInfo()
// Wrap existing callbacks with logging
let originalOnSubmitError = config.onSubmitError
config.onSubmitError = { error in
logError(error)
originalOnSubmitError?(error)
}
let originalOnPostAuthorisation = config.onPostAuthorisation
config.onPostAuthorisation = { result in
logAuthorisationResult(result)
originalOnPostAuthorisation?(result)
}
}
static func logEnvironmentInfo() {
print("=== Card Payment Environment ===")
print("Device Model: \(UIDevice.current.model)")
print("iOS Version: \(UIDevice.current.systemVersion)")
print("Bundle ID: \(Bundle.main.bundleIdentifier ?? "Unknown")")
print("App Version: \(Bundle.main.infoDictionary?["CFBundleShortVersionString"] ?? "Unknown")")
print("Build: \(Bundle.main.infoDictionary?["CFBundleVersion"] ?? "Unknown")")
}
static func logError(_ error: BaseSdkException) {
print("=== Payment Error Debug ===")
print("Error Code: \(error.errorCode)")
print("Error Message: \(error.errorMessage)")
if let userInfo = (error as NSError).userInfo as? [String: Any] {
print("Additional info:")
for (key, value) in userInfo {
print(" \(key): \(value)")
}
}
}
static func logAuthorisationResult(_ result: SubmitResult) {
print("=== Authorisation Result Debug ===")
print("Result type: \(type(of: result))")
switch result {
case let authorised as AuthorisedSubmitResult:
print("State: Authorised")
print("Transaction ID: \(authorised.fundingData.transactionId)")
print("Provider message: \(authorised.provider.message)")
case let captured as CapturedSubmitResult:
print("State: Captured")
print("Transaction ID: \(captured.fundingData.transactionId)")
print("Provider message: \(captured.provider.message)")
case let refused as RefusedSubmitResult:
print("State: Refused")
print("Decline reason: \(refused.stateData?.message ?? "Unknown")")
print("Decline code: \(refused.stateData?.code ?? "Unknown")")
if let advice = refused.provider.merchantAdvice {
print("Merchant advice: \(advice.message)")
}
case let failed as FailedSubmitResult:
print("State: Failed")
print("HTTP Status: \(failed.httpStatusCode ?? 0)")
print("Error code: \(failed.errorCode ?? "Unknown")")
print("Error reason: \(failed.errorReason)")
print("Correlation ID: \(failed.correlationId ?? "None")")
default:
print("Unknown result type")
}
}
static func logFieldValues(
cardNumber: CardNumberComponent?,
expiry: CardExpiryDateComponent?,
cvc: CardCvcComponent?
) {
print("=== Field Values Debug ===")
// Note: getValue() is internal and not accessible
print("Use onValidation callback to monitor field states")
print("Enable validationOnChange for real-time monitoring")
}
}
// Usage in your implementation
var submitConfig = CardSubmitComponentConfig()
CardPaymentDebugger.enableDebugMode(&submitConfig)
// Before submission
CardPaymentDebugger.logFieldValues(
cardNumber: cardNumber,
expiry: cardExpiry,
cvc: cardCvc
)// Pre-flight checks before initialising payment
func performPaymentPreflightChecks() async -> Bool {
let checks: [(String, () async -> Bool, String)] = [
("Session validity", {
return self.validateSessionData()
}, "Session data is invalid or expired"),
("Network connectivity", {
return await self.checkNetworkConnectivity()
}, "Network connection required"),
("Component configuration", {
return self.validateComponentConfiguration()
}, "Component configuration is invalid"),
("3DS configuration", {
return self.validate3DSConfiguration()
}, "3DS configuration is invalid")
]
var allPassed = true
print("=== Payment Pre-flight Checks ===")
for (name, test, failureMessage) in checks {
let passed = await test()
print("\(passed ? "✅" : "❌") \(name): \(passed ? "PASSED" : failureMessage)")
if !passed {
allPassed = false
}
}
return allPassed
}
private func validateSessionData() -> Bool {
guard !checkoutConfig.session.sessionId.isEmpty,
!checkoutConfig.session.hmacKey.isEmpty,
!checkoutConfig.session.encryptionKey.isEmpty else {
return false
}
return true
}
private func checkNetworkConnectivity() async -> Bool {
guard let url = URL(string: "https://api.pxp.io/health") else { return false }
do {
let (_, response) = try await URLSession.shared.data(from: url)
return (response as? HTTPURLResponse)?.statusCode == 200
} catch {
return false
}
}
private func validateComponentConfiguration() -> Bool {
// Check all required components are created and linked
return submitConfig.cardNumberComponent != nil &&
submitConfig.cardExpiryDateComponent != nil &&
submitConfig.cardCvcComponent != nil
}
private func validate3DSConfiguration() -> Bool {
let preInitSet = submitConfig.onPreInitiateAuthentication != nil
let preAuthSet = submitConfig.onPreAuthentication != nil
// Either both set or neither set
return preInitSet == preAuthSet
}
// Use before payment
func initiatePayment() async {
let preflightPassed = await performPaymentPreflightChecks()
if preflightPassed {
await submitPayment()
} else {
showPreflightFailureMessage()
}
}// Error monitoring setup
class PaymentErrorMonitor {
static func captureError(_ error: Error, context: [String: Any] = [:]) {
var errorData: [String: Any] = [
"error_message": error.localizedDescription,
"error_type": String(describing: type(of: error)),
"device_model": UIDevice.current.model,
"ios_version": UIDevice.current.systemVersion,
"bundle_id": Bundle.main.bundleIdentifier ?? "Unknown",
"timestamp": ISO8601DateFormatter().string(from: Date())
]
// Add context
errorData.merge(context) { _, new in new }
// Add SDK-specific data
if let sdkError = error as? BaseSdkException {
errorData["error_code"] = sdkError.errorCode
errorData["sdk_error"] = true
}
// Send to your error tracking service
print("📊 Error captured: \(errorData)")
// Example: Crashlytics.record(error: error, userInfo: errorData)
}
static func trackPaymentEvent(_ eventType: String, data: [String: Any] = [:]) {
var eventData = data
eventData["event_type"] = eventType
eventData["timestamp"] = ISO8601DateFormatter().string(from: Date())
eventData["component"] = "CardPayment"
print("📈 Event tracked: \(eventData)")
// Send to your analytics service
}
}
// Integration with card submit
var submitConfig = CardSubmitComponentConfig()
submitConfig.onSubmitError = { error in
PaymentErrorMonitor.captureError(error, context: [
"flow_step": "card_submit",
"transaction_amount": transactionData.amount,
"currency": transactionData.currency
])
}
submitConfig.onPostAuthorisation = { result in
let eventType: String
switch result {
case is AuthorisedSubmitResult, is CapturedSubmitResult:
eventType = "payment_success"
case is RefusedSubmitResult:
eventType = "payment_declined"
case is FailedSubmitResult:
eventType = "payment_failed"
default:
eventType = "payment_unknown"
}
PaymentErrorMonitor.trackPaymentEvent(eventType, data: [
"result_type": String(describing: type(of: result))
])
}