@fun-xyz/fiat-contract 0.7.0 → 0.9.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/dist/table.js CHANGED
@@ -36,7 +36,7 @@ __export(table_exports, {
36
36
  module.exports = __toCommonJS(table_exports);
37
37
 
38
38
  // package.json
39
- var version = "0.7.0";
39
+ var version = "0.9.0";
40
40
 
41
41
  // src/table.ts
42
42
  var TABLE_VERSION = version;
@@ -142,6 +142,8 @@ var TRANSITION_TABLE = {
142
142
  ],
143
143
  mayReturn: [
144
144
  "SESSION_AUTH",
145
+ "QUOTE",
146
+ "PAYMENT/CAPTURE",
145
147
  ...KYC_ANY,
146
148
  "ORDER/AWAITING_CONFIRMATION",
147
149
  "PENDING_ORDER"
@@ -162,21 +164,23 @@ var TRANSITION_TABLE = {
162
164
  {
163
165
  id: "capture",
164
166
  mode: "CLIENT_SURFACE",
165
- endpoint: "GET /fiat/kyc",
166
- note: 'report target per FE bindings ("POST surface report \u2192 GET /fiat/kyc") and contract Flow C.'
167
+ endpoint: "POST /fiat/kyc/capture",
168
+ note: "Reports SDK submission; provider eligibility is re-evaluated through quote orchestration."
167
169
  }
168
170
  ],
169
171
  mayReturn: [
172
+ "KYC/CAPTURE",
170
173
  "KYC/INPUT_REQUIRED",
171
174
  "KYC/NO_ACTION_REQUIRED:IN_REVIEW",
172
175
  "KYC/NO_ACTION_REQUIRED:ON_HOLD",
173
176
  "KYC/NO_ACTION_REQUIRED:REJECTED",
177
+ "QUOTE",
178
+ "PAYMENT/CAPTURE",
179
+ "PAYMENT/INSTRUCT",
174
180
  "ORDER/AWAITING_CONFIRMATION"
175
181
  ],
176
182
  terminal: false,
177
- notes: [
178
- "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."
179
- ]
183
+ notes: ["Carries the handshake-gated KYC_SDK surface; provider selects the identity-vendor adapter."]
180
184
  },
181
185
  "KYC/INPUT_REQUIRED": {
182
186
  key: "KYC/INPUT_REQUIRED",
@@ -188,16 +192,20 @@ var TRANSITION_TABLE = {
188
192
  { id: "submit_round", mode: "SUBMIT", endpoint: "POST /fiat/kyc/form" }
189
193
  ],
190
194
  mayReturn: [
195
+ "KYC/CAPTURE",
191
196
  "KYC/INPUT_REQUIRED",
192
197
  "KYC/NO_ACTION_REQUIRED:IN_REVIEW",
193
198
  "KYC/NO_ACTION_REQUIRED:ON_HOLD",
194
199
  "KYC/NO_ACTION_REQUIRED:REJECTED",
200
+ "QUOTE",
201
+ "PAYMENT/CAPTURE",
202
+ "PAYMENT/INSTRUCT",
195
203
  "ORDER/AWAITING_CONFIRMATION"
196
204
  ],
197
205
  terminal: false,
198
206
  notes: [
199
207
  "One POST per round; conditional requirements may open another round.",
200
- "FILE fields stream to POST /fiat/kyc/document \u2014 an upload path the harness owns, not a transition.",
208
+ "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.",
201
209
  "Hosted KYC links arrive as HOSTED_LINK form fields, not as a Surface."
202
210
  ]
203
211
  },
@@ -208,12 +216,16 @@ var TRANSITION_TABLE = {
208
216
  reason: "IN_REVIEW",
209
217
  screens: [9],
210
218
  docStatus: "SPECIFIED",
211
- allowedTransitions: [{ id: "poll", mode: "AWAIT", endpoint: "GET /fiat/kyc" }],
219
+ allowedTransitions: [{ id: "poll", mode: "AWAIT", endpoint: "GET /fiat/quotes/:quoteRef" }],
212
220
  mayReturn: [
213
221
  "KYC/NO_ACTION_REQUIRED:IN_REVIEW",
222
+ "KYC/CAPTURE",
214
223
  "KYC/INPUT_REQUIRED",
215
224
  "KYC/NO_ACTION_REQUIRED:ON_HOLD",
216
225
  "KYC/NO_ACTION_REQUIRED:REJECTED",
226
+ "QUOTE",
227
+ "PAYMENT/CAPTURE",
228
+ "PAYMENT/INSTRUCT",
217
229
  "ORDER/AWAITING_CONFIRMATION"
218
230
  ],
219
231
  terminal: false,
@@ -243,20 +255,11 @@ var TRANSITION_TABLE = {
243
255
  reason: "REJECTED",
244
256
  screens: [11],
245
257
  docStatus: "SPECIFIED",
246
- allowedTransitions: [
247
- {
248
- id: "escalate",
249
- mode: "SUBMIT",
250
- endpoint: "GET /fiat/kyc",
251
- when: "an escalation round is offered",
252
- 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."
253
- }
254
- ],
255
- mayReturn: KYC_ANY,
256
- terminal: false,
258
+ allowedTransitions: [],
259
+ mayReturn: [],
260
+ terminal: true,
257
261
  notes: [
258
- "Terminal *variant*: failureReason {KYC_REJECTED, retryable: false, recovery: CONTACT_SUPPORT} + transitions [] \u2014 renders in place, never routes through ORDER{CREATED, FAILED}. Because escalation is also legal here, the entry is not flatly terminal.",
259
- "TODO(open-decision): contract OQ7 \u2014 terminal vs escalating rejections are not enumerated."
262
+ "Only explicit non-retryable outcomes reach REJECTED. Correctable failures return CAPTURE or INPUT_REQUIRED; provider-specific declines re-route before becoming user rejection."
260
263
  ]
261
264
  },
262
265
  PENDING_ORDER: {
@@ -418,8 +421,10 @@ var DOCUMENTED_ENDPOINTS = [
418
421
  "POST /fiat/payment-session",
419
422
  "POST /fiat/session",
420
423
  "POST /fiat/session/verify",
421
- "GET /fiat/kyc",
424
+ "GET /fiat/quotes/:quoteRef",
422
425
  "POST /fiat/kyc/form",
426
+ "POST /fiat/kyc/capture",
427
+ "POST /fiat/kyc/sdk-token",
423
428
  "POST /fiat/kyc/document",
424
429
  "POST /fiat/instruments",
425
430
  "POST /fiat/orders",
@@ -431,13 +436,17 @@ var DOCUMENTED_ENDPOINTS = [
431
436
  "POST /fiat/orders/:id/surface-result"
432
437
  ];
433
438
  var UNOFFERED_ENDPOINTS = [
439
+ {
440
+ endpoint: "POST /fiat/kyc/sdk-token",
441
+ reason: "SDK token refresh is adapter-driven rather than a flow transition."
442
+ },
434
443
  {
435
444
  endpoint: "GET /fiat/payment-methods",
436
445
  reason: "Discovery, called before any flow state exists \u2014 there is no state to hang it off."
437
446
  },
438
447
  {
439
448
  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)."
449
+ 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)."
441
450
  },
442
451
  {
443
452
  endpoint: "POST /fiat/instruments",
package/dist/table.mjs CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  tableEntry,
12
12
  unofferedEndpoints,
13
13
  unreachableStates
14
- } from "./chunk-OGNYHIOE.mjs";
14
+ } from "./chunk-5QGVY5Z4.mjs";
15
15
  export {
16
16
  DOCUMENTED_ENDPOINTS,
17
17
  STATE_KEYS,
package/dist/types.d.ts CHANGED
@@ -56,19 +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' | '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`;
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
- * FiatProvider is metadata: it selects the embedded provider SDK for CLIENT_SURFACE steps and
62
- * feeds analytics. It never drives sequencing (contract §The fiat step response).
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.
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.
61
+ * Routing metadata, not sequencing policy. Reserved values may be emitted only after their adapter
62
+ * and conformance fixture land.
70
63
  */
71
- export type FiatProvider = 'TRANSAK' | 'SWAPPED' | 'BANXA';
64
+ export type FiatProvider = 'TRANSAK' | 'SWAPPED' | 'BANXA' | 'MOONPAY' | 'STRIPE' | 'COINBASE' | 'CROSSMINT';
72
65
  /**
73
66
  * The order status vocabulary. Monotonic ranks, no backwards moves. Terminal outcomes are
74
67
  * OrderStatus, never FlowState kinds.
@@ -158,6 +151,11 @@ export interface Quote {
158
151
  paymentMethod: PaymentMethodCategory;
159
152
  expiresAt: string;
160
153
  }
154
+ /** Input bounds for the quote screen. Decimal strings in the quote currency. */
155
+ export interface QuoteLimits {
156
+ min: string;
157
+ max: string;
158
+ }
161
159
  /** Deferred to the credit-card iteration (FE doc port ledger) — optional wherever it appears. */
162
160
  export interface Instrument {
163
161
  brandLabel: string;
@@ -202,7 +200,7 @@ export interface Refund {
202
200
  expectedBy: string;
203
201
  }
204
202
  /** 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';
203
+ export type SurfaceKind = 'URL_REDIRECT' | 'KYC_SDK' | 'PCI_COMPONENT' | 'PAY_SHEET' | 'ACH_COMPONENT' | 'AUTH_COMPONENT';
206
204
  /**
207
205
  * Identity vendors Fun verifies into. Its own axis rather than a `FiatProvider` value, because
208
206
  * Fun is the KYC-of-record in its own tenant — an identity surface is never the onramp's to mint.
@@ -431,6 +429,8 @@ export type FlowState =
431
429
  {
432
430
  kind: 'QUOTE';
433
431
  quote: Quote;
432
+ limits: QuoteLimits;
433
+ helper?: string;
434
434
  surface?: Surface;
435
435
  error?: FailureReason;
436
436
  } | {
@@ -439,23 +439,27 @@ export type FlowState =
439
439
  error?: FailureReason;
440
440
  } | {
441
441
  kind: 'SESSION_AUTH';
442
- channel: 'EMAIL_OTP';
442
+ channel: 'EMAIL_OTP' | 'SMS_OTP' | 'PROVIDER_RENDERED';
443
+ surface?: Surface;
443
444
  error?: FailureReason;
444
445
  }
445
- /** Dropped from v1 (no UK module coverage) kind retained, handshake-gated. */
446
+ /** Pre-order KYC states carry the routed quote so the entry screen can keep price visible. */
446
447
  | {
447
448
  kind: 'KYC';
448
449
  phase: 'CAPTURE';
450
+ quote: Quote;
449
451
  surface: Surface;
450
452
  error?: FailureReason;
451
453
  } | {
452
454
  kind: 'KYC';
453
455
  phase: 'INPUT_REQUIRED';
456
+ quote: Quote;
454
457
  form: FormDescriptor;
455
458
  error?: FailureReason;
456
459
  } | {
457
460
  kind: 'KYC';
458
461
  phase: 'NO_ACTION_REQUIRED';
462
+ quote: Quote;
459
463
  /**
460
464
  * Note the deliberate name collision (Charles's nit, review 2026-08-14): `ON_HOLD` appears
461
465
  * here as a KYC reason *and* in `OrderStatus`. They are different things and both names come
@@ -470,10 +474,9 @@ export type FlowState =
470
474
  reason: 'IN_REVIEW' | 'ON_HOLD' | 'REJECTED';
471
475
  retryAfter?: string;
472
476
  /**
473
- * Terminal REJECTED variant carries this (FE doc Screen 11: `KYC_REJECTED`,
474
- * `retryable: false`, `recovery: CONTACT_SUPPORT`) and renders in place it never routes
475
- * through `ORDER{CREATED, FAILED}`.
476
- * TODO(open-decision): contract OQ7 — which rejections escalate vs terminate.
477
+ * REJECTED is reserved for an explicit non-retryable outcome and renders in place — it
478
+ * never routes through `ORDER{CREATED, FAILED}`. Correctable outcomes return CAPTURE or
479
+ * INPUT_REQUIRED; a provider-specific decline is handled by quote rerouting first.
477
480
  */
478
481
  failureReason?: FailureReason;
479
482
  error?: FailureReason;
@@ -490,11 +493,7 @@ export type FlowState =
490
493
  error?: FailureReason;
491
494
  } | {
492
495
  kind: 'BLOCKED';
493
- /**
494
- * Observed value: `REGION_UNSUPPORTED` (FE doc Screen 16). The contract types this as
495
- * `string`; no enum is published, so none is invented here.
496
- */
497
- reason: string;
496
+ reason: BlockedReason;
498
497
  /** `null` renders "no comeback date" (FE doc Screen 16 emits it explicitly). */
499
498
  retryAfter?: string | null;
500
499
  error?: FailureReason;
@@ -532,6 +531,7 @@ export type FlowState =
532
531
  error?: FailureReason;
533
532
  };
534
533
  export type FlowStateKind = FlowState['kind'];
534
+ export type BlockedReason = 'REGION_UNSUPPORTED' | 'ASSET_UNSUPPORTED' | 'ALL_PROVIDERS_DECLINED' | 'AMOUNT_UNSERVABLE';
535
535
  /**
536
536
  * Every `/fiat/*` response. `transitions: []` = nothing the wizard can do; whether the *flow*
537
537
  * is over is read from the table (`terminal`), never from array emptiness (ON_HOLD is the proof).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fun-xyz/fiat-contract",
3
- "version": "0.7.0",
3
+ "version": "0.9.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": {