Skip to main content

Overview

The PayMaya SDK provides a sandbox environment for comprehensive testing of all payment flows without processing real transactions. This guide covers testing strategies, test cards, and common scenarios.

Sandbox Environment Setup

Before testing, ensure your client is configured for the sandbox environment:
val payMayaCheckoutClient = PayMayaCheckout.newBuilder()
    .clientPublicKey("pk-test-xxxxxxxxxxxxxxxx") // Sandbox public key
    .environment(PayMayaEnvironment.SANDBOX)
    .logLevel(LogLevel.DEBUG) // Verbose logging for testing
    .build()
Sandbox transactions do not process real payments. Never use production keys in test environments.

Test Credit Cards

PayMaya provides test credit card numbers for different scenarios in the sandbox environment.
For the complete list of test cards, visit the official PayMaya Test Credit Cards documentation.

Successful Payment Cards

Use these cards to test successful payment flows:

Visa

Card Number: 4123450131001381
Expiry: Any future date
CVV: Any 3 digits
Result: Payment success

Mastercard

Card Number: 5123450000000008
Expiry: Any future date
CVV: Any 3 digits
Result: Payment success

Failed Payment Cards

Test payment failure scenarios:

Declined Card

Card Number: 4571736000000075
Result: Payment declined/failed

Test Card Details

For all test cards, you can use:
  • Expiry Date: Any future date (e.g., 12/25, 01/26)
  • CVV: Any 3-digit number (e.g., 123, 456)
  • Cardholder Name: Any name
Test cards only work in the sandbox environment. They will be rejected in production.

Testing Checkout Flow

Basic Checkout Test

fun testCheckoutPayment() {
    // 1. Create checkout request
    val request = CheckoutRequest(
        totalAmount = TotalAmount(
            value = BigDecimal("100.00"),
            currency = "PHP"
        ),
        buyer = null, // Optional for testing
        items = listOf(
            Item(
                name = "Test Product",
                quantity = 1,
                amount = TotalAmount(
                    value = BigDecimal("100.00"),
                    currency = "PHP"
                )
            )
        ),
        requestReferenceNumber = "TEST-${System.currentTimeMillis()}",
        redirectUrl = RedirectUrl(
            success = "http://success.test",
            failure = "http://failure.test",
            cancel = "http://cancel.test"
        ),
        metadata = null
    )
    
    // 2. Start checkout
    payMayaCheckoutClient.startCheckoutActivityForResult(this, request)
}

// 3. Handle result
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    payMayaCheckoutClient.onActivityResult(requestCode, resultCode, data)?.let { result ->
        when (result) {
            is PayMayaCheckoutResult.Success -> {
                Log.d(TAG, "✓ Test passed: Successful payment")
                Log.d(TAG, "Checkout ID: ${result.checkoutId}")
            }
            is PayMayaCheckoutResult.Cancel -> {
                Log.d(TAG, "Test: Payment canceled")
            }
            is PayMayaCheckoutResult.Failure -> {
                Log.e(TAG, "✗ Test failed: ${result.exception.message}")
            }
        }
    }
}

Test Scenarios

  1. Launch checkout with valid test card (4123450131001381)
  2. Complete the payment flow
  3. Verify PayMayaCheckoutResult.Success is received
  4. Confirm checkoutId is present
  5. Check payment status shows PAYMENT_SUCCESS
  1. Launch checkout with declined card (4571736000000075)
  2. Complete the payment flow
  3. Verify PayMayaCheckoutResult.Failure is received
  4. Check exception details
  1. Launch checkout
  2. Press back button or close the activity
  3. Verify PayMayaCheckoutResult.Cancel is received
  1. Create checkout with invalid data (e.g., negative amount)
  2. Attempt to start checkout
  3. Verify BadRequestException is thrown or returned

Testing Pay With PayMaya

Single Payment Test

