Skip to content

Testing

Test your Drop-in integration in sandbox before going live.

Overview

Test your Drop-in integration in the sandbox environment before accepting real payments. The sandbox environment allows you to test all payment methods without processing actual transactions.

Use the test environment

Set the environment to Test when initialising Drop-in:

CheckoutDropInConfig(
    environment = Environment.TEST,  // Use sandbox
    // ... rest of config
)

Key points about sandbox testing:

  • No real money is charged.
  • Use test card numbers and sandbox accounts.
  • All payment methods work the same as production.
  • Backend verification still required using test API credentials.

Test card payments

Test card numbers

Use these test cards in sandbox to simulate different scenarios:

Successful payments

The following test cards represent successful payment scenarios:

Card numberNetwork3DS behaviour
4111 1111 1111 1111VisaNo challenge
5555 5555 5555 4444MastercardNo challenge
3782 822463 10005American ExpressNo challenge
4000 0000 0000 0002VisaChallenge required

For all test cards:

  • Expiry date: Any future date (e.g., 12/25).
  • CVV: Any 3 digits (e.g., 123).
  • Cardholder name: Any name.

Failed payments

The following test cards simulate payment failures:

Card numberNetworkResult
4000 0000 0000 0077VisaInsufficient funds
4000 0000 0000 0051VisaCard declined
4000 0000 0000 0069VisaExpired card
4000 0000 0000 0101VisaCVV failure

Testing 3DS authentication

When using the challenge test card (4000 0000 0000 0002), a 3D Secure authentication screen appears in the native Android UI:

  • To pass: enter any code (e.g., 1234).
  • To fail: leave empty or enter wrong code multiple times.
  • To timeout: wait for authentication to timeout.

Verify that your onSuccess callback fires after successful authentication, and that your backend receives the transaction details.

Test PayPal

PayPal sandbox accounts

You need a PayPal sandbox account for testing. Create one in the PayPal Developer Dashboard.

Create a personal (buyer) account for testing customer payments.

Test PayPal payments

  1. Tap the PayPal button in the drop-in.
  2. Log in with your PayPal sandbox account in the web view.
  3. Approve the payment.
  4. Verify that onSuccess fires with transaction details.
  5. Verify your backend receives the payment notification.

Test both transaction intents:

  • Purchase intent: Funds captured immediately.
  • Authorisation intent: Funds authorised but not captured (capture later via backend API).

Test Google Pay

Pre-requisites

  • Android 7.0 (API level 24) or higher
  • Google Play Services installed
  • Google Account signed in
  • Test card added to Google Wallet

Test Google Pay payments

  1. Open your app on an Android device or emulator.
  2. Sign in to your Google Account.
  3. Add a test card to Google Wallet (use test card numbers from above).
  4. Tap the Google Pay button in Drop-in.
  5. Select your test card in the payment sheet.
  6. Tap Pay.
  7. Verify that onSuccess fires with transaction details.

The Google Pay button only appears when the user has cards in their Google Wallet and Google Play Services is available.

Testing on emulator

To test Google Pay on an Android emulator:

  1. Use an emulator with Google Play (API level 24+).
  2. Sign in with a Google Account.
  3. Open Google Wallet and add test cards manually.
  4. Run your app and test Google Pay integration.

Test error handling

Test that your error handling works correctly by simulating failures:

Card errors

Use the failed payment test cards to trigger different error types:

  • Insufficient funds: 4000 0000 0000 0077
  • Card declined: 4000 0000 0000 0051
  • Expired card: 4000 0000 0000 0069
  • CVV failure: 4000 0000 0000 0101

Verify that your onError callback receives the error and displays an appropriate message to the customer.

Network errors

Test that your error handling works correctly for network failures:

Manual testing

  1. Enable airplane mode on your device.
  2. Try to submit a payment.
  3. Verify that your onError callback receives the error.
  4. Verify your error handling shows an appropriate network error message.

Automated testing

Verify that your error callback handles network failures correctly:

class NetworkErrorTest {
    @Test
    fun testNetworkErrorHandling() {
        var errorReceived: BaseSdkException? = null
        
        val config = CheckoutDropInConfig(
            environment = Environment.TEST,
            session = mockSessionData(),
            ownerType = "MerchantGroup",
            ownerId = "MERCHANT-1",
            transactionData = mockTransactionData(),
            onError = { error ->
                errorReceived = error
            }
        )
        
        // Simulate network error (SDK error code SDK0500)
        config.onError?.invoke(mockNetworkError())
        
        // Verify error was received and has correct code
        assertNotNull(errorReceived)
        assertEquals("SDK0500", errorReceived?.errorCode)
        assertTrue(errorReceived?.message?.contains("network", ignoreCase = true) == true)
    }
}

