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 Android. This guide covers test environments, configuration, test scenarios, and debugging techniques.

Always test your integration in the sandbox 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:

val sessionData = SessionConfig(
    sessionId = session.sessionId,
    hmacKey = session.hmacKey,
    data = session.data,
    encryptionKey = session.encryptionKey,
    locale = "en-US",
    allowedFundingTypes = AllowedFundingTypes(
        wallets = WalletsConfig(
            paze = PazeWalletConfig(
                clientId = session.pazeClientId,
                profileId = session.pazeProfileId,
                merchantCategoryCode = session.merchantCategoryCode,
            ),
        ),
    ),
)

val sdkConfig = PxpSdkConfig(
    environment = Environment.TEST,
    session = sessionData,
    transactionData = TransactionData(
        amount = 10.00,
        currency = "USD",
        merchant = "MERCHANT-1",
        intent = TransactionIntentData(card = CardIntentType.Authorisation),
        merchantTransactionId = "test-${UUID.randomUUID()}",
        merchantTransactionDate = { Instant.now().toString() },
        entryType = EntryType.Ecom,
    ),
    clientName = "Test Merchant",
    siteName = "Test Store",
    ownerType = "MerchantGroup",
    ownerId = "MERCHANT-1",
    kountDisabled = true,
    onGetShopper = {
        Shopper(
            id = "test-shopper-1",
            email = "test@example.com",
            firstName = "Test",
            lastName = "Shopper",
        )
    },
)

val checkout = PxpCheckout.builder()
    .withConfig(sdkConfig)
    .withContext(context)
    .build()

Environment options

Use Environment.TEST for UAT and Environment.LIVE for production:

EnvironmentSDK settingPurpose
TestEnvironment.TESTDevelopment and UAT testing
ProductionEnvironment.LIVELive payments

Never use production Paze credentials in development. Test and production 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 do not have UAT shopper credentials.
  3. Install Chrome: Custom Tab checkout requires a Custom Tabs–compatible browser. Chrome is recommended for UAT testing.
  4. Test on a physical device: Redirect handling and Custom Tab behaviour are most reliable on device.

The button appears after presentment validation succeeds when you render Content(). Optional emailAddress and phoneNumber are validated when provided and forwarded to create-session when set.

Test configuration

Basic test setup

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

val config = PazeButtonComponentConfig().apply {
    emailAddress = "test.shopper@example.com"
    phoneNumber = "15551234567"
    style = PazeButtonStyleConfig(
        color = PazeButtonColor.PAZE_BLUE,
        shape = PazeButtonShape.PILL,
        label = PazeButtonLabel.CHECKOUT_WITH,
    )

    onInit = { Log.d("TEST", "Paze initialised") }
    onPresentmentResolved = { customTabAvailable -> Log.d("TEST", "Custom Tab available: $customTabAvailable") }
    onPazeButtonClicked = { Log.d("TEST", "Button clicked") }

    onCheckoutComplete = { result ->
        Log.d("TEST", "Checkout complete: $result")
        true
    }

    onComplete = { result ->
        Log.d("TEST", "Complete payload ID: ${result.completeDecodedResponse?.payloadId}")
    }

    onPostDecryption = { payload ->
        Log.d("TEST", "Card network: ${payload.fundingData.cardNetwork}")
    }

    onPostAuthorisation = { result ->
        Log.d("TEST", "Authorisation: ${result.systemTransactionId}")
    }

    onError = { error ->
        Log.e("TEST", "Error: ${error.errorCode} ${error.message}")
    }
}

val pazeButton = checkout.createComponent<PazeButtonComponent>(ComponentType.PAZE_BUTTON, config)

Render the component in your test screen. Presentment runs when Content() is composed:

pazeButton.Content(modifier = Modifier.fillMaxWidth())

Ensure your host Activity forwards checkout redirects to the component:

import com.pxp.checkout.components.paze.payment.deliverPazeCheckoutResult

override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    setIntent(intent)
    intent.deliverPazeCheckoutResult(pazeButton)
}

Test scenarios

Presentment testing

After presentment validation succeeds, onInit fires, the button is shown, and onPresentmentResolved reports Custom Tab availability. Presentment runs when you render Content().

Log presentment resolution during manual testing:

config.onPresentmentResolved = { customTabAvailable ->
    Log.d("TEST", "Presentment resolved: customTabAvailable=$customTabAvailable")
}

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

// transactionData.currency = "GBP"
// Expected: SDK1213 via onError; button hidden

Test invalid identity:

config.emailAddress = "not-an-email"
// Expected: SDK1210 via onError at presentment

Checkout flow testing

Approve checkout

Return true from onCheckoutComplete to exercise the full happy path:

config.onCheckoutComplete = { result ->
    Log.d("TEST", "Session: ${result.checkoutDecodedResponse?.sessionId}")
    Log.d("TEST", "Card brand: ${result.checkoutDecodedResponse?.maskedCard?.paymentCardBrand}")
    true
}

Reject checkout

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

config.onCheckoutComplete = { _ -> false }

config.onCheckoutIncomplete = { result ->
    Log.d("TEST", "Expected incomplete: ${result.reason}")
}

Block before Custom Tab

Disable the button in your UI until prerequisites are met. onPazeButtonClicked cannot cancel checkout — use it only for logging or other non-blocking side effects.

User cancellation

Close the Custom Tab without completing checkout. Expect onCheckoutIncomplete with a cancellation reason.

Closing the Custom Tab without a redirect (for example, using the back gesture) also triggers onCheckoutIncomplete when the host Activity resumes.

Decryption testing

SDK-managed decryption

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

config.onPreDecryption = { true }

