@fun-xyz/fiat-contract 0.6.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 +15 -12
- package/dist/{chunk-EHYDQU4T.mjs → chunk-LVVQ46YG.mjs} +112 -26
- package/dist/fixtures/index.d.ts +3 -4
- package/dist/index.js +407 -103
- package/dist/index.mjs +306 -80
- package/dist/schemas.d.ts +4 -1
- package/dist/table.d.ts +39 -5
- package/dist/table.js +112 -26
- package/dist/table.mjs +11 -3
- package/dist/types.d.ts +56 -34
- package/package.json +2 -2
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.8.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: {
|
|
@@ -132,21 +162,23 @@ var TRANSITION_TABLE = {
|
|
|
132
162
|
{
|
|
133
163
|
id: "capture",
|
|
134
164
|
mode: "CLIENT_SURFACE",
|
|
135
|
-
endpoint: "
|
|
136
|
-
note:
|
|
165
|
+
endpoint: "POST /fiat/kyc/capture",
|
|
166
|
+
note: "Reports SDK submission; provider eligibility is re-evaluated through quote orchestration."
|
|
137
167
|
}
|
|
138
168
|
],
|
|
139
169
|
mayReturn: [
|
|
170
|
+
"KYC/CAPTURE",
|
|
140
171
|
"KYC/INPUT_REQUIRED",
|
|
141
172
|
"KYC/NO_ACTION_REQUIRED:IN_REVIEW",
|
|
142
173
|
"KYC/NO_ACTION_REQUIRED:ON_HOLD",
|
|
143
174
|
"KYC/NO_ACTION_REQUIRED:REJECTED",
|
|
175
|
+
"QUOTE",
|
|
176
|
+
"PAYMENT/CAPTURE",
|
|
177
|
+
"PAYMENT/INSTRUCT",
|
|
144
178
|
"ORDER/AWAITING_CONFIRMATION"
|
|
145
179
|
],
|
|
146
180
|
terminal: false,
|
|
147
|
-
notes: [
|
|
148
|
-
"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."
|
|
149
|
-
]
|
|
181
|
+
notes: ["Carries the handshake-gated KYC_SDK surface; provider selects the identity-vendor adapter."]
|
|
150
182
|
},
|
|
151
183
|
"KYC/INPUT_REQUIRED": {
|
|
152
184
|
key: "KYC/INPUT_REQUIRED",
|
|
@@ -158,16 +190,20 @@ var TRANSITION_TABLE = {
|
|
|
158
190
|
{ id: "submit_round", mode: "SUBMIT", endpoint: "POST /fiat/kyc/form" }
|
|
159
191
|
],
|
|
160
192
|
mayReturn: [
|
|
193
|
+
"KYC/CAPTURE",
|
|
161
194
|
"KYC/INPUT_REQUIRED",
|
|
162
195
|
"KYC/NO_ACTION_REQUIRED:IN_REVIEW",
|
|
163
196
|
"KYC/NO_ACTION_REQUIRED:ON_HOLD",
|
|
164
197
|
"KYC/NO_ACTION_REQUIRED:REJECTED",
|
|
198
|
+
"QUOTE",
|
|
199
|
+
"PAYMENT/CAPTURE",
|
|
200
|
+
"PAYMENT/INSTRUCT",
|
|
165
201
|
"ORDER/AWAITING_CONFIRMATION"
|
|
166
202
|
],
|
|
167
203
|
terminal: false,
|
|
168
204
|
notes: [
|
|
169
205
|
"One POST per round; conditional requirements may open another round.",
|
|
170
|
-
"FILE fields
|
|
206
|
+
"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.",
|
|
171
207
|
"Hosted KYC links arrive as HOSTED_LINK form fields, not as a Surface."
|
|
172
208
|
]
|
|
173
209
|
},
|
|
@@ -178,12 +214,16 @@ var TRANSITION_TABLE = {
|
|
|
178
214
|
reason: "IN_REVIEW",
|
|
179
215
|
screens: [9],
|
|
180
216
|
docStatus: "SPECIFIED",
|
|
181
|
-
allowedTransitions: [{ id: "poll", mode: "AWAIT", endpoint: "GET /fiat/
|
|
217
|
+
allowedTransitions: [{ id: "poll", mode: "AWAIT", endpoint: "GET /fiat/quotes/:quoteRef" }],
|
|
182
218
|
mayReturn: [
|
|
183
219
|
"KYC/NO_ACTION_REQUIRED:IN_REVIEW",
|
|
220
|
+
"KYC/CAPTURE",
|
|
184
221
|
"KYC/INPUT_REQUIRED",
|
|
185
222
|
"KYC/NO_ACTION_REQUIRED:ON_HOLD",
|
|
186
223
|
"KYC/NO_ACTION_REQUIRED:REJECTED",
|
|
224
|
+
"QUOTE",
|
|
225
|
+
"PAYMENT/CAPTURE",
|
|
226
|
+
"PAYMENT/INSTRUCT",
|
|
187
227
|
"ORDER/AWAITING_CONFIRMATION"
|
|
188
228
|
],
|
|
189
229
|
terminal: false,
|
|
@@ -213,20 +253,11 @@ var TRANSITION_TABLE = {
|
|
|
213
253
|
reason: "REJECTED",
|
|
214
254
|
screens: [11],
|
|
215
255
|
docStatus: "SPECIFIED",
|
|
216
|
-
allowedTransitions: [
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
mode: "SUBMIT",
|
|
220
|
-
endpoint: "GET /fiat/kyc",
|
|
221
|
-
when: "an escalation round is offered",
|
|
222
|
-
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."
|
|
223
|
-
}
|
|
224
|
-
],
|
|
225
|
-
mayReturn: KYC_ANY,
|
|
226
|
-
terminal: false,
|
|
256
|
+
allowedTransitions: [],
|
|
257
|
+
mayReturn: [],
|
|
258
|
+
terminal: true,
|
|
227
259
|
notes: [
|
|
228
|
-
"
|
|
229
|
-
"TODO(open-decision): contract OQ7 \u2014 terminal vs escalating rejections are not enumerated."
|
|
260
|
+
"Only explicit non-retryable outcomes reach REJECTED. Correctable failures return CAPTURE or INPUT_REQUIRED; provider-specific declines re-route before becoming user rejection."
|
|
230
261
|
]
|
|
231
262
|
},
|
|
232
263
|
PENDING_ORDER: {
|
|
@@ -385,10 +416,13 @@ var DOCUMENTED_ENDPOINTS = [
|
|
|
385
416
|
"GET /fiat/payment-methods",
|
|
386
417
|
"POST /fiat/quote",
|
|
387
418
|
"POST /fiat/auth",
|
|
419
|
+
"POST /fiat/payment-session",
|
|
388
420
|
"POST /fiat/session",
|
|
389
421
|
"POST /fiat/session/verify",
|
|
390
|
-
"GET /fiat/
|
|
422
|
+
"GET /fiat/quotes/:quoteRef",
|
|
391
423
|
"POST /fiat/kyc/form",
|
|
424
|
+
"POST /fiat/kyc/capture",
|
|
425
|
+
"POST /fiat/kyc/sdk-token",
|
|
392
426
|
"POST /fiat/kyc/document",
|
|
393
427
|
"POST /fiat/instruments",
|
|
394
428
|
"POST /fiat/orders",
|
|
@@ -399,6 +433,58 @@ var DOCUMENTED_ENDPOINTS = [
|
|
|
399
433
|
"POST /fiat/orders/:id/cancel",
|
|
400
434
|
"POST /fiat/orders/:id/surface-result"
|
|
401
435
|
];
|
|
436
|
+
var UNOFFERED_ENDPOINTS = [
|
|
437
|
+
{
|
|
438
|
+
endpoint: "POST /fiat/kyc/sdk-token",
|
|
439
|
+
reason: "SDK token refresh is adapter-driven rather than a flow transition."
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
endpoint: "GET /fiat/payment-methods",
|
|
443
|
+
reason: "Discovery, called before any flow state exists \u2014 there is no state to hang it off."
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
endpoint: "POST /fiat/kyc/document",
|
|
447
|
+
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)."
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
endpoint: "POST /fiat/instruments",
|
|
451
|
+
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."
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
endpoint: "GET /fiat/orders/:id/instructions",
|
|
455
|
+
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."
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
endpoint: "POST /fiat/orders/:id/reference",
|
|
459
|
+
reason: "The bank-reference submission is offered by no state. TODO(open-decision): PAYMENT{INSTRUCT} is the plausible home \u2014 confirm or wire it."
|
|
460
|
+
}
|
|
461
|
+
];
|
|
462
|
+
var UNREACHABLE_STATES = [
|
|
463
|
+
{
|
|
464
|
+
stateKey: "FUN_AUTH",
|
|
465
|
+
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."
|
|
466
|
+
}
|
|
467
|
+
];
|
|
468
|
+
function unofferedEndpoints() {
|
|
469
|
+
const offered = /* @__PURE__ */ new Set();
|
|
470
|
+
for (const key of STATE_KEYS) {
|
|
471
|
+
for (const allowed of TRANSITION_TABLE[key].allowedTransitions) offered.add(allowed.endpoint);
|
|
472
|
+
}
|
|
473
|
+
return DOCUMENTED_ENDPOINTS.filter((endpoint) => !offered.has(endpoint));
|
|
474
|
+
}
|
|
475
|
+
function unreachableStates(from = "QUOTE") {
|
|
476
|
+
const seen = /* @__PURE__ */ new Set([from]);
|
|
477
|
+
const queue = [from];
|
|
478
|
+
while (queue.length > 0) {
|
|
479
|
+
for (const target of TRANSITION_TABLE[queue.pop()].mayReturn ?? []) {
|
|
480
|
+
if (!seen.has(target)) {
|
|
481
|
+
seen.add(target);
|
|
482
|
+
queue.push(target);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return STATE_KEYS.filter((key) => !seen.has(key));
|
|
487
|
+
}
|
|
402
488
|
function stateKey(state) {
|
|
403
489
|
switch (state.kind) {
|
|
404
490
|
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-LVVQ46YG.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
|
@@ -56,17 +56,12 @@ export type HttpVerb = 'GET' | 'POST';
|
|
|
56
56
|
* cannot express "no slash", so `GET /fiat/orders/${string}` also admits sub-paths. Precision at
|
|
57
57
|
* that level is the table's job (`allowedTransitions` + `endpointMatches`), not the type's.
|
|
58
58
|
*/
|
|
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' |
|
|
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/quotes/${string}` | 'POST /fiat/kyc/form' | 'POST /fiat/kyc/capture' | 'POST /fiat/kyc/sdk-token' | '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
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* TODO(open-decision): the docs name only Transak as a v1 provider for the headless flow
|
|
64
|
-
* (contract §Decisions 3; Swapped-headless does not exist yet, Banxa is Tradeoff #4).
|
|
65
|
-
* `SWAPPED` added at fun-backend's request (2026-08-17) so its legacy fops payment-provider id
|
|
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 response.
|
|
61
|
+
* Routing metadata, not sequencing policy. Reserved values may be emitted only after their adapter
|
|
62
|
+
* and conformance fixture land.
|
|
68
63
|
*/
|
|
69
|
-
export type FiatProvider = 'TRANSAK' | 'SWAPPED';
|
|
64
|
+
export type FiatProvider = 'TRANSAK' | 'SWAPPED' | 'BANXA' | 'MOONPAY' | 'STRIPE';
|
|
70
65
|
/**
|
|
71
66
|
* The order status vocabulary. Monotonic ranks, no backwards moves. Terminal outcomes are
|
|
72
67
|
* OrderStatus, never FlowState kinds.
|
|
@@ -199,27 +194,45 @@ export interface Refund {
|
|
|
199
194
|
amount: string;
|
|
200
195
|
expectedBy: string;
|
|
201
196
|
}
|
|
197
|
+
/** What the user sees, and therefore where it can be placed. */
|
|
198
|
+
export type SurfaceKind = 'URL_REDIRECT' | 'KYC_SDK' | 'PCI_COMPONENT' | 'PAY_SHEET' | 'ACH_COMPONENT';
|
|
202
199
|
/**
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
* `
|
|
200
|
+
* Identity vendors Fun verifies into. Its own axis rather than a `FiatProvider` value, because
|
|
201
|
+
* Fun is the KYC-of-record in its own tenant — an identity surface is never the onramp's to mint.
|
|
202
|
+
* `SUMSUB` alone at 0.7; adding a value later is additive, and a vendor with no installed adapter
|
|
203
|
+
* lands on the client's existing unsupported screen.
|
|
206
204
|
*/
|
|
207
|
-
export type
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
205
|
+
export type KYCProvider = 'SUMSUB';
|
|
206
|
+
/**
|
|
207
|
+
* Which adapter renders the surface. Can differ from `FiatStepResponse.provider`: an identity
|
|
208
|
+
* surface is Fun's to mint, not the onramp's.
|
|
209
|
+
*
|
|
210
|
+
* A routing key only — it never narrows the shape of `params`.
|
|
211
|
+
*/
|
|
212
|
+
export type SurfaceProvider = FiatProvider | KYCProvider;
|
|
213
|
+
/**
|
|
214
|
+
* Carried as data in `QUOTE` (inline in the entry screen's primary slot) and in the CAPTURE
|
|
215
|
+
* phases (a screen of its own). Δ2 unchanged: the CLIENT_SURFACE transition carries only
|
|
216
|
+
* `report`, so a surface is never described twice.
|
|
217
|
+
*
|
|
218
|
+
* `params` is the adapter's vocabulary — opaque here, narrowed at the adapter boundary, and
|
|
219
|
+
* honoured verbatim. A provider's own `environment` value lives here and is never normalised:
|
|
220
|
+
* Transak's is `LOCAL | DEVELOPMENT | STAGING | PRODUCTION`, Banxa's is `sandbox | production`,
|
|
221
|
+
* and URL surfaces carry none, so a Fun-owned enum could only be lossy.
|
|
222
|
+
*
|
|
223
|
+
* `params` values are secrets by default. Client redaction defaults every key closed and opens
|
|
224
|
+
* only names that are safe under *any* provider's vocabulary.
|
|
225
|
+
*/
|
|
226
|
+
export interface Surface {
|
|
227
|
+
kind: SurfaceKind;
|
|
228
|
+
provider: SurfaceProvider;
|
|
229
|
+
params: Record<string, JsonValue>;
|
|
230
|
+
/**
|
|
231
|
+
* When the provider stops honouring the session, if it says so. Lets a client re-quote before
|
|
232
|
+
* the button goes dead; absent, the surface lives as long as the quote that carried it.
|
|
233
|
+
*/
|
|
234
|
+
expiresAt?: string;
|
|
235
|
+
}
|
|
223
236
|
export interface InstructionField {
|
|
224
237
|
id: string;
|
|
225
238
|
labelFallback: string;
|
|
@@ -402,9 +415,16 @@ export type AuthChallenge = Record<string, JsonValue>;
|
|
|
402
415
|
* 2. terminal outcomes are OrderStatus, never kinds
|
|
403
416
|
* 3. every fallible state carries `error?: FailureReason`
|
|
404
417
|
*/
|
|
405
|
-
export type FlowState =
|
|
418
|
+
export type FlowState =
|
|
419
|
+
/**
|
|
420
|
+
* `surface` present means the provider's own component IS the primary CTA, inline on the entry
|
|
421
|
+
* screen — the placement Apple policy forces for a pay sheet, since no visible step may sit
|
|
422
|
+
* between the Apple-branded tap and the sheet. Absent, the quote's SUBMITs drive our button.
|
|
423
|
+
*/
|
|
424
|
+
{
|
|
406
425
|
kind: 'QUOTE';
|
|
407
426
|
quote: Quote;
|
|
427
|
+
surface?: Surface;
|
|
408
428
|
error?: FailureReason;
|
|
409
429
|
} | {
|
|
410
430
|
kind: 'FUN_AUTH';
|
|
@@ -415,20 +435,23 @@ export type FlowState = {
|
|
|
415
435
|
channel: 'EMAIL_OTP';
|
|
416
436
|
error?: FailureReason;
|
|
417
437
|
}
|
|
418
|
-
/**
|
|
438
|
+
/** Pre-order KYC states carry the routed quote so the entry screen can keep price visible. */
|
|
419
439
|
| {
|
|
420
440
|
kind: 'KYC';
|
|
421
441
|
phase: 'CAPTURE';
|
|
442
|
+
quote: Quote;
|
|
422
443
|
surface: Surface;
|
|
423
444
|
error?: FailureReason;
|
|
424
445
|
} | {
|
|
425
446
|
kind: 'KYC';
|
|
426
447
|
phase: 'INPUT_REQUIRED';
|
|
448
|
+
quote: Quote;
|
|
427
449
|
form: FormDescriptor;
|
|
428
450
|
error?: FailureReason;
|
|
429
451
|
} | {
|
|
430
452
|
kind: 'KYC';
|
|
431
453
|
phase: 'NO_ACTION_REQUIRED';
|
|
454
|
+
quote: Quote;
|
|
432
455
|
/**
|
|
433
456
|
* Note the deliberate name collision (Charles's nit, review 2026-08-14): `ON_HOLD` appears
|
|
434
457
|
* here as a KYC reason *and* in `OrderStatus`. They are different things and both names come
|
|
@@ -443,10 +466,9 @@ export type FlowState = {
|
|
|
443
466
|
reason: 'IN_REVIEW' | 'ON_HOLD' | 'REJECTED';
|
|
444
467
|
retryAfter?: string;
|
|
445
468
|
/**
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
* TODO(open-decision): contract OQ7 — which rejections escalate vs terminate.
|
|
469
|
+
* REJECTED is reserved for an explicit non-retryable outcome and renders in place — it
|
|
470
|
+
* never routes through `ORDER{CREATED, FAILED}`. Correctable outcomes return CAPTURE or
|
|
471
|
+
* INPUT_REQUIRED; a provider-specific decline is handled by quote rerouting first.
|
|
450
472
|
*/
|
|
451
473
|
failureReason?: FailureReason;
|
|
452
474
|
error?: FailureReason;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fun-xyz/fiat-contract",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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
|
+
}
|