@fun-xyz/fiat-contract 0.7.0 → 0.8.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 +14 -11
- package/dist/{chunk-OGNYHIOE.mjs → chunk-LVVQ46YG.mjs} +31 -24
- package/dist/fixtures/index.d.ts +3 -4
- package/dist/index.js +217 -94
- package/dist/index.mjs +188 -72
- package/dist/table.d.ts +2 -3
- package/dist/table.js +30 -23
- package/dist/table.mjs +1 -1
- package/dist/types.d.ts +12 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Four things, zero runtime logic beyond validation:
|
|
|
10
10
|
| `src/types.ts` | `FlowState` · `Transition` · `FailureReason` · `Surface` · `Instructions` · `FormDescriptor` · `OrderStatus` · `FiatStepResponse` |
|
|
11
11
|
| `src/schemas.ts` | zod mirrors of every type — the single runtime validator |
|
|
12
12
|
| `src/table.ts` | the transition table **as data**: per state, its legal transition set, the states any call from it may return, and `terminal: boolean` |
|
|
13
|
-
| `src/assert.ts` + `src/fixtures/` | `assertFiatStepResponse` · `assertLegalEmission` · `assertLegalReturn` · `walkTable` · fixture loader +
|
|
13
|
+
| `src/assert.ts` + `src/fixtures/` | `assertFiatStepResponse` · `assertLegalEmission` · `assertLegalReturn` · `walkTable` · fixture loader + 18 recorded step responses (inlined as data — no filesystem, so React Native can bundle it) |
|
|
14
14
|
|
|
15
15
|
## Three entry points — production vs test-time
|
|
16
16
|
|
|
@@ -18,11 +18,11 @@ Four things, zero runtime logic beyond validation:
|
|
|
18
18
|
| --- | --- | --- | --- |
|
|
19
19
|
| `@fun-xyz/fiat-contract/types` | **0.1 KB** (types erase) | every type; no runtime values | production, both repos |
|
|
20
20
|
| `@fun-xyz/fiat-contract/table` | **13.8 KB**, zero deps | `TRANSITION_TABLE`, `stateKey`, `tableEntry`, `isTerminal`, `walkTable`, `TABLE_VERSION`, `TERMINAL_ORDER_STATUSES`, `DOCUMENTED_ENDPOINTS`, `UNOFFERED_ENDPOINTS`, `UNREACHABLE_STATES`, `unofferedEndpoints`, `unreachableStates` | **production frontend** + backend |
|
|
21
|
-
| `@fun-xyz/fiat-contract` | 47.6 KB, needs zod | the above + 46 zod schemas + assertions +
|
|
21
|
+
| `@fun-xyz/fiat-contract` | 47.6 KB, needs zod | the above + 46 zod schemas + assertions + 18 fixtures | tests, and backend dev/test guards |
|
|
22
22
|
|
|
23
23
|
`./table` is not a micro-optimisation. Terminality is table data a **shipped** client must read
|
|
24
24
|
(litmus rule 3 — clients never infer it), and Metro has no cross-module tree-shaking on by default,
|
|
25
|
-
so importing `isTerminal` from the root would ship zod and all
|
|
25
|
+
so importing `isTerminal` from the root would ship zod and all 18 fixtures into a React Native
|
|
26
26
|
bundle. Two CI jobs hold that line: `consumer` asserts requiring `./table` never loads zod into the
|
|
27
27
|
process, and `metro` bundles `./table` with real Metro — in **both** package-exports modes, since
|
|
28
28
|
Metro before RN 0.79 ignores `exports` entirely — then greps the emitted bundle for zod, fixtures and
|
|
@@ -31,7 +31,9 @@ schemas and executes it.
|
|
|
31
31
|
Source of truth: [Fiat Client Contract](https://app.notion.com/p/3b9fc3b2a002815eb270fa4c818268cc)
|
|
32
32
|
(§The fiat step response · §Conformance package · §split `InputSpec` — ACCEPTED) and
|
|
33
33
|
[Fiat Frontend — State Machine & Screen Map](https://app.notion.com/p/3bbfc3b2a00281c994c2cebd17b1d6d3)
|
|
34
|
-
(✅ Decisions · per-screen State details · Event bindings per flow state)
|
|
34
|
+
(✅ Decisions · per-screen State details · Event bindings per flow state), plus
|
|
35
|
+
[Fiat KYC — User Flow & API Interfaces](https://app.notion.com/p/3cefc3b2a00281c88e7fe771f0c64332)
|
|
36
|
+
for quote-bound KYC states and endpoints.
|
|
35
37
|
Tracking: [Headless Fiat Onramp](https://linear.app/funxyz/project/headless-fiat-onramp-0147f4102399) ·
|
|
36
38
|
this package is [ENG-5268](https://linear.app/funxyz/issue/ENG-5268).
|
|
37
39
|
|
|
@@ -52,6 +54,9 @@ this package is [ENG-5268](https://linear.app/funxyz/issue/ENG-5268).
|
|
|
52
54
|
string; `GET /fiat/orders/:id` under `SUBMIT` is legal.
|
|
53
55
|
5. **Fixtures stay synthetic.** `q_8f2`, `o_31c`, `eyJ…`, `"…"` — never paste a real session token,
|
|
54
56
|
bank field, or PII value into a fixture. Redaction applies to fixtures too.
|
|
57
|
+
6. **Surface kind names the renderer; provider selects the adapter.** Sumsub capture is
|
|
58
|
+
`{ kind: 'KYC_SDK', provider: 'SUMSUB' }`. It is not `URL_EMBED`: the client mounts an SDK,
|
|
59
|
+
not a URL or iframe.
|
|
55
60
|
|
|
56
61
|
## Versioning — the package version IS the table version
|
|
57
62
|
|
|
@@ -247,7 +252,7 @@ The table is data, so a runtime walk proves every state resolves to a screen —
|
|
|
247
252
|
exhaustiveness alone, because it also fails when a *new* state is added to the contract.
|
|
248
253
|
|
|
249
254
|
```ts
|
|
250
|
-
import { walkTable, isTerminal, TRANSITION_TABLE } from '@fun-xyz/fiat-contract';
|
|
255
|
+
import { assertFixture, walkTable, isTerminal, TRANSITION_TABLE } from '@fun-xyz/fiat-contract';
|
|
251
256
|
|
|
252
257
|
it('every contract state resolves to a screen', () => {
|
|
253
258
|
walkTable((entry) => {
|
|
@@ -256,7 +261,7 @@ it('every contract state resolves to a screen', () => {
|
|
|
256
261
|
});
|
|
257
262
|
|
|
258
263
|
// Terminality is table data — never `transitions.length === 0`
|
|
259
|
-
isTerminal(
|
|
264
|
+
isTerminal(assertFixture('screen-10-kyc-on-hold').stepResponse.state); // false: empty, not over
|
|
260
265
|
isTerminal({ kind: 'ORDER', phase: 'CREATED', status: 'SETTLED' }); // true
|
|
261
266
|
isTerminal({ kind: 'ORDER', phase: 'CREATED', status: 'PROCESSING' }); // false
|
|
262
267
|
|
|
@@ -437,17 +442,15 @@ The current documented shape is what ships; none of these are settled here.
|
|
|
437
442
|
| --- | --- |
|
|
438
443
|
| `orderId` placement | Both: optional on `PAYMENT{INSTRUCT}` (FE doc v0) **and** optional beside `state` (contract worked example). One fixture of each. |
|
|
439
444
|
| `PENDING_ORDER` removal | Kind ships, with the removal proposal flagged on the type, the table entry, and the fixture. Screen 12 stays frozen. |
|
|
440
|
-
| `[OQ7]` failure enumeration | The published `FailureReason` taxonomy only. Expired instructions, partial payment, per-rail cancel eligibility
|
|
441
|
-
|
|
|
445
|
+
| `[OQ7]` failure enumeration | The published `FailureReason` taxonomy only. Expired instructions, partial payment, and per-rail cancel eligibility remain unenumerated; KYC rejection is terminal only for an explicit non-retryable outcome. |
|
|
446
|
+
| Pre-order KYC sequencing | KYC states carry their quote; SDK submission reports to `POST /fiat/kyc/capture`; pending review polls the quote resource. There is no `GET /fiat/kyc`. |
|
|
442
447
|
| Card capture report target | Both topologies are legal in the table (`POST /fiat/orders` for capture-then-order; `POST /fiat/orders/:id/surface-result` otherwise) pending the Transak answer. |
|
|
443
448
|
| Cancel placement | `cancel` is legal on `PAYMENT{INSTRUCT}` and `ORDER{CREATED}`, marked conditional on the placement decision. |
|
|
444
449
|
| `FUN_AUTH` shape | `challenge: Record<string, JsonValue>`; the table entry is `docStatus: 'UNSPECIFIED'`, so `assertLegalEmission` reports it unjudgeable instead of guessing. |
|
|
445
450
|
| `statusHistory` element shape | The documented minimum (`{status}`) — no invented timestamps. |
|
|
446
451
|
| `QR_IMAGE` instruction | In the union per §The fiat step response, flagged against OQ1's "deliberately not pre-declared". |
|
|
447
452
|
|
|
448
|
-
|
|
449
|
-
`FUN_AUTH` (shape owned by the auth spike) and `KYC/CAPTURE` (Screen 8 tombstone, dropped from v1).
|
|
450
|
-
Every other state has a recorded step response.
|
|
453
|
+
`FUN_AUTH` is the only fixture gap declared in `FIXTURE_COVERAGE_GAPS`; its shape remains owned by the auth spike. Every other state has a recorded step response.
|
|
451
454
|
|
|
452
455
|
## Not decided here — needs a human
|
|
453
456
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// package.json
|
|
2
|
-
var version = "0.
|
|
2
|
+
var version = "0.8.0";
|
|
3
3
|
|
|
4
4
|
// src/table.ts
|
|
5
5
|
var TABLE_VERSION = version;
|
|
@@ -125,21 +125,23 @@ var TRANSITION_TABLE = {
|
|
|
125
125
|
{
|
|
126
126
|
id: "capture",
|
|
127
127
|
mode: "CLIENT_SURFACE",
|
|
128
|
-
endpoint: "
|
|
129
|
-
note:
|
|
128
|
+
endpoint: "POST /fiat/kyc/capture",
|
|
129
|
+
note: "Reports SDK submission; provider eligibility is re-evaluated through quote orchestration."
|
|
130
130
|
}
|
|
131
131
|
],
|
|
132
132
|
mayReturn: [
|
|
133
|
+
"KYC/CAPTURE",
|
|
133
134
|
"KYC/INPUT_REQUIRED",
|
|
134
135
|
"KYC/NO_ACTION_REQUIRED:IN_REVIEW",
|
|
135
136
|
"KYC/NO_ACTION_REQUIRED:ON_HOLD",
|
|
136
137
|
"KYC/NO_ACTION_REQUIRED:REJECTED",
|
|
138
|
+
"QUOTE",
|
|
139
|
+
"PAYMENT/CAPTURE",
|
|
140
|
+
"PAYMENT/INSTRUCT",
|
|
137
141
|
"ORDER/AWAITING_CONFIRMATION"
|
|
138
142
|
],
|
|
139
143
|
terminal: false,
|
|
140
|
-
notes: [
|
|
141
|
-
"Dropped from v1 (no UK headless-module coverage) \u2014 FE Screen 8 is a tombstone. Kind retained as vocabulary; the handshake (supportedStepKinds) keeps the backend from routing v1 SDKs here."
|
|
142
|
-
]
|
|
144
|
+
notes: ["Carries the handshake-gated KYC_SDK surface; provider selects the identity-vendor adapter."]
|
|
143
145
|
},
|
|
144
146
|
"KYC/INPUT_REQUIRED": {
|
|
145
147
|
key: "KYC/INPUT_REQUIRED",
|
|
@@ -151,16 +153,20 @@ var TRANSITION_TABLE = {
|
|
|
151
153
|
{ id: "submit_round", mode: "SUBMIT", endpoint: "POST /fiat/kyc/form" }
|
|
152
154
|
],
|
|
153
155
|
mayReturn: [
|
|
156
|
+
"KYC/CAPTURE",
|
|
154
157
|
"KYC/INPUT_REQUIRED",
|
|
155
158
|
"KYC/NO_ACTION_REQUIRED:IN_REVIEW",
|
|
156
159
|
"KYC/NO_ACTION_REQUIRED:ON_HOLD",
|
|
157
160
|
"KYC/NO_ACTION_REQUIRED:REJECTED",
|
|
161
|
+
"QUOTE",
|
|
162
|
+
"PAYMENT/CAPTURE",
|
|
163
|
+
"PAYMENT/INSTRUCT",
|
|
158
164
|
"ORDER/AWAITING_CONFIRMATION"
|
|
159
165
|
],
|
|
160
166
|
terminal: false,
|
|
161
167
|
notes: [
|
|
162
168
|
"One POST per round; conditional requirements may open another round.",
|
|
163
|
-
"FILE fields
|
|
169
|
+
"FILE fields use POST /fiat/kyc/document to obtain or report a direct provider/vendor upload; document bytes never transit Fun. The harness owns this auxiliary path, not a transition.",
|
|
164
170
|
"Hosted KYC links arrive as HOSTED_LINK form fields, not as a Surface."
|
|
165
171
|
]
|
|
166
172
|
},
|
|
@@ -171,12 +177,16 @@ var TRANSITION_TABLE = {
|
|
|
171
177
|
reason: "IN_REVIEW",
|
|
172
178
|
screens: [9],
|
|
173
179
|
docStatus: "SPECIFIED",
|
|
174
|
-
allowedTransitions: [{ id: "poll", mode: "AWAIT", endpoint: "GET /fiat/
|
|
180
|
+
allowedTransitions: [{ id: "poll", mode: "AWAIT", endpoint: "GET /fiat/quotes/:quoteRef" }],
|
|
175
181
|
mayReturn: [
|
|
176
182
|
"KYC/NO_ACTION_REQUIRED:IN_REVIEW",
|
|
183
|
+
"KYC/CAPTURE",
|
|
177
184
|
"KYC/INPUT_REQUIRED",
|
|
178
185
|
"KYC/NO_ACTION_REQUIRED:ON_HOLD",
|
|
179
186
|
"KYC/NO_ACTION_REQUIRED:REJECTED",
|
|
187
|
+
"QUOTE",
|
|
188
|
+
"PAYMENT/CAPTURE",
|
|
189
|
+
"PAYMENT/INSTRUCT",
|
|
180
190
|
"ORDER/AWAITING_CONFIRMATION"
|
|
181
191
|
],
|
|
182
192
|
terminal: false,
|
|
@@ -206,20 +216,11 @@ var TRANSITION_TABLE = {
|
|
|
206
216
|
reason: "REJECTED",
|
|
207
217
|
screens: [11],
|
|
208
218
|
docStatus: "SPECIFIED",
|
|
209
|
-
allowedTransitions: [
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
mode: "SUBMIT",
|
|
213
|
-
endpoint: "GET /fiat/kyc",
|
|
214
|
-
when: "an escalation round is offered",
|
|
215
|
-
note: "TODO(open-decision): placeholder shape \u2014 the FE doc flags that a GET carrying params is not a real request shape, and OQ7 owns which rejections escalate. FE doc open question 7."
|
|
216
|
-
}
|
|
217
|
-
],
|
|
218
|
-
mayReturn: KYC_ANY,
|
|
219
|
-
terminal: false,
|
|
219
|
+
allowedTransitions: [],
|
|
220
|
+
mayReturn: [],
|
|
221
|
+
terminal: true,
|
|
220
222
|
notes: [
|
|
221
|
-
"
|
|
222
|
-
"TODO(open-decision): contract OQ7 \u2014 terminal vs escalating rejections are not enumerated."
|
|
223
|
+
"Only explicit non-retryable outcomes reach REJECTED. Correctable failures return CAPTURE or INPUT_REQUIRED; provider-specific declines re-route before becoming user rejection."
|
|
223
224
|
]
|
|
224
225
|
},
|
|
225
226
|
PENDING_ORDER: {
|
|
@@ -381,8 +382,10 @@ var DOCUMENTED_ENDPOINTS = [
|
|
|
381
382
|
"POST /fiat/payment-session",
|
|
382
383
|
"POST /fiat/session",
|
|
383
384
|
"POST /fiat/session/verify",
|
|
384
|
-
"GET /fiat/
|
|
385
|
+
"GET /fiat/quotes/:quoteRef",
|
|
385
386
|
"POST /fiat/kyc/form",
|
|
387
|
+
"POST /fiat/kyc/capture",
|
|
388
|
+
"POST /fiat/kyc/sdk-token",
|
|
386
389
|
"POST /fiat/kyc/document",
|
|
387
390
|
"POST /fiat/instruments",
|
|
388
391
|
"POST /fiat/orders",
|
|
@@ -394,13 +397,17 @@ var DOCUMENTED_ENDPOINTS = [
|
|
|
394
397
|
"POST /fiat/orders/:id/surface-result"
|
|
395
398
|
];
|
|
396
399
|
var UNOFFERED_ENDPOINTS = [
|
|
400
|
+
{
|
|
401
|
+
endpoint: "POST /fiat/kyc/sdk-token",
|
|
402
|
+
reason: "SDK token refresh is adapter-driven rather than a flow transition."
|
|
403
|
+
},
|
|
397
404
|
{
|
|
398
405
|
endpoint: "GET /fiat/payment-methods",
|
|
399
406
|
reason: "Discovery, called before any flow state exists \u2014 there is no state to hang it off."
|
|
400
407
|
},
|
|
401
408
|
{
|
|
402
409
|
endpoint: "POST /fiat/kyc/document",
|
|
403
|
-
reason: "FILE fields
|
|
410
|
+
reason: "FILE fields use it for a direct provider/vendor upload handshake and completion report; the harness owns that auxiliary path and it is not a transition (see KYC/INPUT_REQUIRED notes)."
|
|
404
411
|
},
|
|
405
412
|
{
|
|
406
413
|
endpoint: "POST /fiat/instruments",
|
|
@@ -491,4 +498,4 @@ export {
|
|
|
491
498
|
tableEntry,
|
|
492
499
|
isTerminal
|
|
493
500
|
};
|
|
494
|
-
//# sourceMappingURL=chunk-
|
|
501
|
+
//# sourceMappingURL=chunk-LVVQ46YG.mjs.map
|
package/dist/fixtures/index.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* fiat-contract — recorded fixtures + loader.
|
|
3
3
|
*
|
|
4
|
-
* Every `.json` file in this directory
|
|
5
|
-
*
|
|
6
|
-
* exception is `screen-04-order-review.json`, whose payload the doc publishes *by reference*
|
|
4
|
+
* Every `.json` file in this directory records a step response from the source named in its
|
|
5
|
+
* metadata. `screen-04-order-review.json` is the one payload published *by reference*
|
|
7
6
|
* ("quote: <same shape as Screen 1, refreshed>"): its quote is Screen 1's payload verbatim.
|
|
8
7
|
* They are
|
|
9
8
|
* already synthetic (`q_8f2`, `o_31c`, `eyJ…`, `"…"` placeholders); the redaction rule applies to
|
|
@@ -15,7 +14,7 @@
|
|
|
15
14
|
* wire, and the contract has no half-response shape.
|
|
16
15
|
*/
|
|
17
16
|
import type { StateKey } from '../table';
|
|
18
|
-
export type FixtureSource = 'FE_DOC' | 'CONTRACT_DOC';
|
|
17
|
+
export type FixtureSource = 'FE_DOC' | 'CONTRACT_DOC' | 'KYC_DOC';
|
|
19
18
|
export interface FixtureMeta {
|
|
20
19
|
id: string;
|
|
21
20
|
file: string;
|