User cancellation

Test cancellation handling:

  • Card (3DS): close the 3D Secure authentication window without completing authentication.
  • PayPal: close the web view without completing payment.
  • Google Pay: tap outside the payment sheet or press Back.

Verify that your onCancel callback fires (if configured in methodConfig.global).

Test backend verification

Always verify payments on your backend before fulfilling orders. The frontend callbacks can be manipulated by users.

Essential backend tests

Test these critical security checks:

  1. Transaction exists: query the PXP API with systemTransactionId to verify the transaction exists.
  2. Amount matches: verify the transaction amount matches your expected amount.
  3. Transaction succeeded: check that the transaction state is Authorised or Captured.
  4. Prevent replay attacks: ensure the same transaction can't be processed twice.

Example verification:

// Backend verification endpoint (Node.js/Express example)
app.post('/api/verify-payment', async (req, res) => {
  const { systemTransactionId, merchantTransactionId } = req.body;
  
  // Get expected amount from your database
  const order = await db.orders.findOne({ merchantTransactionId });
  const expectedAmount = order.amount;
  
  // Query PXP API to verify transaction
  const transaction = await unityApi.getTransaction(systemTransactionId);
  
  // Verify transaction is successful
  if (transaction.state !== 'Authorised' && transaction.state !== 'Captured') {
    return res.json({ success: false, error: 'Transaction not authorised' });
  }
  
  // Verify amount matches
  const txnAmount = transaction.amounts?.transactionValue || transaction.amount;
  if (Math.abs(txnAmount - expectedAmount) > 0.01) {
    return res.json({ success: false, error: 'Amount mismatch' });
  }
  
  // Check for duplicate processing
  const alreadyProcessed = await db.transactions.findOne({ systemTransactionId });
  if (alreadyProcessed) {
    return res.json({ success: true, orderId: alreadyProcessed.orderId });
  }
  
  // Process order
  const orderId = await fulfillOrder(order);
  await db.transactions.create({ systemTransactionId, orderId });
  
  res.json({ success: true, orderId });
});

Test on physical devices

Test on real Android devices to ensure the best user experience.

Test on devices that represent your user base:

  • Budget device: Samsung Galaxy A series, Motorola Moto G
  • Mid-range device: Google Pixel 6a, Samsung Galaxy S22
  • Premium device: Google Pixel 8, Samsung Galaxy S24
  • Tablet: Samsung Galaxy Tab S9

Device-specific testing

Test the following on each device:

  • Card field rendering and keyboard input
  • 3D Secure authentication UI
  • Google Pay integration (if available)
  • PayPal web view interaction
  • Screen rotation handling
  • Different screen sizes and densities
  • Performance and responsiveness

Screen size testing

Test your UI layout on different screen sizes using Compose previews:

// Preview different screen sizes for layout inspection
@Preview(name = "Phone", device = Devices.PHONE)
@Preview(name = "Foldable", device = Devices.FOLDABLE)
@Preview(name = "Tablet", device = Devices.TABLET)
@Composable
fun CheckoutScreenPreview() {
    CheckoutScreen()
}

Compose @Preview is useful for visual layout inspection during development, but it does not test runtime payment behaviour. Always run actual functional tests on emulators or physical devices to validate payment flows.

Automated testing

Write automated tests for your Drop-in integration:

Unit tests

Test your configuration and callback logic:

import com.pxp.checkout.checkoutdropin.types.CheckoutDropInConfig
import com.pxp.checkout.models.DropInTransactionData
import com.pxp.checkout.models.DropInTransactionIntentData
import com.pxp.checkout.models.Environment
import com.pxp.checkout.models.EntryType
import com.pxp.checkout.models.IntentType
import org.junit.Test
import org.junit.Assert.*

