Skip to content

Testing

Learn how to test your Paze integration effectively before going live.

Overview

Thorough testing is essential to ensure your Paze integration works correctly across presentment, checkout, decryption, and error scenarios on iOS. This guide covers test environments, configuration, test scenarios, and debugging techniques.

Always test your integration in the UAT environment before deploying to production. Use test credentials and Paze UAT wallets to avoid processing real payments during development.

Test environments

Development environment

Initialise the SDK with test environment settings, USD transaction data, and a test shopper callback:

let sessionData = SessionData(
    sessionId: session.sessionId,
    hmacKey: session.hmacKey,
    encryptionKey: session.encryptionKey,
    allowedFundingTypes: AllowedFundingType(
        wallets: Wallets(
            paze: Paze(
                clientId: session.pazeClientId,
                profileId: session.pazeProfileId,
                merchantCategoryCode: session.merchantCategoryCode
            )
        )
    )
)

let checkoutConfig = CheckoutConfig(
    environment: .test,
    session: sessionData,
    transactionData: TransactionData(
        amount: 10.00,
        currency: "USD",
        entryType: .ecom,
        intent: TransactionIntentData(card: .authorisation),
        merchantTransactionId: "test-\(UUID().uuidString)",
        merchantTransactionDate: { Date() }
    ),
    merchantShopperId: "test-shopper-1",
    ownerId: "MERCHANT-GROUP-1",
    clientName: "Test Merchant",
    siteName: "Test Store",
    kountDisabled: true,
    onGetShopper: { async in
        TransactionShopper(
            id: "test-shopper-1",
            email: "test@example.com",
            firstName: "Test",
            lastName: "Shopper"
        )
    }
)

let pxpCheckout = try PxpCheckout.initialize(config: checkoutConfig)

Environment options

The SDK maps portal environments to these settings:

EnvironmentSDK settingPurpose
UAT.testDevelopment and UAT testing
Production.liveLive payments

Never use production Paze credentials in development. UAT and live environments use separate client IDs and API endpoints.

Paze UAT wallets

To test checkout in UAT:

  1. Use UAT credentials end-to-end: Portal Paze client ID on your site, environment: .test in the SDK, and sessions created against the UAT API (api-services.dev.pxp.io).
  2. Register test shoppers with Paze: Use email and phone combinations that Paze has provisioned for your UAT merchant account. Contact your Paze integration contact if you don't have UAT shopper credentials.
  3. Register your URL scheme: Confirm pxpcheckout (or your custom PXP_CALLBACK_SCHEME) is in Info.plist. The default return URL is pxpcheckout://paze.
  4. Test on a physical device: ASWebAuthenticationSession behaviour is most reliable on device. Use the Simulator for presentment and validation testing.

The iOS component uses static presentment. The button appears after SDK validation without calling canCheckout(). This simplifies testing checkout and payment flows before you have UAT-eligible wallet identities.

Test configuration

Basic test setup

Create a Paze button with logging callbacks on each stage of the payment flow:

let config = PazeButtonComponentConfig()
config.emailAddress = "test.shopper@example.com"
config.phoneNumber = "15551234567"
config.style = PazeButtonStyleConfig(color: .pazeBlue, shape: .pill, label: .checkoutWith)

config.onInit = { print("TEST: Paze initialised") }
config.onPresentmentResolved = { isVisible in print("TEST: Button visible:", isVisible) }
config.onPazeButtonClicked = { async in print("TEST: Button clicked") }

config.onCheckoutComplete = { result async in
    print("TEST: Checkout complete:", result)
    return true
}

config.onComplete = { result async in
    print("TEST: Complete payload ID:", result.completeDecodedResponse?.payloadId ?? "")
}

config.onPostDecryption = { payload async in
    print("TEST: Card network:", payload.fundingData.cardNetwork)
}

config.onPostAuthorisation = { data async in
    print("TEST: Authorisation result:", data.systemTransactionId)
}

config.onError = { error in
    print("TEST: Error:", error.errorCode, error.errorMessage)
}

let pazeButton = try pxpCheckout.create(.pazeButton, componentConfig: config)

Display the component in your test view:

pazeButton.buildContent()

Test scenarios

Presentment testing

After presentment validation succeeds, onInit fires, the button renders, and onPresentmentResolved(true) is called. If validation fails, the button stays hidden and onPresentmentResolved(false) is called followed by onError.

