Back to BlogQuality & Testing

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

Rupak Amin

Founder & Lead Engineer, RAITHub

9 min read

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.

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.

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.