Three tools, three different questions
Each answers a different question, and the confusion comes from asking one of them a question another answers.
| Stripe test mode | Stripe CLI | stripe-mock | Finxture | |
|---|---|---|---|---|
| Choose the event order? | Not guaranteed | One trigger at a time | No events | Yes — an authored plan |
| Repeat one delivery? | Only by chance | Yes — events resend |
No events | Yes — it is a scenario |
| Same result on a re-run? | New identifiers | New identifiers | Stateless, nothing varies | Same ids, same bodies |
| Stage fraud or 3DS? | Yes — the only one | No — it fires events | No — validates, forgets | Not today |
| Answer an API read? | Yes — the real API | Yes — drives the real API | Yes — no state | No API at all |
| Account or network? | Account, and a browser | Account, live connection | Neither | Neither — localhost |
What each one is for
Stripe test mode
The concession first, and it is large: a
sandbox is isolated, and its
test cards cover far more than they get
credit for — declines by reason code, disputes, refunds, 3D Secure.
4100000000000019 produces a charge
Radar blocks whatever your rules say;
4000000000003220
forces a 3DS2 challenge. Anyone who says a provider sandbox cannot do fraud or
3DS is out of date; this is the only tool here that can.
Where it stops is control: every outcome is chosen by the value you put in, and that input has to reach Stripe through a browser.
The Stripe CLI
stripe listen forwards events to a local
address over a direct connection to Stripe’s API — no Dashboard endpoint, and a
signing secret that survives restarts. For getting a genuine, correctly signed
event into a handler on port 4242, it is very good.
stripe trigger is not “fake” either:
its events come from real API objects created by real HTTP requests, so related
events fire too. stripe fixtures
scripts a multi-step flow, and
stripe events resend gives a
genuine duplicate delivery — worth knowing before anyone sells you one.
What it does not give is a sequence you chose.
stripe-mock
A mock HTTP server built on Stripe’s OpenAPI specification: it accepts the same parameters, rejects the ones it does not recognize, and needs no account. Right for a CI job asking whether your client calls the right URL.
Its README is blunt about its own boundary: basic sanity checks, and if you need more, test against testmode instead. It answers API calls rather than delivering events — not a gap someone found but a different job.
What none of them stages: the awkward middle
What falls through is the difficult part of a payment — an antifraud decision, a 3DS challenge, where the question is not whether the event arrived but what your system decided, and whether the row it wrote is right. The CLI fires an event; it does not stage a problem. Test mode stages it, but only through a browser and a real account a test suite cannot drive.
Finxture does not run those scenarios either. If you came for a tool that reproduces a 3DS challenge deterministically, nobody here has one.
Where Finxture fits
Narrower than any of them, and aimed at one thing: run a corner case on purpose against your own localhost, then check what your database holds.
A scenario is an authored, reviewed definition executed through a versioned
Stripe adapter and delivered to a loopback address you approved. Take
duplicate delivery: the same projected
payment_intent.succeeded arrives twice, same raw body, each with its own valid
signature — something you can assert on.
What is runnable is the list, and it grows; the value being built here is the list rather than the runner. For the narrower question — a webhook onto localhost without a tunnel — there is a different page.
The problem underneath: two providers, one row in your database
This does not come from Stripe — I have used Stripe once, on my own project. It is the first adapter because it is what everyone reaches for first, not where the pain was.
The pain was dLocal, Braintree, and local banks like Emirates NBD, and it never began inside one provider but at the seam, where two doing the same job met in one scenario: the same real-world event has to leave the same artifacts in your database whichever one handled it. Finxture cannot do that today — one adapter, one provider per run. Motivation, not a feature.
When not to use Finxture
Two cases, stated plainly, because a comparison that ends in a call to action has not told you anything.
Your integration is very small. One payment method, one flow, a handful of orders a day. Opening the provider’s dashboard now and then and looking at what happened with your own eyes is genuinely easier than installing anything, and it will keep being easier. Do that instead.
Your test coverage is already mature. If the corner cases are covered by something that works — fixtures you trust, a test double you built, a suite that has caught real regressions — then a second way of producing the same events is a migration, not an upgrade, and migrations always cost more than they look like they will.
The floor underneath both of those is the honest close. If the product is not for you, the set of cases probably still is, once it is complete. Read the list, check your own tests against it, and if there is a row on it you have never exercised, that is worth knowing whatever you end up using to fix it.