Back to BlogQuality & Testing

Testing Payment Flows End to End: The Failure Cases That Cost Money

Rupak Amin

Founder & Lead Engineer, RAITHub

11 min read

RAITHub ships and tests production software. See QA as a Service or talk to us.

Test a payment flow by its failure cases, not its happy path. The successful card payment everyone demos almost never breaks. Money is lost in the cases around it: a double charge from a retry, an abandoned checkout, a wrong total, a refund that does not update stock, an unhandled dispute, and records that never reconcile. The same cases apply whether you use Stripe, a wallet or a local gateway.

If you would rather have it tested for you, see how RAITHub would test this below. For the Stripe-specific mechanics (CLI triggers, test clocks, signed fake events), see testing payments and webhooks end to end; this post is the business-outcome view that sits on top.

Which payment failures actually cost money?

Write your test plan from this list. Each row is a real way a payment flow loses money or trust, with the outcome a test must assert.

Failure caseWhat a customer seesOutcome the test asserts
Double chargeCharged twice for one orderOne charge and one order per idempotency key
Abandoned checkoutAn order that is neither clearly paid nor clearly cancelledThe order ends paid or unpaid, never half-created
Paid but not fulfilledMoney taken, nothing deliveredFulfilment follows a confirmed payment, or the payment is reversed
Fulfilled but not paidGoods or access given on a failed paymentAccess or dispatch waits for a confirmed payment
Wrong totalA price that disagrees with the cartTotals with tax, discount and currency match a hand calculation
Refund without reversalRefunded but still holding stock or accessA refund updates the order, the stock and the customer
Dispute ignoredA chargeback that never reaches your teamA dispute creates a task and a reversing ledger entry
Records do not matchYour books disagree with the providerReconciliation reports zero unexplained differences

How do you test for a double charge?

By retrying. Networks retry requests, and a create-payment call without an idempotency key will charge twice. Send the same request twice with one key and assert a single charge and a single order row. Then repeat the confirming webhook, because providers may deliver the same event more than once, and confirm the order updates once. The idempotency concept is in what idempotency means in API design.

import { test, expect } from '@playwright/test'

test('a retried checkout creates one order, not two', async ({ request }) => {
  const payload = {
    cartId: process.env.CART_ID,
    idempotencyKey: 'test-key-001',
  }
  const first = await request.post('/api/checkout', { data: payload })
  const second = await request.post('/api/checkout', { data: payload })

  expect(first.status()).toBe(201)
  // The retry returns the same order, it does not create a new one
  const a = await first.json()
  const b = await second.json()
  expect(b.orderId).toBe(a.orderId)
})

How do you test the abandoned-checkout and timing cases?

The gap between "customer pays" and "your system records it" is where orders get stuck. Test the interruptions deliberately.

  • Close the tab mid-payment. The order must end clearly paid or clearly unpaid, never half-created, and the cart or stock hold must resolve.
  • Succeed the payment but fail the order write that follows, and confirm the system either retries the write or reverses the payment, so money and order stay in step.
  • Deliver a confirmation after a timeout, and confirm a late success is still recorded correctly rather than treated as a failure.
  • Deliver a success event and a later failure or refund out of order, and confirm the final state is correct.

How do you test totals, refunds and disputes?

These are arithmetic and state, so test them against a known answer.

  • Check totals with tax, discounts, shipping and currency rounding against a hand calculation; store money as integers so rounding is exact.
  • Issue a full refund and a partial refund, and confirm the order status, the stock and the customer email all update.
  • Raise a dispute or chargeback in test mode and confirm it creates a task for your team and a reversing entry, rather than being silently absorbed.
  • Test each payment method separately. Each has its own success, failure and refund behaviour: TheSkinProof, the founder's own venture built and run by RAITHub, runs four rails (bKash, Nagad, SSLCommerz and cash on delivery), and each needs its own cases.

How do you test reconciliation?

Reconciliation is the backstop that catches the cases the unit tests missed. Pull a provider report for a test day, match every line to one of your records, and flag anything unmatched on either side, including fees and timing differences. Introduce a deliberate mismatch and confirm the job reports it rather than balancing silently. The reconciliation design is in payment reconciliation software.

Free checklist

AI-Built App Launch Readiness Checklist

25 checks before you let real users in. Enter your email and we’ll reveal it below (and send you a copy).

One email, the checklist, no spam. By submitting you agree we can email you this checklist and reply to your enquiry.

What belongs in CI, and what do you check by hand?

Keep slow, flaky network calls out of the per-commit gate, and keep one real check for launch.

WhereWhat runs
Every pull requestIdempotency, totals, out-of-order events and refund logic, with the provider mocked
Nightly, in a sandboxA small suite against the real sandbox: success, decline, refund, one dispute
Before a payment releaseOne full browser checkout per method with test cards, including any extra authentication step
At launch, onceOne real low-value transaction in live mode, then a refund, to test the live configuration

For keeping third-party calls out of flaky browser tests, see fixing flaky end-to-end tests.

Buy, build or hire this testing?

OptionChoose this whenTrade-off
A hosted checkout with little of your own logicYour app does almost nothing when a payment arrivesLittle to test; you still test fulfilment and reconciliation
Your own test suiteA developer can own the failure-case table in CILowest cost to run; you design the interruption and reconciliation tests
A freelance tester before launchYou want one outside pass across methods and refundsTiming and reconciliation bugs are easy to miss; check their experience
A managed QA plan or a one-off pre-launch auditYou want the whole payment flow tested end to end before go-liveAn outside dependency; keep the tests in your repository

