How to Test Payment Flows in Mobile Apps (Complete 2026 Guide)
Payment flows are the most revenue‑critical part of any mobile app. A single bug that lets a user skip a charge, creates a duplicate transaction, or leaks card data can cause immediate financial loss,
Motivation
Payment flows are the most revenue‑critical part of any mobile app. A single bug that lets a user skip a charge, creates a duplicate transaction, or leaks card data can cause immediate financial loss, regulatory fines, and irreversible damage to brand trust. Unlike UI‑only screens, payment paths involve external gateways, asynchronous callbacks, security constraints (PCI‑DSS), and a variety of user‑behavior profiles that affect how the flow is exercised. Testing them therefore requires a matrix that covers functional correctness, error handling, compliance, and real‑world variability that staging environments rarely reproduce.
A well‑designed test strategy catches:
- Happy‑path successes that confirm the integration contract.
- Decline and challenge responses that verify fallback UI and retry logic.
- Idempotency guarantees that protect against double‑charge when network glitches occur.
- Refund and partial‑refund flows that keep accounting in sync.
- Wallet‑specific flows (Google Pay, Apple Pay) that rely on platform‑provided tokens.
- Receipt generation and delivery mechanisms that satisfy post‑purchase communication.
The following sections break down a comprehensive test matrix, show how to execute it manually and with automation, explain how autonomous exploration adds value, highlight production‑only edge cases, and finish with a concise checklist you can bookmark and reuse.
Test Matrix Overview
| Category | Sub‑scenario | Expected Outcome | Key Observables |
|---|---|---|---|
| Happy path | Valid card, sufficient funds, no 3DS | Payment succeeds, receipt shown, analytics event payment_success | HTTP 200 from gateway, UI shows confirmation screen |
| Card decline | Issuer returns insufficient_funds | Error screen, option to try another card, no charge | Gateway response code 200 with decline flag, analytics payment_decline |
| Card decline | Issuer returns lost_or_stolen | Transaction blocked, possible lockout, fraud alert triggered | Same as above + security event |
| 3DS/SCA | Challenge required, user completes correctly | Payment proceeds after challenge, receipt shown | Redirect to ACS, iframe handling, final success |
| 3DS/SCA | Challenge required, user aborts or fails | Payment fails, user stays on payment screen, no charge | No final charge, analytics payment_3ds_failure |
| Network timeout mid‑charge | Socket timeout after Authorize but before Capture | Idempotency key prevents duplicate charge, UI shows retry option | No second Capture request, idempotency key logged |
| Duplicate submit (rapid tap) | User taps pay twice within 200 ms | Only one charge processed, second tap shows “already processing” | Idempotency key reuse, single gateway call |
| Refund | Full refund after successful charge | Funds returned to original method, refund event emitted | Gateway refund API call, UI shows refund status |
| Partial refund | Refund of 30 % of amount | Remaining balance stays charged, partial refund recorded | Separate refund transaction, correct amount |
| Currency conversion | Payment in EUR, user’s card in USD | Amount converted using gateway FX rate, correct settlement | FX rate logged, final settled amount matches expectation |
| Tax calculation | Tax 10 % added to subtotal | Total = subtotal + tax, receipt shows breakdown | Tax line item present, matches jurisdictional rule |
| Wallet – Google Pay | User selects GPay, tokenizes card | Payment succeeds using token, no PAN exposed | Token sent to gateway, PAN never in logs |
| Wallet – Apple Pay | User selects Apple Pay, Face ID confirms | Same as Google Pay, token flow | Token handling, Secure Element usage |
| Receipt generation | After success, receipt screen shown | Receipt contains merchant name, amount, date, transaction ID, refund policy | UI fields populated, optional email/SMS sent |
| Receipt delivery failure | Email service down, SMS gateway error | UI shows receipt saved locally, offers retry, no charge impact | Local storage of receipt, retry mechanism |
Each sub‑scenario maps to a distinct test case. The matrix can be expanded with additional dimensions (e.g., different card brands, issuing countries, sandbox vs. live keys) but the core structure stays the same.
Happy Path Details
A happy‑path test validates that the integration contract between the app and the payment gateway is correctly implemented. It should verify:
- The request payload includes all required fields (amount, currency, order ID, customer email, billing address).
- The gateway returns a successful authorization with a transaction ID.
- The app stores the transaction ID securely (e.g., in encrypted keystore) for later reconciliation.
- The UI transitions to a confirmation screen that shows a human‑readable receipt and triggers an analytics event.
- No sensitive data (PAN, CVV, PIN) appears in logs, crash reports, or screenshots.
Card Decline Scenarios
Declines are not monolithic; issuers return different decline codes that the app must map to user‑friendly messages. Important groups:
| Decline Code | Meaning | Suggested UI Message |
|---|---|---|
insufficient_funds | Account lacks balance | “Your card does not have enough funds. Try another card or add money.” |
expired_card | Card past expiry date | “This card has expired. Please update your expiry date.” |
invalid_cvc | CVV mismatch | “The security code is incorrect. Please re‑enter.” |
lost_or_stolen | Card reported lost/stolen | “This card cannot be used. Contact your bank.” |
fraud_suspected | Gateway’s fraud engine flagged | “We couldn’t process this payment. Try a different payment method.” |
do_not_honor | Generic issuer refusal | “Your bank declined the transaction. Please try another card.” |
Each code should trigger the appropriate error screen, prevent the order from being placed, and log only the decline code (not the PAN) for troubleshooting.
3DS/SCA Challenges
Strong Customer Authentication (SCA) under PSD2 introduces a redirect or iframe challenge. Tests must cover:
- Challenge flow – The app opens the ACS URL in a WebView, handles redirects, and receives the
PaResresponse. - User abort – If the user closes the challenge or clicks “Cancel”, the app must revert to the payment screen without creating a charge.
- Timeout – If the ACS does not respond within the gateway‑defined window (typically 30‑60 s), the app should show a timeout error and allow retry.
- Device binding – Some issuers require device fingerprinting; ensure the app does not block or alter the WebView’s user‑agent in a way that breaks the challenge.
Network Timeouts Mid‑Charge
Network instability can happen after the gateway has authorized but before it captures funds. The system must be idempotent:
- Generate an idempotency key (UUID v4) before the first
Authorizecall and reuse it for any retry. - Detect timeout – Catch socket timeout or HTTP 504 from the gateway.
- Present UI – Show “We’re processing your payment, please wait…” with a retry button.
- Retry logic – On retry, send the same
Authorizerequest with the identical key; the gateway should respond with the original authorization status, not create a second charge.
Idempotency and Double‑Charge Prevention
Even without network glitches, rapid double‑taps or race conditions can cause duplicate requests. Strategies:
- Client‑side disabling – Disable the pay button immediately after first tap and re‑enable only on final outcome (success/error).
- Server‑side idempotency – Rely on the gateway’s idempotency key; ensure the key is generated once per order attempt and stored until a terminal state (success/failure) is reached.
- Audit log – Record each request with its key, timestamp, and outcome; a duplicate key with a different outcome should raise an alert.
Refunds and Partial Refunds
Refund testing must verify both the financial reversal and the UI/UX:
- Full refund – Invoke the gateway’s refund endpoint with the original transaction ID and the full amount. Confirm the gateway returns a refund ID and the app updates the order status to
refunded. - Partial refund – Same as above but with a lesser amount; ensure the app shows a “partially refunded” badge and keeps the remaining charged amount visible.
- Multiple partial refunds – Allow successive partial refunds up to the original amount; the sum must never exceed the original charge.
- Refund receipt – Display a refund receipt that references the original transaction ID and shows the refunded amount.
Currency and Tax Handling
Multi‑currency apps must correctly apply conversion rates and tax rules:
- Currency selection – When the user switches currency, the app must recalculate the subtotal, apply the correct tax percentage for the destination jurisdiction, and request the gateway in the new currency.
- Tax override – Some regions allow tax‑exempt purchases (e.g., B2B). Verify that the app respects exemption flags and does not apply tax.
- Rounding – Use banker’s rounding (round‑half‑to‑even) to avoid cumulative errors; test edge cases like $0.005 rounding up or down.
- FX rate source – If the app fetches rates from a third‑party service, mock the service to return predetermined rates and confirm the final charged amount matches expectation.
Wallet Payments (Google Pay / Apple Pay)
Wallet tests differ because the app never sees the raw PAN:
- Google Pay – Initialize the PaymentsClient, load allowed card networks, and handle the
PaymentDatatoken returned in theonActivityResult. Verify that the token is a JWT signed by Google and that you forward it to your gateway without decoding. - Apple Pay – Use PassKit to create a
PKPaymentRequest, present thePKPaymentAuthorizationViewController, and handle thepaymentAuthorizationWillAuthorizedelegate. The returnedpaymentTokencontains encrypted payment data; send the blob to your gateway. - Security checks – Ensure that neither the token nor any decrypted data appears in logs, screenshots, or crash reports.
- Fallback – If the device does not support the wallet (e.g., older Android without NFC), the app should gracefully hide the wallet button and show only card entry.
Receipt and Confirmation
After a successful payment, the app must provide proof of purchase:
- On‑screen receipt – Show merchant name, order ID, date/time, itemized list, subtotal, tax, total, and payment method (last 4 digits of card or wallet token).
- Email/SMS receipt – Trigger the backend to send a receipt; test that the email contains the same data and that a link to view the receipt online is present.
- Refund policy link – Include a clickable link to your refund/return policy; verify the URL is correct and reachable.
- Analytics – Fire an
order_completedevent with parameters (amount, currency, payment_type) for downstream reporting.
Manual Testing Approaches
Exploratory Testing Checklist
A structured exploratory session helps uncover issues that scripted tests miss. Use the following checklist as a starting point; adapt it to your app’s specific flow.
| Area | Item | How to Verify |
|---|---|---|
| Input validation | Enter non‑numeric characters in card number field | Field rejects input, shows inline error |
| Input validation | Paste a card number with spaces | Spaces stripped or rejected according to gateway spec |
| Input validation | Enter expiry month >12 or year in the past | Error displayed, focus stays on field |
| UI flow | Tap pay button twice rapidly | Only one network request sent, UI shows “processing” state |
| UI flow | Navigate away (home button) while payment pending | App preserves state, resumes on return, does not duplicate charge |
| Error handling | Simulate issuer decline via test card | Decline screen appears, option to try another card |
| Error handling | Close 3DS iframe before completion | Returns to payment screen, no charge |
| Network | Turn off Wi‑Fi/cellular after authorize but before capture | Idempotency key prevents duplicate charge, retry offered |
| Network | Switch from Wi‑Fi to cellular mid‑flow | Transaction continues seamlessly (if gateway supports) |
| Wallet | Attempt Google Pay on device without NFC | GPay button hidden or disabled |
| Wallet | Attempt Apple Pay on device without Face ID/Touch ID | Prompt for passcode, fallback if unavailable |
| Receipt | Verify on‑screen receipt matches email receipt | All fields identical, timestamps within few seconds |
| Receipt | Delete email receipt, try to view again | App offers to re‑send or shows locally saved copy |
| Accessibility | TalkOver | Screen reader reads each form field label and error message |
| Accessibility | Color contrast | Error text meets WCAG AA contrast ratio |
| Security | Check logs (Logcat, Xcode console) for PAN or CVV | No sensitive data appears |
| Security | Verify that screenshots taken during payment do not capture PAN (flagged as secure) | Screenshot is blank or obscured for payment fields |
| Performance | Measure time from tap pay to receipt display under 3G | Should be < 8 s for good UX |
| Compatibility | Test on lowest supported Android/iOS version | No crashes, layout intact |
| Compatibility | Test on tablet layout (larger screens) | Elements scale correctly, no overlap |
Run through this checklist with a variety of test cards (see next section) and note any deviation. Capture screenshots, logs, and video if possible for later analysis.
Use of Test Cards and Sandbox
Most gateways provide a set of test card numbers that trigger specific responses without moving real money. Example sets (replace with your gateway’s documentation):
| Card Number | Brand | Response |
|---|---|---|
| 4242424242424242 | Visa | Success |
| 4000000000000002 | Visa | Insufficient funds |
| 4000000000000119 | Visa | Expired card |
| 4000000000000127 | Visa | Incorrect CVC |
| 5555555555554444 | Mastercard | Success |
| 5200828282828210 | Mastercard | Insufficient funds |
| 6011000990139424 | Discover | Success |
| 6011000990139424 | Discover | Insufficient funds (if configured) |
For 3DS testing, use ranges like:
| Card Number | Brand | 3DS Behavior |
|---|---|---|
| 4000000000003063 | Visa | Challenge required, succeeds if correct OTP entered |
| 4000000000003055 | Visa | Challenge required, fails if wrong OTP entered |
| 378282246310005 | Amex | Success, no 3DS (some issuers exempt) |
| 371449635398431 | Amex | Challenge required |
When using sandbox credentials, never point the app to production keys. Keep sandbox and production configurations in separate build flavors or environment variables, and assert at runtime that the wrong environment is not used (e.g., a guard that throws if BuildConfig.PRODUCTION is true and a test card is detected).
Instrumentation and Logging Considerations (PCI‑DSS)
PCI‑DSS requirement 10.3 states that you must not store sensitive authentication data (SAD) after authorization. In practice, this means:
- Never log the full PAN, CVV2, PIN, or track data.
- Never store SAD in databases, logs, crash dumps, or analytics payloads.
- Mask PAN in any UI that shows the card for reference (show only last four digits).
- Encrypt any persisted token or transaction ID using a platform‑provided keystore (Android Keystore, iOS Keychain).
When you need to debug a payment failure, log only:
- Transaction ID (gateway‑provided, non‑sensitive)
- Amount and currency
- Response code and decline reason (generic, e.g.,
insufficient_funds) - Timestamp
- Error class (network timeout, validation failure)
Avoid logging HTTP headers that may contain the card number (some gateways echo it back in error responses for debugging in sandbox—strip it before logging). Use a logging filter or a custom logger that redacts patterns matching \d{13,19} (potential PAN) before writing to log files.
Automated Approaches
Scripted Tests with Appium (Android) and Playwright (Web)
Automation shines for repeatable regression checks, especially when integrated into CI pipelines. Below are minimal examples that illustrate the core ideas; expand them with data‑driven loops and assertions as needed.
#### Appium Android – Happy Path with Test Card
// pom.xml dependencies: io.appium:java-client, junit-jupiter, assertj
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.MobileBy;
import io.appium.java_client.android.AndroidDriver;
import org.junit.jupiter.api.*;
import org.openqa.selenium.*;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class PaymentFlowTest {
private AppiumDriver driver;
@BeforeEach
public void setUp() throws Exception {
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("platformName", "Android");
caps.setCapability("deviceName", "Pixel_4_API_33");
caps.setCapability("app", "/path/to/app.apk");
caps.setCapability("automationName", "UiAutomator2");
driver = new AndroidDriver<>(new URL("http://localhost:4723/wd/hub"), caps);
}
@AfterEach
public void tearDown() {
if (driver != null) driver.quit();
}
@Test
public void testHappyPathVisaSuccess() {
// 1. Navigate to product and add to cart
driver.findElement(MobileBy.AccessibilityId("add_to_cart")).click();
driver.findElement(MobileBy.AccessibilityId("go_to_cart")).click();
// 2. Initiate checkout
driver.findElement(MobileBy.AccessibilityId("checkout_button")).click();
// 3. Fill card details using test Visa success card
driver.findElement(MobileBy.AccessibilityId("card_number")).sendKeys("4242424242424242");
driver.findElement(MobileBy.AccessibilityId("expiry_date")).sendKeys("12/30");
driver.findElement(MobileBy.AccessibilityId("cvc")).sendKeys("123");
driver.findElement(MobileBy.AccessibilityId("holder_name")).sendKeys("Test User");
// 4. Submit payment
driver.findElement(MobileBy.AccessibilityId("pay_button")).click();
// 5. Wait for success screen
WebDriverWait wait = new WebDriverWait(driver, 30);
wait.until(ExpectedConditions.visibilityOfElementLocated(
MobileBy.AccessibilityId("payment_success")));
// 6. Assert receipt details
String amount = driver.findElement(MobileBy.AccessibilityId("receipt_amount")).getText();
Assertions.assertEquals("$10.00", amount);
String txnId = driver.findElement(MobileBy.AccessibilityId("receipt_txn_id")).getText();
Assertions.assertFalse(txnId.isEmpty(), "Transaction ID should be present");
}
}
Key points:
- Use accessibility IDs for stable locators.
- The test uses the sandbox test card
4242424242424242which always returns a successful authorization. - Assertions focus on UI outcomes and non‑sensitive data (transaction ID).
#### Playwright Web – 3DS Challenge Handling
// test/payment-3ds.spec.js
const { test, expect } = require('@playwright/test');
test.describe('Payment flow with 3DS challenge', () => {
test('user completes 3DS and sees success', async ({ page }) => {
// Navigate to product and checkout
await page.goto('https://example-shop.com/product/123');
await page.click('text=Add to cart');
await page.click('text=Checkout');
// Fill card details – test card that triggers 3DS
await page.fill('#card-number', '4000000000003063');
await page.fill('#expiry', '12/30');
await page.fill('#cvc', '123');
await page.fill('#holder-name', 'Test User');
await page.click('button#pay');
// Expect the 3DS iframe to appear
const frameLocator = page.frameLocator('iframe[title="3D Secure Challenge"]');
await expect(frameLocator.locator('text=Enter your password')).toBeVisible();
// Simulate OTP entry (in sandbox, any 6‑digit works)
await frameLocator.fill('#otp', '123456');
await frameLocator.click('button#submit');
// Wait for success page
await expect(page.locator('text=Payment successful')).toBeVisible({ timeout: 15000 });
await expect(page.locator('#receipt-amount')).toHaveText('$15.00');
});
});
Playwright’s frameLocator handles the shifting context of the 3DS iframe cleanly. The test uses a sandbox card that always triggers a challenge; the OTP can be any value because the sandbox ACS accepts it.
Data‑Driven Test Suites
To cover the matrix efficiently, externalize test data (card numbers, expected outcomes, currency, tax rate) into CSV or JSON files and let the test iterator drive the scenarios. Example in JUnit 5 with @ParameterizedTest:
@ParameterizedTest
@CsvSource({
"42424242424242424242, Visa, success, $10.00",
"4000000000000002, Visa, insufficient_funds, Error: Insufficient funds",
"4000000000003063, Visa, 3ds_success, $12.50",
"6011000990139424, Discover, success, $20.00"
})
public void testCardScenario(String pan, String brand, String expectedOutcome, String expectedAmount) {
// reuse the helper methods from the happy‑path test,
// but vary the input and assert based on expectedOutcome
}
This approach guarantees that each matrix cell gets exercised with a single test method, reducing duplication.
Mocking Payment Gateways
For fast unit‑level validation, mock the gateway HTTP endpoints using libraries like WireMock (Java/MS) or msw (Node.js). Mocks let you simulate latency, network errors, and specific JSON payloads without relying on external sandboxes.
WireMock example (Java) – Simulate a timeout on the capture endpoint:
wireMockService.stubFor(post(urlEqualTo("/v1/charges"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("{\"id\":\"ch_123\",\"status\":\"requires_capture\"}")
.withFixedDelay(5000))); // 5 s delay to provoke timeout
// In the app code, reduce the socket timeout to 2 s to force a timeout scenario
After the timeout, assert that the app shows a retry UI and does not send a second capture request (you can verify with WireMock’s request count).
Contract Testing
Define a contract (e.g., using Pact) between the mobile client and the payment microservice. The contract specifies request/response shapes, required headers, and idempotency‑key usage. Running contract tests in CI guarantees that any change to the server side will not break the client expectations without explicit version bump.
Chaos Injection (Network, Latency)
Tools like toxiproxy, Clumsy (Windows), or netem (Linux) can inject packet loss, duplication, or latency into the device’s network interface. For Android emulators:
# Add 150 ms latency and 5 % loss to the emulator's virtual Ethernet
adb shell tc qdisc add dev eth0 root netem delay 150ms loss 5%
Run your automated suite under these conditions to validate idempotency and timeout handling. Remember to clean up after the test:
adb shell tc qdisc del dev eth0 root
Autonomous Exploration with SUSA
How SUSA Discovers Payment Bugs
SUSA (SUSATest) is an autonomous QA agent that explores an app without predefined scripts. When pointed at an APK or a web URL, it builds a state‑graph of screens, actions, and outcomes, then drives the app through a variety of personas (curious, impatient, novice, adversarial, elderly, accessibility, power‑user, etc.). Each persona has a distinct behavior model—e.g., the impatient persona taps rapidly, the adversarial persona inputs malformed data, the accessibility persona relies on screen‑reader navigation, and the power‑user persona attempts edge‑case gestures like long‑press or two‑finger swipe.
During exploration, SUSA automatically:
- Detects UI elements that look like payment fields (card number, expiry, CVC, pay button).
- Tries a wide range of inputs: valid test cards, invalid formats, extremely long strings, Unicode characters, emojis, and SQL‑like injection attempts.
- Triggers network interruptions at random moments (by toggling airplane mode or using the device’s network‑settings API) to test idempotency.
- Observes responses from the payment gateway (if the app is configured to point at a sandbox) and logs whether a charge was created, declined, or left in a pending state.
- Flags any occurrence of sensitive data in logs, screenshots, or crash dumps by scanning for patterns that resemble PANs or CVVs.
Because SUSA does not rely on pre‑written test cases, it can discover bugs that only appear under unusual combinations—for example, a scenario where the user switches app language mid‑payment, causing a layout shift that obscures the CVC field, leading to a silent submission of an empty CVC and a subsequent decline that the app does not surface to the user.
Persona‑Driven Exploration in Payments
| Persona | Typical Behavior | Payment‑Specific Findings |
|---|---|---|
| Curious | Tries every visible button, explores menus, reads help text | May discover hidden “apply coupon” button that changes tax calculation, exposing a rounding bug. |
| Impatient | Repeatedly taps pay button, does not wait for loading indicators | Uncovers double‑charge risk when idempotency key is not generated until after the first network call. |
| Novice | Enters data slowly, often makes typos, uses autocomplete suggestions | Finds that the app does not strip leading/trailing spaces from card number, causing a mismatch with the gateway’s Luhn check. |
| Adversarial | Submits malformed JSON, attempts to inject script into fields, tries to bypass SSL pinning | Detects insufficient input validation that leads to gateway error messages leaking internal stack traces. |
| Elderly | Uses larger font settings, relies on voice commands, may miss small error toast | Reveals that error messages are shown only as low‑contrast toast notifications, violating WCAG AA for users with reduced vision. |
| Accessibility | Navigates via TalkBack/VoiceOver, expects labels and hints | Finds that the expiry date picker is not announced correctly, causing screen‑reader users to select an invalid month. |
| Power user | Uses shortcuts, long‑press gestures, tries to paste from clipboard, rotates device frequently | Discovers that pasting a card number with spaces triggers a validation failure only when the device is in landscape orientation. |
| Fraud‑simulator (custom) | Attempts rapid successive payments with different cards, simulates stolen‑card patterns | Triggers the gateway’s fraud throttle, exposing that the app does not show a user‑friendly “too many attempts” message and instead shows a generic network error. |
By letting SUSA run with these personas overnight, teams receive a consolidated report that highlights:
- Screens where the pay button remains enabled while a network request is in flight.
- Fields that accept characters that should be rejected (e.g., letters in the expiry field).
- Cases where the app displays a generic “something went wrong” toast instead of a specific decline reason.
- Instances where the app logs the full PAN in Logcat when an exception occurs.
These findings are actionable: developers can add input masks, improve loading state handling, tighten logging filters, and adjust error‑message UI.
Cross‑Session Learning
SUSA stores a compact representation of each explored screen (UI hierarchy, resource IDs, textual content) and remembers which actions led to dead ends (e.g., a button that always shows an error). On subsequent runs, it prioritizes unexplored transitions and re‑tests previously flaky outcomes with varied timing or data. Over time, the agent builds a richer model of the app’s payment flow, reducing the false‑negative rate and surfacing regression bugs that only appear after a UI redesign or a gateway version bump.
When you integrate SUSA into your CI pipeline, you can configure it to:
- Fail the build if any new critical issue (crash, ANR, PCI‑DSS violation, double‑charge risk) is detected.
- Generate Appium or Playwright scripts from the paths it successfully traversed, giving you a starting point for manual test maintenance.
Because SUSA never logs PANs or CVVs, its reports are safe to share across teams and with stakeholders who need assurance about compliance.
Production‑Only Edge Cases
Even the most thorough staging suite can miss issues that only manifest when real issuers, networks, and fraud systems are involved. Below are categories of production‑only bugs that have repeatedly slipped through pre‑release testing, along with detection strategies.
Real‑World Card Issuer Behaviors
Issuers sometimes return decline codes that are not documented in the sandbox simulator. Examples:
pickup_card– The card is reported as confiscated; the acquirer may instruct the merchant to retain the card.invalid_transaction– A generic code used by some European banks for transactions that exceed a daily limit, even if funds are sufficient.scp_failed– Strong Customer Authentication procedure failed because the issuer’s ACS could not be reached.
These codes often map to a generic decline in sandbox, causing the app to show a vague error. In production, users may be confused or think the problem is with the app rather than their bank. Mitigation: maintain a mapping table of issuer‑specific codes received from the gateway’s error webhook, and display a user‑friendly message based on the known meaning; for unknown codes, fall back to a standard “Please contact your bank” message and log the raw code for later analysis.
Fraud‑Screening Triggers
Live fraud engines evaluate numerous signals: velocity, geolocation, device reputation, BIN patterns, and more. A transaction that passes sandbox can be blocked in production because:
- The IP address originates from a data center (common when using emulators or CI runners).
- The device ID appears on a blocklist due to prior fraudulent activity.
- The billing address ZIP does not match the issuer’s records (AVS mismatch).
To surface these issues before release, run a subset of tests through a fraud‑simulation service (many gateways offer a “fraud mode” sandbox that returns fraud_suspected based on custom
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free