config.onPostDecryption = { payload ->
    Log.d("TEST", "Network token present: ${payload.fundingData.networkToken.isNotEmpty()}")
    Log.d("TEST", "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. The SDK skips onPostDecryption, onPreAuthorisation, onPostAuthorisation, and onSubmitError on this path. See Backend decryption.

Error scenario testing

Exercise common validation and configuration failures:

// Invalid currency — fails at presentment
// transactionData.currency = "GBP" → SDK1213

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

// Missing clientId in session
// Omit paze.clientId from session → SDK1202 at presentment

// Missing merchantCategoryCode in session
// Omit merchantCategoryCode → SDK1202A at complete

Debugging tools

Callback logging

Log each callback during manual testing:

config.onInit = { Log.d("Paze", "onInit") }
config.onPresentmentResolved = { Log.d("Paze", "onPresentmentResolved: $it") }
config.onPazeButtonClicked = { Log.d("Paze", "onPazeButtonClicked") }
config.onCheckoutComplete = { Log.d("Paze", "onCheckoutComplete: $it"); true }
config.onCheckoutIncomplete = { Log.d("Paze", "onCheckoutIncomplete: ${it.reason}") }
config.onComplete = { Log.d("Paze", "onComplete") }
config.onPreDecryption = { Log.d("Paze", "onPreDecryption"); true }
config.onPostDecryption = { Log.d("Paze", "onPostDecryption") }
config.onPreAuthorisation = { Log.d("Paze", "onPreAuthorisation"); PazePreAuthorisationResult.Proceed }
config.onPostAuthorisation = { Log.d("Paze", "onPostAuthorisation: ${it.systemTransactionId}") }
config.onSubmitError = { Log.d("Paze", "onSubmitError: $it") }
config.onError = { Log.e("Paze", "onError: ${it.errorCode} ${it.message}") }

Analytics event tracking

Log analytics events from PxpSdkConfig. See Analytics for the full Paze* event set:

import com.pxp.checkout.analytics.PazeAnalyticsEvent
import com.pxp.checkout.analytics.ComponentLifecycleAnalyticsEvent

analyticsEvent = { event ->
    Log.d("Paze", "Analytics: ${event.eventName}")
    when (event) {
        is PazeAnalyticsEvent -> {
            Log.d("Paze", "  componentId=${event.componentId} merchantTransactionId=${event.merchantTransactionId}")
        }
        is ComponentLifecycleAnalyticsEvent -> {
            Log.d("Paze", "  lifecycle=${event.eventType} componentId=${event.componentId}")
        }
    }
}

Track these events and callbacks during a full payment test:

Event/callbackWhen it fires
ComponentLifecycleAnalyticsEvent
(MOUNT)
Presentment validation succeeded (Content() composed). Event name is ComponentLifecycleEvent.
PazeButtonRenderedPresentment succeeded.
onPresentmentResolvedCustom Tab availability resolved.
onPazeButtonClickedAfter checkout validation, before the Custom Tab opens.
PazeButtonClickedImmediately after onPazeButtonClicked, before the Custom Tab opens.
onCheckoutCompleteCheckout returned COMPLETE.
onCheckoutIncompleteCheckout incomplete, cancelled, tab closed without redirect, or merchant rejected.
PazeCheckoutCompletedMerchant approved checkout.
onPostDecryptionSDK decryption succeeded.
onPostAuthorisationSDK received MerchantSubmitResult (when the callback is registered). Verify the outcome on your backend.

Environment diagnostics

Run these checks when checkout fails to return to your app:

import com.pxp.checkout.components.paze.payment.CustomTabLauncher

fun diagnosePaze(sdkConfig: PxpSdkConfig, config: PazeButtonComponentConfig, context: Context) {
    Log.d("Paze", "=== Diagnostics ===")
    Log.d("Paze", "Environment: ${sdkConfig.environment}")
    Log.d("Paze", "Currency: ${sdkConfig.transactionData.currency}")
    Log.d("Paze", "Amount: ${sdkConfig.transactionData.amount}")
    Log.d("Paze", "Client ID: ${sdkConfig.session.allowedFundingTypes?.wallets?.paze?.clientId?.isNotBlank()}")
    Log.d("Paze", "MCC: ${sdkConfig.session.allowedFundingTypes?.wallets?.paze?.merchantCategoryCode ?: "not set"}")
    Log.d("Paze", "Email: ${config.emailAddress ?: "not set"}")
    Log.d("Paze", "Phone: ${config.phoneNumber ?: "not set"}")
    Log.d("Paze", "Custom Tab: ${CustomTabLauncher.canLaunchCustomTab(context)}")
}

Test checklist

Before going live, verify each scenario in UAT:

ScenarioExpected outcome
Portal Paze client ID savedSession includes allowedFundingTypes.wallets.paze.clientId
MCC in sessionmerchantCategoryCode present; missing value throws SDK1202A at complete.
USD transactionPresentment succeeds; non-USD throws SDK1213.
Static presentmentButton renders after validation when Content() is composed; email/phone optional.
Custom Tab checkoutPaze wallet opens; shopper can complete or cancel.
Redirect handlingonNewIntent + deliverPazeCheckoutResult processes pxpcheckout://callback or pxpcheckout://paze redirects.
Approve checkoutFull SDK-managed flow through onPostDecryption and onPostAuthorisation (when registered).
Reject checkoutonCheckoutIncomplete fires.
Pre-checkout blockButton disabled in UI until prerequisites are met (onPazeButtonClicked cannot cancel checkout).
SDK decryptiononPostDecryption receives token data.
Manual decryptionsecuredPayload forwarded to backend; your backend completes decryption and authorisation.
Authorisation failureonSubmitError with FailedSubmitResult.
Backend verificationOrder fulfilled only after server-side transaction check.
Live credentialsEnvironment.LIVE with production portal settings.