@fun-xyz/fiat-contract 0.5.0 → 0.7.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 +34 -34
- package/dist/assert.d.ts +2 -2
- package/dist/{chunk-3R5GGUAK.mjs → chunk-OGNYHIOE.mjs} +84 -5
- package/dist/fixtures/index.d.ts +7 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +203 -22
- package/dist/index.mjs +130 -20
- package/dist/schemas.d.ts +5 -2
- package/dist/table.d.ts +41 -6
- package/dist/table.js +84 -5
- package/dist/table.mjs +11 -3
- package/dist/types.d.ts +60 -33
- package/package.json +2 -2
package/dist/table.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Rules the entries obey:
|
|
12
12
|
* - Terminality of `ORDER{CREATED}` rides `status`, because terminal outcomes are OrderStatus,
|
|
13
|
-
* never kinds (contract §The
|
|
13
|
+
* never kinds (contract §The fiat step response). The set is enumerated below as
|
|
14
14
|
* `TERMINAL_ORDER_STATUSES` — clients read it, they never derive it.
|
|
15
15
|
* - A terminal *state* carries no transitions. A terminal *status* still can: `FAILED` with a
|
|
16
16
|
* retryable `failureReason` carries the recovery CTA (FE doc Screen 14).
|
|
@@ -32,7 +32,7 @@ export declare const TABLE_VERSION: string;
|
|
|
32
32
|
*/
|
|
33
33
|
export type StateKey = 'QUOTE' | 'FUN_AUTH' | 'SESSION_AUTH' | 'KYC/CAPTURE' | 'KYC/INPUT_REQUIRED' | 'KYC/NO_ACTION_REQUIRED:IN_REVIEW' | 'KYC/NO_ACTION_REQUIRED:ON_HOLD' | 'KYC/NO_ACTION_REQUIRED:REJECTED' | 'PENDING_ORDER' | 'BLOCKED' | 'PAYMENT/CAPTURE' | 'PAYMENT/INSTRUCT' | 'ORDER/AWAITING_CONFIRMATION' | 'ORDER/CREATED';
|
|
34
34
|
/**
|
|
35
|
-
* Terminal per the OrderStatus machine's monotonic ranks (contract §The
|
|
35
|
+
* Terminal per the OrderStatus machine's monotonic ranks (contract §The fiat step response).
|
|
36
36
|
*
|
|
37
37
|
* `CAPTURE_ORPHANED` is deliberately absent — see the TODO(open-decision) on the value itself.
|
|
38
38
|
* Non-terminal is the safe default for an unknown status: a client that wrongly believes the flow
|
|
@@ -48,7 +48,7 @@ export type TerminalOrderStatus = (typeof TERMINAL_ORDER_STATUSES)[number];
|
|
|
48
48
|
export type EndpointTemplate = FiatEndpoint;
|
|
49
49
|
export interface AllowedTransition {
|
|
50
50
|
/**
|
|
51
|
-
* The id observed in the docs'
|
|
51
|
+
* The id observed in the docs' step responses. Informational: ids are localization keys and the
|
|
52
52
|
* docs explicitly allow unknown ids (the client falls back to `labelFallback`), so
|
|
53
53
|
* `assertLegalEmission` matches on `mode` + `endpoint`, not on `id`.
|
|
54
54
|
*/
|
|
@@ -84,10 +84,45 @@ export interface TableEntry {
|
|
|
84
84
|
export declare const TRANSITION_TABLE: Readonly<Record<StateKey, TableEntry>>;
|
|
85
85
|
export declare const STATE_KEYS: StateKey[];
|
|
86
86
|
/**
|
|
87
|
-
* The `/fiat/*` surface
|
|
88
|
-
*
|
|
87
|
+
* The `/fiat/*` surface. Informational data — emissions are checked against each state's
|
|
88
|
+
* `allowedTransitions`, not against this list.
|
|
89
|
+
*
|
|
90
|
+
* Sourced from contract §Endpoints, with one deliberate divergence: `POST /fiat/payment-session` is
|
|
91
|
+
* named by the table (QUOTE's capture-first entry) while the doc's endpoint table still lists no
|
|
92
|
+
* session-minting call for that rail.
|
|
93
|
+
*/
|
|
94
|
+
export declare const DOCUMENTED_ENDPOINTS: readonly ["GET /fiat/payment-methods", "POST /fiat/quote", "POST /fiat/auth", "POST /fiat/payment-session", "POST /fiat/session", "POST /fiat/session/verify", "GET /fiat/kyc", "POST /fiat/kyc/form", "POST /fiat/kyc/document", "POST /fiat/instruments", "POST /fiat/orders", "GET /fiat/orders/:id", "GET /fiat/orders/:id/instructions", "POST /fiat/orders/:id/confirm-payment", "POST /fiat/orders/:id/reference", "POST /fiat/orders/:id/cancel", "POST /fiat/orders/:id/surface-result"];
|
|
95
|
+
/**
|
|
96
|
+
* Documented endpoints no flow state offers, and why. Declared so an endpoint nothing can reach is
|
|
97
|
+
* visible instead of silent — the table test asserts the unoffered set equals exactly these.
|
|
98
|
+
*/
|
|
99
|
+
export declare const UNOFFERED_ENDPOINTS: readonly {
|
|
100
|
+
endpoint: EndpointTemplate;
|
|
101
|
+
reason: string;
|
|
102
|
+
}[];
|
|
103
|
+
/**
|
|
104
|
+
* States entered out-of-band rather than by returning from a call. Declared so an unreachable state
|
|
105
|
+
* is visible instead of silent — the table test asserts reachability from QUOTE equals (all state
|
|
106
|
+
* keys − these).
|
|
107
|
+
*/
|
|
108
|
+
export declare const UNREACHABLE_STATES: readonly {
|
|
109
|
+
stateKey: StateKey;
|
|
110
|
+
reason: string;
|
|
111
|
+
}[];
|
|
112
|
+
/**
|
|
113
|
+
* The live answer to what `UNOFFERED_ENDPOINTS` declares: documented endpoints that no state's
|
|
114
|
+
* `allowedTransitions` names. The table test asserts the two agree, so a new orphan and a stale
|
|
115
|
+
* declaration both fail.
|
|
116
|
+
*/
|
|
117
|
+
export declare function unofferedEndpoints(): readonly EndpointTemplate[];
|
|
118
|
+
/**
|
|
119
|
+
* The live answer to what `UNREACHABLE_STATES` declares: states no `mayReturn` path reaches from
|
|
120
|
+
* `QUOTE`, the flow's entry. The table test asserts the two agree.
|
|
121
|
+
*
|
|
122
|
+
* `mayReturn` is the only edge set the contract publishes, so it is the only graph there is to walk
|
|
123
|
+
* — a state absent from every one of them cannot be arrived at by following the contract.
|
|
89
124
|
*/
|
|
90
|
-
export declare
|
|
125
|
+
export declare function unreachableStates(from?: StateKey): readonly StateKey[];
|
|
91
126
|
/** State → its table key. The one place kind/phase/reason is flattened. */
|
|
92
127
|
export declare function stateKey(state: FlowState): StateKey;
|
|
93
128
|
export declare const tableEntry: (state: FlowState) => TableEntry;
|
package/dist/table.js
CHANGED
|
@@ -25,14 +25,18 @@ __export(table_exports, {
|
|
|
25
25
|
TABLE_VERSION: () => TABLE_VERSION,
|
|
26
26
|
TERMINAL_ORDER_STATUSES: () => TERMINAL_ORDER_STATUSES,
|
|
27
27
|
TRANSITION_TABLE: () => TRANSITION_TABLE,
|
|
28
|
+
UNOFFERED_ENDPOINTS: () => UNOFFERED_ENDPOINTS,
|
|
29
|
+
UNREACHABLE_STATES: () => UNREACHABLE_STATES,
|
|
28
30
|
isTerminal: () => isTerminal,
|
|
29
31
|
stateKey: () => stateKey,
|
|
30
|
-
tableEntry: () => tableEntry
|
|
32
|
+
tableEntry: () => tableEntry,
|
|
33
|
+
unofferedEndpoints: () => unofferedEndpoints,
|
|
34
|
+
unreachableStates: () => unreachableStates
|
|
31
35
|
});
|
|
32
36
|
module.exports = __toCommonJS(table_exports);
|
|
33
37
|
|
|
34
38
|
// package.json
|
|
35
|
-
var version = "0.
|
|
39
|
+
var version = "0.7.0";
|
|
36
40
|
|
|
37
41
|
// src/table.ts
|
|
38
42
|
var TABLE_VERSION = version;
|
|
@@ -63,22 +67,46 @@ var TRANSITION_TABLE = {
|
|
|
63
67
|
endpoint: "POST /fiat/session",
|
|
64
68
|
when: "no valid provider session token (own-tables check)"
|
|
65
69
|
},
|
|
70
|
+
{
|
|
71
|
+
id: "open_session",
|
|
72
|
+
mode: "SUBMIT",
|
|
73
|
+
endpoint: "POST /fiat/payment-session",
|
|
74
|
+
when: "returning user, session + KYC valid, capture-first rail \u2014 the method authorises before any order exists",
|
|
75
|
+
note: "Returns PAYMENT{CAPTURE} carrying the Surface. Without this entry the capture-first rail has no legal way in: a transition-following client takes POST /fiat/orders instead, and the backend refuses that when the provider declares the capture-first capability and no authorisation is present. Named open_session per the Fiat Contract 0.7 doc, which adds quote-time-minted surface rows beside it."
|
|
76
|
+
},
|
|
66
77
|
{
|
|
67
78
|
id: "continue",
|
|
68
79
|
mode: "SUBMIT",
|
|
69
80
|
endpoint: "POST /fiat/orders",
|
|
70
|
-
when: "returning user, session + KYC valid \u2014 the quote screen doubles as review"
|
|
81
|
+
when: "returning user, session + KYC valid, order-first rail \u2014 the quote screen doubles as review",
|
|
82
|
+
note: "Narrowed to the order-first rail: on a capture-first method the order cannot be created before the payment is authorised, so the sibling payment-session entry is the one that applies."
|
|
71
83
|
},
|
|
72
84
|
{
|
|
73
85
|
id: "continue",
|
|
74
86
|
mode: "SUBMIT",
|
|
75
87
|
endpoint: "POST /fiat/auth",
|
|
76
88
|
when: "no ambient Fun identity (non-fomo surfaces only)"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: "capture",
|
|
92
|
+
mode: "CLIENT_SURFACE",
|
|
93
|
+
endpoint: "POST /fiat/orders",
|
|
94
|
+
when: "capture-then-order rail whose session is minted at quote time (inline pay sheet)",
|
|
95
|
+
note: "The 0.7 row that makes the single-screen Apple Pay design legal: the surface rides QUOTE, the provider component IS the primary CTA, and its result creates the order. Apple policy forbids a visible step between an Apple-branded tap and the sheet, and TransakApplePay exposes no programmatic trigger, so a two-step via payment-session cannot serve this rail. Requires state.surface (\u03942)."
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: "capture",
|
|
99
|
+
mode: "CLIENT_SURFACE",
|
|
100
|
+
endpoint: "POST /fiat/orders/:id/surface-result",
|
|
101
|
+
when: "order-then-capture rail whose provider order was created with the quote-time session",
|
|
102
|
+
note: "Same quote-time placement, other topology: the order already exists, so the surface reports against it rather than creating one."
|
|
77
103
|
}
|
|
78
104
|
],
|
|
79
105
|
mayReturn: [
|
|
80
106
|
"SESSION_AUTH",
|
|
81
107
|
...KYC_ANY,
|
|
108
|
+
"PAYMENT/CAPTURE",
|
|
109
|
+
"PAYMENT/INSTRUCT",
|
|
82
110
|
"PENDING_ORDER",
|
|
83
111
|
"ORDER/AWAITING_CONFIRMATION",
|
|
84
112
|
"ORDER/CREATED",
|
|
@@ -86,8 +114,10 @@ var TRANSITION_TABLE = {
|
|
|
86
114
|
],
|
|
87
115
|
terminal: false,
|
|
88
116
|
notes: [
|
|
89
|
-
"Re-quote on amount change is a client-local event, not a transition.",
|
|
90
|
-
|
|
117
|
+
"Re-quote on amount change is a client-local event, not a transition. A new quote carries a new surface; the client remounts the adapter rather than reusing the old session.",
|
|
118
|
+
"A surface on QUOTE and a SUBMIT to the same endpoint may not coexist \u2014 two paths to one order. assertLegalEmission enforces it; the server picks one per rail.",
|
|
119
|
+
`Both PAYMENT states are reachable directly from QUOTE: the FE doc canvas draws S1 \u2192 S5 (card, capture-first) and S1 \u2192 S6 (bank). This entry was first ported from the FE doc's bindings table, whose "May receive back" column omits both \u2014 the canvas is the correct source of the two.`,
|
|
120
|
+
'TODO(open-decision): contract \xA7"Proposal: remove PENDING_ORDER" would additionally have QUOTE return ORDER{CREATED} for an active order. That decision does not gate the PAYMENT edges above.'
|
|
91
121
|
]
|
|
92
122
|
},
|
|
93
123
|
FUN_AUTH: {
|
|
@@ -385,6 +415,7 @@ var DOCUMENTED_ENDPOINTS = [
|
|
|
385
415
|
"GET /fiat/payment-methods",
|
|
386
416
|
"POST /fiat/quote",
|
|
387
417
|
"POST /fiat/auth",
|
|
418
|
+
"POST /fiat/payment-session",
|
|
388
419
|
"POST /fiat/session",
|
|
389
420
|
"POST /fiat/session/verify",
|
|
390
421
|
"GET /fiat/kyc",
|
|
@@ -399,6 +430,54 @@ var DOCUMENTED_ENDPOINTS = [
|
|
|
399
430
|
"POST /fiat/orders/:id/cancel",
|
|
400
431
|
"POST /fiat/orders/:id/surface-result"
|
|
401
432
|
];
|
|
433
|
+
var UNOFFERED_ENDPOINTS = [
|
|
434
|
+
{
|
|
435
|
+
endpoint: "GET /fiat/payment-methods",
|
|
436
|
+
reason: "Discovery, called before any flow state exists \u2014 there is no state to hang it off."
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
endpoint: "POST /fiat/kyc/document",
|
|
440
|
+
reason: "FILE fields stream to it directly; the harness owns that upload path and it is not a transition (see KYC/INPUT_REQUIRED notes)."
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
endpoint: "POST /fiat/instruments",
|
|
444
|
+
reason: "No state offers it and no published doc explains why. TODO(open-decision): saved instruments may simply be unreached in v1 \u2014 confirm, or wire it to the state that should offer it."
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
endpoint: "GET /fiat/orders/:id/instructions",
|
|
448
|
+
reason: "PAYMENT{INSTRUCT} carries its instructions in state and offers no fetch. TODO(open-decision): that state also notes no re-issue endpoint exists, which sits oddly beside this one being published \u2014 confirm or retire it."
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
endpoint: "POST /fiat/orders/:id/reference",
|
|
452
|
+
reason: "The bank-reference submission is offered by no state. TODO(open-decision): PAYMENT{INSTRUCT} is the plausible home \u2014 confirm or wire it."
|
|
453
|
+
}
|
|
454
|
+
];
|
|
455
|
+
var UNREACHABLE_STATES = [
|
|
456
|
+
{
|
|
457
|
+
stateKey: "FUN_AUTH",
|
|
458
|
+
reason: "No bindings row published (docStatus UNSPECIFIED) and never rendered in fomo, where identity is ambient. Entered out-of-band, not by a mayReturn edge."
|
|
459
|
+
}
|
|
460
|
+
];
|
|
461
|
+
function unofferedEndpoints() {
|
|
462
|
+
const offered = /* @__PURE__ */ new Set();
|
|
463
|
+
for (const key of STATE_KEYS) {
|
|
464
|
+
for (const allowed of TRANSITION_TABLE[key].allowedTransitions) offered.add(allowed.endpoint);
|
|
465
|
+
}
|
|
466
|
+
return DOCUMENTED_ENDPOINTS.filter((endpoint) => !offered.has(endpoint));
|
|
467
|
+
}
|
|
468
|
+
function unreachableStates(from = "QUOTE") {
|
|
469
|
+
const seen = /* @__PURE__ */ new Set([from]);
|
|
470
|
+
const queue = [from];
|
|
471
|
+
while (queue.length > 0) {
|
|
472
|
+
for (const target of TRANSITION_TABLE[queue.pop()].mayReturn ?? []) {
|
|
473
|
+
if (!seen.has(target)) {
|
|
474
|
+
seen.add(target);
|
|
475
|
+
queue.push(target);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return STATE_KEYS.filter((key) => !seen.has(key));
|
|
480
|
+
}
|
|
402
481
|
function stateKey(state) {
|
|
403
482
|
switch (state.kind) {
|
|
404
483
|
case "QUOTE":
|
package/dist/table.mjs
CHANGED
|
@@ -4,18 +4,26 @@ import {
|
|
|
4
4
|
TABLE_VERSION,
|
|
5
5
|
TERMINAL_ORDER_STATUSES,
|
|
6
6
|
TRANSITION_TABLE,
|
|
7
|
+
UNOFFERED_ENDPOINTS,
|
|
8
|
+
UNREACHABLE_STATES,
|
|
7
9
|
isTerminal,
|
|
8
10
|
stateKey,
|
|
9
|
-
tableEntry
|
|
10
|
-
|
|
11
|
+
tableEntry,
|
|
12
|
+
unofferedEndpoints,
|
|
13
|
+
unreachableStates
|
|
14
|
+
} from "./chunk-OGNYHIOE.mjs";
|
|
11
15
|
export {
|
|
12
16
|
DOCUMENTED_ENDPOINTS,
|
|
13
17
|
STATE_KEYS,
|
|
14
18
|
TABLE_VERSION,
|
|
15
19
|
TERMINAL_ORDER_STATUSES,
|
|
16
20
|
TRANSITION_TABLE,
|
|
21
|
+
UNOFFERED_ENDPOINTS,
|
|
22
|
+
UNREACHABLE_STATES,
|
|
17
23
|
isTerminal,
|
|
18
24
|
stateKey,
|
|
19
|
-
tableEntry
|
|
25
|
+
tableEntry,
|
|
26
|
+
unofferedEndpoints,
|
|
27
|
+
unreachableStates
|
|
20
28
|
};
|
|
21
29
|
//# sourceMappingURL=table.mjs.map
|
package/dist/types.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* fiat-contract — types
|
|
3
3
|
*
|
|
4
|
-
* The published contract between `fun-backend` (emits
|
|
4
|
+
* The published contract between `fun-backend` (emits step responses) and
|
|
5
5
|
* `funkit`/`connect-core` (renders them). Zero runtime logic lives here.
|
|
6
6
|
*
|
|
7
7
|
* Sources of truth (do not extend this file from anywhere else):
|
|
8
8
|
* - Contract doc: Fiat Client Contract — Step-Driven Flow & Endpoint Schematics
|
|
9
9
|
* https://app.notion.com/p/3b9fc3b2a002815eb270fa4c818268cc
|
|
10
|
-
* §"The
|
|
10
|
+
* §"The fiat step response" · §"Conformance package — concrete spec"
|
|
11
11
|
* §"Proposal (2026-08-13): split InputSpec → params + inputs + expects" (ACCEPTED)
|
|
12
12
|
* - FE doc: Fiat Frontend — State Machine & Screen Map
|
|
13
13
|
* https://app.notion.com/p/3bbfc3b2a00281c994c2cebd17b1d6d3
|
|
@@ -59,14 +59,16 @@ export type HttpVerb = 'GET' | 'POST';
|
|
|
59
59
|
export type FiatEndpoint = 'GET /fiat/payment-methods' | 'POST /fiat/quote' | 'POST /fiat/auth' | 'POST /fiat/payment-session' | 'POST /fiat/session' | 'POST /fiat/session/verify' | 'GET /fiat/kyc' | 'POST /fiat/kyc/form' | 'POST /fiat/kyc/document' | 'POST /fiat/instruments' | 'POST /fiat/orders' | `GET /fiat/orders/${string}` | `GET /fiat/orders/${string}/instructions` | `POST /fiat/orders/${string}/confirm-payment` | `POST /fiat/orders/${string}/reference` | `POST /fiat/orders/${string}/cancel` | `POST /fiat/orders/${string}/surface-result`;
|
|
60
60
|
/**
|
|
61
61
|
* FiatProvider is metadata: it selects the embedded provider SDK for CLIENT_SURFACE steps and
|
|
62
|
-
* feeds analytics. It never drives sequencing (contract §The
|
|
62
|
+
* feeds analytics. It never drives sequencing (contract §The fiat step response).
|
|
63
63
|
* TODO(open-decision): the docs name only Transak as a v1 provider for the headless flow
|
|
64
64
|
* (contract §Decisions 3; Swapped-headless does not exist yet, Banxa is Tradeoff #4).
|
|
65
65
|
* `SWAPPED` added at fun-backend's request (2026-08-17) so its legacy fops payment-provider id
|
|
66
66
|
* has one shared vocabulary instead of a second fun-backend-local enum — it never appears as a
|
|
67
|
-
* `FiatStepResponse.provider` value since Swapped's fops flow doesn't emit this
|
|
67
|
+
* `FiatStepResponse.provider` value since Swapped's fops flow doesn't emit this response.
|
|
68
|
+
* `BANXA` added at 0.7 (Provider Landscape: Tier 1). A vendor name here is a routing key only —
|
|
69
|
+
* it never decides which fields exist, which is what keeps provider churn out of this package.
|
|
68
70
|
*/
|
|
69
|
-
export type FiatProvider = 'TRANSAK' | 'SWAPPED';
|
|
71
|
+
export type FiatProvider = 'TRANSAK' | 'SWAPPED' | 'BANXA';
|
|
70
72
|
/**
|
|
71
73
|
* The order status vocabulary. Monotonic ranks, no backwards moves. Terminal outcomes are
|
|
72
74
|
* OrderStatus, never FlowState kinds.
|
|
@@ -101,7 +103,7 @@ export type FailureCategory = 'PAYMENT_DECLINED' | 'KYC_REJECTED' | 'PROVIDER_OU
|
|
|
101
103
|
* (the FE doc's Screen 11 note writes `SUPPORT` as shorthand — same value).
|
|
102
104
|
*/
|
|
103
105
|
export type Recovery = 'REQUOTE' | 'RETRY_PAYMENT' | 'CONTACT_SUPPORT' | 'NONE';
|
|
104
|
-
/** Three expiries, three recoveries (contract §The
|
|
106
|
+
/** Three expiries, three recoveries (contract §The fiat step response, FailureReason comment). */
|
|
105
107
|
export type ExpiredWhich = 'QUOTE' | 'PAYMENT_WINDOW' | 'INSTRUCTIONS';
|
|
106
108
|
/**
|
|
107
109
|
* `code?` is a copy key (e.g. `BAD_CODE`), not an error code to branch on.
|
|
@@ -121,7 +123,7 @@ export type FailureReason = {
|
|
|
121
123
|
code?: string;
|
|
122
124
|
};
|
|
123
125
|
/**
|
|
124
|
-
* Amounts are decimal strings in the docs'
|
|
126
|
+
* Amounts are decimal strings in the docs' step responses ("100.00") — never numbers.
|
|
125
127
|
* Named to pair with `CryptoAmount`: every use site is a `fiat:` field.
|
|
126
128
|
* `currency` is ISO 4217 — see `./codes`.
|
|
127
129
|
*/
|
|
@@ -172,7 +174,7 @@ export interface OrderRef {
|
|
|
172
174
|
}
|
|
173
175
|
/**
|
|
174
176
|
* Payload of `ORDER{CREATED}` (FE doc Screens 7 · 13 · 14 · 15).
|
|
175
|
-
* `crypto` is optional because the Screen 14/15
|
|
177
|
+
* `crypto` is optional because the Screen 14/15 responses omit it (fiat-only failure/refund views).
|
|
176
178
|
*/
|
|
177
179
|
export interface OrderSummary {
|
|
178
180
|
orderId: string;
|
|
@@ -199,27 +201,45 @@ export interface Refund {
|
|
|
199
201
|
amount: string;
|
|
200
202
|
expectedBy: string;
|
|
201
203
|
}
|
|
204
|
+
/** What the user sees, and therefore where it can be placed. */
|
|
205
|
+
export type SurfaceKind = 'URL_REDIRECT' | 'URL_EMBED' | 'PCI_COMPONENT' | 'PAY_SHEET' | 'ACH_COMPONENT';
|
|
202
206
|
/**
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
* `
|
|
207
|
+
* Identity vendors Fun verifies into. Its own axis rather than a `FiatProvider` value, because
|
|
208
|
+
* Fun is the KYC-of-record in its own tenant — an identity surface is never the onramp's to mint.
|
|
209
|
+
* `SUMSUB` alone at 0.7; adding a value later is additive, and a vendor with no installed adapter
|
|
210
|
+
* lands on the client's existing unsupported screen.
|
|
206
211
|
*/
|
|
207
|
-
export type
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
212
|
+
export type KYCProvider = 'SUMSUB';
|
|
213
|
+
/**
|
|
214
|
+
* Which adapter renders the surface. Can differ from `FiatStepResponse.provider`: an identity
|
|
215
|
+
* surface is Fun's to mint, not the onramp's.
|
|
216
|
+
*
|
|
217
|
+
* A routing key only — it never narrows the shape of `params`.
|
|
218
|
+
*/
|
|
219
|
+
export type SurfaceProvider = FiatProvider | KYCProvider;
|
|
220
|
+
/**
|
|
221
|
+
* Carried as data in `QUOTE` (inline in the entry screen's primary slot) and in the CAPTURE
|
|
222
|
+
* phases (a screen of its own). Δ2 unchanged: the CLIENT_SURFACE transition carries only
|
|
223
|
+
* `report`, so a surface is never described twice.
|
|
224
|
+
*
|
|
225
|
+
* `params` is the adapter's vocabulary — opaque here, narrowed at the adapter boundary, and
|
|
226
|
+
* honoured verbatim. A provider's own `environment` value lives here and is never normalised:
|
|
227
|
+
* Transak's is `LOCAL | DEVELOPMENT | STAGING | PRODUCTION`, Banxa's is `sandbox | production`,
|
|
228
|
+
* and URL surfaces carry none, so a Fun-owned enum could only be lossy.
|
|
229
|
+
*
|
|
230
|
+
* `params` values are secrets by default. Client redaction defaults every key closed and opens
|
|
231
|
+
* only names that are safe under *any* provider's vocabulary.
|
|
232
|
+
*/
|
|
233
|
+
export interface Surface {
|
|
234
|
+
kind: SurfaceKind;
|
|
235
|
+
provider: SurfaceProvider;
|
|
236
|
+
params: Record<string, JsonValue>;
|
|
237
|
+
/**
|
|
238
|
+
* When the provider stops honouring the session, if it says so. Lets a client re-quote before
|
|
239
|
+
* the button goes dead; absent, the surface lives as long as the quote that carried it.
|
|
240
|
+
*/
|
|
241
|
+
expiresAt?: string;
|
|
242
|
+
}
|
|
223
243
|
export interface InstructionField {
|
|
224
244
|
id: string;
|
|
225
245
|
labelFallback: string;
|
|
@@ -228,8 +248,8 @@ export interface InstructionField {
|
|
|
228
248
|
/**
|
|
229
249
|
* Payload of `PAYMENT{INSTRUCT}` — push rails. Values are PII-adjacent: never logged.
|
|
230
250
|
* TODO(open-decision): contract OQ1 lists `QR_IMAGE` as *deliberately not pre-declared* in the
|
|
231
|
-
* v1 vocabulary while §The
|
|
232
|
-
*
|
|
251
|
+
* v1 vocabulary while §The fiat step response's Instructions bullet enumerates it. Kept in the union
|
|
252
|
+
* per that section (and the conformance-package spec); handshake-gate it if OQ1 lands the
|
|
233
253
|
* other way.
|
|
234
254
|
* TODO(open-decision): contract OQ7 — expired instructions have no documented recovery and no
|
|
235
255
|
* re-issue endpoint exists.
|
|
@@ -402,9 +422,16 @@ export type AuthChallenge = Record<string, JsonValue>;
|
|
|
402
422
|
* 2. terminal outcomes are OrderStatus, never kinds
|
|
403
423
|
* 3. every fallible state carries `error?: FailureReason`
|
|
404
424
|
*/
|
|
405
|
-
export type FlowState =
|
|
425
|
+
export type FlowState =
|
|
426
|
+
/**
|
|
427
|
+
* `surface` present means the provider's own component IS the primary CTA, inline on the entry
|
|
428
|
+
* screen — the placement Apple policy forces for a pay sheet, since no visible step may sit
|
|
429
|
+
* between the Apple-branded tap and the sheet. Absent, the quote's SUBMITs drive our button.
|
|
430
|
+
*/
|
|
431
|
+
{
|
|
406
432
|
kind: 'QUOTE';
|
|
407
433
|
quote: Quote;
|
|
434
|
+
surface?: Surface;
|
|
408
435
|
error?: FailureReason;
|
|
409
436
|
} | {
|
|
410
437
|
kind: 'FUN_AUTH';
|
|
@@ -437,7 +464,7 @@ export type FlowState = {
|
|
|
437
464
|
* empty transitions, NOT terminal.
|
|
438
465
|
* - `OrderStatus.ON_HOLD` = post-order compliance or amount mismatch, resolves to
|
|
439
466
|
* `PROCESSING` or `REFUNDED`.
|
|
440
|
-
* They cannot be confused in code — different types, different positions in the
|
|
467
|
+
* They cannot be confused in code — different types, different positions in the response —
|
|
441
468
|
* but they can be confused in conversation, so say which one you mean.
|
|
442
469
|
*/
|
|
443
470
|
reason: 'IN_REVIEW' | 'ON_HOLD' | 'REJECTED';
|
|
@@ -480,7 +507,7 @@ export type FlowState = {
|
|
|
480
507
|
kind: 'PAYMENT';
|
|
481
508
|
phase: 'INSTRUCT';
|
|
482
509
|
/**
|
|
483
|
-
* TODO(open-decision): `orderId` placement —
|
|
510
|
+
* TODO(open-decision): `orderId` placement — beside `state` (contract §Worked example,
|
|
484
511
|
* steps 4–5) vs inside state (FE doc v0, Screen 6). Both are accepted here; the FE doc's
|
|
485
512
|
* in-state placement is the current shape, `FiatStepResponse.orderId` covers the other.
|
|
486
513
|
*/
|
|
@@ -495,7 +522,7 @@ export type FlowState = {
|
|
|
495
522
|
} | {
|
|
496
523
|
kind: 'ORDER';
|
|
497
524
|
phase: 'CREATED';
|
|
498
|
-
/** Absent in the contract's terse worked-example
|
|
525
|
+
/** Absent in the contract's terse worked-example responses, present in every FE screen. */
|
|
499
526
|
order?: OrderSummary;
|
|
500
527
|
status: OrderStatus;
|
|
501
528
|
statusHistory?: StatusHistoryEntry[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fun-xyz/fiat-contract",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Published conformance contract for Fun's headless fiat onramp: FlowState/Transition types, zod schemas, the transition table as data, recorded fixtures, assertion helpers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -78,4 +78,4 @@
|
|
|
78
78
|
]
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
-
}
|
|
81
|
+
}
|