fun testSinglePayment() {
    val request = SinglePaymentRequest(
        totalAmount = TotalAmount(
            value = BigDecimal("50.00"),
            currency = "PHP"
        ),
        requestReferenceNumber = "PWPM-${System.currentTimeMillis()}",
        redirectUrl = RedirectUrl(
            success = "http://success.test",
            failure = "http://failure.test",
            cancel = "http://cancel.test"
        ),
        metadata = null
    )
    
    payWithPayMayaClient.startSinglePaymentActivityForResult(this, request)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    PayWithPayMaya.onActivityResult(requestCode, resultCode, data)?.let { result ->
        when (result) {
            is SinglePaymentResult.Success -> {
                Log.d(TAG, "✓ Single payment successful: ${result.paymentId}")
            }
            is SinglePaymentResult.Cancel -> {
                Log.d(TAG, "Single payment canceled")
            }
            is SinglePaymentResult.Failure -> {
                Log.e(TAG, "✗ Single payment failed: ${result.exception}")
            }
        }
    }
}
fun testWalletLink() {
    val request = CreateWalletLinkRequest(
        requestReferenceNumber = "LINK-${System.currentTimeMillis()}",
        redirectUrl = RedirectUrl(
            success = "http://success.test",
            failure = "http://failure.test",
            cancel = "http://cancel.test"
        ),
        metadata = null
    )
    
    payWithPayMayaClient.startCreateWalletLinkActivityForResult(this, request)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    PayWithPayMaya.onActivityResult(requestCode, resultCode, data)?.let { result ->
        when (result) {
            is CreateWalletLinkResult.Success -> {
                Log.d(TAG, "✓ Wallet link created: ${result.linkId}")
            }
            is CreateWalletLinkResult.Cancel -> {
                Log.d(TAG, "Wallet link creation canceled")
            }
            is CreateWalletLinkResult.Failure -> {
                Log.e(TAG, "✗ Wallet link failed: ${result.exception}")
            }
        }
    }
}

Testing Vault (Card Tokenization)

fun testCardTokenization() {
    payMayaVaultClient.startTokenizeCardActivityForResult(this)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    payMayaVaultClient.onActivityResult(requestCode, resultCode, data)?.let { result ->
        when (result) {
            is PayMayaVaultResult.Success -> {
                Log.d(TAG, "✓ Card tokenized successfully")
                Log.d(TAG, "Token ID: ${result.paymentTokenId}")
                Log.d(TAG, "Issuer: ${result.issuer}")
                Log.d(TAG, "State: ${result.state}")
                Log.d(TAG, "Created: ${result.createdAt}")
            }
            is PayMayaVaultResult.Cancel -> {
                Log.d(TAG, "Card tokenization canceled")
            }
        }
    }
}

Vault Test Cases

  1. Valid Card Entry
    • Enter test card details
    • Verify successful tokenization
    • Confirm token ID is returned
  2. Invalid Card
    • Enter invalid card number
    • Verify validation error is shown
    • Confirm user can retry
  3. Cancel Tokenization
    • Start tokenization flow
    • Press back or cancel
    • Verify PayMayaVaultResult.Cancel is received

Testing Payment Status

fun testPaymentStatusCheck(checkoutId: String) {
    lifecycleScope.launch(Dispatchers.IO) {
        val result = payMayaCheckoutClient.checkPaymentStatus(checkoutId)
        
        withContext(Dispatchers.Main) {
            when (result) {
                is CheckPaymentStatusResult.Success -> {
                    Log.d(TAG, "✓ Status check successful: ${result.status}")
                    verifyExpectedStatus(result.status)
                }
                is CheckPaymentStatusResult.Failure -> {
                    Log.e(TAG, "✗ Status check failed: ${result.exception}")
                }
                else -> {
                    Log.d(TAG, "Status check canceled")
                }
            }
        }
    }
}

private fun verifyExpectedStatus(actual: PaymentStatus) {
    // Verify status matches expected value
    val expected = PaymentStatus.PAYMENT_SUCCESS
    if (actual == expected) {
        Log.d(TAG, "✓ Status matches expected: $expected")
    } else {
        Log.e(TAG, "✗ Status mismatch. Expected: $expected, Got: $actual")
    }
}

Exception Handling Tests

class PaymentExceptionTests {
    
