7 silent payment flow bugs I've found before fintech launches
The bugs that pass code review, pass QA, and still cost fintechs five figures within a week of launch. Each with the test case that would have caught it.
Fintech bugs cost more than other bugs. A broken signup form is embarrassing. A broken checkout button is lost revenue. But a bug that silently charges a user twice, or lets a transfer go through without authorization, or quietly drops decimals in an EUR→USD conversion — that's a regulatory incident, a customer trust collapse, and a potential six-figure refund operation.
Over the last three years I've audited payment flows for fintechs, e-commerce platforms, and marketplaces across Europe, Turkey, and the US. These are the 7 bugs that keep showing up — the ones that pass code review, pass internal QA, and still slip through to production.
If you're launching a fintech product in the next 90 days, treat this as the pre-launch checklist I wish every founder would use.
(For client confidentiality, every example below has been anonymized and the specifics altered.)
Bug #1 — The Stripe webhook race condition
What happens: A user clicks "Pay." Stripe processes the charge successfully. Stripe fires the payment_intent.succeeded webhook to your backend. Your backend marks the order "paid." But Stripe also sends the user back to your success URL — and that handler *also* tries to mark the order "paid." Depending on timing, you can end up with duplicate side effects: double-sent confirmation emails, inventory decremented twice, points credited twice.
Why it slips through: In staging, Stripe's test mode fires events too fast or too slow to reproduce the race. Everything works in dev. In production, with real latency, it fires.
The test case: Simulate a 2-3 second delay on the webhook endpoint. Watch for duplicate side effects. The fix is idempotency keys on every side-effect handler.
Bug #2 — The 3DS redirect loop
What happens: Your user is on a mobile browser. They tap "Pay." The 3D Secure challenge opens in a new tab (or an iframe, worse). They authenticate, come back — and the original tab shows a loading spinner forever because the return URL fired after the window regained focus and your state had been cleared.
Why it slips through: On desktop Chrome in staging, the flow works perfectly. The bug only shows up on actual mobile Safari, where iframe handling and tab state are different.
The test case: Complete every payment flow on a real iPhone with Safari, not a desktop emulator. Test with 3DS test cards that force a challenge every time.
Bug #3 — Decimal rounding in currency conversion
What happens: You're charging in EUR but your backend stores USD internally. The conversion price * 1.0847 creates a float like 43.5094819.... You truncate to 2 decimals and store 43.50. Next time you refund the user, you refund 43.50 USD → convert back to EUR → user receives €40.09 instead of €40.10. One cent lost. Multiply by 50,000 refunds a year.
Why it slips through: Unit tests use round numbers like $100.00. The bug only shows up with messy real-world amounts.
The test case: Test with amounts like €43.57, £22.03, $19.99 across every currency conversion. Any variance between charge and refund amount is a bug.
Bug #4 — The partial 2FA bypass
What happens: Login flow requires email + password + 2FA. User enters email and password. Backend creates a *partial session* (just a flag) while waiting for 2FA. User abandons the tab. Attacker gets the user's cookie somehow (XSS, device sharing, cookie theft). The attacker lands on the 2FA screen with the partial session cookie — but a specific API endpoint, like /api/profile, checks only the partial session flag and returns full profile data.
Why it slips through: The 2FA screen itself is secure. The developer didn't think "what else is behind a partial session?"
The test case: List every API endpoint. For each, test with a partial session cookie. Any endpoint that returns real data before full 2FA completion is a critical bug.
Bug #5 — The silent card storage downgrade
What happens: User adds a card for recurring billing. Your frontend sends it to Stripe for tokenization — but a developer added error handling that, on failure, posts the card to your own backend "temporarily" to retry later. Now you're storing raw card data without PCI DSS scope, which means a leak = catastrophic.
Why it slips through: The code path only executes on tokenization failures, which are rare in testing. Everything looks compliant in logs.
The test case: Force the Stripe tokenization to fail (invalid API key, network block, Stripe test error cards). Monitor every outgoing network request. Nothing with raw card data should ever leave the browser without going to a PCI-compliant tokenizer.
Bug #6 — The webhook replay vulnerability
What happens: Your Stripe webhook endpoint doesn't verify the signature header. An attacker who finds the endpoint URL (easy — it's often in frontend bundle logs or old emails) can POST a fake payment_intent.succeeded event with any amount and any customer ID. Your backend marks an order "paid" that was never actually paid.
Why it slips through: Everything works for legitimate webhook events because Stripe does sign them. The verification code was just forgotten.
The test case: POST a crafted webhook payload without a valid signature. If your backend accepts it and processes the "payment," you have a critical bug. All webhook endpoints should return 403 for unsigned requests.
Bug #7 — The timezone-based double charge
What happens: Your billing system runs on UTC. A user in Istanbul (UTC+3) signs up at 11pm their time, which is 8pm UTC. Billing runs daily at midnight UTC. First charge: at 4am Istanbul time the next day. But your "trial ends in 7 days" messaging to the user said 11pm. The user, seeing their trial still active, uses the product. Then at midnight UTC on day 7, they're charged. Then on day 7 at 11pm Istanbul time (which is 8pm UTC next day), the "trial end" job runs again and charges them a second time.
Why it slips through: Everyone on the team is in the same timezone and never notices the drift. Unit tests use new Date() without a reference timezone.
The test case: Create test users in 5+ different timezones (UTC-8, UTC-3, UTC+0, UTC+3, UTC+9). Run the full billing lifecycle. Any discrepancy between the user's perceived timeline and what they're actually charged is a bug.
The lesson behind all 7
Every bug on this list has three things in common:
1. The code works in dev. It only breaks under production conditions — latency, real devices, real data, real timezones. 2. Code review wouldn't catch it. The individual lines look correct. The bug lives in the interaction between systems. 3. The regulatory consequences are worse than the bug itself. Each one, in production, has triggered either a PCI incident, a data breach notification, or a mass refund operation at a company I've worked with.
This is why fintech QA is different from generic QA. You're not testing whether the button works. You're testing whether the money math holds up when systems lie, networks delay, and users do unexpected things.
A Fintech QA Audit at Testopius specifically covers these failure modes — payment flows, 2FA/KYC, webhook security, decimal rounding, timezone edge cases. If you're planning a launch in the next 90 days, it's cheaper to catch these before go-live than after.
Frequently asked questions
How long does a fintech payment flow audit take?
Can you test our Stripe setup without Stripe test credentials?
Does an audit cover compliance (PCI DSS, PSD2) or just testing?
What test tools do you use for fintech-specific testing?
Need this done for you?
I test web, mobile, and desktop apps for startups. Fixed prices, fast turnaround.
See packagesRelated posts
AI agents are rewiring QA in 2026 — the winners, the losers, and what I'm doing differently
Playwright MCP, Browser-use, and Claude Code have reshaped what manual QA actually does. Here's who's winning in this shift, who's getting squeezed out, and the workflow I've adopted after 6 months of real client work.
10 bugs I always find in signup flows
The same 10 signup bugs show up in every product I audit. Fix these before your first user hits register.