class CheckoutConfigTest {
    @Test
    fun testConfigCreation() {
        val config = CheckoutDropInConfig(
            environment = Environment.TEST,
            session = mockSessionData(),
            ownerType = "MerchantGroup",
            ownerId = "MERCHANT-1",
            transactionData = DropInTransactionData(
                currency = "GBP",
                amount = 99.99,
                entryType = EntryType.Ecom,
                intent = DropInTransactionIntentData(
                    card = IntentType.Authorisation
                ),
                merchant = "Demo Store",
                merchantTransactionId = "test-txn-123",
                merchantTransactionDate = { "2026-05-18T12:00:00Z" }
            )
        )
        
        assertEquals(Environment.TEST, config.environment)
        assertEquals("MERCHANT-1", config.ownerId)
        assertEquals("GBP", config.transactionData.currency)
    }
    
    @Test
    fun testCallbackExecution() {
        var successCalled = false
        var errorCalled = false
        
        val config = CheckoutDropInConfig(
            environment = Environment.TEST,
            session = mockSessionData(),
            ownerType = "MerchantGroup",
            ownerId = "MERCHANT-1",
            transactionData = DropInTransactionData(
                currency = "GBP",
                amount = 99.99,
                entryType = EntryType.Ecom,
                intent = DropInTransactionIntentData(
                    card = IntentType.Authorisation
                ),
                merchant = "Demo Store",
                merchantTransactionId = "test-txn-123",
                merchantTransactionDate = { "2026-05-18T12:00:00Z" }
            ),
            onSuccess = { result ->
                successCalled = true
            },
            onError = { error ->
                errorCalled = true
            }
        )
        
        // Test callbacks
        config.onSuccess?.invoke(mockSuccessResult())
        assertTrue(successCalled)
        assertFalse(errorCalled)
    }
}

Integration tests

Test the full payment flow:

import androidx.compose.ui.test.*
import androidx.compose.ui.test.junit4.createComposeRule
import org.junit.Rule
import org.junit.Test

class CheckoutFlowTest {
    @get:Rule
    val composeTestRule = createComposeRule()
    
    @Test
    fun testCardPaymentFlow() {
        composeTestRule.setContent {
            CheckoutScreen()
        }
        
        // Wait for Drop-in to render
        composeTestRule.waitForIdle()
        
        // Verify payment methods are displayed
        composeTestRule.onNodeWithText("Card").assertIsDisplayed()
        composeTestRule.onNodeWithText("PayPal").assertIsDisplayed()
        
        // Select card payment
        composeTestRule.onNodeWithText("Card").performClick()
        
        // Verify card form is displayed
        composeTestRule.onNodeWithText("Card number").assertIsDisplayed()
        composeTestRule.onNodeWithText("Expiry date").assertIsDisplayed()
        composeTestRule.onNodeWithText("CVV").assertIsDisplayed()
    }
    
    @Test
    fun testPaymentSubmission() {
        composeTestRule.setContent {
            CheckoutScreen()
        }
        
        // Wait for Drop-in to render
        composeTestRule.waitForIdle()
        
        // Fill card details and submit
        composeTestRule.onNodeWithText("Card").performClick()
        composeTestRule.onNodeWithText("Card number").performTextInput("4111111111111111")
        composeTestRule.onNodeWithText("Expiry date").performTextInput("12/25")
        composeTestRule.onNodeWithText("CVV").performTextInput("123")
        composeTestRule.onNodeWithText("Pay").performClick()
        
        // Verify payment submission
        composeTestRule.waitForIdle()
    }
}

Pre-production checklist

Before going live, verify the following:

Configuration

  • Change environment to Environment.LIVE.
  • Use production API credentials.
  • Verify correct ownerId and ownerType.
  • Confirm currency and amount formatting.

Testing

  • Test all enabled payment methods.
  • Test successful payments.
  • Test failed payments and error handling.
  • Test user cancellation scenarios.
  • Verify backend verification works correctly.
  • Test on physical devices (not just emulators).

Security

  • Backend verification implemented.
  • Replay attack prevention in place.
  • HTTPS enabled on all backend endpoints.
  • No sensitive data logged.
  • ProGuard/R8 obfuscation enabled for production builds.

User experience

  • Test on different screen sizes and orientations.
  • Loading states display correctly.
  • Success screen implemented.
  • Error messages are user-friendly.
  • Accessibility features tested (TalkBack, large text).

Performance

  • App startup time is acceptable.
  • Payment submission is responsive.
  • No memory leaks detected.
  • Network timeouts configured appropriately.

Code quality

  • Lint checks pass.
  • Unit tests pass.
  • Integration tests pass.
  • Code review completed.
  • Documentation updated.