config.onPresentmentResolved = { isVisible in
    print("Presentment resolved:", isVisible)
}

Test presentment failure by initialising with a non-USD currency:

let invalidTransactionData = TransactionData(
    amount: 10.00,
    currency: "GBP",
    entryType: .ecom,
    intent: TransactionIntentData(card: .authorisation),
    merchantTransactionId: "test-gbp",
    merchantTransactionDate: { Date() }
)
// Expected: SDK1213 via onError, onPresentmentResolved(false)

Checkout flow testing

Approve checkout

Return true from onCheckoutComplete to exercise the full happy path:

config.onCheckoutComplete = { result async in
    print("Checkout data:", result.checkoutDecodedResponse?.sessionId ?? "")
    print("Card brand:", result.checkoutDecodedResponse?.maskedCard?.paymentCardBrand ?? "")
    return true
}

Reject checkout

Return false from onCheckoutComplete to confirm the SDK treats checkout as incomplete:

config.onCheckoutComplete = { _ async in false }

config.onCheckoutIncomplete = { result async in
    print("Expected incomplete result:", result.reason ?? "")
    // Merchant rejection reason from localised resources
}

User cancellation

Dismiss the ASWebAuthenticationSession without completing checkout. Expect onCheckoutIncomplete with reason: "User cancelled.".

Decryption testing

SDK-managed decryption

Return true from onPreDecryption (or omit the callback) to test the default SDK decryption path:

config.onPreDecryption = { async in true }

config.onPostDecryption = { payload async in
    print("Network token present:", !payload.fundingData.networkToken.isEmpty)
    print("Card network:", payload.fundingData.cardNetwork)
}

Manual decryption

Return false from onPreDecryption and handle securedPayload in onComplete. Your test backend should call the PXP decrypt-token API. See Backend decryption.

config.onPreDecryption = { async in false }

config.onComplete = { result async in
    let securedPayload = result.completeDecodedResponse?.securedPayload
    print("Secured payload received:", securedPayload != nil)
    await sendToTestBackend(securedPayload: securedPayload)
}

Error scenario testing

Use these patterns to trigger common validation failures:

// Invalid currency (fails at presentment)
// Set transactionData.currency to "GBP" → SDK1213

// Invalid phone format
config.phoneNumber = "+15551234567"
// Expected: SDK1212 at presentment or checkout

// Missing merchant category code in session
// Omit merchantCategoryCode from session → SDK1202A at complete (via onError)

Debugging tools

Callback logging

Log each callback during manual testing:

config.onInit = { print("[Paze] onInit") }
config.onPresentmentResolved = { v in print("[Paze] onPresentmentResolved:", v) }
config.onPazeButtonClicked = { async in print("[Paze] onPazeButtonClicked") }
config.onCheckoutComplete = { r async in print("[Paze] onCheckoutComplete:", r); return true }
config.onCheckoutIncomplete = { r async in print("[Paze] onCheckoutIncomplete:", r.reason ?? "") }
config.onComplete = { r async in print("[Paze] onComplete") }
config.onPreDecryption = { async in print("[Paze] onPreDecryption"); return true }
config.onPostDecryption = { r async in print("[Paze] onPostDecryption") }
config.onPreAuthorisation = { async in print("[Paze] onPreAuthorisation"); return .proceed }
config.onPostAuthorisation = { r async in print("[Paze] onPostAuthorisation:", r.systemTransactionId) }
config.onSubmitError = { e async in print("[Paze] onSubmitError:", e) }
config.onError = { e in print("[Paze] onError:", e.errorCode, e.errorMessage) }

Analytics event tracking

Log analytics events from CheckoutConfig to verify the payment funnel. For the full event list, see Analytics:

analyticsEvent: { event in
    print("Analytics:", event.eventName, event)
}

Key events to verify during testing:

EventWhen it fires
PazeButtonRenderedThe Paze button became visible.
PazeButtonClickedCheckout-time validation passes after the shopper taps the button, before the Paze web session opens.
PazePopupLaunchedThe Paze web checkout session opened.
PazeCheckoutCompletedCheckout returned COMPLETE and the merchant approved.
PazeCheckoutAbandonedCheckout was incomplete, the session was dismissed, or the merchant rejected checkout.
PazeDecryptionInitiatedSDK decryption started.
PazeDecryptionCompletedSDK decryption succeeded.
PazeTransactionInitiatedTransaction submission to PXP started.
PostAuthorisationonPostAuthorisation is configured and the SDK receives a MerchantSubmitResult, immediately before your callback runs.

