@crvouga/mockingbird-service-stripe 0.1.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.
Files changed (142) hide show
  1. package/README.md +254 -0
  2. package/dist/account.d.ts +8 -0
  3. package/dist/account.d.ts.map +1 -0
  4. package/dist/account.js +13 -0
  5. package/dist/account.js.map +1 -0
  6. package/dist/charges.d.ts +4 -0
  7. package/dist/charges.d.ts.map +1 -0
  8. package/dist/charges.js +89 -0
  9. package/dist/charges.js.map +1 -0
  10. package/dist/checkout-sessions.d.ts +5 -0
  11. package/dist/checkout-sessions.d.ts.map +1 -0
  12. package/dist/checkout-sessions.js +303 -0
  13. package/dist/checkout-sessions.js.map +1 -0
  14. package/dist/constants.d.ts +3 -0
  15. package/dist/constants.d.ts.map +1 -0
  16. package/dist/constants.js +3 -0
  17. package/dist/constants.js.map +1 -0
  18. package/dist/coupons.d.ts +4 -0
  19. package/dist/coupons.d.ts.map +1 -0
  20. package/dist/coupons.js +158 -0
  21. package/dist/coupons.js.map +1 -0
  22. package/dist/customers.d.ts +4 -0
  23. package/dist/customers.d.ts.map +1 -0
  24. package/dist/customers.js +218 -0
  25. package/dist/customers.js.map +1 -0
  26. package/dist/disputes.d.ts +4 -0
  27. package/dist/disputes.d.ts.map +1 -0
  28. package/dist/disputes.js +36 -0
  29. package/dist/disputes.js.map +1 -0
  30. package/dist/errors.d.ts +29 -0
  31. package/dist/errors.d.ts.map +1 -0
  32. package/dist/errors.js +74 -0
  33. package/dist/errors.js.map +1 -0
  34. package/dist/events.d.ts +4 -0
  35. package/dist/events.d.ts.map +1 -0
  36. package/dist/events.js +76 -0
  37. package/dist/events.js.map +1 -0
  38. package/dist/expand.d.ts +11 -0
  39. package/dist/expand.d.ts.map +1 -0
  40. package/dist/expand.js +45 -0
  41. package/dist/expand.js.map +1 -0
  42. package/dist/fields.d.ts +25 -0
  43. package/dist/fields.d.ts.map +1 -0
  44. package/dist/fields.js +117 -0
  45. package/dist/fields.js.map +1 -0
  46. package/dist/generated/openapi.d.ts +7 -0
  47. package/dist/generated/openapi.d.ts.map +1 -0
  48. package/dist/generated/openapi.js +5 -0
  49. package/dist/generated/openapi.js.map +1 -0
  50. package/dist/index.d.ts +43 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +172 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/internal.d.ts +134 -0
  55. package/dist/internal.d.ts.map +1 -0
  56. package/dist/internal.js +460 -0
  57. package/dist/internal.js.map +1 -0
  58. package/dist/invoice-items.d.ts +4 -0
  59. package/dist/invoice-items.d.ts.map +1 -0
  60. package/dist/invoice-items.js +179 -0
  61. package/dist/invoice-items.js.map +1 -0
  62. package/dist/invoices.d.ts +4 -0
  63. package/dist/invoices.d.ts.map +1 -0
  64. package/dist/invoices.js +312 -0
  65. package/dist/invoices.js.map +1 -0
  66. package/dist/list.d.ts +24 -0
  67. package/dist/list.d.ts.map +1 -0
  68. package/dist/list.js +68 -0
  69. package/dist/list.js.map +1 -0
  70. package/dist/params.d.ts +29 -0
  71. package/dist/params.d.ts.map +1 -0
  72. package/dist/params.js +165 -0
  73. package/dist/params.js.map +1 -0
  74. package/dist/payment-intents.d.ts +4 -0
  75. package/dist/payment-intents.d.ts.map +1 -0
  76. package/dist/payment-intents.js +232 -0
  77. package/dist/payment-intents.js.map +1 -0
  78. package/dist/payment-methods.d.ts +4 -0
  79. package/dist/payment-methods.d.ts.map +1 -0
  80. package/dist/payment-methods.js +99 -0
  81. package/dist/payment-methods.js.map +1 -0
  82. package/dist/prices.d.ts +4 -0
  83. package/dist/prices.d.ts.map +1 -0
  84. package/dist/prices.js +176 -0
  85. package/dist/prices.js.map +1 -0
  86. package/dist/products.d.ts +6 -0
  87. package/dist/products.d.ts.map +1 -0
  88. package/dist/products.js +202 -0
  89. package/dist/products.js.map +1 -0
  90. package/dist/promotion-codes.d.ts +4 -0
  91. package/dist/promotion-codes.d.ts.map +1 -0
  92. package/dist/promotion-codes.js +193 -0
  93. package/dist/promotion-codes.js.map +1 -0
  94. package/dist/qa-corpus.d.ts +26 -0
  95. package/dist/qa-corpus.d.ts.map +1 -0
  96. package/dist/qa-corpus.js +42 -0
  97. package/dist/qa-corpus.js.map +1 -0
  98. package/dist/refunds.d.ts +4 -0
  99. package/dist/refunds.d.ts.map +1 -0
  100. package/dist/refunds.js +125 -0
  101. package/dist/refunds.js.map +1 -0
  102. package/dist/render.d.ts +60 -0
  103. package/dist/render.d.ts.map +1 -0
  104. package/dist/render.js +426 -0
  105. package/dist/render.js.map +1 -0
  106. package/dist/reshape-qa.d.ts +8 -0
  107. package/dist/reshape-qa.d.ts.map +1 -0
  108. package/dist/reshape-qa.js +102 -0
  109. package/dist/reshape-qa.js.map +1 -0
  110. package/dist/search.d.ts +32 -0
  111. package/dist/search.d.ts.map +1 -0
  112. package/dist/search.js +64 -0
  113. package/dist/search.js.map +1 -0
  114. package/dist/setup-intents.d.ts +4 -0
  115. package/dist/setup-intents.d.ts.map +1 -0
  116. package/dist/setup-intents.js +129 -0
  117. package/dist/setup-intents.js.map +1 -0
  118. package/dist/state.d.ts +485 -0
  119. package/dist/state.d.ts.map +1 -0
  120. package/dist/state.js +143 -0
  121. package/dist/state.js.map +1 -0
  122. package/dist/subscription-schedules.d.ts +4 -0
  123. package/dist/subscription-schedules.d.ts.map +1 -0
  124. package/dist/subscription-schedules.js +122 -0
  125. package/dist/subscription-schedules.js.map +1 -0
  126. package/dist/subscriptions.d.ts +23 -0
  127. package/dist/subscriptions.d.ts.map +1 -0
  128. package/dist/subscriptions.js +260 -0
  129. package/dist/subscriptions.js.map +1 -0
  130. package/dist/test-tokens.d.ts +27 -0
  131. package/dist/test-tokens.d.ts.map +1 -0
  132. package/dist/test-tokens.js +61 -0
  133. package/dist/test-tokens.js.map +1 -0
  134. package/dist/url.d.ts +6 -0
  135. package/dist/url.d.ts.map +1 -0
  136. package/dist/url.js +38 -0
  137. package/dist/url.js.map +1 -0
  138. package/dist/version.d.ts +3 -0
  139. package/dist/version.d.ts.map +1 -0
  140. package/dist/version.js +3 -0
  141. package/dist/version.js.map +1 -0
  142. package/package.json +72 -0
