@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/dist/index.mjs CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  tableEntry,
13
13
  unofferedEndpoints,
14
14
  unreachableStates
15
- } from "./chunk-OGNYHIOE.mjs";
15
+ } from "./chunk-LVVQ46YG.mjs";
16
16
 
17
17
  // src/codes.ts
18
18
  var COUNTRY_CURRENCY_LIST = [
@@ -295,7 +295,7 @@ var HTTP_VERBS = ["GET", "POST"];
295
295
  var ENDPOINT_PATTERN = /^(GET|POST) \/fiat\/[^\s]*$/;
296
296
  var $endpoint = z.string().regex(ENDPOINT_PATTERN, 'endpoint must read "<GET|POST> /fiat/\u2026"');
297
297
  var FiatEndpointSchema = $endpoint;
298
- var $provider = z.enum(["TRANSAK", "SWAPPED", "BANXA"]);
298
+ var $provider = z.enum(["TRANSAK", "SWAPPED", "BANXA", "MOONPAY", "STRIPE"]);
299
299
  var FiatProviderSchema = $provider;
300
300
  var $kycProvider = z.enum(["SUMSUB"]);
301
301
  var KYCProviderSchema = $kycProvider;
@@ -392,7 +392,7 @@ var $refund = obj({
392
392
  expectedBy: z.string()
393
393
  });
394
394
  var RefundSchema = $refund;
395
- var $surfaceKind = z.enum(["URL_REDIRECT", "URL_EMBED", "PCI_COMPONENT", "PAY_SHEET", "ACH_COMPONENT"]);
395
+ var $surfaceKind = z.enum(["URL_REDIRECT", "KYC_SDK", "PCI_COMPONENT", "PAY_SHEET", "ACH_COMPONENT"]);
396
396
  var SurfaceKindSchema = $surfaceKind;
397
397
  var $surface = obj({
398
398
  kind: $surfaceKind,
@@ -557,6 +557,7 @@ var SessionAuthStateSchema = $sessionAuthState;
557
557
  var $kycCaptureState = obj({
558
558
  kind: z.literal("KYC"),
559
559
  phase: z.literal("CAPTURE"),
560
+ quote: $quote,
560
561
  surface: $surface,
561
562
  error: $failureReason.optional()
562
563
  });
@@ -564,6 +565,7 @@ var KycCaptureStateSchema = $kycCaptureState;
564
565
  var $kycInputRequiredState = obj({
565
566
  kind: z.literal("KYC"),
566
567
  phase: z.literal("INPUT_REQUIRED"),
568
+ quote: $quote,
567
569
  form: $formDescriptor,
568
570
  error: $failureReason.optional()
569
571
  });
@@ -571,6 +573,7 @@ var KycInputRequiredStateSchema = $kycInputRequiredState;
571
573
  var $kycNoActionRequiredState = obj({
572
574
  kind: z.literal("KYC"),
573
575
  phase: z.literal("NO_ACTION_REQUIRED"),
576
+ quote: $quote,
574
577
  reason: z.enum(["IN_REVIEW", "ON_HOLD", "REJECTED"]),
575
578
  retryAfter: z.string().optional(),
576
579
  failureReason: $failureReason.optional(),
@@ -778,45 +781,43 @@ var FIXTURE_DATA = {
778
781
  "state": {
779
782
  "kind": "KYC",
780
783
  "phase": "INPUT_REQUIRED",
784
+ "quote": {
785
+ "quoteRef": "q_8f2",
786
+ "fiat": {
787
+ "currency": "GBP",
788
+ "amount": "100.00"
789
+ },
790
+ "crypto": {
791
+ "currency": "USDC",
792
+ "network": "polygon",
793
+ "amount": "98.41"
794
+ },
795
+ "rate": "0.9841",
796
+ "totalFee": "1.59",
797
+ "feeBreakdown": [
798
+ {
799
+ "id": "provider_fee",
800
+ "labelFallback": "Processing fee",
801
+ "amount": "1.59"
802
+ }
803
+ ],
804
+ "paymentMethod": "bank_transfer",
805
+ "expiresAt": "\u2026"
806
+ },
781
807
  "form": {
782
- "formId": "personal_details",
808
+ "formId": "kyc_q_8f2_1",
783
809
  "fields": [
784
810
  {
785
- "id": "firstName",
811
+ "id": "tin",
786
812
  "type": "TEXT",
787
- "labelFallback": "First name",
813
+ "labelFallback": "Tax ID",
788
814
  "required": true
789
815
  },
790
816
  {
791
- "id": "dob",
792
- "type": "DATE",
793
- "labelFallback": "Date of birth",
794
- "required": true
795
- },
796
- {
797
- "id": "country",
798
- "type": "COUNTRY",
799
- "labelFallback": "Country",
817
+ "id": "address",
818
+ "type": "TEXT",
819
+ "labelFallback": "Home address",
800
820
  "required": true
801
- },
802
- {
803
- "id": "sourceOfFunds",
804
- "type": "SELECT",
805
- "labelFallback": "Source of funds",
806
- "required": true,
807
- "options": [
808
- {
809
- "id": "salary",
810
- "labelFallback": "Salary"
811
- }
812
- ]
813
- },
814
- {
815
- "id": "idProof",
816
- "type": "HOSTED_LINK",
817
- "labelFallback": "Verify your ID",
818
- "required": true,
819
- "url": "\u2026"
820
821
  }
821
822
  ]
822
823
  }
@@ -828,7 +829,8 @@ var FIXTURE_DATA = {
828
829
  "mode": "SUBMIT",
829
830
  "endpoint": "POST /fiat/kyc/form",
830
831
  "params": {
831
- "formId": "personal_details"
832
+ "quoteRef": "q_8f2",
833
+ "formId": "kyc_q_8f2_1"
832
834
  },
833
835
  "inputs": {
834
836
  "values": {
@@ -913,11 +915,84 @@ var FIXTURE_DATA = {
913
915
  }
914
916
  ]
915
917
  },
918
+ "screen-08-kyc-capture": {
919
+ "state": {
920
+ "kind": "KYC",
921
+ "phase": "CAPTURE",
922
+ "quote": {
923
+ "quoteRef": "q_8f2",
924
+ "fiat": {
925
+ "currency": "GBP",
926
+ "amount": "100.00"
927
+ },
928
+ "crypto": {
929
+ "currency": "USDC",
930
+ "network": "polygon",
931
+ "amount": "98.41"
932
+ },
933
+ "rate": "0.9841",
934
+ "totalFee": "1.59",
935
+ "feeBreakdown": [
936
+ {
937
+ "id": "provider_fee",
938
+ "labelFallback": "Processing fee",
939
+ "amount": "1.59"
940
+ }
941
+ ],
942
+ "paymentMethod": "bank_transfer",
943
+ "expiresAt": "\u2026"
944
+ },
945
+ "surface": {
946
+ "kind": "KYC_SDK",
947
+ "provider": "SUMSUB",
948
+ "params": {
949
+ "accessToken": "eyJ\u2026"
950
+ },
951
+ "expiresAt": "\u2026"
952
+ }
953
+ },
954
+ "provider": "TRANSAK",
955
+ "transitions": [
956
+ {
957
+ "id": "capture",
958
+ "mode": "CLIENT_SURFACE",
959
+ "report": {
960
+ "endpoint": "POST /fiat/kyc/capture",
961
+ "params": {
962
+ "quoteRef": "q_8f2"
963
+ }
964
+ }
965
+ }
966
+ ]
967
+ },
916
968
  "screen-09-kyc-in-review": {
917
969
  "state": {
918
970
  "kind": "KYC",
919
971
  "phase": "NO_ACTION_REQUIRED",
920
- "reason": "IN_REVIEW"
972
+ "reason": "IN_REVIEW",
973
+ "quote": {
974
+ "quoteRef": "q_8f2",
975
+ "fiat": {
976
+ "currency": "GBP",
977
+ "amount": "100.00"
978
+ },
979
+ "crypto": {
980
+ "currency": "USDC",
981
+ "network": "polygon",
982
+ "amount": "98.41"
983
+ },
984
+ "rate": "0.9841",
985
+ "totalFee": "1.59",
986
+ "feeBreakdown": [
987
+ {
988
+ "id": "provider_fee",
989
+ "labelFallback": "Processing fee",
990
+ "amount": "1.59"
991
+ }
992
+ ],
993
+ "paymentMethod": "bank_transfer",
994
+ "expiresAt": "\u2026"
995
+ }
921
996
  },
922
997
  "provider": "TRANSAK",
923
998
  "transitions": [
@@ -925,10 +1000,7 @@ var FIXTURE_DATA = {
925
1000
  "id": "poll",
926
1001
  "mode": "AWAIT",
927
1002
  "poll": {
928
- "endpoint": "GET /fiat/kyc",
929
- "params": {
930
- "quoteRef": "q_8f2"
931
- },
1003
+ "endpoint": "GET /fiat/quotes/q_8f2",
932
1004
  "intervalMs": 3e3,
933
1005
  "backoffFactor": 1.5,
934
1006
  "maxIntervalMs": 1e4,
@@ -942,28 +1014,70 @@ var FIXTURE_DATA = {
942
1014
  "kind": "KYC",
943
1015
  "phase": "NO_ACTION_REQUIRED",
944
1016
  "reason": "ON_HOLD",
945
- "retryAfter": "2026-08-20T09:00:00Z"
1017
+ "retryAfter": "2026-08-20T09:00:00Z",
1018
+ "quote": {
1019
+ "quoteRef": "q_8f2",
1020
+ "fiat": {
1021
+ "currency": "GBP",
1022
+ "amount": "100.00"
1023
+ },
1024
+ "crypto": {
1025
+ "currency": "USDC",
1026
+ "network": "polygon",
1027
+ "amount": "98.41"
1028
+ },
1029
+ "rate": "0.9841",
1030
+ "totalFee": "1.59",
1031
+ "feeBreakdown": [
1032
+ {
1033
+ "id": "provider_fee",
1034
+ "labelFallback": "Processing fee",
1035
+ "amount": "1.59"
1036
+ }
1037
+ ],
1038
+ "paymentMethod": "bank_transfer",
1039
+ "expiresAt": "\u2026"
1040
+ }
946
1041
  },
947
1042
  "provider": "TRANSAK",
948
1043
  "transitions": []
949
1044
  },
950
- "screen-11-kyc-rejected-escalation": {
1045
+ "screen-11-kyc-rejected": {
951
1046
  "state": {
952
1047
  "kind": "KYC",
953
1048
  "phase": "NO_ACTION_REQUIRED",
954
- "reason": "REJECTED"
1049
+ "reason": "REJECTED",
1050
+ "quote": {
1051
+ "quoteRef": "q_8f2",
1052
+ "fiat": {
1053
+ "currency": "GBP",
1054
+ "amount": "100.00"
1055
+ },
1056
+ "crypto": {
1057
+ "currency": "USDC",
1058
+ "network": "polygon",
1059
+ "amount": "98.41"
1060
+ },
1061
+ "rate": "0.9841",
1062
+ "totalFee": "1.59",
1063
+ "feeBreakdown": [
1064
+ {
1065
+ "id": "provider_fee",
1066
+ "labelFallback": "Processing fee",
1067
+ "amount": "1.59"
1068
+ }
1069
+ ],
1070
+ "paymentMethod": "bank_transfer",
1071
+ "expiresAt": "\u2026"
1072
+ },
1073
+ "failureReason": {
1074
+ "category": "KYC_REJECTED",
1075
+ "retryable": false,
1076
+ "recovery": "CONTACT_SUPPORT"
1077
+ }
955
1078
  },
956
1079
  "provider": "TRANSAK",
957
- "transitions": [
958
- {
959
- "id": "escalate",
960
- "mode": "SUBMIT",
961
- "endpoint": "GET /fiat/kyc",
962
- "params": {
963
- "tier": "escalated"
964
- }
965
- }
966
- ]
1080
+ "transitions": []
967
1081
  },
968
1082
  "screen-12-pending-order": {
969
1083
  "state": {
@@ -1241,8 +1355,8 @@ var FIXTURES = [
1241
1355
  {
1242
1356
  id: "screen-03-kyc-input-required",
1243
1357
  file: "screen-03-kyc-input-required.json",
1244
- source: "FE_DOC",
1245
- docRef: "Screen 3 \xB7 Identity form (KYC)",
1358
+ source: "KYC_DOC",
1359
+ docRef: "Fiat KYC \xB7 L1 input round",
1246
1360
  screen: 3,
1247
1361
  stateKey: "KYC/INPUT_REQUIRED",
1248
1362
  notes: ["Carries the FORM_VALUES input spec \u2014 the no-double-carry case of the accepted split."]
@@ -1270,34 +1384,40 @@ var FIXTURES = [
1270
1384
  "TODO(open-decision): report target per card topology is a Transak ask (contract Flow A)."
1271
1385
  ]
1272
1386
  },
1387
+ {
1388
+ id: "screen-08-kyc-capture",
1389
+ file: "screen-08-kyc-capture.json",
1390
+ source: "KYC_DOC",
1391
+ docRef: "Fiat KYC \xB7 embedded Sumsub WebSDK",
1392
+ screen: 8,
1393
+ stateKey: "KYC/CAPTURE",
1394
+ notes: ["The client mounts the Sumsub adapter; no URL or iframe is emitted."]
1395
+ },
1273
1396
  {
1274
1397
  id: "screen-09-kyc-in-review",
1275
1398
  file: "screen-09-kyc-in-review.json",
1276
- source: "FE_DOC",
1277
- docRef: "Screen 9 \xB7 Verifying identity",
1399
+ source: "KYC_DOC",
1400
+ docRef: "Fiat KYC \xB7 pending quote",
1278
1401
  screen: 9,
1279
1402
  stateKey: "KYC/NO_ACTION_REQUIRED:IN_REVIEW"
1280
1403
  },
1281
1404
  {
1282
1405
  id: "screen-10-kyc-on-hold",
1283
1406
  file: "screen-10-kyc-on-hold.json",
1284
- source: "FE_DOC",
1285
- docRef: "Screen 10 \xB7 Verification on hold",
1407
+ source: "KYC_DOC",
1408
+ docRef: "Fiat KYC \xB7 verification on hold",
1286
1409
  screen: 10,
1287
1410
  stateKey: "KYC/NO_ACTION_REQUIRED:ON_HOLD",
1288
1411
  notes: ["transitions: [] and NOT terminal \u2014 the fixture that proves terminality is table data."]
1289
1412
  },
1290
1413
  {
1291
- id: "screen-11-kyc-rejected-escalation",
1292
- file: "screen-11-kyc-rejected-escalation.json",
1293
- source: "FE_DOC",
1294
- docRef: "Screen 11 \xB7 Verification unsuccessful",
1414
+ id: "screen-11-kyc-rejected",
1415
+ file: "screen-11-kyc-rejected.json",
1416
+ source: "KYC_DOC",
1417
+ docRef: "Fiat KYC \xB7 non-retryable rejection",
1295
1418
  screen: 11,
1296
1419
  stateKey: "KYC/NO_ACTION_REQUIRED:REJECTED",
1297
- notes: [
1298
- "TODO(open-decision): the escalation transition is a flagged placeholder \u2014 a GET carrying params is not a real request shape (FE doc Screen 11 + open question 7).",
1299
- "The terminal REJECTED variant (failureReason + transitions []) is described in prose, not published as JSON \u2014 no fixture."
1300
- ]
1420
+ notes: ["Only an explicit non-retryable provider or identity-vendor outcome reaches this state."]
1301
1421
  },
1302
1422
  {
1303
1423
  id: "screen-12-pending-order",
@@ -1342,7 +1462,7 @@ var FIXTURES = [
1342
1462
  docRef: "Screen 16 \xB7 Not available",
1343
1463
  screen: 16,
1344
1464
  stateKey: "BLOCKED",
1345
- notes: ["The only flatly terminal state entry in the table."]
1465
+ notes: ["A flatly terminal availability state; explicit non-retryable KYC rejection is terminal too."]
1346
1466
  },
1347
1467
  {
1348
1468
  id: "worked-example-01-quote",
@@ -1374,10 +1494,6 @@ var FIXTURE_COVERAGE_GAPS = [
1374
1494
  {
1375
1495
  stateKey: "FUN_AUTH",
1376
1496
  reason: "No step response exists in either doc \u2014 shape owned by the auth spike, and FUN_AUTH never renders in fomo. TODO(open-decision): contract \xA7Auth & ref binding, item 5."
1377
- },
1378
- {
1379
- stateKey: "KYC/CAPTURE",
1380
- reason: "Dropped from v1 (FE Screen 8 tombstone, no UK module coverage); no step response was ever published. Kind retained as handshake-gated vocabulary."
1381
1497
  }
1382
1498
  ];
1383
1499
  var fixtureMeta = (id) => {
package/dist/table.d.ts CHANGED
@@ -42,8 +42,7 @@ export type StateKey = 'QUOTE' | 'FUN_AUTH' | 'SESSION_AUTH' | 'KYC/CAPTURE' | '
42
42
  export declare const TERMINAL_ORDER_STATUSES: readonly ["SETTLED", "EXPIRED", "CANCELLED", "FAILED", "REFUNDED"];
43
43
  export type TerminalOrderStatus = (typeof TERMINAL_ORDER_STATUSES)[number];
44
44
  /**
45
- * Endpoint template as written in the docs `:id` stands for an order id, so
46
- * `"POST /fiat/orders/:id/cancel"` matches the emitted `"POST /fiat/orders/o_31c/cancel"`.
45
+ * Endpoint template as written in the docs. Named `:` segments match one emitted path segment.
47
46
  */
48
47
  export type EndpointTemplate = FiatEndpoint;
49
48
  export interface AllowedTransition {
@@ -91,7 +90,7 @@ export declare const STATE_KEYS: StateKey[];
91
90
  * named by the table (QUOTE's capture-first entry) while the doc's endpoint table still lists no
92
91
  * session-minting call for that rail.
93
92
  */
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"];
93
+ 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/quotes/:quoteRef", "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/: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
94
  /**
96
95
  * Documented endpoints no flow state offers, and why. Declared so an endpoint nothing can reach is
97
96
  * visible instead of silent — the table test asserts the unoffered set equals exactly these.
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.8.0";
40
40
 
41
41
  // src/table.ts
42
42
  var TABLE_VERSION = version;
@@ -162,21 +162,23 @@ var TRANSITION_TABLE = {
162
162
  {
163
163
  id: "capture",
164
164
  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.'
165
+ endpoint: "POST /fiat/kyc/capture",
166
+ note: "Reports SDK submission; provider eligibility is re-evaluated through quote orchestration."
167
167
  }
168
168
  ],
169
169
  mayReturn: [
170
+ "KYC/CAPTURE",
170
171
  "KYC/INPUT_REQUIRED",
171
172
  "KYC/NO_ACTION_REQUIRED:IN_REVIEW",
172
173
  "KYC/NO_ACTION_REQUIRED:ON_HOLD",
173
174
  "KYC/NO_ACTION_REQUIRED:REJECTED",
175
+ "QUOTE",
176
+ "PAYMENT/CAPTURE",
177
+ "PAYMENT/INSTRUCT",
174
178
  "ORDER/AWAITING_CONFIRMATION"
175
179
  ],
176
180
  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
- ]
181
+ notes: ["Carries the handshake-gated KYC_SDK surface; provider selects the identity-vendor adapter."]
180
182
  },
181
183
  "KYC/INPUT_REQUIRED": {
182
184
  key: "KYC/INPUT_REQUIRED",
@@ -188,16 +190,20 @@ var TRANSITION_TABLE = {
188
190
  { id: "submit_round", mode: "SUBMIT", endpoint: "POST /fiat/kyc/form" }
189
191
  ],
190
192
  mayReturn: [
193
+ "KYC/CAPTURE",
191
194
  "KYC/INPUT_REQUIRED",
192
195
  "KYC/NO_ACTION_REQUIRED:IN_REVIEW",
193
196
  "KYC/NO_ACTION_REQUIRED:ON_HOLD",
194
197
  "KYC/NO_ACTION_REQUIRED:REJECTED",
198
+ "QUOTE",
199
+ "PAYMENT/CAPTURE",
200
+ "PAYMENT/INSTRUCT",
195
201
  "ORDER/AWAITING_CONFIRMATION"
196
202
  ],
197
203
  terminal: false,
198
204
  notes: [
199
205
  "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.",
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.",
201
207
  "Hosted KYC links arrive as HOSTED_LINK form fields, not as a Surface."
202
208
  ]
203
209
  },
@@ -208,12 +214,16 @@ var TRANSITION_TABLE = {
208
214
  reason: "IN_REVIEW",
209
215
  screens: [9],
210
216
  docStatus: "SPECIFIED",
211
- allowedTransitions: [{ id: "poll", mode: "AWAIT", endpoint: "GET /fiat/kyc" }],
217
+ allowedTransitions: [{ id: "poll", mode: "AWAIT", endpoint: "GET /fiat/quotes/:quoteRef" }],
212
218
  mayReturn: [
213
219
  "KYC/NO_ACTION_REQUIRED:IN_REVIEW",
220
+ "KYC/CAPTURE",
214
221
  "KYC/INPUT_REQUIRED",
215
222
  "KYC/NO_ACTION_REQUIRED:ON_HOLD",
216
223
  "KYC/NO_ACTION_REQUIRED:REJECTED",
224
+ "QUOTE",
225
+ "PAYMENT/CAPTURE",
226
+ "PAYMENT/INSTRUCT",
217
227
  "ORDER/AWAITING_CONFIRMATION"
218
228
  ],
219
229
  terminal: false,
@@ -243,20 +253,11 @@ var TRANSITION_TABLE = {
243
253
  reason: "REJECTED",
244
254
  screens: [11],
245
255
  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,
256
+ allowedTransitions: [],
257
+ mayReturn: [],
258
+ terminal: true,
257
259
  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."
260
+ "Only explicit non-retryable outcomes reach REJECTED. Correctable failures return CAPTURE or INPUT_REQUIRED; provider-specific declines re-route before becoming user rejection."
260
261
  ]
261
262
  },
262
263
  PENDING_ORDER: {
@@ -418,8 +419,10 @@ var DOCUMENTED_ENDPOINTS = [
418
419
  "POST /fiat/payment-session",
419
420
  "POST /fiat/session",
420
421
  "POST /fiat/session/verify",
421
- "GET /fiat/kyc",
422
+ "GET /fiat/quotes/:quoteRef",
422
423
  "POST /fiat/kyc/form",
424
+ "POST /fiat/kyc/capture",
425
+ "POST /fiat/kyc/sdk-token",
423
426
  "POST /fiat/kyc/document",
424
427
  "POST /fiat/instruments",
425
428
  "POST /fiat/orders",
@@ -431,13 +434,17 @@ var DOCUMENTED_ENDPOINTS = [
431
434
  "POST /fiat/orders/:id/surface-result"
432
435
  ];
433
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
+ },
434
441
  {
435
442
  endpoint: "GET /fiat/payment-methods",
436
443
  reason: "Discovery, called before any flow state exists \u2014 there is no state to hang it off."
437
444
  },
438
445
  {
439
446
  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)."
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)."
441
448
  },
442
449
  {
443
450
  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-LVVQ46YG.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';
72
65
  /**
73
66
  * The order status vocabulary. Monotonic ranks, no backwards moves. Terminal outcomes are
74
67
  * OrderStatus, never FlowState kinds.
@@ -202,7 +195,7 @@ export interface Refund {
202
195
  expectedBy: string;
203
196
  }
204
197
  /** 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';
198
+ export type SurfaceKind = 'URL_REDIRECT' | 'KYC_SDK' | 'PCI_COMPONENT' | 'PAY_SHEET' | 'ACH_COMPONENT';
206
199
  /**
207
200
  * Identity vendors Fun verifies into. Its own axis rather than a `FiatProvider` value, because
208
201
  * Fun is the KYC-of-record in its own tenant — an identity surface is never the onramp's to mint.
@@ -442,20 +435,23 @@ export type FlowState =
442
435
  channel: 'EMAIL_OTP';
443
436
  error?: FailureReason;
444
437
  }
445
- /** 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. */
446
439
  | {
447
440
  kind: 'KYC';
448
441
  phase: 'CAPTURE';
442
+ quote: Quote;
449
443
  surface: Surface;
450
444
  error?: FailureReason;
451
445
  } | {
452
446
  kind: 'KYC';
453
447
  phase: 'INPUT_REQUIRED';
448
+ quote: Quote;
454
449
  form: FormDescriptor;
455
450
  error?: FailureReason;
456
451
  } | {
457
452
  kind: 'KYC';
458
453
  phase: 'NO_ACTION_REQUIRED';
454
+ quote: Quote;
459
455
  /**
460
456
  * Note the deliberate name collision (Charles's nit, review 2026-08-14): `ON_HOLD` appears
461
457
  * here as a KYC reason *and* in `OrderStatus`. They are different things and both names come
@@ -470,10 +466,9 @@ export type FlowState =
470
466
  reason: 'IN_REVIEW' | 'ON_HOLD' | 'REJECTED';
471
467
  retryAfter?: string;
472
468
  /**
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.
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.
477
472
  */
478
473
  failureReason?: FailureReason;
479
474
  error?: FailureReason;
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.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": {