@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/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.6.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
- 'If the PENDING_ORDER removal lands, QUOTE additionally returns PAYMENT{INSTRUCT} / ORDER{CREATED} directly. TODO(open-decision): contract \xA7"Proposal: remove PENDING_ORDER".'
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: "GET /fiat/kyc",
136
- note: 'report target per FE bindings ("POST surface report \u2192 GET /fiat/kyc") and contract Flow C.'
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 stream to POST /fiat/kyc/document \u2014 an upload path the harness owns, not a transition.",
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/kyc" }],
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
- id: "escalate",
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
- "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.",
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/kyc",
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
- } from "./chunk-EHYDQU4T.mjs";
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' | '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.
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
- * Carried as *data* in CAPTURE phases only (`PAYMENT{CAPTURE}` · `KYC{CAPTURE}`).
204
- * Δ2: the CLIENT_SURFACE transition carries just `report`single-carry, no duplication.
205
- * `session` values are secrets: never log them (FE doc §Decisions, redaction).
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 tenantan 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 Surface = {
208
- kind: 'URL_REDIRECT';
209
- url: string;
210
- } | {
211
- kind: 'URL_EMBED';
212
- url: string;
213
- } | {
214
- kind: 'PCI_COMPONENT';
215
- session: string;
216
- } | {
217
- kind: 'PAY_SHEET';
218
- session: string;
219
- } | {
220
- kind: 'ACH_COMPONENT';
221
- session: string;
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
- /** Dropped from v1 (no UK module coverage) kind retained, handshake-gated. */
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
- * Terminal REJECTED variant carries this (FE doc Screen 11: `KYC_REJECTED`,
447
- * `retryable: false`, `recovery: CONTACT_SUPPORT`) and renders in place it never routes
448
- * through `ORDER{CREATED, FAILED}`.
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.6.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
+ }