package/README.md ADDED
@@ -0,0 +1,254 @@
1
+ # @crvouga/mockingbird-service-stripe
2
+
3
+ Stateful, in-process mock of the [Stripe API](https://docs.stripe.com/api) for test suites. It is
4
+ driven by a vendored subset of Stripe's OpenAPI contract and verified by differential property tests
5
+ against Stripe test mode. Covered: customers (including search and balance transactions), payment
6
+ methods, payment and setup intents, charges, refunds, disputes (read-only), checkout sessions,
7
+ invoices and invoice items, subscriptions and subscription schedules, coupons and promotion codes,
8
+ products, prices, and an event ledger (`/v1/events`) with a webhook hook.
9
+
10
+ Use it when server-side code talks to Stripe through `fetch` or stripe-node and you want the suite
11
+ to run offline with no `api.stripe.com` egress. It does not serve Stripe.js or hosted checkout
12
+ (`js.stripe.com`, `checkout.stripe.com`); browser-driven checkout still needs real Stripe test mode.
13
+
14
+ - Operation coverage (88 of 108 operations in the vendored spec, with reasons for each gap):
15
+ [SUPPORT.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/stripe/SUPPORT.md)
16
+ - Scope and proof: [stripe-drop-in.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/stripe/docs/stripe-drop-in.md)
17
+ · Consumer wiring checklist: [qa-followon.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/stripe/docs/qa-followon.md)
18
+ · [QA coverage](https://github.com/crvouga/mockingbird/blob/main/packages/service/stripe/docs/qa-coverage.md)
19
+ - Stripe API reference: https://docs.stripe.com/api · Upstream OpenAPI: https://github.com/stripe/openapi
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ npm install -D @crvouga/mockingbird-service-stripe
25
+ ```
26
+
27
+ ESM only. Requires Node >= 22 or Bun >= 1.2. No native dependencies: state lives in an in-memory
28
+ SQLite engine (`@crvouga/mockingbird-service-sqlite`, pure TypeScript). To serve it over HTTP also
29
+ install an adapter: `@crvouga/mockingbird-adapter-node` or `@crvouga/mockingbird-adapter-bun`.
30
+
31
+ ## Usage
32
+
33
+ Behaviour the examples rely on (all from the source):
34
+
35
+ - **Any host works.** Routing uses only the path (`/v1/...`), so `https://api.stripe.com`,
36
+ `http://127.0.0.1:<port>` or any made-up origin is fine.
37
+ - **Auth is required.** Every request needs `Authorization: Bearer <key>` where the key matches
38
+ `^(sk|rk)_test_[A-Za-z0-9]+$`. Missing header or any other shape (including `sk_live_...` and
39
+ keys with extra underscores such as `sk_test_my_key`) returns Stripe's 401 error body.
40
+ - **One key = one account.** State is partitioned by bearer key, so an object created with
41
+ `sk_test_a` is `resource_missing` (404) under `sk_test_b`.
42
+ - Request bodies are `application/x-www-form-urlencoded` with Stripe's bracket notation, exactly
43
+ as stripe-node sends them. Every response carries `request-id` and `stripe-version` headers.
44
+ - `Idempotency-Key` on POSTs is honoured: a replay returns the cached response, a replay with
45
+ different parameters returns 400.
46
+
47
+ ### In-process (inject `fetch`)
48
+
49
+ ```ts
50
+ import { StripeAPI } from "@crvouga/mockingbird-service-stripe"
51
+
52
+ const stripe = new StripeAPI({ now: () => Date.UTC(2026, 0, 1) })
53
+
54
+ const auth = { authorization: "Bearer sk_test_mockingbird" }
55
+
56
+ const created = await stripe.fetch(
57
+ new Request("https://api.stripe.com/v1/customers", {
58
+ method: "POST",
59
+ headers: { ...auth, "content-type": "application/x-www-form-urlencoded" },
60
+ body: new URLSearchParams({ email: "qa@example.com", "metadata[userId]": "1001" }),
61
+ }),
62
+ )
63
+ const customer = (await created.json()) as { id: string; created: number }
64
+ console.log(created.status, customer.id) // 200 "cus_..."
65
+
66
+ // Any code that accepts a fetch function can be pointed at the mock:
67
+ const mockFetch = (input: string | URL | Request, init?: RequestInit) =>
68
+ stripe.fetch(new Request(input, init))
69
+ const listed = await mockFetch("https://api.stripe.com/v1/customers?limit=10", { headers: auth })
70
+ console.log(((await listed.json()) as { data: unknown[] }).data.length) // 1
71
+ ```
72
+
73
+ `now` drives `created`-style fields (Stripe returns seconds; `now` returns milliseconds).
74
+
75
+ ### Over HTTP
76
+
77
+ ```ts
78
+ import { serve } from "@crvouga/mockingbird-adapter-bun"
79
+ import { StripeAPI } from "@crvouga/mockingbird-service-stripe"
80
+
81
+ const stripe = new StripeAPI()
82
+ const server = serve(stripe, { port: 0, hostname: "127.0.0.1" }) // port 0 = ephemeral
83
+ const baseUrl = `http://127.0.0.1:${server.port}`
84
+
85
+ const response = await fetch(`${baseUrl}/v1/products?limit=3`, {
86
+ headers: { authorization: "Bearer sk_test_mockingbird" },
87
+ })
88
+ console.log(response.status) // 200
89
+
90
+ server.stop()
91
+ ```
92
+
93
+ On Node use `@crvouga/mockingbird-adapter-node`, whose `serve` is async and returns a
94
+ `node:http` server:
95
+
96
+ ```js
97
+ import { serve } from "@crvouga/mockingbird-adapter-node"
98
+ const server = await serve(stripe, { port: 0, host: "127.0.0.1" })
99
+ const { port } = server.address()
100
+ // ... later: server.close()
101
+ ```
102
+
103
+ ### Pointing stripe-node at it
104
+
105
+ stripe-node accepts `host`, `port` and `protocol`. This is the construction the package's own
106
+ client smoke test uses (stripe 16.x, `apiVersion: "2024-06-20"`):
107
+
108
+ ```js
109
+ import Stripe from "stripe"
110
+
111
+ const client = new Stripe("sk_test_mockingbird", {
112
+ apiVersion: "2024-06-20",
113
+ host: "127.0.0.1",
114
+ port: server.port, // from serve() above
115
+ protocol: "http",
116
+ })
117
+ await client.customers.create({ email: "qa@example.com" })
118
+ ```
119
+
120
+ Add a base-URL override (e.g. `STRIPE_API_BASE_URL=http://127.0.0.1:12111`) at every place your app
121
+ constructs a Stripe client; a client built with `new Stripe(key)` and no options cannot be
122
+ redirected. Test payment methods and tokens such as `pm_card_visa`, `pm_card_authenticationRequired`
123
+ and `tok_chargeDeclinedInsufficientFunds` behave like their Stripe counterparts
124
+ (`QA_TEST_PAYMENT_METHODS` and `QA_TEST_CARD_TOKENS` list the ones the suites exercise).
125
+
126
+ ### Webhooks
127
+
128
+ The mock records an event for every state change (readable through `GET /v1/events` and
129
+ `webhookEvents()`), and calls `onWebhook` with each one. Delivery and signing are up to you;
130
+ Stripe signs `"<t>.<body>"` with HMAC-SHA256 keyed by the `whsec_` secret verbatim, which
131
+ `stripe.webhooks.constructEvent` accepts:
132
+
133
+ ```ts
134
+ import { createHmac } from "node:crypto"
135
+ import { accountOfKey, StripeAPI } from "@crvouga/mockingbird-service-stripe"
136
+
137
+ const WEBHOOK_URL = "http://127.0.0.1:3100/webhooks/stripe"
138
+ const WEBHOOK_SECRET = "whsec_local_test"
139
+ const account = accountOfKey("sk_test_mockingbird")
140
+
141
+ const stripe = new StripeAPI({
142
+ onWebhook: (event) => {
143
+ if (event.account !== account) return
144
+ const t = Math.floor(Date.now() / 1000)
145
+ const v1 = createHmac("sha256", WEBHOOK_SECRET).update(`${t}.${event.body}`).digest("hex")
146
+ void fetch(WEBHOOK_URL, {
147
+ method: "POST",
148
+ headers: { "content-type": "application/json", "stripe-signature": `t=${t},v1=${v1}` },
149
+ body: event.body,
150
+ }).catch(() => undefined)
151
+ },
152
+ })
153
+
154
+ // Or assert on recorded events directly:
155
+ console.log(stripe.webhookEvents(account).map((event) => event.type))
156
+ ```
157
+
158
+ ### Resetting between tests
159
+
160
+ `reset()` clears every account's objects, the event ledger and the idempotency cache. Create one
161
+ instance per suite and reset it in `beforeEach`:
162
+
163
+ ```ts
164
+ import { beforeEach, expect, test } from "bun:test"
165
+ import { StripeAPI } from "@crvouga/mockingbird-service-stripe"
166
+
167
+ const stripe = new StripeAPI()
168
+ beforeEach(() => stripe.reset())
169
+
170
+ test("starts empty", async () => {
171
+ const response = await stripe.fetch(
172
+ new Request("https://api.stripe.com/v1/customers", {
173
+ headers: { authorization: "Bearer sk_test_mockingbird" },
174
+ }),
175
+ )
176
+ expect(((await response.json()) as { data: unknown[] }).data).toEqual([])
177
+ })
178
+ ```
179
+
180
+ ## API
181
+
182
+ `StripeAPI` is the main export; the rest supports account scoping, contract introspection and the
183
+ QA corpus used by the parity suites.
184
+
185
+ | Export | Description |
186
+ | --- | --- |
187
+ | `StripeAPI` | Class. `new StripeAPI(options?)`; implements `FetchAPI` (`fetch(request: Request): Promise<Response>`). |
188
+ | `accountOfKey` | `(key: string) => string` — the opaque `acct_...` partition id for an API key (use it to filter `webhookEvents`). |
189
+ | `accountOf` | `(request: Request) => string` — the partition id for a request's bearer key. |
190
+ | `STRIPE_NAMESPACE` | `"stripe"` — SQLite namespace holding every Stripe record when sharing a `sqlite` client. |
191
+ | `document` | The vendored Stripe OpenAPI document (Mockingbird subset) that drives routing and validation. |
192
+ | `operationIds` | Every `operationId` in `document` (108). |
193
+ | `supportedOperationIds` | The `operationId`s the mock implements (88); the rest return a Stripe-shaped error. |
194
+ | `QA_SURFACE_OPS` | Operations the QA suites exercise (same set as `supportedOperationIds`). |
195
+ | `QA_METADATA` | Pinned metadata values (`intent`, `source`, `userId`) the suites send. |
196
+ | `QA_AMOUNTS` | Pinned amounts in cents: `1000`, `15000`, `17999`. |
197
+ | `QA_CUSTOMER` | Pinned customer `email`, `name`, `phone`. |
198
+ | `QA_TEST_PAYMENT_METHODS` | Test payment method ids the suites attach (`pm_card_visa`, ...). |
199
+ | `QA_TEST_CARD_TOKENS` | Test card tokens the suites use (`tok_visa`, decline tokens, ...). |
200
+ | `QA_SEARCH_QUERIES` | Search queries the suites issue against `/v1/customers/search`. |
201
+ | `QA_COUPON_CODES` | Coupon / promotion codes used by the coupon flows. |
202
+ | `reshapeQaCommand` | Parity-walk hook that pins sampled commands onto QA corpus values (for `@crvouga/mockingbird-parity`). |
203
+
204
+ `StripeAPI` members:
205
+
206
+ | Member | Description |
207
+ | --- | --- |
208
+ | `fetch(request)` | Handle one Stripe REST request. |
209
+ | `reset()` | `Promise<void>` — clear all state, events and cached idempotent responses. |
210
+ | `webhookEvents(account?)` | `StripeWebhookEvent[]`, oldest first; `account` narrows to one `accountOfKey(...)` partition. |
211
+ | `webhookDeliveryAttempts(account?)` | Delivery attempts recorded for the event ledger. |
212
+ | `importStateFrom(source)` | Adopt another `StripeAPI` instance's state (used by seeded parity walks). |
213
+ | `app` | The underlying Hono app. |
214
+ | `sqlite` | The `SqliteClient` holding state. |
215
+
216
+ Options and types:
217
+
218
+ ```text
219
+ type StripeAPIOptions = {
220
+ sqlite?: SqliteClient // share one client across services; default: fresh in-memory DB
221
+ now?: () => number // clock in ms for created-style fields; default Date.now
222
+ onWebhook?: WebhookPublisher // called with every event the mock records
223
+ }
224
+ type StripeWebhookEvent = { type: string; account: string; body: string } // body is the JSON event
225
+ type WebhookPublisher = (event: StripeWebhookEvent) => void
226
+ type OperationId / SupportedOperationId // string unions of operationIds / supportedOperationIds
227
+ ```
228
+
229
+ `SqliteClient` is the port from `@crvouga/mockingbird-sqlite`; `Database` from
230
+ `@crvouga/mockingbird-service-sqlite` satisfies it, as do better-sqlite3 and wrapped `bun:sqlite`.
231
+
232
+ ## Development
233
+
234
+ For contributors to the mockingbird repo only; these scripts are not shipped in the npm package.
235
+
236
+ ```bash
237
+ bun test # self-parity, auth/idempotency and namespace suites (offline)
238
+ bun run mock:server # serve over HTTP on PORT (default 12111), GET /health for readiness
239
+ bun run client-parity # stripe-node smoke proof, including webhook signature verification
240
+ bun run parity # live differential parity against Stripe test mode
241
+ MOCKINGBIRD_STRIPE_SECRET_KEY=sk_test_... bun run parity -- --only GetPrices,GetProducts
242
+ ```
243
+
244
+ `mock:server` delivers webhooks to the targets in `MOCKINGBIRD_STRIPE_WEBHOOK_TARGETS` (a JSON array
245
+ of `{apiKey, url, secret}`, matched by the API key that produced the event) or to a single fallback
246
+ target from `MOCKINGBIRD_STRIPE_WEBHOOK_URL` + `MOCKINGBIRD_STRIPE_WEBHOOK_SECRET`:
247
+
248
+ ```bash
249
+ PORT=12111 MOCKINGBIRD_STRIPE_WEBHOOK_TARGETS='[{"apiKey":"sk_test_mso","url":"http://127.0.0.1:3100/billing/webhooks/stripe/mso","secret":"whsec_..."}]' bun run mock:server
250
+ ```
251
+
252
+ Live parity needs a real `sk_test_` key and mutates a shared test account.
253
+
254
+ Part of [mockingbird](https://github.com/crvouga/mockingbird) — agent integration guide: [`@crvouga/mockingbird`](https://github.com/crvouga/mockingbird/tree/main/packages/facade#readme).
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Real Stripe partitions objects by account; the mock partitions by the bearer key that created
3
+ * them, so two different test keys are two accounts and a PC object is `resource_missing` on the
4
+ * MSO account. The key itself is never stored — only this deterministic, opaque token.
5
+ */
6
+ export declare const accountOfKey: (key: string) => string;
7
+ export declare const accountOf: (request: Request) => string;
8
+ //# sourceMappingURL=account.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account.d.ts","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,WAAkC,CAAA;AAE1E,eAAO,MAAM,SAAS,GAAI,SAAS,OAAO,KAAG,MAI5C,CAAA"}
@@ -0,0 +1,13 @@
1
+ import { opaqueToken } from "@crvouga/mockingbird-service";
2
+ /**
3
+ * Real Stripe partitions objects by account; the mock partitions by the bearer key that created
4
+ * them, so two different test keys are two accounts and a PC object is `resource_missing` on the
5
+ * MSO account. The key itself is never stored — only this deterministic, opaque token.
6
+ */
7
+ export const accountOfKey = (key) => `acct_${opaqueToken(key, 8)}`;
8
+ export const accountOf = (request) => {
9
+ const authorization = request.headers.get("authorization");
10
+ const match = authorization === null ? null : /^Bearer\s+(\S+)$/.exec(authorization);
11
+ return accountOfKey(match?.[1] ?? "");
12
+ };
13
+ //# sourceMappingURL=account.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account.js","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAE1D;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,GAAW,EAAE,EAAE,CAAC,QAAQ,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAA;AAE1E,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,OAAgB,EAAU,EAAE;IACpD,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAA;IAC1D,MAAM,KAAK,GAAG,aAAa,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;IACpF,OAAO,YAAY,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;AACvC,CAAC,CAAA"}
@@ -0,0 +1,4 @@
1
+ import { type OperationHandler } from "@crvouga/mockingbird-service";
2
+ import { type Services } from "./internal.js";
3
+ export declare const chargeHandlers: (services: Services) => Record<string, OperationHandler>;
4
+ //# sourceMappingURL=charges.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"charges.d.ts","sourceRoot":"","sources":["../src/charges.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAElF,OAAO,EAAkD,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAA;AA6E7F,eAAO,MAAM,cAAc,GAAI,UAAU,QAAQ,KAAG,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAuBjF,CAAA"}
@@ -0,0 +1,89 @@
1
+ import { jsonResponse } from "@crvouga/mockingbird-service";
2
+ import { applyExpand } from "./expand.js";
3
+ import { requestScope, requireCharge } from "./internal.js";
4
+ import { matchesCreated, paginate } from "./list.js";
5
+ import { queryParams } from "./params.js";
6
+ import { renderCharge, renderCoupon, renderCustomer, renderInvoice, renderPaymentIntent, renderPaymentMethod, } from "./render.js";
7
+ const stringOf = (params, key) => {
8
+ const value = params[key];
9
+ return typeof value === "string" && value !== "" ? value : null;
10
+ };
11
+ const expanders = (scope) => {
12
+ const invoice = (id) => {
13
+ const record = scope.account.invoices.get(id);
14
+ return record ? renderInvoice(record, scope.account) : undefined;
15
+ };
16
+ const coupon = (id) => {
17
+ const record = scope.account.coupons.get(id);
18
+ return record ? renderCoupon(record) : undefined;
19
+ };
20
+ const paymentIntent = (id) => {
21
+ const record = scope.account.paymentIntents.get(id);
22
+ return record ? renderPaymentIntent(record) : undefined;
23
+ };
24
+ const customer = (id) => {
25
+ const entry = scope.account.customers.get(id);
26
+ return entry?.kind === "live" ? renderCustomer(entry.customer) : undefined;
27
+ };
28
+ const paymentMethod = (id) => {
29
+ const record = scope.account.paymentMethods.get(id);
30
+ return record ? renderPaymentMethod(record) : undefined;
31
+ };
32
+ return {
33
+ invoice,
34
+ "invoice.discounts.coupon": coupon,
35
+ payment_intent: paymentIntent,
36
+ "payment_intent.invoice": invoice,
37
+ customer,
38
+ payment_method: paymentMethod,
39
+ "data.invoice": invoice,
40
+ "data.invoice.discounts.coupon": coupon,
41
+ "data.payment_intent": paymentIntent,
42
+ "data.payment_intent.invoice": invoice,
43
+ "data.customer": customer,
44
+ };
45
+ };
46
+ /**
47
+ * Stripe expands a path's ancestors along with it, and `invoice.discounts.coupon` only walks into
48
+ * an invoice once the shallower `invoice` pass has replaced the id with the rendered object, so
49
+ * every prefix is added and the list is applied shallowest first.
50
+ */
51
+ const orderedPaths = (expand) => {
52
+ const requested = Array.isArray(expand)
53
+ ? expand.filter((entry) => typeof entry === "string")
54
+ : typeof expand === "string" && expand !== ""
55
+ ? [expand]
56
+ : [];
57
+ const paths = new Set();
58
+ for (const path of requested) {
59
+ const parts = path.split(".");
60
+ for (let depth = 1; depth <= parts.length; depth += 1)
61
+ paths.add(parts.slice(0, depth).join("."));
62
+ }
63
+ return [...paths].sort((a, b) => a.split(".").length - b.split(".").length);
64
+ };
65
+ const render = (scope, charge, expand) => applyExpand(renderCharge(charge), orderedPaths(expand), expanders(scope));
66
+ export const chargeHandlers = (services) => ({
67
+ GetCharges: async (context) => {
68
+ const scope = requestScope(services, context);
69
+ const params = queryParams(context);
70
+ const customer = stringOf(params, "customer");
71
+ const paymentIntent = stringOf(params, "payment_intent");
72
+ const page = await paginate(scope.account.charges, params, {
73
+ url: "/v1/charges",
74
+ kind: "charge",
75
+ where: (record) => matchesCreated(record.created, params.created) &&
76
+ (customer === null || record.customer === customer) &&
77
+ (paymentIntent === null || record.payment_intent === paymentIntent),
78
+ render: renderCharge,
79
+ });
80
+ return jsonResponse(200, applyExpand(page, orderedPaths(params.expand), expanders(scope)));
81
+ },
82
+ GetChargesCharge: async (context) => {
83
+ const scope = requestScope(services, context);
84
+ const params = queryParams(context);
85
+ const charge = requireCharge(scope, context.params.charge ?? "");
86
+ return jsonResponse(200, render(scope, charge, params.expand));
87
+ },
88
+ });
89
+ //# sourceMappingURL=charges.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"charges.js","sourceRoot":"","sources":["../src/charges.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { type OperationHandler } from "@crvouga/mockingbird-service";
2
+ import { createDiscount, requireSubscription, type Services } from "./internal.js";
3
+ export declare const checkoutSessionHandlers: (services: Services) => Record<string, OperationHandler>;
4
+ export { createDiscount, requireSubscription };
5
+ //# sourceMappingURL=checkout-sessions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkout-sessions.d.ts","sourceRoot":"","sources":["../src/checkout-sessions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAGlF,OAAO,EAEL,cAAc,EAKd,mBAAmB,EACnB,KAAK,QAAQ,EACd,MAAM,eAAe,CAAA;AAiGtB,eAAO,MAAM,uBAAuB,GAAI,UAAU,QAAQ,KAAG,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAgP3F,CAAA;AAED,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,CAAA"}