Doing it yourself is realistic: plan 2 to 4 days for a developer who knows the stack, more per extra payment method. The main risk of going alone is testing the successful payment and skipping the interruptions and reconciliation where money is actually lost.

Why RAITHub for this

  • Payment flows under real conditions are everyday work. PropDesk collects rent through Stripe inside a 1,024-test suite; TheSkinProof runs four payment rails with 750+ tests; PadhAI is designed for 9 gateways. RAITHub has also built a fintech dashboard for a client.
  • Tests written from the failure cases, so retries, abandoned checkouts and refunds are covered on every release, not left for launch week.
  • Honest limits. RAITHub has not shipped a regulated fintech product. Its security testing is application-level against OWASP guidance, not a CREST- or PCI-certified penetration test, and it holds no PCI DSS, SOC 2 or ISO 27001 certification. A PCI or compliance assessment needs a qualified assessor.

When you don't need us

  • You use a hosted checkout or payment links and your app does little when a payment arrives.
  • Your team already has the failure-case table in CI.
  • You need a PCI or compliance assessment; that needs a qualified assessor.

How RAITHub would test this

  • Scope: agree the payment methods, the fulfilment rules and the reconciliation source on a free 15-minute call.
  • Plan: a risk map built from the failure-case table, with a test for each outcome.
  • Test: idempotent retries, abandoned and out-of-order cases, totals, refunds and disputes per method, and a day of reconciliation with a deliberate mismatch.
  • Deliver: a ranked bug report with reproduction steps and a suggested fix, plus the failure-case tests as automated tests in your repository.
  • What you receive: the report, the tests and a handover note. IP is yours; an NDA is standard.

The audit is fixed-price, quoted in writing after the call. See QA as a Service, AI-built app testing, and the FinTech industry page. To book it, ask for a pre-launch QA audit.

Documentation checked on 10 October 2026.

Frequently asked questions

What payment failures should you test for?

Double charges, abandoned checkouts, paid-but-not-fulfilled and fulfilled-but-not-paid, wrong totals, refunds that do not reverse stock or access, ignored disputes, and records that do not reconcile. Test the failure cases first, since the successful payment rarely breaks.

How do you test that a customer is not charged twice?

Retry the checkout with the same idempotency key and assert a single charge and a single order, then repeat the confirming webhook and assert the order updates once. Networks retry, so an unprotected create call will charge twice.

What should happen if the tab is closed mid-payment?

The order must end clearly paid or clearly unpaid, never half-created, and any cart or stock hold must resolve. Test it by closing the tab during payment and checking the final order state and the stock.

Do you need to test each payment method separately?

Yes. Each method has its own success, failure and refund behaviour, so a case that passes for cards can fail for a wallet or a local gateway. Write success, decline and refund cases for every method you offer.

Why does reconciliation need its own test?

Because it catches what unit tests miss: fees, timing differences and disputes. Test it with a day of mixed transactions and a deliberate mismatch, and confirm the job reports unexplained differences rather than balancing silently.

Is this the same as a PCI assessment?

No. This is functional testing of your payment flow's behaviour against OWASP guidance. A PCI assessment is a formal compliance process that needs a qualified assessor. RAITHub tests behaviour and is not PCI, SOC 2 or ISO 27001 certified.

Can I just test payments manually a few times before launch?

A few manual runs miss the cases that actually lose money: a declined card, a timeout, a closed tab mid-payment, a duplicate webhook. These are hard to trigger by hand and easy to forget. Automated tests against the processor's test mode reproduce them on every build, so a regression surfaces before a customer hits it.

"I'm about to launch and I'm scared of double-charging customers or taking money without fulfilling the order. What payment tests matter most?"

Cover three things first: no double charge under retries or a duplicate webhook (use idempotency keys), no order marked paid without a confirmed payment, and a clean recovery when the customer closes the tab mid-flow. Then add reconciliation so your records and the processor's always agree. Those protect both the customer and your revenue.

Who should I hire to test my payment flows before launch?

Hire a team that tests the failure cases, double charges, closed tabs mid-payment, each method, and reconciliation, not just the happy path. RAITHub tests payment flows end to end this way. Note this is functional payment testing, not a PCI assessment, so it is not a compliance sign-off; arrange that separately where required.

Is manual testing a real alternative to automated payment tests before launch?

A few manual runs catch the obvious breaks, but they cannot reliably reproduce double-charge, timeout and reconciliation cases, or repeat before every release. Automated payment tests cover those failure paths consistently. Use manual exploratory checks plus automated tests for the money-moving flows, rather than relying on a handful of manual passes.

What is involved in end-to-end payment testing, and how long does it take?

The work is mapping each payment method and failure case, scripting tests for double charges, abandoned payments and refunds, and checking reconciliation. A focused pass over a typical checkout is usually days to a couple of weeks. You get tests you keep, run before each release, covering the cases that quietly cost money.

Can I just test payments manually a few times myself before going live?

You can for a quick sanity check, but the expensive failures, charging twice, taking money without fulfilling, or a payment that silently never reconciles, rarely show up in a few manual runs. Automate the failure cases so they are retested on every change. For flows that move real money, that repeatability is worth more than ad hoc manual checks.

payment testingcheckout testingpayment failure casesrefund testingreconciliation testingend to end testing

Ready to discuss your project?

Book a free 15-minute technical audit with our engineering team.