Stripe clone
Run checkout, subscriptions, invoices, and webhook-driven billing against a stateful Stripe twin — real error shapes, HMAC webhook signing, idempotency keys, and a virtual billing clock. No live card network; use test PANs only.
Clone ID: stripe · Binary: clones-stripe · SDK: stripe · Dashboard: Stripe explorer
Start here
| Question | Answer |
|---|---|
| Best for | Payment intents, subscriptions, invoices, checkout sessions, disputes, and webhook assertions |
| Connect with | @molar/clones SDK, STRIPE_API_BASE pointed at the clone, REST at {clone.url}/t/{runId}/v1/..., route mode for api.stripe.com, or MCP |
| Known limits | Compact vertical slice — unimplemented paths return 501; webhooks in-process only; org concurrent sandbox quota on hosted cloud |
| Seeds | Start empty, use control-plane empty / smoke / demo, or call seedData() with JSON (see below) |
Quick start
import { stripe } from "@molar/clones";
const clone = await stripe.start({ seed: "checkout-v1", runId: "run-stripe-01" });
await clone.seedData({
customers: [{ id: "cus_jane", email: "jane@example.com" }],
payment_methods: [{ pan: "4242424242424242", customer: "cus_jane" }],
});
const pi = await clone.createPaymentIntent({
amount: 2000, currency: "usd", customer: "cus_jane", payment_method: "pm_...", confirm: true,
});
await clone.advanceClock("30d");
await clone.expectWebhookFired("invoice.paid");
await clone.stop();
MCP tools
Use MCP for clock control, webhook assertions, and control-plane spawn. Use REST or the SDK for full Stripe resource CRUD.
| Tool | Description |
|---|---|
molar_clone_spawn | Spawn a Stripe session (kind: "stripe", optional seed) |
molar_clone_seed | Apply seed JSON to an existing run |
molar_clone_advance_clock | Advance virtual time on any clone admin endpoint |
stripe_advance_clock | Advance the Stripe billing clock (adminUrl, duration e.g. 30d) |
stripe_expect_webhook | Poll admin logs until an event type appears (eventType, timeout) |
molar_clone_snapshot / molar_clone_restore | Save or restore gzip snapshot bytes (base64) |
molar_clone_inject_error | Inject HTTP errors on a path for chaos testing |
Shared clone tools (molar_clone_list, molar_clone_route, world_snapshot, world_restore) work across vendors — see MCP server.
REST API coverage
The clone handles Stripe-compatible REST at {base}/t/{runId}/v1/... with Authorization: Bearer sk_test_stripe_{runId}.
| Resource | Operations |
|---|---|
/v1/customers | Create, list, get, update, delete |
/v1/payment_methods | Create, list, get, attach, detach |
/v1/payment_intents | Create, list, get, confirm, capture, cancel (incl. deterministic 3DS on 4000002500003155) |
/v1/charges | List, get |
/v1/refunds | Create, list, get |
/v1/subscriptions | Create, list, get, update, cancel |
/v1/invoices | Create, list, get, finalize, pay, void |
/v1/checkout/sessions | Create, get (hosted checkout stub) |
/v1/webhook_endpoints | Create, list, delete |
/v1/disputes | List, get, close; force via POST /_clone/stripe/dispute |
Idempotency keys on POST are cached for 24h (replay and mismatch behave like Stripe).
If your app uses the official Stripe SDK, point STRIPE_API_BASE at the clone URL (see API & SDK).
Seeds and initial state
Molar does not ship named Stripe fixture files like billing-v1. You have three ways to set starting state:
| Approach | When to use |
|---|---|
| Empty tenant | Default — no customers or products |
| Control-plane seeds | Hosted spawn with empty, smoke, or demo (generic placeholder resources for smoke tests) |
seedData() JSON | Full control — customers and payment methods before your test runs |
await clone.seedData({
customers: [{ email: "alice@example.com", name: "Alice" }],
payment_methods: [{ pan: "4242424242424242", customer: "cus_..." }],
});
The seed string on start() still matters: it drives deterministic IDs and webhook secrets via HKDF. Dashboard presets (for example Checkout Stripe + Receipt) use seed names like checkout-world as labels — pair them with seedData() or scenario setup steps.
Reset a tenant: POST /_clone/reset/{runId} on the admin URL, or spawn a fresh runId.
Test cards
| PAN | Behavior |
|---|---|
4242424242424242 | Succeeds |
4000000000000002 | card_declined |
4000002500003155 | requires_action (deterministic 3DS redirect) |
Limits
| Limit | Detail |
|---|---|
| Concurrent clones | Org quota enforced by Molar API (429 when exceeded) |
| Webhook delivery | In-process only — no outbound internet; use expectWebhookFired or admin poll |
| Clock advance | Subscription renewals and invoice finalization fire on current_period_end |
| Chaos presets | card_declined, rate_limit, idempotency_conflict, webhook_delay (dashboard + injectError) |
Authentication
Authorization: Bearer sk_test_stripe_<runId>
{clone.url}/t/{runId}/v1/...
Use clone.testKey and clone.publishableKey from the SDK.
Environment variables
MOLAR_CLONE_STRIPE_URL, MOLAR_CLONE_STRIPE_ADMIN_URL, STRIPE_SECRET_KEY, STRIPE_API_BASE
See Troubleshooting for 501 gaps and connection errors.