@volter/twin-stripe 0.1.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -27
- package/client/dashboard-api.ts +286 -0
- package/client/stripe-mirror.css +272 -159
- package/client/stripe-mirror.tsx +1384 -541
- package/dist/client/dashboard-api.d.ts +107 -0
- package/dist/client/dashboard-api.js +238 -0
- package/dist/client/dashboard-api.ts +286 -0
- package/dist/client/stripe-mirror.bundle.js +236 -0
- package/dist/client/stripe-mirror.css +275 -0
- package/dist/client/stripe-mirror.d.ts +134 -0
- package/dist/client/stripe-mirror.js +823 -0
- package/dist/client/stripe-mirror.tsx +1534 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +39 -0
- package/dist/src/generated/events.gen.json +1 -0
- package/dist/src/generated/surface.gen.json +1 -0
- package/dist/src/generated/ui.gen.json +1 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.js +73 -0
- package/dist/src/manifest.d.ts +2 -0
- package/dist/src/manifest.js +1065 -0
- package/dist/src/screens/checkout.d.ts +31 -0
- package/dist/src/screens/checkout.js +241 -0
- package/dist/src/screens/consent-skin.d.ts +4 -0
- package/dist/src/screens/consent-skin.js +18 -0
- package/dist/src/screens/financial-connections.d.ts +5 -0
- package/dist/src/screens/financial-connections.js +90 -0
- package/dist/src/screens/identity.d.ts +5 -0
- package/dist/src/screens/identity.js +86 -0
- package/dist/src/screens/industries.d.ts +1 -0
- package/dist/src/screens/industries.js +267 -0
- package/dist/src/screens/onboarding.d.ts +13 -0
- package/dist/src/screens/onboarding.js +225 -0
- package/dist/src/screens/portal.d.ts +5 -0
- package/dist/src/screens/portal.js +214 -0
- package/dist/src/screens/public-details.d.ts +5 -0
- package/dist/src/screens/public-details.js +90 -0
- package/dist/src/semantics/after-payment.d.ts +22 -0
- package/dist/src/semantics/after-payment.js +93 -0
- package/dist/src/semantics/apps-secrets.d.ts +2 -0
- package/dist/src/semantics/apps-secrets.js +54 -0
- package/dist/src/semantics/balance.d.ts +11 -0
- package/dist/src/semantics/balance.js +195 -0
- package/dist/src/semantics/billing.d.ts +2 -0
- package/dist/src/semantics/billing.js +220 -0
- package/dist/src/semantics/charges.d.ts +28 -0
- package/dist/src/semantics/charges.js +201 -0
- package/dist/src/semantics/checkout.d.ts +15 -0
- package/dist/src/semantics/checkout.js +303 -0
- package/dist/src/semantics/connect.d.ts +5 -0
- package/dist/src/semantics/connect.js +476 -0
- package/dist/src/semantics/coupons.d.ts +6 -0
- package/dist/src/semantics/coupons.js +92 -0
- package/dist/src/semantics/credit-notes.d.ts +2 -0
- package/dist/src/semantics/credit-notes.js +172 -0
- package/dist/src/semantics/customers.d.ts +6 -0
- package/dist/src/semantics/customers.js +429 -0
- package/dist/src/semantics/disputes.d.ts +2 -0
- package/dist/src/semantics/disputes.js +51 -0
- package/dist/src/semantics/entitlements.d.ts +2 -0
- package/dist/src/semantics/entitlements.js +95 -0
- package/dist/src/semantics/ephemeral-keys.d.ts +2 -0
- package/dist/src/semantics/ephemeral-keys.js +34 -0
- package/dist/src/semantics/files.d.ts +2 -0
- package/dist/src/semantics/files.js +125 -0
- package/dist/src/semantics/invoices.d.ts +18 -0
- package/dist/src/semantics/invoices.js +541 -0
- package/dist/src/semantics/issuing.d.ts +13 -0
- package/dist/src/semantics/issuing.js +570 -0
- package/dist/src/semantics/ledger.d.ts +54 -0
- package/dist/src/semantics/ledger.js +181 -0
- package/dist/src/semantics/payment-intents.d.ts +18 -0
- package/dist/src/semantics/payment-intents.js +404 -0
- package/dist/src/semantics/payment-links.d.ts +2 -0
- package/dist/src/semantics/payment-links.js +133 -0
- package/dist/src/semantics/payment-methods.d.ts +20 -0
- package/dist/src/semantics/payment-methods.js +138 -0
- package/dist/src/semantics/plans.d.ts +5 -0
- package/dist/src/semantics/plans.js +121 -0
- package/dist/src/semantics/platform.d.ts +9 -0
- package/dist/src/semantics/platform.js +206 -0
- package/dist/src/semantics/products.d.ts +2 -0
- package/dist/src/semantics/products.js +140 -0
- package/dist/src/semantics/radar.d.ts +2 -0
- package/dist/src/semantics/radar.js +83 -0
- package/dist/src/semantics/refunds.d.ts +9 -0
- package/dist/src/semantics/refunds.js +195 -0
- package/dist/src/semantics/renewals.d.ts +47 -0
- package/dist/src/semantics/renewals.js +251 -0
- package/dist/src/semantics/setup-intents.d.ts +2 -0
- package/dist/src/semantics/setup-intents.js +84 -0
- package/dist/src/semantics/shared.d.ts +78 -0
- package/dist/src/semantics/shared.js +192 -0
- package/dist/src/semantics/subscription-schedules.d.ts +2 -0
- package/dist/src/semantics/subscription-schedules.js +119 -0
- package/dist/src/semantics/subscriptions.d.ts +11 -0
- package/dist/src/semantics/subscriptions.js +605 -0
- package/dist/src/semantics/tax.d.ts +2 -0
- package/dist/src/semantics/tax.js +197 -0
- package/dist/src/semantics/terminal.d.ts +5 -0
- package/dist/src/semantics/terminal.js +182 -0
- package/dist/src/semantics/test-clocks.d.ts +6 -0
- package/dist/src/semantics/test-clocks.js +73 -0
- package/dist/src/semantics/tokens.d.ts +4 -0
- package/dist/src/semantics/tokens.js +44 -0
- package/dist/src/semantics/transfers.d.ts +2 -0
- package/dist/src/semantics/transfers.js +154 -0
- package/dist/src/semantics/treasury.d.ts +2 -0
- package/dist/src/semantics/treasury.js +377 -0
- package/dist/src/semantics/webhook-endpoints.d.ts +3 -0
- package/dist/src/semantics/webhook-endpoints.js +85 -0
- package/dist/src/stripe-budget.d.ts +55 -0
- package/dist/src/stripe-budget.js +155 -0
- package/dist/src/stripe-capabilities.d.ts +3 -0
- package/dist/src/stripe-capabilities.js +5052 -0
- package/dist/src/stripe-conformance.d.ts +41 -0
- package/dist/src/stripe-conformance.js +96 -0
- package/dist/src/stripe-connector.d.ts +161 -0
- package/dist/src/stripe-connector.js +414 -0
- package/dist/src/stripe-emit.d.ts +2 -0
- package/dist/src/stripe-emit.js +145 -0
- package/dist/src/stripe-events.d.ts +93 -0
- package/dist/src/stripe-events.js +388 -0
- package/dist/src/stripe-js.d.ts +4 -0
- package/dist/src/stripe-js.js +70 -0
- package/dist/src/stripe-mirror-ui.d.ts +15 -0
- package/dist/src/stripe-mirror-ui.js +87 -0
- package/dist/src/stripe-params.d.ts +3 -0
- package/dist/src/stripe-params.js +43 -0
- package/dist/src/stripe-perform-harness.d.ts +9 -0
- package/dist/src/stripe-perform-harness.js +26 -0
- package/dist/src/stripe-server.d.ts +33 -0
- package/dist/src/stripe-server.js +326 -0
- package/dist/src/stripe-shared.d.ts +106 -0
- package/dist/src/stripe-shared.js +273 -0
- package/dist/src/stripe-twin.d.ts +155 -0
- package/dist/src/stripe-twin.js +1226 -0
- package/dist/src/stripe-ui-conformance.d.ts +5 -0
- package/dist/src/stripe-ui-conformance.js +79 -0
- package/dist/src/stripe-ui-structure.d.ts +3 -0
- package/dist/src/stripe-ui-structure.js +168 -0
- package/dist/src/stripe-version.d.ts +10 -0
- package/dist/src/stripe-version.js +285 -0
- package/dist/test-fixtures/stripe-known-deviations.json +105 -0
- package/dist/test-fixtures/stripe-openapi-operations.SOURCE.md +14 -0
- package/dist/test-fixtures/stripe-openapi-operations.json +4717 -0
- package/dist/test-fixtures/stripe-schemas.SOURCE.md +35 -0
- package/dist/test-fixtures/stripe-schemas.json +3740 -0
- package/package.json +18 -10
- package/src/cli.ts +7 -7
- package/src/generated/events.gen.json +1 -0
- package/src/generated/surface.gen.json +1 -0
- package/src/generated/ui.gen.json +1 -0
- package/src/index.ts +31 -9
- package/src/manifest.ts +1097 -0
- package/src/screens/checkout.tsx +252 -0
- package/src/screens/consent-skin.ts +20 -0
- package/src/screens/financial-connections.tsx +101 -0
- package/src/screens/identity.tsx +96 -0
- package/src/screens/industries.ts +267 -0
- package/src/screens/onboarding.tsx +243 -0
- package/src/screens/portal.tsx +218 -0
- package/src/screens/public-details.tsx +105 -0
- package/src/semantics/after-payment.ts +113 -0
- package/src/semantics/apps-secrets.ts +58 -0
- package/src/semantics/balance.ts +209 -0
- package/src/semantics/billing.ts +216 -0
- package/src/semantics/charges.ts +211 -0
- package/src/semantics/checkout.ts +297 -0
- package/src/semantics/connect.ts +471 -0
- package/src/semantics/coupons.ts +97 -0
- package/src/semantics/credit-notes.ts +168 -0
- package/src/semantics/customers.ts +432 -0
- package/src/semantics/disputes.ts +62 -0
- package/src/semantics/entitlements.ts +94 -0
- package/src/semantics/ephemeral-keys.ts +34 -0
- package/src/semantics/files.ts +143 -0
- package/src/semantics/invoices.ts +541 -0
- package/src/semantics/issuing.ts +585 -0
- package/src/semantics/ledger.ts +216 -0
- package/src/semantics/payment-intents.ts +420 -0
- package/src/semantics/payment-links.ts +148 -0
- package/src/semantics/payment-methods.ts +143 -0
- package/src/semantics/plans.ts +131 -0
- package/src/semantics/platform.ts +220 -0
- package/src/semantics/products.ts +154 -0
- package/src/semantics/radar.ts +85 -0
- package/src/semantics/refunds.ts +218 -0
- package/src/semantics/renewals.ts +274 -0
- package/src/semantics/setup-intents.ts +87 -0
- package/src/semantics/shared.ts +215 -0
- package/src/semantics/subscription-schedules.ts +129 -0
- package/src/semantics/subscriptions.ts +610 -0
- package/src/semantics/tax.ts +220 -0
- package/src/semantics/terminal.ts +195 -0
- package/src/semantics/test-clocks.ts +77 -0
- package/src/semantics/tokens.ts +52 -0
- package/src/semantics/transfers.ts +174 -0
- package/src/semantics/treasury.ts +383 -0
- package/src/semantics/webhook-endpoints.ts +87 -0
- package/src/stripe-budget.ts +4 -4
- package/src/stripe-capabilities.ts +1456 -222
- package/src/stripe-conformance.ts +6 -5
- package/src/stripe-connector.ts +68 -40
- package/src/stripe-emit.ts +14 -7
- package/src/stripe-events.ts +94 -36
- package/src/stripe-js.ts +70 -0
- package/src/stripe-mirror-ui.ts +28 -298
- package/src/stripe-params.ts +44 -0
- package/src/stripe-perform-harness.ts +29 -0
- package/src/stripe-server.ts +263 -38
- package/src/stripe-shared.ts +294 -0
- package/src/stripe-twin.ts +429 -5325
- package/src/stripe-ui-conformance.ts +70 -107
- package/src/stripe-ui-structure.ts +124 -348
- package/src/stripe-version.ts +278 -0
- package/test-fixtures/stripe-known-deviations.json +2 -7
- package/test-fixtures/stripe-openapi-operations.json +1188 -2855
- package/src/stripe-form.ts +0 -35
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { RateBudget, type RateBudgetDeclaration, type RateBudgetOptions, type RateBudgetReservation, type RateBudgetSnapshot } from '@volter/world-core';
|
|
2
|
+
/** Rolling window, in ms. Spend older than this is pruned. */
|
|
3
|
+
export declare const STRIPE_BUDGET_WINDOW_MS = 60000;
|
|
4
|
+
/**
|
|
5
|
+
* Weighted units allowed inside one window. 120/60s = 120 reads a minute (2/second) — 2% of
|
|
6
|
+
* Stripe's documented live-mode 100/second and 8% of the 25/second a sandbox key or any single
|
|
7
|
+
* endpoint gets.
|
|
8
|
+
*/
|
|
9
|
+
export declare const STRIPE_BUDGET_CEILING = 120;
|
|
10
|
+
/** Seconds. A `Retry-After` above this means the key is throttled hard — fail loudly, don't sleep. */
|
|
11
|
+
export declare const STRIPE_BUDGET_MAX_RETRY_AFTER_S = 300;
|
|
12
|
+
/** Per-call cost, keyed by `"<METHOD> <path>"`. See the header for what is documented vs. judged. */
|
|
13
|
+
export declare const STRIPE_CALL_WEIGHTS: {
|
|
14
|
+
/** `POST /v1/payouts` — documented at 15 creates/second, and the most irreversible object here. */
|
|
15
|
+
readonly payout: 5;
|
|
16
|
+
/** Any other `POST`/`DELETE` — creates charges, refunds, subscriptions, receipt e-mail. */
|
|
17
|
+
readonly write: 2;
|
|
18
|
+
/** Reads: list/retrieve. */
|
|
19
|
+
readonly other: 1;
|
|
20
|
+
};
|
|
21
|
+
/** THE PACK'S DECLARATION — pure data, the only Stripe-specific thing in the whole budget. */
|
|
22
|
+
export declare const STRIPE_RATE_BUDGET: RateBudgetDeclaration;
|
|
23
|
+
/**
|
|
24
|
+
* Price one call. The key is `"<METHOD> <path>"` — Stripe's executor carries filters as separate
|
|
25
|
+
* `params` rather than in the path, so there is no query string to split off here; a `?` is still
|
|
26
|
+
* handled defensively in case a caller inlines one. An unclassified endpoint costs `defaultWeight`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function stripeCallWeight(method: string, path: string): number;
|
|
29
|
+
/** Where Stripe's ledger lives. Key-keyed and cwd-independent by default (limits attach to the
|
|
30
|
+
* account behind the secret key, so a cwd-scoped ledger would hand the same key a fresh allowance
|
|
31
|
+
* in every checkout, worktree and CI matrix leg); pass `root` for world-scoped accounting.
|
|
32
|
+
*
|
|
33
|
+
* A live key and a test key are different strings, so they get different ledgers — which matches
|
|
34
|
+
* Stripe, whose live and sandbox limits are separate (100/s vs 25/s). */
|
|
35
|
+
export declare function stripeBudgetPath(opts?: {
|
|
36
|
+
root?: string;
|
|
37
|
+
token?: string;
|
|
38
|
+
} | string): string;
|
|
39
|
+
/** Construction options for Stripe's budget. The vendor is fixed; everything else may only TIGHTEN. */
|
|
40
|
+
export type StripeBudgetOptions = Omit<RateBudgetOptions, 'vendor'>;
|
|
41
|
+
/**
|
|
42
|
+
* Stripe's budget — the shared kernel guard bound to this vendor's declaration. A real subclass,
|
|
43
|
+
* not an alias, so `budget instanceof StripeBudget` in `liveStripeExecute` means "a budget that
|
|
44
|
+
* accounts against STRIPE's ledger under STRIPE's ceiling": another vendor's `RateBudget` (with its
|
|
45
|
+
* own, possibly larger, ceiling) is NOT assignable there.
|
|
46
|
+
*/
|
|
47
|
+
export declare class StripeBudget extends RateBudget {
|
|
48
|
+
constructor(opts?: StripeBudgetOptions);
|
|
49
|
+
}
|
|
50
|
+
/** The typed refusal. One error class shared with every other vendor's budget; `err.vendor` says
|
|
51
|
+
* which one refused, and `err.kind` says why. */
|
|
52
|
+
export { RateBudgetError as StripeBudgetError } from '@volter/world-core';
|
|
53
|
+
export type { RateBudgetErrorKind as StripeBudgetErrorKind } from '@volter/world-core';
|
|
54
|
+
export type StripeBudgetReservation = RateBudgetReservation;
|
|
55
|
+
export type StripeBudgetSnapshot = RateBudgetSnapshot;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// Stripe's CLIENT-SIDE RATE BUDGET — the pack's DECLARATION (the numbers) plus the thin typed
|
|
2
|
+
// bindings `liveStripeExecute` uses. The MECHANISM — the durable token-keyed ledger, the rolling
|
|
3
|
+
// window, reserve-under-lock, the `Retry-After`/429 cooldown, fail-CLOSED on a corrupt ledger —
|
|
4
|
+
// lives ONCE in the vendor-agnostic kernel (`@volter/world-core` → `rateBudget.ts`). Read that module's
|
|
5
|
+
// header for the full rationale AND for the honest list of what the guard does not guarantee (an
|
|
6
|
+
// injected clock or ledger path still defeats it — it guards carelessness, not malice).
|
|
7
|
+
//
|
|
8
|
+
// ── WHY THIS EXISTS ─────────────────────────────────────────────────────────────────────────
|
|
9
|
+
// A real ~4.5-DAY vendor lockout (Figma, 2026-07-25) happened because raw API calls were made
|
|
10
|
+
// outside the pack's connector — no cache, no batching, no ceiling. Discipline only binds the code
|
|
11
|
+
// that follows it; a BUDGET binds the code that does not. Stripe is the pack where a runaway loop
|
|
12
|
+
// is not merely rude: its writes MOVE MONEY.
|
|
13
|
+
//
|
|
14
|
+
// ── HOW THE CEILING WAS CHOSEN ──────────────────────────────────────────────────────────────
|
|
15
|
+
// Stripe DOES publish scalar limits, so this models the real thing rather than guessing. From
|
|
16
|
+
// https://docs.stripe.com/rate-limits (read 2026-07-26):
|
|
17
|
+
// • live mode: 100 requests/second
|
|
18
|
+
// • sandbox (test mode): 25 requests/second
|
|
19
|
+
// • an individual endpoint: 25 requests/second unless noted otherwise
|
|
20
|
+
// • Payouts, create: 15 requests/second
|
|
21
|
+
// • Subscriptions: 10 new invoices per subscription per minute
|
|
22
|
+
// • Payment Intents: 1,000 update requests per PaymentIntent per hour
|
|
23
|
+
// Over the limit: 429, with a `Stripe-Rate-Limited-Reason` header naming which limit was hit.
|
|
24
|
+
//
|
|
25
|
+
// The ceiling is 120 weighted units per 60s — 120 calls a minute, i.e. 2 requests/second at the
|
|
26
|
+
// default weight. That is 2% of the live-mode 100/s and 8% of the tightest general limit (25/s,
|
|
27
|
+
// which is what a sandbox key and any single endpoint get). It is more permissive than the kernel's
|
|
28
|
+
// undeclared fallback (30 calls/min) precisely because those limits are documented and are one to
|
|
29
|
+
// three ORDERS of magnitude higher; without them it would not be.
|
|
30
|
+
//
|
|
31
|
+
// It bounds the 60-second AVERAGE; it does NOT pace (the kernel refuses, it never sleeps — see its
|
|
32
|
+
// header). Stripe's own window is a SECOND, so a tight loop can legitimately fire all 120 inside
|
|
33
|
+
// one second here — 120 req/s, above the sandbox 25/s — and in that shape Stripe's 429 arrives
|
|
34
|
+
// before this ceiling does. The backstop is then the cooldown: the guard reads the 429 off the
|
|
35
|
+
// response and refuses every later call without touching Stripe. The honest claim is therefore
|
|
36
|
+
// "bounds the minute, and converts Stripe's first 429 into a hard stop", not "refuses before Stripe
|
|
37
|
+
// ever 429s". Pacing is the caller's job; this is the ceiling underneath it.
|
|
38
|
+
//
|
|
39
|
+
// ── HOW THE WEIGHTS WERE CHOSEN ─────────────────────────────────────────────────────────────
|
|
40
|
+
// • `POST` / `DELETE` cost 2. Stripe counts them the same as a read, so this is NOT a published
|
|
41
|
+
// ratio — it is a judgement call about blast radius: a write creates a charge, a refund, a
|
|
42
|
+
// subscription or a receipt e-mail, and unlike a read it cannot be taken back. Halving the rate
|
|
43
|
+
// at which a runaway loop can do that is worth the cost to a legitimate push, which is small
|
|
44
|
+
// (a push is a handful of writes).
|
|
45
|
+
// • `POST /v1/payouts` costs 5, because Stripe documents payout creation at 15/second — six times
|
|
46
|
+
// tighter than live mode's 100/s — and a payout is the single most irreversible object in the
|
|
47
|
+
// API. At weight 5 at most 24 land in a window: 0.4/s against a documented 15/s.
|
|
48
|
+
import { declareRateBudget, rateBudgetPath, rateBudgetWeight, RateBudget, } from '@volter/world-core';
|
|
49
|
+
const VENDOR = 'stripe';
|
|
50
|
+
/** Rolling window, in ms. Spend older than this is pruned. */
|
|
51
|
+
export const STRIPE_BUDGET_WINDOW_MS = 60_000;
|
|
52
|
+
/**
|
|
53
|
+
* Weighted units allowed inside one window. 120/60s = 120 reads a minute (2/second) — 2% of
|
|
54
|
+
* Stripe's documented live-mode 100/second and 8% of the 25/second a sandbox key or any single
|
|
55
|
+
* endpoint gets.
|
|
56
|
+
*/
|
|
57
|
+
export const STRIPE_BUDGET_CEILING = 120;
|
|
58
|
+
/** Seconds. A `Retry-After` above this means the key is throttled hard — fail loudly, don't sleep. */
|
|
59
|
+
export const STRIPE_BUDGET_MAX_RETRY_AFTER_S = 300;
|
|
60
|
+
/** Per-call cost, keyed by `"<METHOD> <path>"`. See the header for what is documented vs. judged. */
|
|
61
|
+
export const STRIPE_CALL_WEIGHTS = {
|
|
62
|
+
/** `POST /v1/payouts` — documented at 15 creates/second, and the most irreversible object here. */
|
|
63
|
+
payout: 5,
|
|
64
|
+
/** Any other `POST`/`DELETE` — creates charges, refunds, subscriptions, receipt e-mail. */
|
|
65
|
+
write: 2,
|
|
66
|
+
/** Reads: list/retrieve. */
|
|
67
|
+
other: 1,
|
|
68
|
+
};
|
|
69
|
+
/** THE PACK'S DECLARATION — pure data, the only Stripe-specific thing in the whole budget. */
|
|
70
|
+
export const STRIPE_RATE_BUDGET = {
|
|
71
|
+
windowMs: STRIPE_BUDGET_WINDOW_MS,
|
|
72
|
+
ceiling: STRIPE_BUDGET_CEILING,
|
|
73
|
+
defaultWeight: STRIPE_CALL_WEIGHTS.other,
|
|
74
|
+
maxRetryAfterSeconds: STRIPE_BUDGET_MAX_RETRY_AFTER_S,
|
|
75
|
+
rules: [
|
|
76
|
+
{ match: '^POST /v1/payouts$', weight: STRIPE_CALL_WEIGHTS.payout },
|
|
77
|
+
{ match: '^(POST|DELETE) ', weight: STRIPE_CALL_WEIGHTS.write },
|
|
78
|
+
],
|
|
79
|
+
reason: 'Stripe documents scalar limits (docs.stripe.com/rate-limits, read 2026-07-26): 100 requests/' +
|
|
80
|
+
'second in live mode, 25/second in sandbox, 25/second for an individual endpoint unless noted, ' +
|
|
81
|
+
'15 payout creates/second, 10 new invoices per subscription per minute, 1,000 PaymentIntent ' +
|
|
82
|
+
'updates per intent per hour; over it, 429 with a Stripe-Rate-Limited-Reason header. 120 ' +
|
|
83
|
+
'weighted units / 60s is 120 reads a minute = 2 requests/second — 2% of live mode and 8% of the ' +
|
|
84
|
+
"tightest general limit. It is more permissive than the kernel's undeclared fallback (30 calls/" +
|
|
85
|
+
'min) BECAUSE those documented limits are one to three orders of magnitude higher. POST/DELETE ' +
|
|
86
|
+
'cost 2 — NOT a published ratio, a judgement call about blast radius: a write creates a charge, ' +
|
|
87
|
+
'refund or receipt e-mail and cannot be taken back. POST /v1/payouts costs 5 because Stripe ' +
|
|
88
|
+
'documents payout creation at 15/second, six times tighter than live mode. The window bounds the ' +
|
|
89
|
+
"60s AVERAGE and does not pace, and Stripe's own window is a SECOND, so an intra-second burst " +
|
|
90
|
+
"reaches Stripe's limiter first — the 429 cooldown is the backstop for that shape, not this " +
|
|
91
|
+
'ceiling.',
|
|
92
|
+
};
|
|
93
|
+
// Declared at module load, so merely importing this module (which `stripe-connector.ts` does) is
|
|
94
|
+
// enough to arm the real ceiling. `RateBudget` reads its policy live precisely so this declaration
|
|
95
|
+
// takes effect the moment it lands, and constructing through the subclass below (which imports this
|
|
96
|
+
// module) is what makes the ordering a non-issue in practice.
|
|
97
|
+
declareRateBudget(VENDOR, STRIPE_RATE_BUDGET);
|
|
98
|
+
/**
|
|
99
|
+
* Price one call. The key is `"<METHOD> <path>"` — Stripe's executor carries filters as separate
|
|
100
|
+
* `params` rather than in the path, so there is no query string to split off here; a `?` is still
|
|
101
|
+
* handled defensively in case a caller inlines one. An unclassified endpoint costs `defaultWeight`.
|
|
102
|
+
*/
|
|
103
|
+
export function stripeCallWeight(method, path) {
|
|
104
|
+
const { bare, query } = splitQuery(path);
|
|
105
|
+
// UPPER-CASE the method: `fetch` normalizes a known lowercase method before sending, so
|
|
106
|
+
// `execute('post', …)` really does issue a POST and must be priced as one.
|
|
107
|
+
return rateBudgetWeight(VENDOR, `${String(method).toUpperCase()} ${bare}`, query);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* `/v1/x?a=1` -> `{ bare: '/v1/x', query: { a: '1' } }`. Rules match the path; `whenQuery*` the query.
|
|
111
|
+
*
|
|
112
|
+
* NORMALIZED, because the anchored rules are otherwise trivially evaded (§9 finding, 2026-07-26):
|
|
113
|
+
* `fetch` upper-cases a known method before sending, so `execute('post', …)` issues a real WRITE
|
|
114
|
+
* that a `^POST ` rule would price as a read; and a trailing slash makes a path miss a `$` anchor
|
|
115
|
+
* while most routers treat it as the same endpoint. Both are input variations, not attacks, and
|
|
116
|
+
* either one silently voids the "expensive endpoints are priced up" claim the ceiling rests on.
|
|
117
|
+
*/
|
|
118
|
+
function splitQuery(path) {
|
|
119
|
+
const at = path.indexOf('?');
|
|
120
|
+
const query = {};
|
|
121
|
+
if (at !== -1)
|
|
122
|
+
for (const [k, v] of new URLSearchParams(path.slice(at + 1)))
|
|
123
|
+
query[k] = v;
|
|
124
|
+
const raw = at === -1 ? path : path.slice(0, at);
|
|
125
|
+
// Collapse a trailing slash, but never turn the root path into the empty string.
|
|
126
|
+
const bare = raw.length > 1 && raw.endsWith('/') ? raw.replace(/\/+$/, '') : raw;
|
|
127
|
+
return { bare, query };
|
|
128
|
+
}
|
|
129
|
+
/** Where Stripe's ledger lives. Key-keyed and cwd-independent by default (limits attach to the
|
|
130
|
+
* account behind the secret key, so a cwd-scoped ledger would hand the same key a fresh allowance
|
|
131
|
+
* in every checkout, worktree and CI matrix leg); pass `root` for world-scoped accounting.
|
|
132
|
+
*
|
|
133
|
+
* A live key and a test key are different strings, so they get different ledgers — which matches
|
|
134
|
+
* Stripe, whose live and sandbox limits are separate (100/s vs 25/s). */
|
|
135
|
+
export function stripeBudgetPath(opts = {}) {
|
|
136
|
+
const o = typeof opts === 'string' ? { root: opts } : opts;
|
|
137
|
+
// VENDOR spread LAST: a loosely-typed `{ vendor: 'other', … }` slipping through (TypeScript's
|
|
138
|
+
// excess-property check only catches object literals) must not redirect this pack's ledger to
|
|
139
|
+
// another vendor's file.
|
|
140
|
+
return rateBudgetPath({ ...o, vendor: VENDOR });
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Stripe's budget — the shared kernel guard bound to this vendor's declaration. A real subclass,
|
|
144
|
+
* not an alias, so `budget instanceof StripeBudget` in `liveStripeExecute` means "a budget that
|
|
145
|
+
* accounts against STRIPE's ledger under STRIPE's ceiling": another vendor's `RateBudget` (with its
|
|
146
|
+
* own, possibly larger, ceiling) is NOT assignable there.
|
|
147
|
+
*/
|
|
148
|
+
export class StripeBudget extends RateBudget {
|
|
149
|
+
constructor(opts = {}) {
|
|
150
|
+
super({ ...opts, vendor: VENDOR });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/** The typed refusal. One error class shared with every other vendor's budget; `err.vendor` says
|
|
154
|
+
* which one refused, and `err.kind` says why. */
|
|
155
|
+
export { RateBudgetError as StripeBudgetError } from '@volter/world-core';
|