@emulates/flex 2.3.1
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/CHANGELOG.md +9 -0
- package/DISCOVERY.md +55 -0
- package/README.md +234 -0
- package/SUPPORT.md +24 -0
- package/dist/chunk-O6YIBDG6.js +6137 -0
- package/dist/chunk-O6YIBDG6.js.map +7 -0
- package/dist/chunk-TYLVTAND.js +871 -0
- package/dist/chunk-TYLVTAND.js.map +7 -0
- package/dist/cli.js +19 -0
- package/dist/cli.js.map +7 -0
- package/dist/index.d.ts +1267 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +7 -0
- package/dist/server.d.ts +1659 -0
- package/dist/server.js +12 -0
- package/dist/server.js.map +7 -0
- package/openapi.yaml +1180 -0
- package/package.json +129 -0
package/CHANGELOG.md
ADDED
package/DISCOVERY.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# @emulates/flex discovery
|
|
2
|
+
|
|
3
|
+
This is the installed-package index for coding agents and tooling. All relative links resolve
|
|
4
|
+
inside `node_modules/@emulates/flex/`; no repository checkout is needed to discover the emulator's
|
|
5
|
+
supported surface or documented behavior.
|
|
6
|
+
|
|
7
|
+
## Capability and behavior sources
|
|
8
|
+
|
|
9
|
+
| Question | Authoritative file | What it contains |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| Behaviour and integration | [`README.md`](README.md) | Routes, state transitions, auth, webhooks, controls, presets and deliberate omissions. |
|
|
12
|
+
| Exact capabilities | [`SUPPORT.md`](SUPPORT.md) | Supported, unsupported and parity-covered operations or commands, including reasons for gaps. |
|
|
13
|
+
| Wire contract | [`openapi.yaml`](openapi.yaml) | Machine-readable paths, methods, schemas, responses and parity annotations. |
|
|
14
|
+
| Public API | [`dist/index.d.ts`](dist/index.d.ts) | The installed package's exact TypeScript exports and signatures. |
|
|
15
|
+
| Package metadata | [`package.json`](package.json) | Runtime/entry-point claims, vendor links, parity scope/tier and `emulates.discovery`. |
|
|
16
|
+
|
|
17
|
+
Read these together: the contract/capability matrix says *what* is available, while the README
|
|
18
|
+
defines stateful behavior, lifecycle rules, test controls, and intentional oracle differences.
|
|
19
|
+
If prose and an executable surface disagree, report a parity mismatch instead of adding a
|
|
20
|
+
consumer-side workaround.
|
|
21
|
+
|
|
22
|
+
## Parity and oracle
|
|
23
|
+
|
|
24
|
+
- Declared parity surface: **Checkout, subscriptions, and webhooks**.
|
|
25
|
+
- Parity tier: **cold** (the repository controls when live checks run).
|
|
26
|
+
- Oracle: **Live vendor API or sandbox**.
|
|
27
|
+
- Repository command: `bun run parity:service -- flex`.
|
|
28
|
+
- Evidence model: Run from an Emulates checkout; credentials come only from .env.local or GitHub Actions secrets. Missing credentials exit 2.
|
|
29
|
+
|
|
30
|
+
The npm package contains evidence summaries and the exact contract, not credentials or the
|
|
31
|
+
repository-only parity harness. Self-parity/property and acceptance tests run in the Emulates
|
|
32
|
+
repository; live parity is an additional oracle check, not a substitute for the packaged matrix.
|
|
33
|
+
|
|
34
|
+
## Runtime introspection
|
|
35
|
+
|
|
36
|
+
- `GET /__admin/health`
|
|
37
|
+
- `GET /__admin`
|
|
38
|
+
- `GET /__admin/state`
|
|
39
|
+
- `GET /__admin/requests`
|
|
40
|
+
- `GET /__admin/metrics`
|
|
41
|
+
- `GET /__admin/faults/presets`
|
|
42
|
+
- `GET /__admin/ui`
|
|
43
|
+
|
|
44
|
+
For HTTP services, use `x-emulates-namespace` (or the documented credential/path carrier) so
|
|
45
|
+
parallel tests do not share state. Admin state, journal, metrics and fault-preset endpoints are
|
|
46
|
+
designed for assertions and diagnosis by consuming test suites.
|
|
47
|
+
|
|
48
|
+
## Report a mismatch or missing capability
|
|
49
|
+
|
|
50
|
+
Follow the [agent reporting contract](https://github.com/crvouga/emulators/blob/main/docs/REPORTING_ISSUES.md). Include package version,
|
|
51
|
+
operation/command, a minimal redacted request, actual emulator result, expected oracle result or vendor
|
|
52
|
+
documentation, and whether the mismatch appears in the matrix. Never include keys, tokens,
|
|
53
|
+
customer data, prompts, PHI, card data, or unredacted recordings.
|
|
54
|
+
|
|
55
|
+
Service key: `flex`.
|
package/README.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# @emulates/flex
|
|
2
|
+
|
|
3
|
+
> Part of [Emulates](https://github.com/crvouga/emulators): high-fidelity, in-process emulators for APIs and databases.
|
|
4
|
+
|
|
5
|
+
Stateful emulator of the **Flex** (withflex.com) HSA/FSA payments API for test suites: products
|
|
6
|
+
(answered from a recorded catalog corpus), checkout sessions in `payment` (one-time),
|
|
7
|
+
`subscription`, `off_session` and `setup` modes, subscriptions, customers, setup intents,
|
|
8
|
+
refunds, the **hosted checkout page**, and the Svix-signed webhooks Flex posts back. A UI checkout that drove the real
|
|
9
|
+
`checkout.withflex.com` page and then waited on a 5-minute reconciler settles here in
|
|
10
|
+
milliseconds: the page is local, and the signed webhook reaches the app as soon as the card is
|
|
11
|
+
accepted.
|
|
12
|
+
|
|
13
|
+
- Operation coverage: [SUPPORT.md](https://github.com/crvouga/emulators/blob/main/packages/service/flex/SUPPORT.md)
|
|
14
|
+
- Flex publishes no machine-readable spec: the contract (`openapi.yaml`) is hand-authored from
|
|
15
|
+
the wire shapes our consumer reads and writes (`B/billing/flex/`), and every field its zod
|
|
16
|
+
schemas require is served. Subscription mode, `price_data.recurring` and the subscription
|
|
17
|
+
object follow the [Flex API reference](https://docs.withflex.com/api-reference) (there is
|
|
18
|
+
no sandbox recording).
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -D @emulates/flex
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
ESM only. Node >= 22 or Bun >= 1.2. No native dependencies. Serve it with
|
|
27
|
+
`npx emulates-flex serve`, `createServer` from `./server` (Node), or `createRuntime` with any
|
|
28
|
+
Fetch server.
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
Point the app at the emulator:
|
|
33
|
+
|
|
34
|
+
| Env | Value |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| `FLEX_API_BASE_URL` | `http://127.0.0.1:8792` (or `…/__admin/ns/<namespace>`) |
|
|
37
|
+
| `FLEX_API_KEY` | any `fsk_test_…` key (test mode); `fsk_…` is live mode; other formats get 401 |
|
|
38
|
+
| `FLEX_WEBHOOK_SECRET` | the same value as `--webhook-secret`: `fwhsec_<base64>` or `whsec_<base64>` |
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx emulates-flex serve --port 8792 \
|
|
42
|
+
--webhook-url http://127.0.0.1:3000/billing/webhooks/flex \
|
|
43
|
+
--webhook-secret "$FLEX_WEBHOOK_SECRET"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { createRuntime } from "@emulates/flex"
|
|
48
|
+
|
|
49
|
+
const flex = createRuntime({
|
|
50
|
+
webhooks: {
|
|
51
|
+
url: "http://127.0.0.1:3000/billing/webhooks/flex",
|
|
52
|
+
secret: "fwhsec_ZmxleC1tb2NrLXNpZ25pbmcta2V5",
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
const post = (path: string, body: unknown) =>
|
|
56
|
+
flex.fetch(
|
|
57
|
+
new Request(`http://flex.test${path}`, {
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: { "content-type": "application/json", authorization: "Bearer fsk_test_suite" },
|
|
60
|
+
body: JSON.stringify(body),
|
|
61
|
+
}),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
const { checkout_session } = (await (
|
|
65
|
+
await post("/v1/checkout/sessions", {
|
|
66
|
+
checkout_session: {
|
|
67
|
+
success_url: "https://app.test/done?session_id={CHECKOUT_SESSION_ID}",
|
|
68
|
+
cancel_url: "https://app.test/cart",
|
|
69
|
+
client_reference_id: "attempt-1",
|
|
70
|
+
line_items: [
|
|
71
|
+
{ price_data: { product: "fprod_01m0tgysj4ahvf8fas60c2ef2d", unit_amount: 4500 }, quantity: 1 },
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
).json()) as { checkout_session: { checkout_session_id: string; url: string } }
|
|
76
|
+
// …open checkout_session.url in the browser and pay with 4000 0512 3000 0072, or:
|
|
77
|
+
await post(`/__admin/sessions/${checkout_session.checkout_session_id}/complete`, {})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Browser E2E suites
|
|
81
|
+
|
|
82
|
+
Run `npx emulates-flex serve` on a port, point `FLEX_API_BASE_URL` at it, and the browser
|
|
83
|
+
lands on the emulator's page instead of `checkout.withflex.com`: no network, no shared Flex
|
|
84
|
+
account, and the signed webhook reaches the app as soon as Pay is clicked. The page URL takes
|
|
85
|
+
the emulator's origin, or `--public-url` when given. A suite that finds the Flex tab by host
|
|
86
|
+
(`/\bcheckout\.withflex\.com\b/`) keeps working with
|
|
87
|
+
`--public-url http://checkout.withflex.com.localhost:8792`: Chromium resolves every
|
|
88
|
+
`*.localhost` name to loopback. Other browsers may need a hosts entry.
|
|
89
|
+
|
|
90
|
+
### Routes
|
|
91
|
+
|
|
92
|
+
All API bodies are wrapped: `{product: {…}}`, `{checkout_session: {…}}`, `{customer: {…}}`,
|
|
93
|
+
`{setup_intent: {…}}`, `{products: […], has_more}`, `{checkout_sessions: […], has_more}`.
|
|
94
|
+
Errors are `{error: {type, message, param?}}`.
|
|
95
|
+
|
|
96
|
+
| Route | Behaviour |
|
|
97
|
+
| --- | --- |
|
|
98
|
+
| `GET /v1/products?limit=&starting_after=` | Oldest first; `limit` 1–100 (default 10). The corpus comes first, then created products. |
|
|
99
|
+
| `POST /v1/products` | `{product: {name, description?, url?, client_reference_id?, metadata?}}`. New products are active, `hsa_fsa_eligibility: null` until classified (`PUT /__admin/products/:id`). |
|
|
100
|
+
| `GET /v1/products/{id}` | `product_id, name, description, url, client_reference_id, hsa_fsa_eligibility, visit_type, active, test_mode, metadata, created_at`. |
|
|
101
|
+
| `PATCH /v1/products/{id}` | `{product: {active?, name?, description?, url?, metadata?}}`; emits `product.updated`. |
|
|
102
|
+
| `POST /v1/checkout/sessions` | `Idempotency-Key` honoured. `mode` `payment` (≥1 line item), `subscription` (≥1 line item whose `price_data.recurring` is `{interval: day\|week\|month\|year, interval_count?}`, else 400; optional `subscription_data: {cancel_at_period_end?, metadata?}`), `setup` (a customer and no line items, else 400), `off_session` (customer + a saved `payment_method`, charged before answering: the response is already `complete`, or its expanded payment intent is `requires_payment_method` / `requires_action` per `offSessionOutcome`). Unknown or inactive products, customers and payment methods are 400. `redirect_url` and `url` are the hosted page. |
|
|
103
|
+
| `GET /v1/checkout/sessions/{id}?expand_customer=true&expand_payment_intent=true` | The session; expansions return `customer` / `payment_intent` objects instead of ids. |
|
|
104
|
+
| `GET /v1/checkout/sessions?client_reference_id=&limit=&starting_after=` | Newest first (our ambiguous-create recovery). |
|
|
105
|
+
| `POST /v1/checkout/sessions/{id}/refund` | `Idempotency-Key` honoured. `{checkout_session: {}}` (full) or `{checkout_session: {amount}}`; 400 when unpaid or over-refunded. |
|
|
106
|
+
| `POST /v1/customers` | `Idempotency-Key` honoured. `{customer: {first_name, last_name, email, phone}}` (all required). |
|
|
107
|
+
| `GET /v1/setup_intents/{id}?expand=customer,payment_method` | `setup_intent_id, status, customer, payment_method`. |
|
|
108
|
+
| `GET /v1/subscriptions/{id}` | `{subscription: {subscription_id, status, items, customer, default_payment_method, cancel_at_period_end, current_period_start, current_period_end, canceled_at, metadata, test_mode, created_at}}`. A paid subscription-mode session starts one: `active`, `items` = its recurring line items, the period one interval of the first recurring item (month/year steps clamp to the month's last day), charged to the card just used; the session's `subscription` holds its id. |
|
|
109
|
+
|
|
110
|
+
Auth: `Authorization: Bearer fsk_test_…` or `fsk_…`; a missing key, or any other format
|
|
111
|
+
(`sk_test_…`, `whsec_…`), is 401 `authentication_error`. `test_mode` on created objects follows
|
|
112
|
+
the key. Same key + same body replays the stored response; same key + a different body is 400
|
|
113
|
+
`idempotency_error`; a concurrent request with an in-flight key is 409.
|
|
114
|
+
|
|
115
|
+
### Hosted page
|
|
116
|
+
|
|
117
|
+
`GET /pay/{sessionId}` renders a plain form (no scripts) with `data-testid`s
|
|
118
|
+
`flex-mock-email`, `-first-name`, `-last-name`, `-phone`, `-card`, `-exp`, `-cvc`, `-zip`,
|
|
119
|
+
`-pay`, `-cancel`, `-error`, `-amount`, and on the letter step `-lmn-submit`. Inputs also carry
|
|
120
|
+
the `name`s and placeholders our codecept locators look for (`cardNumber`, `expiry`, `cvc`,
|
|
121
|
+
`postalCode`, `email`, …). `POST /pay/{sessionId}` submits it:
|
|
122
|
+
|
|
123
|
+
| Card | Result |
|
|
124
|
+
| --- | --- |
|
|
125
|
+
| `4000 0512 3000 0072` | HSA card: succeeds. |
|
|
126
|
+
| `4242 4242 4242 4242` (or any other valid card) | Succeeds; if a line item's product is `letter_of_medical_necessity`, the session gets `next_action: {type: "collect_letter_of_medical_necessity", collect_letter_of_medical_necessity: {url}}` and the browser goes to that step; submitting it completes the payment. |
|
|
127
|
+
| `4000 0000 0000 0002` | Declines: 402 page with `<div role="alert">Your card was declined.</div>`; the payment intent is `requires_payment_method`. |
|
|
128
|
+
|
|
129
|
+
Success 302s to `success_url` with `{CHECKOUT_SESSION_ID}` substituted (raw and
|
|
130
|
+
`%7BCHECKOUT_SESSION_ID%7D`); `GET /pay/{id}/cancel` (the Cancel link) 302s to `cancel_url`,
|
|
131
|
+
leaving the session open. A contact email on a session without a customer creates one. In a
|
|
132
|
+
namespace the page URL carries `/__admin/ns/<name>` (the browser sends no headers); `publicUrl`
|
|
133
|
+
overrides the origin.
|
|
134
|
+
|
|
135
|
+
### Webhooks
|
|
136
|
+
|
|
137
|
+
Svix-signed (`svix-id`, `svix-timestamp` = wall clock, `svix-signature: v1,<base64
|
|
138
|
+
HMAC-SHA256(key, "<id>.<ts>.<body>")>`, key = base64-decoded secret after `fwhsec_`/`whsec_`).
|
|
139
|
+
Body: `{event: {event_id, event_type, object, event_dt, test_mode, created_at}}`.
|
|
140
|
+
Checkout events carry the session (so `object.checkout_session_id`), payment-intent events the
|
|
141
|
+
intent plus `checkout_session_id`, refund events `checkout_session` and `payment_intent`, and
|
|
142
|
+
`product.updated` the product (`object.product_id`).
|
|
143
|
+
|
|
144
|
+
| Event | When |
|
|
145
|
+
| --- | --- |
|
|
146
|
+
| `payment_intent.succeeded`, then `checkout.session.completed` | the page (or `…/complete`, or an off-session charge) settles a session |
|
|
147
|
+
| `customer.subscription.created` (the subscription), before those two | a subscription-mode session settles |
|
|
148
|
+
| `checkout.session.async_payment_succeeded` | settling a session whose intent was `processing` |
|
|
149
|
+
| `checkout.session.async_payment_failed` | a decline (page, admin, off-session) |
|
|
150
|
+
| `checkout.session.expired` | `…/expire`, or `expires_at` passing on the emulator clock (default 24 h) |
|
|
151
|
+
| `refund.created`, `charge.refunded`, `checkout.session.refunded`, `refund.updated`, `charge.refund.updated` | each refund |
|
|
152
|
+
| `product.updated` | `PATCH /v1/products/{id}` and `PUT /__admin/products/:id` |
|
|
153
|
+
| `checkout_session.completed`, `checkout_session.expired` | the aliases, with `PUT /__admin/settings {"eventNaming": "underscored"}` |
|
|
154
|
+
|
|
155
|
+
`POST /__admin/events {type, session | product}` emits any type on demand. Non-2xx answers are
|
|
156
|
+
retried (immediately, 5 s, 5 min, 30 min, 2 h); `GET /__admin/webhooks`, `…/events`,
|
|
157
|
+
`POST /__admin/webhooks/flush`, `…/:id/replay`, `PUT /__admin/webhook-endpoints` as usual.
|
|
158
|
+
|
|
159
|
+
### Admin (beyond the standard contract)
|
|
160
|
+
|
|
161
|
+
| Route | Effect |
|
|
162
|
+
| --- | --- |
|
|
163
|
+
| `PUT /__admin/products/:id` | `{hsa_fsa_eligibility?, active?, test_mode?, visit_type?, client_reference_id?, name?, metadata?}`; emits `product.updated`. |
|
|
164
|
+
| `POST /__admin/sessions/:id/complete` | `{card?}`: settle as if paid (HSA card unless `card` is `4242…`). |
|
|
165
|
+
| `POST /__admin/sessions/:id/decline` | Payment intent → `requires_payment_method`. |
|
|
166
|
+
| `POST /__admin/sessions/:id/expire` | Session → `expired`. |
|
|
167
|
+
| `POST /__admin/sessions/:id/require_action` | `{next_action_type?}`: `collect_letter_of_medical_necessity` (default), `provide_second_payment_method`, `provide_alternative_payment_method`, `payment_failed`. |
|
|
168
|
+
| `PUT /__admin/sessions/:id/payment-intent` | `{status, amount_received?}`: `requires_payment_method`, `requires_action`, `processing`, `succeeded`, `canceled`. |
|
|
169
|
+
| `GET /__admin/sessions`, `GET /__admin/sessions/:id` | The namespace's sessions. |
|
|
170
|
+
| `POST /__admin/events` | Emit any event type for a session or product. |
|
|
171
|
+
| `GET/PUT /__admin/settings` | `{eventNaming, offSessionOutcome, sessionTtlSeconds, lmnOnRegularCard, publicUrl}`. |
|
|
172
|
+
| `POST /__admin/tick` | Expire due sessions now (the served emulator ticks every 100 ms). |
|
|
173
|
+
|
|
174
|
+
Every orchestrator state is reachable: pending (open), action_required (`require_action`, or an
|
|
175
|
+
intent `requires_action`), processing, canceled (intent `canceled`, or expired), failed
|
|
176
|
+
(`decline`), succeeded, refunded (full refund), quarantined (partial refund,
|
|
177
|
+
`amount_mismatch`, duplicate sessions).
|
|
178
|
+
|
|
179
|
+
Fault presets (`POST /__admin/faults {"preset": "<name>", "count"?: n}`; `GET /__admin/faults/presets`):
|
|
180
|
+
`create_4xx` (400, nothing created), `create_5xx` (creates, then 500: recovery adopts it),
|
|
181
|
+
`create_5xx_not_created`, `timeout` (creates, answers after 16 s, past the client's 15 s abort;
|
|
182
|
+
`params.delayMs` overrides), `invalid_shape` (no `redirect_url`/`url`), `amount_mismatch`
|
|
183
|
+
(`amount_total` + 100), `duplicate_sessions_for_client_reference` (two sessions, then 500),
|
|
184
|
+
`refund_4xx`, `server_error`, `webhook_duplicate`, `webhook_reorder`, `webhook_drop`.
|
|
185
|
+
|
|
186
|
+
### Namespaces
|
|
187
|
+
|
|
188
|
+
`x-emulates-namespace`, a `/__admin/ns/<name>` prefix on `FLEX_API_BASE_URL`, or by API key:
|
|
189
|
+
`PUT /__admin/credentials {"credentials": {"<FLEX_API_KEY>": "<namespace>"}}`.
|
|
190
|
+
|
|
191
|
+
### Corpus
|
|
192
|
+
|
|
193
|
+
`src/corpus/products.ts` is the product side of every row of the consumer's
|
|
194
|
+
`flexCatalogMappings` reference fixture (663 rows, regenerated with
|
|
195
|
+
`bun scripts/corpus.ts <fixture.json>`): product id, client reference, the `acme_purpose` /
|
|
196
|
+
`acme_merchant_product_id` / `acme_client_reference_id` metadata our catalog validation
|
|
197
|
+
compares, eligibility and visit type. Every product is active and test-mode, so our validation
|
|
198
|
+
reproduces each mapping row's own `active` flag. No sandbox recording exists (no credentials),
|
|
199
|
+
so product names are synthesised.
|
|
200
|
+
|
|
201
|
+
### Deliberately not modelled
|
|
202
|
+
|
|
203
|
+
- Real card processing, Stripe iframes and split-tender payments: the page is a plain form, and
|
|
204
|
+
split payment is reachable only as `next_action: provide_second_payment_method`.
|
|
205
|
+
- The letter-of-medical-necessity questionnaire: one submit button stands in for it.
|
|
206
|
+
- Test/live data separation: a live key sees the same objects (only `test_mode` differs).
|
|
207
|
+
- Subscription lifecycle after checkout: renewals, invoices and the `invoice.*` events,
|
|
208
|
+
trials, `customer.subscription.updated` / `.deleted`, and the subscription update/cancel
|
|
209
|
+
routes. A subscription stays `active` for its first period. The Prices API (`price` ids in
|
|
210
|
+
line items) is not modelled either: use inline `price_data`.
|
|
211
|
+
- Coupons, promotion codes (`allow_promotion_codes` is echoed only), partial captures,
|
|
212
|
+
disputes.
|
|
213
|
+
- Flex's exact error texts and ids: shapes follow what our consumer reads; ids look like
|
|
214
|
+
`fprod_01z…`, `fcs_01z…`, `fcus_…`, `fpi_…`, `fseti_…`, `fpm_…`, `fevt_…`.
|
|
215
|
+
|
|
216
|
+
## API
|
|
217
|
+
|
|
218
|
+
| Export | Kind | Description |
|
|
219
|
+
| --- | --- | --- |
|
|
220
|
+
| `FlexAPI` | class | The in-process emulator: `fetch(request)`, `reset()`, `settle(id)`, `decline(id)`, `expire(id)`, `requireAction(id, type)`, `setPaymentIntent(id, patch)`, `applyRefund(id, amount)`, `putProduct(product)`, `emitFor(type, target)`, `tick()`, `sessions()`, `subscriptions()`, `present(session, view)`. Options: `sqlite`, `now`, `namespace`, `publicNamespace`, `products`, `settings`, `onEvent`. |
|
|
221
|
+
| `createRuntime` | function | The emulator with the full service contract. Options: `webhooks: {url, secret, retryDelaysMs?, fetch?}`, `products`, `settings`, `tickMs`, `clock`, `seed`, `adminKey`, `onLog`. |
|
|
222
|
+
| `FLEX_PRESETS` | object | Every named fault preset. |
|
|
223
|
+
| `FLEX_NAMESPACE` | string | The service name, `"flex"`. |
|
|
224
|
+
| `FLEX_EVENT_TYPES` | array | Every webhook event type, aliases included. |
|
|
225
|
+
| `keyMode` | function | `"test"` for `fsk_test_…`, `"live"` for `fsk_…`, otherwise `undefined`. |
|
|
226
|
+
| `isNextActionType` | function | Whether a string is a next-action type. |
|
|
227
|
+
| `CARDS`, `classifyCard`, `substituteSessionId` | values | The hosted page's test cards, its card classifier, and the `{CHECKOUT_SESSION_ID}` substitution. |
|
|
228
|
+
| `CORPUS_ROWS`, `corpusProduct` | values | The recorded product corpus and its row → product mapping. |
|
|
229
|
+
| `DEFAULT_SETTINGS`, `ELIGIBILITIES`, `NEXT_ACTION_TYPES`, `PAYMENT_INTENT_STATUSES`, `SUBSCRIPTION_STATUSES` | values | Defaults and enums. |
|
|
230
|
+
| `periodEnd` | function | `periodEnd(startMs, {interval, interval_count?})`: the end of a billing period (epoch ms). |
|
|
231
|
+
| `document`, `operationIds`, `supportedOperationIds` | values | The vendored OpenAPI contract and its operation ids. |
|
|
232
|
+
| `createServer`, `serveTarget`, `DEFAULT_PORT` (`./server`) | Node | Serve over `node:http` (expiry ticks every 100 ms); the `serve` CLI target (`--webhook-url`, `--webhook-secret`, `--public-url`, `--event-naming`); port 8792. |
|
|
233
|
+
|
|
234
|
+
Part of [emulators](https://github.com/crvouga/emulators).
|
package/SUPPORT.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Flex HSA/FSA payments API (Emulates subset) — operation support
|
|
2
|
+
|
|
3
|
+
Generated from `openapi.yaml`; do not edit by hand.
|
|
4
|
+
|
|
5
|
+
- operations in spec: **14**
|
|
6
|
+
- supported by the emulator: **14**
|
|
7
|
+
- parity enabled: **11**
|
|
8
|
+
|
|
9
|
+
| operationId | route | emulator | parity | notes |
|
|
10
|
+
| --- | --- | --- | --- | --- |
|
|
11
|
+
| `ListProducts` | `GET /v1/products` | ✅ supported | ✅ | |
|
|
12
|
+
| `CreateProduct` | `POST /v1/products` | ✅ supported | ⚠️ unsafe (opt-in) | |
|
|
13
|
+
| `GetProduct` | `GET /v1/products/{productId}` | ✅ supported | ✅ | |
|
|
14
|
+
| `UpdateProduct` | `PATCH /v1/products/{productId}` | ✅ supported | ⚠️ unsafe (opt-in) | |
|
|
15
|
+
| `ListCheckoutSessions` | `GET /v1/checkout/sessions` | ✅ supported | ✅ | |
|
|
16
|
+
| `CreateCheckoutSession` | `POST /v1/checkout/sessions` | ✅ supported | ⚠️ unsafe (opt-in) | |
|
|
17
|
+
| `GetCheckoutSession` | `GET /v1/checkout/sessions/{sessionId}` | ✅ supported | ✅ | |
|
|
18
|
+
| `RefundCheckoutSession` | `POST /v1/checkout/sessions/{sessionId}/refund` | ✅ supported | ⚠️ unsafe (opt-in) | |
|
|
19
|
+
| `CreateCustomer` | `POST /v1/customers` | ✅ supported | ⚠️ unsafe (opt-in) | |
|
|
20
|
+
| `GetSetupIntent` | `GET /v1/setup_intents/{setupIntentId}` | ✅ supported | ✅ | |
|
|
21
|
+
| `GetSubscription` | `GET /v1/subscriptions/{subscriptionId}` | ✅ supported | ✅ | |
|
|
22
|
+
| `HostedCheckoutPage` | `GET /pay/{sessionId}` | ✅ supported | ❌ disabled | A browser-facing HTML page; covered by the acceptance suite, not by JSON walks. |
|
|
23
|
+
| `SubmitHostedCheckout` | `POST /pay/{sessionId}` | ✅ supported | ❌ disabled | A browser form post; covered by the acceptance suite. |
|
|
24
|
+
| `CancelHostedCheckout` | `GET /pay/{sessionId}/cancel` | ✅ supported | ❌ disabled | A browser navigation to cancel_url; covered by the acceptance suite. |
|