@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/index.js
CHANGED
|
@@ -51,6 +51,7 @@ __export(index_exports, {
|
|
|
51
51
|
InstructionsSchema: () => InstructionsSchema,
|
|
52
52
|
InstrumentSchema: () => InstrumentSchema,
|
|
53
53
|
JsonSchema: () => JsonSchema,
|
|
54
|
+
KYCProviderSchema: () => KYCProviderSchema,
|
|
54
55
|
KycCaptureStateSchema: () => KycCaptureStateSchema,
|
|
55
56
|
KycInputRequiredStateSchema: () => KycInputRequiredStateSchema,
|
|
56
57
|
KycNoActionRequiredStateSchema: () => KycNoActionRequiredStateSchema,
|
|
@@ -73,6 +74,8 @@ __export(index_exports, {
|
|
|
73
74
|
SelectOptionSchema: () => SelectOptionSchema,
|
|
74
75
|
SessionAuthStateSchema: () => SessionAuthStateSchema,
|
|
75
76
|
StatusHistoryEntrySchema: () => StatusHistoryEntrySchema,
|
|
77
|
+
SurfaceKindSchema: () => SurfaceKindSchema,
|
|
78
|
+
SurfaceProviderSchema: () => SurfaceProviderSchema,
|
|
76
79
|
SurfaceSchema: () => SurfaceSchema,
|
|
77
80
|
TABLE_VERSION: () => TABLE_VERSION,
|
|
78
81
|
TERMINAL_ORDER_STATUSES: () => TERMINAL_ORDER_STATUSES,
|
|
@@ -81,6 +84,8 @@ __export(index_exports, {
|
|
|
81
84
|
TransitionParamsSchema: () => TransitionParamsSchema,
|
|
82
85
|
TransitionSchema: () => TransitionSchema,
|
|
83
86
|
TxSchema: () => TxSchema,
|
|
87
|
+
UNOFFERED_ENDPOINTS: () => UNOFFERED_ENDPOINTS,
|
|
88
|
+
UNREACHABLE_STATES: () => UNREACHABLE_STATES,
|
|
84
89
|
assertAllFixtures: () => assertAllFixtures,
|
|
85
90
|
assertFiatStepResponse: () => assertFiatStepResponse,
|
|
86
91
|
assertFixture: () => assertFixture,
|
|
@@ -95,6 +100,8 @@ __export(index_exports, {
|
|
|
95
100
|
looksLikeFieldSpec: () => looksLikeFieldSpec,
|
|
96
101
|
stateKey: () => stateKey,
|
|
97
102
|
tableEntry: () => tableEntry,
|
|
103
|
+
unofferedEndpoints: () => unofferedEndpoints,
|
|
104
|
+
unreachableStates: () => unreachableStates,
|
|
98
105
|
walkTable: () => walkTable
|
|
99
106
|
});
|
|
100
107
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -380,8 +387,12 @@ var HTTP_VERBS = ["GET", "POST"];
|
|
|
380
387
|
var ENDPOINT_PATTERN = /^(GET|POST) \/fiat\/[^\s]*$/;
|
|
381
388
|
var $endpoint = import_zod.z.string().regex(ENDPOINT_PATTERN, 'endpoint must read "<GET|POST> /fiat/\u2026"');
|
|
382
389
|
var FiatEndpointSchema = $endpoint;
|
|
383
|
-
var $provider = import_zod.z.enum(["TRANSAK", "SWAPPED"]);
|
|
390
|
+
var $provider = import_zod.z.enum(["TRANSAK", "SWAPPED", "BANXA"]);
|
|
384
391
|
var FiatProviderSchema = $provider;
|
|
392
|
+
var $kycProvider = import_zod.z.enum(["SUMSUB"]);
|
|
393
|
+
var KYCProviderSchema = $kycProvider;
|
|
394
|
+
var $surfaceProvider = import_zod.z.enum([...$provider.options, ...$kycProvider.options]);
|
|
395
|
+
var SurfaceProviderSchema = $surfaceProvider;
|
|
385
396
|
var $orderStatus = import_zod.z.enum([
|
|
386
397
|
"CREATING",
|
|
387
398
|
"AWAITING_PAYMENT",
|
|
@@ -473,13 +484,22 @@ var $refund = obj({
|
|
|
473
484
|
expectedBy: import_zod.z.string()
|
|
474
485
|
});
|
|
475
486
|
var RefundSchema = $refund;
|
|
476
|
-
var $
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
487
|
+
var $surfaceKind = import_zod.z.enum(["URL_REDIRECT", "URL_EMBED", "PCI_COMPONENT", "PAY_SHEET", "ACH_COMPONENT"]);
|
|
488
|
+
var SurfaceKindSchema = $surfaceKind;
|
|
489
|
+
var $surface = obj({
|
|
490
|
+
kind: $surfaceKind,
|
|
491
|
+
provider: $surfaceProvider,
|
|
492
|
+
/**
|
|
493
|
+
* The one deliberate non-strict leaf in the contract. Provider inputs are the adapter's
|
|
494
|
+
* vocabulary, so typing them here would drag every provider field change through a contract
|
|
495
|
+
* release and both consumers — and a normalised `environment` could only be lossy across
|
|
496
|
+
* Transak's four values and Banxa's two. Both ends narrow instead: fun-backend emits from a
|
|
497
|
+
* typed provider session, the client adapter validates with its own schema, and a recorded
|
|
498
|
+
* fixture per `(provider, kind)` keeps the two honest.
|
|
499
|
+
*/
|
|
500
|
+
params: import_zod.z.record(import_zod.z.string(), JsonSchema),
|
|
501
|
+
expiresAt: import_zod.z.string().optional()
|
|
502
|
+
});
|
|
483
503
|
var SurfaceSchema = $surface;
|
|
484
504
|
var $instructionField = obj({
|
|
485
505
|
id: import_zod.z.string(),
|
|
@@ -609,6 +629,8 @@ var AuthChallengeSchema = $authChallenge;
|
|
|
609
629
|
var $quoteState = obj({
|
|
610
630
|
kind: import_zod.z.literal("QUOTE"),
|
|
611
631
|
quote: $quote,
|
|
632
|
+
/** Present when the provider's component is the primary CTA, inline on the entry screen. */
|
|
633
|
+
surface: $surface.optional(),
|
|
612
634
|
error: $failureReason.optional()
|
|
613
635
|
});
|
|
614
636
|
var QuoteStateSchema = $quoteState;
|
|
@@ -718,7 +740,7 @@ var $stepResponse = obj({
|
|
|
718
740
|
var FiatStepResponseSchema = $stepResponse;
|
|
719
741
|
|
|
720
742
|
// package.json
|
|
721
|
-
var version = "0.
|
|
743
|
+
var version = "0.7.0";
|
|
722
744
|
|
|
723
745
|
// src/table.ts
|
|
724
746
|
var TABLE_VERSION = version;
|
|
@@ -749,22 +771,46 @@ var TRANSITION_TABLE = {
|
|
|
749
771
|
endpoint: "POST /fiat/session",
|
|
750
772
|
when: "no valid provider session token (own-tables check)"
|
|
751
773
|
},
|
|
774
|
+
{
|
|
775
|
+
id: "open_session",
|
|
776
|
+
mode: "SUBMIT",
|
|
777
|
+
endpoint: "POST /fiat/payment-session",
|
|
778
|
+
when: "returning user, session + KYC valid, capture-first rail \u2014 the method authorises before any order exists",
|
|
779
|
+
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."
|
|
780
|
+
},
|
|
752
781
|
{
|
|
753
782
|
id: "continue",
|
|
754
783
|
mode: "SUBMIT",
|
|
755
784
|
endpoint: "POST /fiat/orders",
|
|
756
|
-
when: "returning user, session + KYC valid \u2014 the quote screen doubles as review"
|
|
785
|
+
when: "returning user, session + KYC valid, order-first rail \u2014 the quote screen doubles as review",
|
|
786
|
+
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."
|
|
757
787
|
},
|
|
758
788
|
{
|
|
759
789
|
id: "continue",
|
|
760
790
|
mode: "SUBMIT",
|
|
761
791
|
endpoint: "POST /fiat/auth",
|
|
762
792
|
when: "no ambient Fun identity (non-fomo surfaces only)"
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
id: "capture",
|
|
796
|
+
mode: "CLIENT_SURFACE",
|
|
797
|
+
endpoint: "POST /fiat/orders",
|
|
798
|
+
when: "capture-then-order rail whose session is minted at quote time (inline pay sheet)",
|
|
799
|
+
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)."
|
|
800
|
+
},
|
|
801
|
+
{
|
|
802
|
+
id: "capture",
|
|
803
|
+
mode: "CLIENT_SURFACE",
|
|
804
|
+
endpoint: "POST /fiat/orders/:id/surface-result",
|
|
805
|
+
when: "order-then-capture rail whose provider order was created with the quote-time session",
|
|
806
|
+
note: "Same quote-time placement, other topology: the order already exists, so the surface reports against it rather than creating one."
|
|
763
807
|
}
|
|
764
808
|
],
|
|
765
809
|
mayReturn: [
|
|
766
810
|
"SESSION_AUTH",
|
|
767
811
|
...KYC_ANY,
|
|
812
|
+
"PAYMENT/CAPTURE",
|
|
813
|
+
"PAYMENT/INSTRUCT",
|
|
768
814
|
"PENDING_ORDER",
|
|
769
815
|
"ORDER/AWAITING_CONFIRMATION",
|
|
770
816
|
"ORDER/CREATED",
|
|
@@ -772,8 +818,10 @@ var TRANSITION_TABLE = {
|
|
|
772
818
|
],
|
|
773
819
|
terminal: false,
|
|
774
820
|
notes: [
|
|
775
|
-
"Re-quote on amount change is a client-local event, not a transition.",
|
|
776
|
-
|
|
821
|
+
"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.",
|
|
822
|
+
"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.",
|
|
823
|
+
`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.`,
|
|
824
|
+
'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.'
|
|
777
825
|
]
|
|
778
826
|
},
|
|
779
827
|
FUN_AUTH: {
|
|
@@ -1071,6 +1119,7 @@ var DOCUMENTED_ENDPOINTS = [
|
|
|
1071
1119
|
"GET /fiat/payment-methods",
|
|
1072
1120
|
"POST /fiat/quote",
|
|
1073
1121
|
"POST /fiat/auth",
|
|
1122
|
+
"POST /fiat/payment-session",
|
|
1074
1123
|
"POST /fiat/session",
|
|
1075
1124
|
"POST /fiat/session/verify",
|
|
1076
1125
|
"GET /fiat/kyc",
|
|
@@ -1085,6 +1134,54 @@ var DOCUMENTED_ENDPOINTS = [
|
|
|
1085
1134
|
"POST /fiat/orders/:id/cancel",
|
|
1086
1135
|
"POST /fiat/orders/:id/surface-result"
|
|
1087
1136
|
];
|
|
1137
|
+
var UNOFFERED_ENDPOINTS = [
|
|
1138
|
+
{
|
|
1139
|
+
endpoint: "GET /fiat/payment-methods",
|
|
1140
|
+
reason: "Discovery, called before any flow state exists \u2014 there is no state to hang it off."
|
|
1141
|
+
},
|
|
1142
|
+
{
|
|
1143
|
+
endpoint: "POST /fiat/kyc/document",
|
|
1144
|
+
reason: "FILE fields stream to it directly; the harness owns that upload path and it is not a transition (see KYC/INPUT_REQUIRED notes)."
|
|
1145
|
+
},
|
|
1146
|
+
{
|
|
1147
|
+
endpoint: "POST /fiat/instruments",
|
|
1148
|
+
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."
|
|
1149
|
+
},
|
|
1150
|
+
{
|
|
1151
|
+
endpoint: "GET /fiat/orders/:id/instructions",
|
|
1152
|
+
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."
|
|
1153
|
+
},
|
|
1154
|
+
{
|
|
1155
|
+
endpoint: "POST /fiat/orders/:id/reference",
|
|
1156
|
+
reason: "The bank-reference submission is offered by no state. TODO(open-decision): PAYMENT{INSTRUCT} is the plausible home \u2014 confirm or wire it."
|
|
1157
|
+
}
|
|
1158
|
+
];
|
|
1159
|
+
var UNREACHABLE_STATES = [
|
|
1160
|
+
{
|
|
1161
|
+
stateKey: "FUN_AUTH",
|
|
1162
|
+
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."
|
|
1163
|
+
}
|
|
1164
|
+
];
|
|
1165
|
+
function unofferedEndpoints() {
|
|
1166
|
+
const offered = /* @__PURE__ */ new Set();
|
|
1167
|
+
for (const key of STATE_KEYS) {
|
|
1168
|
+
for (const allowed of TRANSITION_TABLE[key].allowedTransitions) offered.add(allowed.endpoint);
|
|
1169
|
+
}
|
|
1170
|
+
return DOCUMENTED_ENDPOINTS.filter((endpoint) => !offered.has(endpoint));
|
|
1171
|
+
}
|
|
1172
|
+
function unreachableStates(from = "QUOTE") {
|
|
1173
|
+
const seen = /* @__PURE__ */ new Set([from]);
|
|
1174
|
+
const queue = [from];
|
|
1175
|
+
while (queue.length > 0) {
|
|
1176
|
+
for (const target of TRANSITION_TABLE[queue.pop()].mayReturn ?? []) {
|
|
1177
|
+
if (!seen.has(target)) {
|
|
1178
|
+
seen.add(target);
|
|
1179
|
+
queue.push(target);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
return STATE_KEYS.filter((key) => !seen.has(key));
|
|
1184
|
+
}
|
|
1088
1185
|
function stateKey(state) {
|
|
1089
1186
|
switch (state.kind) {
|
|
1090
1187
|
case "QUOTE":
|
|
@@ -1167,6 +1264,64 @@ var FIXTURE_DATA = {
|
|
|
1167
1264
|
}
|
|
1168
1265
|
]
|
|
1169
1266
|
},
|
|
1267
|
+
"screen-01b-quote-with-pay-sheet": {
|
|
1268
|
+
"state": {
|
|
1269
|
+
"kind": "QUOTE",
|
|
1270
|
+
"quote": {
|
|
1271
|
+
"quoteRef": "q_8f2",
|
|
1272
|
+
"fiat": {
|
|
1273
|
+
"currency": "GBP",
|
|
1274
|
+
"amount": "100.00"
|
|
1275
|
+
},
|
|
1276
|
+
"crypto": {
|
|
1277
|
+
"currency": "USDC",
|
|
1278
|
+
"network": "polygon",
|
|
1279
|
+
"amount": "98.41"
|
|
1280
|
+
},
|
|
1281
|
+
"rate": "0.9841",
|
|
1282
|
+
"totalFee": "1.59",
|
|
1283
|
+
"feeBreakdown": [
|
|
1284
|
+
{
|
|
1285
|
+
"id": "provider_fee",
|
|
1286
|
+
"labelFallback": "Processing fee",
|
|
1287
|
+
"amount": "1.09"
|
|
1288
|
+
},
|
|
1289
|
+
{
|
|
1290
|
+
"id": "network_fee",
|
|
1291
|
+
"labelFallback": "Network fee",
|
|
1292
|
+
"amount": "0.50"
|
|
1293
|
+
}
|
|
1294
|
+
],
|
|
1295
|
+
"paymentMethod": "bank_transfer",
|
|
1296
|
+
"expiresAt": "\u2026"
|
|
1297
|
+
},
|
|
1298
|
+
"surface": {
|
|
1299
|
+
"kind": "PAY_SHEET",
|
|
1300
|
+
"provider": "TRANSAK",
|
|
1301
|
+
"params": {
|
|
1302
|
+
"sessionId": "txs_\u2026",
|
|
1303
|
+
"environment": "STAGING"
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
},
|
|
1307
|
+
"provider": "TRANSAK",
|
|
1308
|
+
"transitions": [
|
|
1309
|
+
{
|
|
1310
|
+
"id": "capture",
|
|
1311
|
+
"mode": "CLIENT_SURFACE",
|
|
1312
|
+
"report": {
|
|
1313
|
+
"endpoint": "POST /fiat/orders",
|
|
1314
|
+
"params": {
|
|
1315
|
+
"quoteRef": "q_8f2",
|
|
1316
|
+
"depositAddress": "\u2026"
|
|
1317
|
+
},
|
|
1318
|
+
"expects": [
|
|
1319
|
+
"paymentAuthorisationId"
|
|
1320
|
+
]
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
]
|
|
1324
|
+
},
|
|
1170
1325
|
"screen-02-session-auth": {
|
|
1171
1326
|
"state": {
|
|
1172
1327
|
"kind": "SESSION_AUTH",
|
|
@@ -1305,7 +1460,11 @@ var FIXTURE_DATA = {
|
|
|
1305
1460
|
"phase": "CAPTURE",
|
|
1306
1461
|
"surface": {
|
|
1307
1462
|
"kind": "PCI_COMPONENT",
|
|
1308
|
-
"
|
|
1463
|
+
"provider": "TRANSAK",
|
|
1464
|
+
"params": {
|
|
1465
|
+
"sessionId": "eyJ\u2026",
|
|
1466
|
+
"environment": "STAGING"
|
|
1467
|
+
}
|
|
1309
1468
|
}
|
|
1310
1469
|
},
|
|
1311
1470
|
"provider": "TRANSAK",
|
|
@@ -1630,6 +1789,18 @@ var FIXTURES = [
|
|
|
1630
1789
|
screen: 1,
|
|
1631
1790
|
stateKey: "QUOTE"
|
|
1632
1791
|
},
|
|
1792
|
+
{
|
|
1793
|
+
id: "screen-01b-quote-with-pay-sheet",
|
|
1794
|
+
file: "screen-01b-quote-with-pay-sheet.json",
|
|
1795
|
+
source: "CONTRACT_DOC",
|
|
1796
|
+
docRef: "Fiat Contract 0.7 \xA7The contract diff \xB7 example envelopes",
|
|
1797
|
+
screen: 1,
|
|
1798
|
+
stateKey: "QUOTE",
|
|
1799
|
+
notes: [
|
|
1800
|
+
"The 0.7 single-screen shape: the quote carries its own PAY_SHEET, so the provider component is the primary CTA rather than something a later state reveals.",
|
|
1801
|
+
"Quote payload is screen-01-quote verbatim; only the surface and the transition differ."
|
|
1802
|
+
]
|
|
1803
|
+
},
|
|
1633
1804
|
{
|
|
1634
1805
|
id: "screen-02-session-auth",
|
|
1635
1806
|
file: "screen-02-session-auth.json",
|
|
@@ -1766,18 +1937,18 @@ var FIXTURES = [
|
|
|
1766
1937
|
docRef: "\xA7Worked example, steps 4\u20135 (order + bank instructions)",
|
|
1767
1938
|
stateKey: "PAYMENT/INSTRUCT",
|
|
1768
1939
|
notes: [
|
|
1769
|
-
"The
|
|
1940
|
+
"The response-level `orderId` case. TODO(open-decision): orderId placement (beside `state` here vs inside it in the FE doc v0)."
|
|
1770
1941
|
]
|
|
1771
1942
|
}
|
|
1772
1943
|
];
|
|
1773
1944
|
var FIXTURE_COVERAGE_GAPS = [
|
|
1774
1945
|
{
|
|
1775
1946
|
stateKey: "FUN_AUTH",
|
|
1776
|
-
reason: "No
|
|
1947
|
+
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."
|
|
1777
1948
|
},
|
|
1778
1949
|
{
|
|
1779
1950
|
stateKey: "KYC/CAPTURE",
|
|
1780
|
-
reason: "Dropped from v1 (FE Screen 8 tombstone, no UK module coverage); no
|
|
1951
|
+
reason: "Dropped from v1 (FE Screen 8 tombstone, no UK module coverage); no step response was ever published. Kind retained as handshake-gated vocabulary."
|
|
1781
1952
|
}
|
|
1782
1953
|
];
|
|
1783
1954
|
var fixtureMeta = (id) => {
|
|
@@ -1815,7 +1986,7 @@ var formatIssues = (error) => {
|
|
|
1815
1986
|
};
|
|
1816
1987
|
function assertFiatStepResponse(json) {
|
|
1817
1988
|
const result = FiatStepResponseSchema.safeParse(json);
|
|
1818
|
-
if (!result.success) throw new ContractViolation("invalid
|
|
1989
|
+
if (!result.success) throw new ContractViolation("invalid fiat step response", formatIssues(result.error));
|
|
1819
1990
|
return result.data;
|
|
1820
1991
|
}
|
|
1821
1992
|
function endpointMatches(template, actual) {
|
|
@@ -1867,6 +2038,16 @@ function checkLegalEmission(state, transitions) {
|
|
|
1867
2038
|
`${entry.key} is terminal (table) and must carry transitions: [] \u2014 got ${transitions.length}`
|
|
1868
2039
|
);
|
|
1869
2040
|
}
|
|
2041
|
+
const surfaceTargets = new Set(
|
|
2042
|
+
transitions.filter((t) => t.mode === "CLIENT_SURFACE").map((t) => t.report.endpoint)
|
|
2043
|
+
);
|
|
2044
|
+
for (const transition of transitions) {
|
|
2045
|
+
if (transition.mode === "SUBMIT" && surfaceTargets.has(transition.endpoint)) {
|
|
2046
|
+
issues.push(
|
|
2047
|
+
`transition "${transition.id}": SUBMIT ${transition.endpoint} coexists with a CLIENT_SURFACE reporting to it`
|
|
2048
|
+
);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
1870
2051
|
for (const transition of transitions) {
|
|
1871
2052
|
issues.push(...bodyKeyIssues(transition));
|
|
1872
2053
|
if (transition.mode === "CLIENT_SURFACE" && !("surface" in state)) {
|
|
@@ -1912,14 +2093,14 @@ function walkTable(fn) {
|
|
|
1912
2093
|
function assertFixture(id) {
|
|
1913
2094
|
const meta = fixtureMeta(id);
|
|
1914
2095
|
const json = loadFixture(id);
|
|
1915
|
-
const
|
|
1916
|
-
if (stateKey(
|
|
2096
|
+
const stepResponse = assertFiatStepResponse(json);
|
|
2097
|
+
if (stateKey(stepResponse.state) !== meta.stateKey) {
|
|
1917
2098
|
throw new ContractViolation(
|
|
1918
|
-
`fixture ${id}: manifest says ${meta.stateKey},
|
|
2099
|
+
`fixture ${id}: manifest says ${meta.stateKey}, step response carries ${stateKey(stepResponse.state)}`
|
|
1919
2100
|
);
|
|
1920
2101
|
}
|
|
1921
|
-
assertLegalEmission(
|
|
1922
|
-
return { meta,
|
|
2102
|
+
assertLegalEmission(stepResponse.state, stepResponse.transitions);
|
|
2103
|
+
return { meta, stepResponse };
|
|
1923
2104
|
}
|
|
1924
2105
|
function assertAllFixtures() {
|
|
1925
2106
|
return FIXTURES.map((meta) => assertFixture(meta.id));
|
package/dist/index.mjs
CHANGED
|
@@ -5,10 +5,14 @@ import {
|
|
|
5
5
|
TABLE_VERSION,
|
|
6
6
|
TERMINAL_ORDER_STATUSES,
|
|
7
7
|
TRANSITION_TABLE,
|
|
8
|
+
UNOFFERED_ENDPOINTS,
|
|
9
|
+
UNREACHABLE_STATES,
|
|
8
10
|
isTerminal,
|
|
9
11
|
stateKey,
|
|
10
|
-
tableEntry
|
|
11
|
-
|
|
12
|
+
tableEntry,
|
|
13
|
+
unofferedEndpoints,
|
|
14
|
+
unreachableStates
|
|
15
|
+
} from "./chunk-OGNYHIOE.mjs";
|
|
12
16
|
|
|
13
17
|
// src/codes.ts
|
|
14
18
|
var COUNTRY_CURRENCY_LIST = [
|
|
@@ -291,8 +295,12 @@ var HTTP_VERBS = ["GET", "POST"];
|
|
|
291
295
|
var ENDPOINT_PATTERN = /^(GET|POST) \/fiat\/[^\s]*$/;
|
|
292
296
|
var $endpoint = z.string().regex(ENDPOINT_PATTERN, 'endpoint must read "<GET|POST> /fiat/\u2026"');
|
|
293
297
|
var FiatEndpointSchema = $endpoint;
|
|
294
|
-
var $provider = z.enum(["TRANSAK", "SWAPPED"]);
|
|
298
|
+
var $provider = z.enum(["TRANSAK", "SWAPPED", "BANXA"]);
|
|
295
299
|
var FiatProviderSchema = $provider;
|
|
300
|
+
var $kycProvider = z.enum(["SUMSUB"]);
|
|
301
|
+
var KYCProviderSchema = $kycProvider;
|
|
302
|
+
var $surfaceProvider = z.enum([...$provider.options, ...$kycProvider.options]);
|
|
303
|
+
var SurfaceProviderSchema = $surfaceProvider;
|
|
296
304
|
var $orderStatus = z.enum([
|
|
297
305
|
"CREATING",
|
|
298
306
|
"AWAITING_PAYMENT",
|
|
@@ -384,13 +392,22 @@ var $refund = obj({
|
|
|
384
392
|
expectedBy: z.string()
|
|
385
393
|
});
|
|
386
394
|
var RefundSchema = $refund;
|
|
387
|
-
var $
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
395
|
+
var $surfaceKind = z.enum(["URL_REDIRECT", "URL_EMBED", "PCI_COMPONENT", "PAY_SHEET", "ACH_COMPONENT"]);
|
|
396
|
+
var SurfaceKindSchema = $surfaceKind;
|
|
397
|
+
var $surface = obj({
|
|
398
|
+
kind: $surfaceKind,
|
|
399
|
+
provider: $surfaceProvider,
|
|
400
|
+
/**
|
|
401
|
+
* The one deliberate non-strict leaf in the contract. Provider inputs are the adapter's
|
|
402
|
+
* vocabulary, so typing them here would drag every provider field change through a contract
|
|
403
|
+
* release and both consumers — and a normalised `environment` could only be lossy across
|
|
404
|
+
* Transak's four values and Banxa's two. Both ends narrow instead: fun-backend emits from a
|
|
405
|
+
* typed provider session, the client adapter validates with its own schema, and a recorded
|
|
406
|
+
* fixture per `(provider, kind)` keeps the two honest.
|
|
407
|
+
*/
|
|
408
|
+
params: z.record(z.string(), JsonSchema),
|
|
409
|
+
expiresAt: z.string().optional()
|
|
410
|
+
});
|
|
394
411
|
var SurfaceSchema = $surface;
|
|
395
412
|
var $instructionField = obj({
|
|
396
413
|
id: z.string(),
|
|
@@ -520,6 +537,8 @@ var AuthChallengeSchema = $authChallenge;
|
|
|
520
537
|
var $quoteState = obj({
|
|
521
538
|
kind: z.literal("QUOTE"),
|
|
522
539
|
quote: $quote,
|
|
540
|
+
/** Present when the provider's component is the primary CTA, inline on the entry screen. */
|
|
541
|
+
surface: $surface.optional(),
|
|
523
542
|
error: $failureReason.optional()
|
|
524
543
|
});
|
|
525
544
|
var QuoteStateSchema = $quoteState;
|
|
@@ -674,6 +693,64 @@ var FIXTURE_DATA = {
|
|
|
674
693
|
}
|
|
675
694
|
]
|
|
676
695
|
},
|
|
696
|
+
"screen-01b-quote-with-pay-sheet": {
|
|
697
|
+
"state": {
|
|
698
|
+
"kind": "QUOTE",
|
|
699
|
+
"quote": {
|
|
700
|
+
"quoteRef": "q_8f2",
|
|
701
|
+
"fiat": {
|
|
702
|
+
"currency": "GBP",
|
|
703
|
+
"amount": "100.00"
|
|
704
|
+
},
|
|
705
|
+
"crypto": {
|
|
706
|
+
"currency": "USDC",
|
|
707
|
+
"network": "polygon",
|
|
708
|
+
"amount": "98.41"
|
|
709
|
+
},
|
|
710
|
+
"rate": "0.9841",
|
|
711
|
+
"totalFee": "1.59",
|
|
712
|
+
"feeBreakdown": [
|
|
713
|
+
{
|
|
714
|
+
"id": "provider_fee",
|
|
715
|
+
"labelFallback": "Processing fee",
|
|
716
|
+
"amount": "1.09"
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
"id": "network_fee",
|
|
720
|
+
"labelFallback": "Network fee",
|
|
721
|
+
"amount": "0.50"
|
|
722
|
+
}
|
|
723
|
+
],
|
|
724
|
+
"paymentMethod": "bank_transfer",
|
|
725
|
+
"expiresAt": "\u2026"
|
|
726
|
+
},
|
|
727
|
+
"surface": {
|
|
728
|
+
"kind": "PAY_SHEET",
|
|
729
|
+
"provider": "TRANSAK",
|
|
730
|
+
"params": {
|
|
731
|
+
"sessionId": "txs_\u2026",
|
|
732
|
+
"environment": "STAGING"
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
},
|
|
736
|
+
"provider": "TRANSAK",
|
|
737
|
+
"transitions": [
|
|
738
|
+
{
|
|
739
|
+
"id": "capture",
|
|
740
|
+
"mode": "CLIENT_SURFACE",
|
|
741
|
+
"report": {
|
|
742
|
+
"endpoint": "POST /fiat/orders",
|
|
743
|
+
"params": {
|
|
744
|
+
"quoteRef": "q_8f2",
|
|
745
|
+
"depositAddress": "\u2026"
|
|
746
|
+
},
|
|
747
|
+
"expects": [
|
|
748
|
+
"paymentAuthorisationId"
|
|
749
|
+
]
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
]
|
|
753
|
+
},
|
|
677
754
|
"screen-02-session-auth": {
|
|
678
755
|
"state": {
|
|
679
756
|
"kind": "SESSION_AUTH",
|
|
@@ -812,7 +889,11 @@ var FIXTURE_DATA = {
|
|
|
812
889
|
"phase": "CAPTURE",
|
|
813
890
|
"surface": {
|
|
814
891
|
"kind": "PCI_COMPONENT",
|
|
815
|
-
"
|
|
892
|
+
"provider": "TRANSAK",
|
|
893
|
+
"params": {
|
|
894
|
+
"sessionId": "eyJ\u2026",
|
|
895
|
+
"environment": "STAGING"
|
|
896
|
+
}
|
|
816
897
|
}
|
|
817
898
|
},
|
|
818
899
|
"provider": "TRANSAK",
|
|
@@ -1137,6 +1218,18 @@ var FIXTURES = [
|
|
|
1137
1218
|
screen: 1,
|
|
1138
1219
|
stateKey: "QUOTE"
|
|
1139
1220
|
},
|
|
1221
|
+
{
|
|
1222
|
+
id: "screen-01b-quote-with-pay-sheet",
|
|
1223
|
+
file: "screen-01b-quote-with-pay-sheet.json",
|
|
1224
|
+
source: "CONTRACT_DOC",
|
|
1225
|
+
docRef: "Fiat Contract 0.7 \xA7The contract diff \xB7 example envelopes",
|
|
1226
|
+
screen: 1,
|
|
1227
|
+
stateKey: "QUOTE",
|
|
1228
|
+
notes: [
|
|
1229
|
+
"The 0.7 single-screen shape: the quote carries its own PAY_SHEET, so the provider component is the primary CTA rather than something a later state reveals.",
|
|
1230
|
+
"Quote payload is screen-01-quote verbatim; only the surface and the transition differ."
|
|
1231
|
+
]
|
|
1232
|
+
},
|
|
1140
1233
|
{
|
|
1141
1234
|
id: "screen-02-session-auth",
|
|
1142
1235
|
file: "screen-02-session-auth.json",
|
|
@@ -1273,18 +1366,18 @@ var FIXTURES = [
|
|
|
1273
1366
|
docRef: "\xA7Worked example, steps 4\u20135 (order + bank instructions)",
|
|
1274
1367
|
stateKey: "PAYMENT/INSTRUCT",
|
|
1275
1368
|
notes: [
|
|
1276
|
-
"The
|
|
1369
|
+
"The response-level `orderId` case. TODO(open-decision): orderId placement (beside `state` here vs inside it in the FE doc v0)."
|
|
1277
1370
|
]
|
|
1278
1371
|
}
|
|
1279
1372
|
];
|
|
1280
1373
|
var FIXTURE_COVERAGE_GAPS = [
|
|
1281
1374
|
{
|
|
1282
1375
|
stateKey: "FUN_AUTH",
|
|
1283
|
-
reason: "No
|
|
1376
|
+
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."
|
|
1284
1377
|
},
|
|
1285
1378
|
{
|
|
1286
1379
|
stateKey: "KYC/CAPTURE",
|
|
1287
|
-
reason: "Dropped from v1 (FE Screen 8 tombstone, no UK module coverage); no
|
|
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."
|
|
1288
1381
|
}
|
|
1289
1382
|
];
|
|
1290
1383
|
var fixtureMeta = (id) => {
|
|
@@ -1322,7 +1415,7 @@ var formatIssues = (error) => {
|
|
|
1322
1415
|
};
|
|
1323
1416
|
function assertFiatStepResponse(json) {
|
|
1324
1417
|
const result = FiatStepResponseSchema.safeParse(json);
|
|
1325
|
-
if (!result.success) throw new ContractViolation("invalid
|
|
1418
|
+
if (!result.success) throw new ContractViolation("invalid fiat step response", formatIssues(result.error));
|
|
1326
1419
|
return result.data;
|
|
1327
1420
|
}
|
|
1328
1421
|
function endpointMatches(template, actual) {
|
|
@@ -1374,6 +1467,16 @@ function checkLegalEmission(state, transitions) {
|
|
|
1374
1467
|
`${entry.key} is terminal (table) and must carry transitions: [] \u2014 got ${transitions.length}`
|
|
1375
1468
|
);
|
|
1376
1469
|
}
|
|
1470
|
+
const surfaceTargets = new Set(
|
|
1471
|
+
transitions.filter((t) => t.mode === "CLIENT_SURFACE").map((t) => t.report.endpoint)
|
|
1472
|
+
);
|
|
1473
|
+
for (const transition of transitions) {
|
|
1474
|
+
if (transition.mode === "SUBMIT" && surfaceTargets.has(transition.endpoint)) {
|
|
1475
|
+
issues.push(
|
|
1476
|
+
`transition "${transition.id}": SUBMIT ${transition.endpoint} coexists with a CLIENT_SURFACE reporting to it`
|
|
1477
|
+
);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1377
1480
|
for (const transition of transitions) {
|
|
1378
1481
|
issues.push(...bodyKeyIssues(transition));
|
|
1379
1482
|
if (transition.mode === "CLIENT_SURFACE" && !("surface" in state)) {
|
|
@@ -1419,14 +1522,14 @@ function walkTable(fn) {
|
|
|
1419
1522
|
function assertFixture(id) {
|
|
1420
1523
|
const meta = fixtureMeta(id);
|
|
1421
1524
|
const json = loadFixture(id);
|
|
1422
|
-
const
|
|
1423
|
-
if (stateKey(
|
|
1525
|
+
const stepResponse = assertFiatStepResponse(json);
|
|
1526
|
+
if (stateKey(stepResponse.state) !== meta.stateKey) {
|
|
1424
1527
|
throw new ContractViolation(
|
|
1425
|
-
`fixture ${id}: manifest says ${meta.stateKey},
|
|
1528
|
+
`fixture ${id}: manifest says ${meta.stateKey}, step response carries ${stateKey(stepResponse.state)}`
|
|
1426
1529
|
);
|
|
1427
1530
|
}
|
|
1428
|
-
assertLegalEmission(
|
|
1429
|
-
return { meta,
|
|
1531
|
+
assertLegalEmission(stepResponse.state, stepResponse.transitions);
|
|
1532
|
+
return { meta, stepResponse };
|
|
1430
1533
|
}
|
|
1431
1534
|
function assertAllFixtures() {
|
|
1432
1535
|
return FIXTURES.map((meta) => assertFixture(meta.id));
|
|
@@ -1463,6 +1566,7 @@ export {
|
|
|
1463
1566
|
InstructionsSchema,
|
|
1464
1567
|
InstrumentSchema,
|
|
1465
1568
|
JsonSchema,
|
|
1569
|
+
KYCProviderSchema,
|
|
1466
1570
|
KycCaptureStateSchema,
|
|
1467
1571
|
KycInputRequiredStateSchema,
|
|
1468
1572
|
KycNoActionRequiredStateSchema,
|
|
@@ -1485,6 +1589,8 @@ export {
|
|
|
1485
1589
|
SelectOptionSchema,
|
|
1486
1590
|
SessionAuthStateSchema,
|
|
1487
1591
|
StatusHistoryEntrySchema,
|
|
1592
|
+
SurfaceKindSchema,
|
|
1593
|
+
SurfaceProviderSchema,
|
|
1488
1594
|
SurfaceSchema,
|
|
1489
1595
|
TABLE_VERSION,
|
|
1490
1596
|
TERMINAL_ORDER_STATUSES,
|
|
@@ -1493,6 +1599,8 @@ export {
|
|
|
1493
1599
|
TransitionParamsSchema,
|
|
1494
1600
|
TransitionSchema,
|
|
1495
1601
|
TxSchema,
|
|
1602
|
+
UNOFFERED_ENDPOINTS,
|
|
1603
|
+
UNREACHABLE_STATES,
|
|
1496
1604
|
assertAllFixtures,
|
|
1497
1605
|
assertFiatStepResponse,
|
|
1498
1606
|
assertFixture,
|
|
@@ -1507,6 +1615,8 @@ export {
|
|
|
1507
1615
|
looksLikeFieldSpec,
|
|
1508
1616
|
stateKey,
|
|
1509
1617
|
tableEntry,
|
|
1618
|
+
unofferedEndpoints,
|
|
1619
|
+
unreachableStates,
|
|
1510
1620
|
walkTable
|
|
1511
1621
|
};
|
|
1512
1622
|
//# sourceMappingURL=index.mjs.map
|
package/dist/schemas.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* fiat-contract — zod schemas
|
|
3
3
|
*
|
|
4
4
|
* zod is the single runtime validator: TypeScript cannot validate at the boundary, and the
|
|
5
|
-
* table (`src/table.ts`) cannot reject a *malformed*
|
|
5
|
+
* table (`src/table.ts`) cannot reject a *malformed* step response — only an illegal *sequence*.
|
|
6
6
|
*
|
|
7
7
|
* Every object schema is `.strict()`: an undeclared key is a contract violation, not extra data.
|
|
8
8
|
*
|
|
@@ -27,13 +27,15 @@
|
|
|
27
27
|
* They are module-private, so none of it reaches the published declarations.
|
|
28
28
|
*/
|
|
29
29
|
import { z } from 'zod';
|
|
30
|
-
import type { CryptoAmount, FailureReason, FeeLine, FiatEndpoint, FieldSpec, FlowState, FormDescriptor, FormField, FormFieldType, Instructions, InstructionField, Instrument, JsonValue, FiatAmount, OrderRef, OrderStatus, OrderSummary, PollSpec, FiatProvider, Quote, Recovery, Refund, ReportSpec, SelectOption, StatusHistoryEntry, FiatStepResponse, Surface, Transition, TransitionInputs, TransitionParams, Tx } from './types';
|
|
30
|
+
import type { CryptoAmount, FailureReason, FeeLine, FiatEndpoint, FieldSpec, FlowState, FormDescriptor, FormField, FormFieldType, Instructions, InstructionField, Instrument, JsonValue, FiatAmount, OrderRef, OrderStatus, OrderSummary, PollSpec, FiatProvider, Quote, Recovery, Refund, ReportSpec, SelectOption, StatusHistoryEntry, FiatStepResponse, Surface, SurfaceKind, SurfaceProvider, KYCProvider, Transition, TransitionInputs, TransitionParams, Tx } from './types';
|
|
31
31
|
export declare const JsonSchema: z.ZodType<JsonValue>;
|
|
32
32
|
export declare const HTTP_VERBS: readonly ["GET", "POST"];
|
|
33
33
|
/** `"POST /fiat/session/verify"`, `"GET /fiat/orders/o_31c"` — the verb rides the string. */
|
|
34
34
|
export declare const ENDPOINT_PATTERN: RegExp;
|
|
35
35
|
export declare const FiatEndpointSchema: z.ZodType<FiatEndpoint>;
|
|
36
36
|
export declare const FiatProviderSchema: z.ZodType<FiatProvider>;
|
|
37
|
+
export declare const KYCProviderSchema: z.ZodType<KYCProvider>;
|
|
38
|
+
export declare const SurfaceProviderSchema: z.ZodType<SurfaceProvider>;
|
|
37
39
|
export declare const OrderStatusSchema: z.ZodType<OrderStatus>;
|
|
38
40
|
export declare const RecoverySchema: z.ZodType<Recovery>;
|
|
39
41
|
export declare const FailureReasonSchema: z.ZodType<FailureReason>;
|
|
@@ -47,6 +49,7 @@ export declare const OrderSummarySchema: z.ZodType<OrderSummary>;
|
|
|
47
49
|
export declare const StatusHistoryEntrySchema: z.ZodType<StatusHistoryEntry>;
|
|
48
50
|
export declare const TxSchema: z.ZodType<Tx>;
|
|
49
51
|
export declare const RefundSchema: z.ZodType<Refund>;
|
|
52
|
+
export declare const SurfaceKindSchema: z.ZodType<SurfaceKind>;
|
|
50
53
|
export declare const SurfaceSchema: z.ZodType<Surface>;
|
|
51
54
|
export declare const InstructionFieldSchema: z.ZodType<InstructionField>;
|
|
52
55
|
export declare const InstructionsSchema: z.ZodType<Instructions>;
|