    @Test
    fun testBadRequestException() {
        // Test with invalid API key
        val client = PayMayaCheckout.newBuilder()
            .clientPublicKey("invalid-key")
            .environment(PayMayaEnvironment.SANDBOX)
            .build()
        
        // Attempt checkout - should fail with BadRequestException
        // Verify exception is caught and handled properly
    }
    
    @Test
    fun testNetworkFailure() {
        // Disable network
        // Attempt payment
        // Verify appropriate exception handling
    }
    
    @Test
    fun testInvalidAmount() {
        // Create request with invalid amount
        // Verify validation or exception
    }
}

Common Testing Scenarios

Happy Path

Test successful payment flow with valid test cards from start to finish.

Network Issues

Test behavior during network interruptions or timeouts.

User Cancellation

Verify proper handling when users cancel at different stages.

Invalid Input

Test with invalid data to ensure proper validation and error messages.

Back Button

Test pressing back button during payment flow.

App Backgrounding

Test behavior when app goes to background during payment.

Multiple Requests

Test handling of rapid successive payment requests.

Status Polling

Test status checking for various payment states.

Automated Testing Example

class PayMayaCheckoutTest {
    private lateinit var client: PayMayaCheckout
    
    @Before
    fun setup() {
        client = PayMayaCheckout.newBuilder()
            .clientPublicKey("pk-test-xxxxxxxxxxxxxxxx")
            .environment(PayMayaEnvironment.SANDBOX)
            .logLevel(LogLevel.DEBUG)
            .build()
    }
    
    @Test
    fun testSuccessfulCheckout() {
        val request = createTestCheckoutRequest()
        
        // Use ActivityScenario or Espresso to test UI flow
        val scenario = launchActivity<CheckoutActivity>()
        
        // Perform checkout
        // Enter test card details
        // Verify success result
        
        scenario.onActivity { activity ->
            val result = /* get result from activity */
            assertTrue(result is PayMayaCheckoutResult.Success)
        }
    }
    
    @Test
    fun testCheckoutCancellation() {
        // Test cancellation flow
    }
    
    @Test
    fun testInvalidRequest() {
        // Test with invalid data
    }
    
    private fun createTestCheckoutRequest(): CheckoutRequest {
        return CheckoutRequest(
            totalAmount = TotalAmount(BigDecimal("100.00"), "PHP"),
            items = listOf(
                Item("Test Item", 1, TotalAmount(BigDecimal("100.00"), "PHP"))
            ),
            requestReferenceNumber = "TEST-${System.currentTimeMillis()}",
            redirectUrl = RedirectUrl(
                success = "http://success.test",
                failure = "http://failure.test",
                cancel = "http://cancel.test"
            )
        )
    }
}

Testing Checklist

Before going to production, ensure you’ve tested:
  • Successful payment with test cards
  • Failed payment scenarios
  • User cancellation at various stages
  • Payment status checking
  • Exception handling for all error types
  • Network failure scenarios
  • Invalid input validation
  • Multiple payment flows (Checkout, Pay With PayMaya, Vault)
  • Back button behavior
  • App backgrounding during payment
  • Status polling for pending payments
  • Result handling in onActivityResult
  • Logging output in debug mode

Debugging Tips

Set LogLevel.VERBOSE to see complete HTTP request and response details:
.logLevel(LogLevel.VERBOSE)
Filter logcat by PayMaya tag:
adb logcat | grep -i paymaya
Ensure you’re using sandbox keys (prefix: pk-test-) in test environment.
Use Android Studio’s Network Profiler or device developer options to simulate poor network conditions.

Best Practices

Separate Test Keys

Use dedicated test keys for development separate from shared sandbox keys.

Test All Paths

Test success, failure, and cancellation paths for complete coverage.

Automated Tests

Create automated tests for regression testing during development.

Log Everything

Use verbose logging during testing to understand SDK behavior.

Edge Cases

Test edge cases like rapid clicks, back button, and app lifecycle events.

Document Results

Keep track of test results and any issues discovered.

Resources

Next Steps

Environment Setup

Configure production environment after testing

Result Handling

Handle payment results in your app

Build docs developers (and LLMs) love