Button visibility isn't an analytics event. Track it with onPresentmentResolved.

Environment diagnostics

Run these checks when the button doesn't appear or checkout fails to return to your app:

func diagnosePaze(checkoutConfig: CheckoutConfig) {
    print("=== Paze Diagnostics ===")
    print("Environment:", checkoutConfig.environment == .test ? "test" : "live")
    print("Currency:", checkoutConfig.transactionData.currency)
    print("Amount:", checkoutConfig.transactionData.amount)

    let paze = checkoutConfig.session.allowedFundingTypes?.wallets?.paze
    print("Client ID:", paze?.clientId?.isEmpty == false ? "Present" : "Missing")
    print("MCC:", paze?.merchantCategoryCode?.isEmpty == false ? "Present" : "Missing")

    if let urlTypes = Bundle.main.object(forInfoDictionaryKey: "CFBundleURLTypes") as? [[String: Any]],
       let schemes = urlTypes.first?["CFBundleURLSchemes"] as? [String] {
        print("URL schemes:", schemes)
    } else {
        print("URL schemes: NOT REGISTERED")
    }
}

Test checklist

Before going live, verify each scenario.

Configuration

Confirm your session and SDK setup:

  • Session includes allowedFundingTypes.wallets.paze.clientId.
  • Session includes merchantCategoryCode.
  • Currency is USD.
  • transactionData.entryType is .ecom (required for authorisation, not validated at presentment).
  • Amount is greater than 0.
  • onGetShopper returns valid shopper data.
  • URL callback scheme is registered in Info.plist.

Presentment

Verify button visibility and presentment callbacks:

  • Button renders after successful validation.
  • onInit and onPresentmentResolved(true) fire when the button is visible.
  • Validation failures call onPresentmentResolved(false) and onError.

Checkout

Exercise the web checkout flow:

  • Button tap opens the Paze web checkout session.
  • Successful checkout triggers onCheckoutComplete.
  • Returning false from onCheckoutComplete triggers onCheckoutIncomplete.
  • Dismissing the web session triggers onCheckoutIncomplete with user cancellation.
  • App returns via pxpcheckout://paze (or your configured {scheme}://paze callback).

Decryption and authorisation

Verify decryption and submission callbacks:

  • SDK-managed decryption returns token data in onPostDecryption.
  • Manual decryption receives securedPayload in onComplete.
  • onPreAuthorisation returning .cancel aborts authorisation.
  • onPostAuthorisation fires when the SDK receives a MerchantSubmitResult.
  • Submission failures fire onSubmitError (not onError).

Error handling

Confirm errors surface through the right callbacks:

  • Invalid configuration surfaces errors via onError.
  • Network errors are handled gracefully.
  • Session expiry is handled with user guidance.

Pre-launch checklist

Technical setup

Confirm URL scheme, environment, and device coverage:

  • URL callback scheme is registered and unique to your app.
  • Paze client ID is configured in Unity Portal for the correct site.
  • environment: .live is set with production Paze credentials.
  • Transaction currency is set to USD.
  • You've tested on physical iOS devices (recommended for the full checkout flow).
  • Comprehensive error handling covers all payment scenarios.

Button and branding

Verify the Paze button meets branding and layout requirements:

  • The integration uses PazeButtonStyleConfig with approved style values.
  • The Paze button has equal prominence with other payment methods.
  • The button renders correctly across supported iPhone and iPad layouts.

Security

Confirm credentials and data handling meet your security requirements:

  • No sensitive payment data is stored in app storage.
  • Session credentials are fetched from the backend at runtime.
  • All successful transactions are verified on the backend.
  • HMAC credentials are stored securely on the server only.
  • The privacy policy covers payment and identity data usage.

User experience

Check error handling and fallback behaviour in the app:

  • Clear error messages appear when Paze is unavailable.
  • The app falls back gracefully to other payment methods.
  • Loading indicators appear during checkout and processing.
  • Expired sessions are handled with user guidance.

Go-live testing

Complete end-to-end tests in UAT before switching to live credentials:

  • End-to-end payment testing completed in the UAT environment.
  • Checkout approval and rejection flows tested.
  • SDK-managed and manual decryption paths tested.
  • Transaction failure scenarios tested.