{"_id":"@volter/twin-stripe","_rev":"3-a3289a35f36dcc9ff49d52ce834d1f76","name":"@volter/twin-stripe","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.0":{"name":"@volter/twin-stripe","version":"0.1.0","keywords":["twin","local","mock","mirror","simulator","fixtures","testing","sdk","api","localstack","stripe","payments"],"author":{"url":"https://github.com/volter-ai","name":"Volter"},"license":"Apache-2.0","_id":"@volter/twin-stripe@0.1.0","maintainers":[{"name":"volter-oliverio","email":"oliver@volter.ai"},{"name":"aaronvolter","email":"aaron@volter.ai"}],"homepage":"https://github.com/volter-ai/twin/tree/main/packages/twin/stripe#readme","bugs":{"url":"https://github.com/volter-ai/twin/issues"},"bin":{"world-stripe":"src/cli.ts"},"dist":{"shasum":"83c3c2dbdd4ca18f71494e9e190db97f33aa01a3","tarball":"https://registry.npmjs.org/@volter/twin-stripe/-/twin-stripe-0.1.0.tgz","fileCount":19,"integrity":"sha512-15v7jMgwn0vgS8eyLMCq4Ya9ngmYySBgHCR/Kht9HZGF+D8whwr1Abqy/Yrssm4EEQyBDRSkTuuS2336WOfvEg==","signatures":[{"sig":"MEQCIDxfYqettemJ6S61gwi7GaP6Ioo1UODWd5UzHDpv03HhAiBvltzQpNLoDd/FS15u6ey7Za+Ow23Iqo5eLS+PvwbPKg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":866397},"type":"module","engines":{"bun":">=1.2.0"},"exports":{".":"./src/index.ts"},"scripts":{"test":"bun test src/*.test.ts","typecheck":"tsc --noEmit"},"_npmUser":{"name":"aaronvolter","email":"aaron@volter.ai"},"repository":{"url":"git+https://github.com/volter-ai/twin.git","type":"git","directory":"packages/twin/stripe"},"_npmVersion":"11.12.1","description":"Local Stripe twin — a faithful, stateful local Stripe API your real `stripe` SDK talks to unmodified. Mirror, simulate, and fork. Built on @volter/twin.","directories":{},"_nodeVersion":"23.9.0","dependencies":{"react":"^19.2.7","react-dom":"^19.2.7"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"stripe":"^22.2.1","@types/bun":"^1.2.20","typescript":"^5.9.0","@types/node":"^24.0.0","@types/react":"^19.2.17","@volter/twin":"0.1.0","@types/react-dom":"^19.2.3","@volter/twin-tooling":"0.1.0"},"peerDependencies":{"@volter/twin":"0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/twin-stripe_0.1.0_1783762339751_0.35559163371985236","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@volter/twin-stripe","version":"0.1.1","keywords":["twin","local","mock","mirror","simulator","fixtures","testing","sdk","api","localstack","stripe","payments"],"author":{"url":"https://github.com/volter-ai","name":"Volter"},"license":"Apache-2.0","_id":"@volter/twin-stripe@0.1.1","maintainers":[{"name":"volter-oliverio","email":"oliver@volter.ai"},{"name":"aaronvolter","email":"aaron@volter.ai"}],"homepage":"https://github.com/volter-ai/twin/tree/main/packages/twin/stripe#readme","bugs":{"url":"https://github.com/volter-ai/twin/issues"},"bin":{"world-stripe":"src/cli.ts"},"dist":{"shasum":"134bfa76120fcbe082c732c9e27739185d4632a3","tarball":"https://registry.npmjs.org/@volter/twin-stripe/-/twin-stripe-0.1.1.tgz","fileCount":22,"integrity":"sha512-gaO/nviutZB8aG+7PSWJqrzyIBiv84V6/aSyWNsbRCU3TD7bznyAGxVnlYJsflImEUWAi6WZj8j6vTjsupM49g==","signatures":[{"sig":"MEYCIQDrvUFc3rUdBr3VhfbymlOZ2U4V6fffnTe8vM0xmhZg+wIhAO6sR4I206N3q+PzFzUlc6sExqb9uCU3EIoSsCvGyrUQ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1173615},"type":"module","engines":{"bun":">=1.2.0"},"exports":{".":"./src/index.ts"},"gitHead":"e87d5438eeca672238616056384e566748d3b78e","scripts":{"test":"bun test src/*.test.ts","typecheck":"tsc --noEmit"},"_npmUser":{"name":"aaronvolter","email":"aaron@volter.ai"},"repository":{"url":"git+https://github.com/volter-ai/twin.git","type":"git","directory":"packages/twin/stripe"},"_npmVersion":"11.19.0","description":"Local Stripe twin — a faithful, stateful local Stripe API your real `stripe` SDK talks to unmodified. Mirror, simulate, and fork. Built on @volter/twin.","directories":{},"_nodeVersion":"26.8.1","dependencies":{"react":"^19.2.7","react-dom":"^19.2.7"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"stripe":"^22.2.1","@types/bun":"^1.2.20","typescript":"^5.9.0","@types/node":"^24.0.0","@types/react":"^19.2.17","@volter/twin":"0.1.0","@types/react-dom":"^19.2.3","@volter/twin-tooling":"0.1.0"},"peerDependencies":{"@volter/twin":"0.1.1"},"_npmOperationalInternal":{"tmp":"tmp/twin-stripe_0.1.1_1788656645438_0.3020785331340725","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@volter/twin-stripe","version":"0.1.2","description":"Local Stripe twin — a faithful, stateful local Stripe API your real `stripe` SDK talks to unmodified. Mirror, simulate, and fork. Built on @volter/twin.","keywords":["twin","local","mock","mirror","simulator","fixtures","testing","sdk","api","localstack","stripe","payments"],"author":{"name":"Volter","url":"https://github.com/volter-ai"},"license":"Apache-2.0","publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/volter-ai/twin.git","directory":"packages/twin/stripe"},"homepage":"https://github.com/volter-ai/twin/tree/main/packages/twin/stripe#readme","type":"module","exports":{".":"./src/index.ts"},"bin":{"world-stripe":"src/cli.ts"},"scripts":{"test":"bun test src/*.test.ts","typecheck":"tsc --noEmit"},"dependencies":{"react":"^19.2.7","react-dom":"^19.2.7"},"peerDependencies":{"@volter/twin":"^0.1.2"},"devDependencies":{"@volter/twin":"0.1.0","@volter/twin-tooling":"0.1.0","@types/bun":"^1.2.20","@types/node":"^24.0.0","@types/react":"^19.2.17","@types/react-dom":"^19.2.3","stripe":"^22.2.1","typescript":"^5.9.0"},"engines":{"bun":">=1.2.0"},"gitHead":"9f13128ca1f6b4724e63356badd7679789ecc11c","_id":"@volter/twin-stripe@0.1.2","bugs":{"url":"https://github.com/volter-ai/twin/issues"},"_nodeVersion":"26.8.1","_npmVersion":"11.19.0","dist":{"integrity":"sha512-tXdTagWlU+W9TD8WQ3613uMw6pbheDeZ13PH2jhtdt4KhVxCwwr22YQ6zGq49+hAVe0hXyY2PXIrz3OHwYNaRw==","shasum":"4a12065df4d74dddb3943cd9863a5d0eeb55021e","tarball":"https://registry.npmjs.org/@volter/twin-stripe/-/twin-stripe-0.1.2.tgz","fileCount":22,"unpackedSize":1173616,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIHDQPjbNqs5ySf73II1nivcxXwrl6k5BF/+lVMRGrZmLAiEA/2CgxsyB5f5aVAEmVObPAHfSIq5HwzP+UZFNBV4HOm0="}]},"_npmUser":{"name":"aaronvolter","email":"aaron@volter.ai"},"directories":{},"maintainers":[{"name":"volter-oliverio","email":"oliver@volter.ai"},{"name":"aaronvolter","email":"aaron@volter.ai"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/twin-stripe_0.1.2_1788657099656_0.2203853309366941"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-11T09:32:19.604Z","modified":"2026-09-06T01:11:40.014Z","0.1.0":"2026-07-11T09:32:19.911Z","0.1.1":"2026-09-06T01:04:05.583Z","0.1.2":"2026-09-06T01:11:39.858Z"},"bugs":{"url":"https://github.com/volter-ai/twin/issues"},"author":{"name":"Volter","url":"https://github.com/volter-ai"},"license":"Apache-2.0","homepage":"https://github.com/volter-ai/twin/tree/main/packages/twin/stripe#readme","keywords":["twin","local","mock","mirror","simulator","fixtures","testing","sdk","api","localstack","stripe","payments"],"repository":{"type":"git","url":"git+https://github.com/volter-ai/twin.git","directory":"packages/twin/stripe"},"description":"Local Stripe twin — a faithful, stateful local Stripe API your real `stripe` SDK talks to unmodified. Mirror, simulate, and fork. Built on @volter/twin.","maintainers":[{"name":"volter-oliverio","email":"oliver@volter.ai"},{"name":"aaronvolter","email":"aaron@volter.ai"}],"readme":"# @volter/twin-stripe\n\nThe **Stripe twin** — a local, spec-correct replica of the Stripe REST API on the\nshared [`@volter/twin`](../control-plane) kernel. The real `stripe` SDK works against it\nunmodified; it's the QA stack's authoritative local Stripe (it replaced the old\nhand-made in-process mock).\n\n## Surface\n\n- **REST API** (`stripe-twin.ts` → `handleStripeTwinRequest`; HTTP wrapper\n  `stripe-server.ts` → `createStripeTwinServer`): charges, customers,\n  payment_intents, setup_intents, payment_methods, products, prices, invoices,\n  invoiceitems, subscriptions, refunds, coupons, promotion_codes, identity\n  verification sessions, ephemeral_keys, checkout sessions, billing/customer-portal\n  sessions + configurations.\n- **Writes** are local transactions (action log); reads are the projection (R3/R5/R18).\n- **Card declines** (`stripe-twin.ts`): Stripe's documented test cards get vendor-faithful\n  outcomes on charge create / PaymentIntent confirm — `4242…4242` (and `pm_card_visa`/`tok_visa`)\n  succeed; `4000…0002`, `4000…9995`, `4000…0069`, `4000…0127`, `4000…0119` (and the matching\n  `pm_card_*`/`tok_*` tokens) return the real `card_error` envelope (HTTP 402) and leave a\n  confirmed PaymentIntent at `requires_payment_method`. Unknown cards succeed. See\n  `_card.declines-test-set-only` in `stripe-known-deviations.json`.\n- **Idempotency keys**: the `Idempotency-Key` header is honored on POST — a replay with the\n  same key returns the stored response without re-applying the write (`_idempotency.stored-per-root`).\n- **Events** (`stripe-events.ts`): fires Stripe `event` objects on write\n  (`payment_intent.succeeded`, `customer.subscription.created`, `invoice.paid`, …).\n- **Conformance** (`stripe-conformance.ts`): field name + type checked vs Stripe's\n  real OpenAPI (standing gate).\n- **UI mirror** (`stripe-mirror-ui.ts`): a Stripe-dashboard-style React app over the\n  twin's own REST API.\n\n## CLI\n\n```bash\nworld-stripe serve [--read-only] [--port N] [--root DIR]\nworld-stripe mirror [--port N] [--root DIR]\nworld-stripe conformance [--fields FILE] [--root DIR]\n```\n\n`serve` defaults to `simulator` (the stack uses the twin as a writable Stripe).\nPoint the real `stripe` SDK at it with `{ host, port, protocol: 'http' }`.\n\n## Interaction surfaces\n\n1. **SDK/API** — *zero edits (preferred):* `STRIPE_TWIN_URL=http://127.0.0.1:PORT node --require @volter/twin/inject your-app` redirects the real `stripe` SDK from `api.stripe.com` to the twin (`cookbook/zero-edit-inject`). For the browser too: `volter-twin proxy --target <app> --map stripe=http://127.0.0.1:PORT` (browser + backend share one twin). *Or* override directly: `new Stripe(key, { host: '127.0.0.1', port: PORT, protocol: 'http' })`.\n2. **API + CLI** — `world-stripe serve` (writable) + drive with `volter-twin status|plan|refs stripe`, then push.\n3. **Read-only** — `world-stripe serve --read-only`: unlimited local reads, no rate limits; writes refuse like Stripe (4xx).\n4. **UI mirror** — `world-stripe mirror` renders a Stripe-dashboard-style view of the twin's state.\n\n(See Getting Started → \"Twin interaction surfaces\".)\n\nStable on the twin rubric: fidelity, read/write/fork, sync, observability, event emission, and conformance are tracked with explicit coverage gaps.\n\n## Coverage\n\nGoal: **honest, explicitly tracked coverage of Stripe's core feature surface.** The only\naccepted carve-outs are the explicit **out-of-scope** items listed below. Anything not done\nor carved out is a gap to close.\n\n**Done** — core resources: **customers**, **charges**, **payment_intents** (+ confirm),\n**setup_intents** (+ confirm), **payment_methods** (+ detach), **subscriptions** (create/\nupdate/cancel), **prices**, **products**, **invoices** (+ finalize/pay/void), **invoiceitems**,\n**refunds**, **disputes** (+ update/close), **payouts** (+ cancel), **balance_transactions**,\n**balance** (synthesized from the ledger), **events** (Events API list/retrieve),\n**Checkout Sessions** (create/retrieve/list/`line_items`/expire + modeled completion that\ncreates+links a payment_intent/subscription/setup_intent; `amount_total` computed from\nline_items), **Customer Portal** (`billing_portal` sessions + configurations create/retrieve/\nlist/update), **Connect** (connected **accounts** create/retrieve/list/update/delete +\n`login_links`, and **transfers** platform→connected-account create/retrieve/list filtered by\ndestination; a new account starts un-onboarded with charges/payouts disabled + a `requirements`\nhash), plus **ephemeral_keys**, **identity verification_sessions**, **file_links**\n(synthesized) and **coupons**/**promotion_codes** (retrieve/list, seed-only). Cursor pagination\n(`limit`/`starting_after`/`ending_before` + `has_more`); per-resource list filters; `expand[]`\non the modeled paths; vendor-faithful **test-card declines** (`4242…` succeeds; documented\ndecline PANs + `pm_card_*`/`tok_*` tokens return the real `card_error` 402); **idempotency keys**\n(`Idempotency-Key` replay, no re-write); **events/webhooks** (`stripe-events.ts`); spec\n**conformance** gate; **UI mirror** (rung-5 ✅, `client/stripe-mirror.tsx` + structure check);\n**connector** pull (customers + subscriptions) + push (create/update/cancel/confirm/detach/\nfinalize/pay/void/close to the real REST surface).\n\nThis cycle added: **ACH/SEPA micro-deposit verification** (`payment_intents`/`setup_intents`\n`/verify_microdeposits` — confirm with `pm_us_bank_account` → `requires_action` with a\n`verify_with_microdeposits` next_action, then the 32/45 amounts or `SM11AA` descriptor →\n`succeeded`), **Customer.list_payment_methods** (`/v1/customers/:id/payment_methods`),\n**subscription items** CRUD (create/retrieve/list/update/delete, kept in sync with the parent\nsub's `items` list), **subscriptions/invoices search**, **invoice line items**\n(`/lines` + `add_lines`/`update_lines`/`remove_lines` with recomputed totals), **invoice\nedge-action gating** (finalize/pay/mark_uncollectible/void state machine), **Tax transactions**\n(`create_from_calculation` + `create_reversal` with negated lines), **quote** cancel-from-draft/\nopen lifecycle gates, **customer + subscription discount delete**, **promotion-code update**\n(active toggle), **charge fraud-marking** (`fraud_details[user_report]`), and the **Radar**\nfamily — **Reviews** (`/approve`), **Value Lists** (+ items), **Rules** — plus a **Radar\ndashboard screen** in the UI mirror.\n\n**Issuing** (modeled) — cardholders/cards/authorizations/transactions/disputes + test helpers\n(present authorization, capture, force capture, fund_balance), including the **real-time\nauthorization leg**: an endpoint enrolled for `issuing_authorization.request` receives the\nsigned request event **synchronously** at present time and its JSON response\n(`{approved, amount?}`) decides the authorization within Stripe's 2-second window (timeout →\ndeclined `webhook_timeout`, invalid response → `webhook_error`); **spending_controls**\n(category/country allow+block lists, per_authorization/all_time/calendar-window spending\nlimits) are enforced ahead of the webhook with the vendor's `spending_controls` decline\nreason; and **captures debit the issuing balance** (`balance_transaction` type\n`issuing_transaction`, `balance_type: 'issuing'`, served in `GET /v1/balance`'s `issuing`\nsection). Remaining issuing gaps (authorization holds, expire/increment/reverse test\nhelpers) are tracked todos in `stripe-capabilities.ts`. **Terminal**\n(locations/readers/connection tokens + process_payment_intent) is likewise modeled — the old\n\"Planned\" listing for both families was stale.\n\n**Planned** (known-missing, will do) — **Treasury**\n(financial accounts), **Climate**, **Financial Connections**, **Entitlements**, **Billing**\ncredit grants + usage alerts, **Connect** remaining surfaces (account sessions, connected-account\npayouts, top-ups, payout reverse), **Reporting/Sigma**, the legacy **Sources** API, and\nadditional list endpoints/filters as needed. (See `stripe-capabilities.ts` for the full honest\ntodo list — every entry not explicitly out-of-scope is a tracked gap.)\n\n**Out of scope** (deliberately not modeled, with reason) —\n- Pixel-rendering Stripe-**hosted** Checkout / Customer Portal *pages*: the hosted HTML is\n  Stripe's, not an API object — the twin models the **Session/API object + redirect `url`**\n  instead (now **Done**; the page pixels stay out of scope). See\n  `_checkout.hosted-page-pixels` in `stripe-known-deviations.json`.\n- Real **money settlement / bank movement** (proposed — pending owner approval): actually moving\n  funds is real-world infra, not the API — the payout/balance_transaction/balance *objects* and\n  their lifecycle are modeled.\n\n## Rate budget — the fail-closed backstop on live calls\n\n`liveStripeExecute` is the **one place** this pack issues a live request, so every call it makes is charged\nagainst a persistent, fail-closed spend ledger **before** the request goes out. Past the ceiling, or\nwhile a `Retry-After`/429 cooldown is armed, it **throws instead of calling**. The ledger is keyed by\nvendor and a hash of the credential (limits are per credential, so it is deliberately *not*\ncwd-scoped) and persists across processes, so a fresh process does not get a fresh allowance; a\ncorrupt ledger counts as a **full** window rather than zero spend. There is no option to disable it,\nand no value you can pass for `budget` that yields an unguarded client — an injected budget is\nvalidated by *method identity*, so a subclass or a `Proxy` that replaces `checkBudget` is refused.\n\nThe declared numbers: **120 weighted units / 60s** = 2 requests/second — 2% of Stripe's documented 100/s live mode and 8% of the 25/s a sandbox key or any single endpoint gets. `POST`/`DELETE` cost 2 (not a published ratio — a judgement call about blast radius: a write creates a charge, refund or receipt e-mail and cannot be taken back) and `POST /v1/payouts` costs 5 (documented at 15 creates/second). Stripe's own window is a **second** while this one is a minute, so an intra-second burst reaches Stripe's limiter first; the 429 cooldown is the backstop for that shape.\n\nThe mechanism is **shared and vendor-agnostic** — it lives in the kernel (`@volter/twin` →\n`control-plane/src/rateBudget.ts`); what lives here in [`src/stripe-budget.ts`](src/stripe-budget.ts) is this vendor's\n**declaration** (window, ceiling, per-endpoint weights, and a `reason` citing the limits above) plus\nthe vendor-bound `StripeBudget`. The rule is ratified as\n[ARCHITECTURE.md](../../../ARCHITECTURE.md) **D8**, and the kernel module's header documents what the\nguard does *not* guarantee — read that before trusting it.\n","readmeFilename":"README.md"}