# 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:

```kotlin
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 number | Network | 3DS behaviour |
|  --- | --- | --- |
| `4111 1111 1111 1111` | Visa | No challenge |
| `5555 5555 5555 4444` | Mastercard | No challenge |
| `3782 822463 10005` | American Express | No challenge |
| `4000 0000 0000 0002` | Visa | Challenge 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 number | Network | Result |
|  --- | --- | --- |
| `4000 0000 0000 0077` | Visa | Insufficient funds |
| `4000 0000 0000 0051` | Visa | Card declined |
| `4000 0000 0000 0069` | Visa | Expired card |
| `4000 0000 0000 0101` | Visa | CVV 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:

```kotlin
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:

```javascript
// 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.

### Recommended test devices

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:

```kotlin
// 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:

```kotlin
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:

```kotlin
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.