@fun-xyz/fiat-contract 0.2.1
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 +454 -0
- package/dist/assert.d.ts +66 -0
- package/dist/chunk-IUYT5BR2.mjs +412 -0
- package/dist/chunk-KIQOUUEZ.mjs +1 -0
- package/dist/fixtures/data.d.ts +8 -0
- package/dist/fixtures/index.d.ts +55 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +1665 -0
- package/dist/index.mjs +1253 -0
- package/dist/schemas.d.ts +98 -0
- package/dist/table.d.ts +97 -0
- package/dist/table.js +432 -0
- package/dist/table.mjs +21 -0
- package/dist/types.d.ts +486 -0
- package/dist/types.js +19 -0
- package/dist/types.mjs +2 -0
- package/package.json +81 -0
- package/table.d.ts +1 -0
- package/table.js +5 -0
- package/types.d.ts +1 -0
- package/types.js +3 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1253 @@
|
|
|
1
|
+
import "./chunk-KIQOUUEZ.mjs";
|
|
2
|
+
import {
|
|
3
|
+
DOCUMENTED_ENDPOINTS,
|
|
4
|
+
STATE_KEYS,
|
|
5
|
+
TABLE_VERSION,
|
|
6
|
+
TERMINAL_ORDER_STATUSES,
|
|
7
|
+
TRANSITION_TABLE,
|
|
8
|
+
isTerminal,
|
|
9
|
+
stateKey,
|
|
10
|
+
tableEntry
|
|
11
|
+
} from "./chunk-IUYT5BR2.mjs";
|
|
12
|
+
|
|
13
|
+
// src/schemas.ts
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
var obj = (shape) => z.object(shape).strict();
|
|
16
|
+
var JsonSchema = z.lazy(
|
|
17
|
+
() => z.union([
|
|
18
|
+
z.string(),
|
|
19
|
+
z.number(),
|
|
20
|
+
z.boolean(),
|
|
21
|
+
z.null(),
|
|
22
|
+
z.array(JsonSchema),
|
|
23
|
+
z.record(z.string(), JsonSchema)
|
|
24
|
+
])
|
|
25
|
+
);
|
|
26
|
+
var HTTP_VERBS = ["GET", "POST"];
|
|
27
|
+
var ENDPOINT_PATTERN = /^(GET|POST) \/fiat\/[^\s]*$/;
|
|
28
|
+
var $endpoint = z.string().regex(ENDPOINT_PATTERN, 'endpoint must read "<GET|POST> /fiat/\u2026"');
|
|
29
|
+
var FiatEndpointSchema = $endpoint;
|
|
30
|
+
var $provider = z.enum(["TRANSAK", "SWAPPED"]);
|
|
31
|
+
var FiatProviderSchema = $provider;
|
|
32
|
+
var $orderStatus = z.enum([
|
|
33
|
+
"CREATING",
|
|
34
|
+
"AWAITING_PAYMENT",
|
|
35
|
+
"PAYMENT_VERIFYING",
|
|
36
|
+
"PROCESSING",
|
|
37
|
+
"PENDING_DELIVERY",
|
|
38
|
+
"ON_HOLD",
|
|
39
|
+
"CRYPTO_SENT",
|
|
40
|
+
"SETTLED",
|
|
41
|
+
"EXPIRED",
|
|
42
|
+
"CANCELLED",
|
|
43
|
+
"FAILED",
|
|
44
|
+
"REFUNDED",
|
|
45
|
+
"CAPTURE_ORPHANED"
|
|
46
|
+
]);
|
|
47
|
+
var OrderStatusSchema = $orderStatus;
|
|
48
|
+
var $recovery = z.enum(["REQUOTE", "RETRY_PAYMENT", "CONTACT_SUPPORT", "NONE"]);
|
|
49
|
+
var RecoverySchema = $recovery;
|
|
50
|
+
var $failureReason = z.union([
|
|
51
|
+
obj({
|
|
52
|
+
category: z.enum([
|
|
53
|
+
"PAYMENT_DECLINED",
|
|
54
|
+
"KYC_REJECTED",
|
|
55
|
+
"PROVIDER_OUTAGE",
|
|
56
|
+
"USER_ERROR",
|
|
57
|
+
"CANCELLED"
|
|
58
|
+
]),
|
|
59
|
+
retryable: z.boolean(),
|
|
60
|
+
recovery: $recovery,
|
|
61
|
+
code: z.string().optional()
|
|
62
|
+
}),
|
|
63
|
+
obj({
|
|
64
|
+
category: z.literal("EXPIRED"),
|
|
65
|
+
which: z.enum(["QUOTE", "PAYMENT_WINDOW", "INSTRUCTIONS"]),
|
|
66
|
+
retryable: z.boolean(),
|
|
67
|
+
recovery: $recovery,
|
|
68
|
+
code: z.string().optional()
|
|
69
|
+
})
|
|
70
|
+
]);
|
|
71
|
+
var FailureReasonSchema = $failureReason;
|
|
72
|
+
var $money = obj({ currency: z.string(), amount: z.string() });
|
|
73
|
+
var FiatAmountSchema = $money;
|
|
74
|
+
var $cryptoAmount = obj({
|
|
75
|
+
currency: z.string(),
|
|
76
|
+
network: z.string(),
|
|
77
|
+
amount: z.string()
|
|
78
|
+
});
|
|
79
|
+
var CryptoAmountSchema = $cryptoAmount;
|
|
80
|
+
var $feeLine = obj({ id: z.string(), labelFallback: z.string(), amount: z.string() });
|
|
81
|
+
var FeeLineSchema = $feeLine;
|
|
82
|
+
var $quote = obj({
|
|
83
|
+
quoteRef: z.string(),
|
|
84
|
+
fiat: $money,
|
|
85
|
+
crypto: $cryptoAmount,
|
|
86
|
+
rate: z.string(),
|
|
87
|
+
totalFee: z.string(),
|
|
88
|
+
feeBreakdown: z.array($feeLine),
|
|
89
|
+
paymentMethod: z.string(),
|
|
90
|
+
expiresAt: z.string()
|
|
91
|
+
});
|
|
92
|
+
var QuoteSchema = $quote;
|
|
93
|
+
var $instrument = obj({ brandLabel: z.string(), last4: z.string() });
|
|
94
|
+
var InstrumentSchema = $instrument;
|
|
95
|
+
var $orderRef = obj({
|
|
96
|
+
orderId: z.string(),
|
|
97
|
+
fiat: $money,
|
|
98
|
+
crypto: $cryptoAmount,
|
|
99
|
+
method: z.string(),
|
|
100
|
+
createdAt: z.string(),
|
|
101
|
+
status: $orderStatus
|
|
102
|
+
});
|
|
103
|
+
var OrderRefSchema = $orderRef;
|
|
104
|
+
var $orderSummary = obj({
|
|
105
|
+
orderId: z.string(),
|
|
106
|
+
fiat: $money,
|
|
107
|
+
crypto: $cryptoAmount.optional(),
|
|
108
|
+
method: z.string(),
|
|
109
|
+
createdAt: z.string(),
|
|
110
|
+
instrument: $instrument.optional()
|
|
111
|
+
});
|
|
112
|
+
var OrderSummarySchema = $orderSummary;
|
|
113
|
+
var $statusHistoryEntry = obj({ status: $orderStatus });
|
|
114
|
+
var StatusHistoryEntrySchema = $statusHistoryEntry;
|
|
115
|
+
var $tx = obj({ chainId: z.number(), hash: z.string() });
|
|
116
|
+
var TxSchema = $tx;
|
|
117
|
+
var $refund = obj({
|
|
118
|
+
currency: z.string(),
|
|
119
|
+
amount: z.string(),
|
|
120
|
+
expectedBy: z.string()
|
|
121
|
+
});
|
|
122
|
+
var RefundSchema = $refund;
|
|
123
|
+
var $surface = z.union([
|
|
124
|
+
obj({ kind: z.literal("URL_REDIRECT"), url: z.string() }),
|
|
125
|
+
obj({ kind: z.literal("URL_EMBED"), url: z.string() }),
|
|
126
|
+
obj({ kind: z.literal("PCI_COMPONENT"), session: z.string() }),
|
|
127
|
+
obj({ kind: z.literal("PAY_SHEET"), session: z.string() }),
|
|
128
|
+
obj({ kind: z.literal("ACH_COMPONENT"), session: z.string() })
|
|
129
|
+
]);
|
|
130
|
+
var SurfaceSchema = $surface;
|
|
131
|
+
var $instructionField = obj({
|
|
132
|
+
id: z.string(),
|
|
133
|
+
labelFallback: z.string(),
|
|
134
|
+
value: z.string()
|
|
135
|
+
});
|
|
136
|
+
var InstructionFieldSchema = $instructionField;
|
|
137
|
+
var $instructions = z.union([
|
|
138
|
+
obj({ kind: z.literal("DEEPLINK"), url: z.string(), appVariants: z.array(z.string()) }),
|
|
139
|
+
obj({ kind: z.literal("QR_IMAGE"), url: z.string() }),
|
|
140
|
+
obj({
|
|
141
|
+
kind: z.literal("BANK_FIELDS"),
|
|
142
|
+
fields: z.array($instructionField),
|
|
143
|
+
expiresAt: z.string().optional()
|
|
144
|
+
}),
|
|
145
|
+
obj({ kind: z.literal("REFERENCE"), code: z.string(), expiresAt: z.string() })
|
|
146
|
+
]);
|
|
147
|
+
var InstructionsSchema = $instructions;
|
|
148
|
+
var $selectOption = obj({ id: z.string(), labelFallback: z.string() });
|
|
149
|
+
var SelectOptionSchema = $selectOption;
|
|
150
|
+
var $formFieldType = z.enum(["TEXT", "DATE", "SELECT", "COUNTRY", "FILE", "HOSTED_LINK"]);
|
|
151
|
+
var FormFieldTypeSchema = $formFieldType;
|
|
152
|
+
var $formField = obj({
|
|
153
|
+
id: z.string(),
|
|
154
|
+
type: $formFieldType,
|
|
155
|
+
labelFallback: z.string(),
|
|
156
|
+
required: z.boolean(),
|
|
157
|
+
regex: z.string().optional(),
|
|
158
|
+
options: z.array($selectOption).optional(),
|
|
159
|
+
url: z.string().optional()
|
|
160
|
+
});
|
|
161
|
+
var FormFieldSchema = $formField;
|
|
162
|
+
var $formDescriptor = obj({ formId: z.string(), fields: z.array($formField) });
|
|
163
|
+
var FormDescriptorSchema = $formDescriptor;
|
|
164
|
+
var FIELD_SPEC_TYPES = [
|
|
165
|
+
"TEXT",
|
|
166
|
+
"DATE",
|
|
167
|
+
"COUNTRY",
|
|
168
|
+
"FILE",
|
|
169
|
+
"SELECT",
|
|
170
|
+
"HOSTED_LINK",
|
|
171
|
+
"FORM_VALUES"
|
|
172
|
+
];
|
|
173
|
+
var $fieldSpec = z.union([
|
|
174
|
+
obj({
|
|
175
|
+
type: z.literal("TEXT"),
|
|
176
|
+
length: z.number().optional(),
|
|
177
|
+
regex: z.string().optional(),
|
|
178
|
+
labelFallback: z.string().optional()
|
|
179
|
+
}),
|
|
180
|
+
obj({ type: z.literal("DATE"), labelFallback: z.string().optional() }),
|
|
181
|
+
obj({ type: z.literal("COUNTRY"), labelFallback: z.string().optional() }),
|
|
182
|
+
obj({ type: z.literal("FILE"), labelFallback: z.string().optional() }),
|
|
183
|
+
obj({
|
|
184
|
+
type: z.literal("SELECT"),
|
|
185
|
+
options: z.array($selectOption).optional(),
|
|
186
|
+
labelFallback: z.string().optional()
|
|
187
|
+
}),
|
|
188
|
+
obj({ type: z.literal("HOSTED_LINK"), url: z.string(), labelFallback: z.string().optional() }),
|
|
189
|
+
obj({ type: z.literal("FORM_VALUES") })
|
|
190
|
+
]);
|
|
191
|
+
var FieldSpecSchema = $fieldSpec;
|
|
192
|
+
var looksLikeFieldSpec = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.type === "string" && FIELD_SPEC_TYPES.includes(value.type);
|
|
193
|
+
var $params = z.record(z.string(), JsonSchema).superRefine((params, ctx) => {
|
|
194
|
+
for (const [key, value] of Object.entries(params)) {
|
|
195
|
+
if (looksLikeFieldSpec(value)) {
|
|
196
|
+
ctx.addIssue({
|
|
197
|
+
code: "custom",
|
|
198
|
+
path: [key],
|
|
199
|
+
message: `params.${key} is FieldSpec-shaped ({type: "\u2026"}). params carry server literals the client spreads verbatim; a spec the client must collect belongs in inputs.`
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
var TransitionParamsSchema = $params;
|
|
205
|
+
var $inputs = z.record(z.string(), $fieldSpec);
|
|
206
|
+
var TransitionInputsSchema = $inputs;
|
|
207
|
+
var $pollSpec = obj({
|
|
208
|
+
endpoint: $endpoint,
|
|
209
|
+
params: $params.optional(),
|
|
210
|
+
intervalMs: z.number(),
|
|
211
|
+
backoffFactor: z.number().optional(),
|
|
212
|
+
maxIntervalMs: z.number().optional(),
|
|
213
|
+
giveUpAfterMs: z.number().optional()
|
|
214
|
+
});
|
|
215
|
+
var PollSpecSchema = $pollSpec;
|
|
216
|
+
var $reportSpec = obj({
|
|
217
|
+
endpoint: $endpoint,
|
|
218
|
+
params: $params.optional(),
|
|
219
|
+
expects: z.array(z.string()).optional()
|
|
220
|
+
});
|
|
221
|
+
var ReportSpecSchema = $reportSpec;
|
|
222
|
+
var $transition = z.union([
|
|
223
|
+
obj({
|
|
224
|
+
id: z.string(),
|
|
225
|
+
mode: z.literal("SUBMIT"),
|
|
226
|
+
endpoint: $endpoint,
|
|
227
|
+
params: $params.optional(),
|
|
228
|
+
inputs: $inputs.optional(),
|
|
229
|
+
labelFallback: z.string().optional()
|
|
230
|
+
}),
|
|
231
|
+
obj({ id: z.string(), mode: z.literal("AWAIT"), poll: $pollSpec }),
|
|
232
|
+
obj({ id: z.string(), mode: z.literal("CLIENT_SURFACE"), report: $reportSpec })
|
|
233
|
+
]);
|
|
234
|
+
var TransitionSchema = $transition;
|
|
235
|
+
var $authChallenge = z.record(z.string(), JsonSchema);
|
|
236
|
+
var AuthChallengeSchema = $authChallenge;
|
|
237
|
+
var $quoteState = obj({
|
|
238
|
+
kind: z.literal("QUOTE"),
|
|
239
|
+
quote: $quote,
|
|
240
|
+
error: $failureReason.optional()
|
|
241
|
+
});
|
|
242
|
+
var QuoteStateSchema = $quoteState;
|
|
243
|
+
var $funAuthState = obj({
|
|
244
|
+
kind: z.literal("FUN_AUTH"),
|
|
245
|
+
challenge: $authChallenge,
|
|
246
|
+
error: $failureReason.optional()
|
|
247
|
+
});
|
|
248
|
+
var FunAuthStateSchema = $funAuthState;
|
|
249
|
+
var $sessionAuthState = obj({
|
|
250
|
+
kind: z.literal("SESSION_AUTH"),
|
|
251
|
+
channel: z.literal("EMAIL_OTP"),
|
|
252
|
+
error: $failureReason.optional()
|
|
253
|
+
});
|
|
254
|
+
var SessionAuthStateSchema = $sessionAuthState;
|
|
255
|
+
var $kycCaptureState = obj({
|
|
256
|
+
kind: z.literal("KYC"),
|
|
257
|
+
phase: z.literal("CAPTURE"),
|
|
258
|
+
surface: $surface,
|
|
259
|
+
error: $failureReason.optional()
|
|
260
|
+
});
|
|
261
|
+
var KycCaptureStateSchema = $kycCaptureState;
|
|
262
|
+
var $kycInputRequiredState = obj({
|
|
263
|
+
kind: z.literal("KYC"),
|
|
264
|
+
phase: z.literal("INPUT_REQUIRED"),
|
|
265
|
+
form: $formDescriptor,
|
|
266
|
+
error: $failureReason.optional()
|
|
267
|
+
});
|
|
268
|
+
var KycInputRequiredStateSchema = $kycInputRequiredState;
|
|
269
|
+
var $kycNoActionRequiredState = obj({
|
|
270
|
+
kind: z.literal("KYC"),
|
|
271
|
+
phase: z.literal("NO_ACTION_REQUIRED"),
|
|
272
|
+
reason: z.enum(["IN_REVIEW", "ON_HOLD", "REJECTED"]),
|
|
273
|
+
retryAfter: z.string().optional(),
|
|
274
|
+
failureReason: $failureReason.optional(),
|
|
275
|
+
error: $failureReason.optional()
|
|
276
|
+
});
|
|
277
|
+
var KycNoActionRequiredStateSchema = $kycNoActionRequiredState;
|
|
278
|
+
var $pendingOrderState = obj({
|
|
279
|
+
kind: z.literal("PENDING_ORDER"),
|
|
280
|
+
order: $orderRef,
|
|
281
|
+
error: $failureReason.optional()
|
|
282
|
+
});
|
|
283
|
+
var PendingOrderStateSchema = $pendingOrderState;
|
|
284
|
+
var $blockedState = obj({
|
|
285
|
+
kind: z.literal("BLOCKED"),
|
|
286
|
+
reason: z.string(),
|
|
287
|
+
retryAfter: z.string().nullable().optional(),
|
|
288
|
+
error: $failureReason.optional()
|
|
289
|
+
});
|
|
290
|
+
var BlockedStateSchema = $blockedState;
|
|
291
|
+
var $paymentCaptureState = obj({
|
|
292
|
+
kind: z.literal("PAYMENT"),
|
|
293
|
+
phase: z.literal("CAPTURE"),
|
|
294
|
+
surface: $surface,
|
|
295
|
+
error: $failureReason.optional()
|
|
296
|
+
});
|
|
297
|
+
var PaymentCaptureStateSchema = $paymentCaptureState;
|
|
298
|
+
var $paymentInstructState = obj({
|
|
299
|
+
kind: z.literal("PAYMENT"),
|
|
300
|
+
phase: z.literal("INSTRUCT"),
|
|
301
|
+
orderId: z.string().optional(),
|
|
302
|
+
instructions: $instructions,
|
|
303
|
+
error: $failureReason.optional()
|
|
304
|
+
});
|
|
305
|
+
var PaymentInstructStateSchema = $paymentInstructState;
|
|
306
|
+
var $orderAwaitingConfirmationState = obj({
|
|
307
|
+
kind: z.literal("ORDER"),
|
|
308
|
+
phase: z.literal("AWAITING_CONFIRMATION"),
|
|
309
|
+
quote: $quote,
|
|
310
|
+
error: $failureReason.optional()
|
|
311
|
+
});
|
|
312
|
+
var OrderAwaitingConfirmationStateSchema = $orderAwaitingConfirmationState;
|
|
313
|
+
var $orderCreatedState = obj({
|
|
314
|
+
kind: z.literal("ORDER"),
|
|
315
|
+
phase: z.literal("CREATED"),
|
|
316
|
+
order: $orderSummary.optional(),
|
|
317
|
+
status: $orderStatus,
|
|
318
|
+
statusHistory: z.array($statusHistoryEntry).optional(),
|
|
319
|
+
tx: $tx.optional(),
|
|
320
|
+
refund: $refund.optional(),
|
|
321
|
+
failureReason: $failureReason.optional(),
|
|
322
|
+
error: $failureReason.optional()
|
|
323
|
+
});
|
|
324
|
+
var OrderCreatedStateSchema = $orderCreatedState;
|
|
325
|
+
var $flowState = z.union([
|
|
326
|
+
$quoteState,
|
|
327
|
+
$funAuthState,
|
|
328
|
+
$sessionAuthState,
|
|
329
|
+
$kycCaptureState,
|
|
330
|
+
$kycInputRequiredState,
|
|
331
|
+
$kycNoActionRequiredState,
|
|
332
|
+
$pendingOrderState,
|
|
333
|
+
$blockedState,
|
|
334
|
+
$paymentCaptureState,
|
|
335
|
+
$paymentInstructState,
|
|
336
|
+
$orderAwaitingConfirmationState,
|
|
337
|
+
$orderCreatedState
|
|
338
|
+
]);
|
|
339
|
+
var FlowStateSchema = $flowState;
|
|
340
|
+
var $stepResponse = obj({
|
|
341
|
+
state: $flowState,
|
|
342
|
+
provider: $provider,
|
|
343
|
+
transitions: z.array($transition),
|
|
344
|
+
orderId: z.string().optional()
|
|
345
|
+
});
|
|
346
|
+
var StepResponseSchema = $stepResponse;
|
|
347
|
+
var $stepResponseFragment = obj({
|
|
348
|
+
state: $flowState,
|
|
349
|
+
provider: $provider.optional(),
|
|
350
|
+
transitions: z.array($transition),
|
|
351
|
+
orderId: z.string().optional()
|
|
352
|
+
});
|
|
353
|
+
var StepResponseFragmentSchema = $stepResponseFragment;
|
|
354
|
+
var EnvelopeSchema = StepResponseSchema;
|
|
355
|
+
|
|
356
|
+
// src/fixtures/data.ts
|
|
357
|
+
var FIXTURE_DATA = {
|
|
358
|
+
"screen-01-quote": {
|
|
359
|
+
"state": {
|
|
360
|
+
"kind": "QUOTE",
|
|
361
|
+
"quote": {
|
|
362
|
+
"quoteRef": "q_8f2",
|
|
363
|
+
"fiat": {
|
|
364
|
+
"currency": "GBP",
|
|
365
|
+
"amount": "100.00"
|
|
366
|
+
},
|
|
367
|
+
"crypto": {
|
|
368
|
+
"currency": "USDC",
|
|
369
|
+
"network": "polygon",
|
|
370
|
+
"amount": "98.41"
|
|
371
|
+
},
|
|
372
|
+
"rate": "0.9841",
|
|
373
|
+
"totalFee": "1.59",
|
|
374
|
+
"feeBreakdown": [
|
|
375
|
+
{
|
|
376
|
+
"id": "provider_fee",
|
|
377
|
+
"labelFallback": "Processing fee",
|
|
378
|
+
"amount": "1.09"
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
"id": "network_fee",
|
|
382
|
+
"labelFallback": "Network fee",
|
|
383
|
+
"amount": "0.50"
|
|
384
|
+
}
|
|
385
|
+
],
|
|
386
|
+
"paymentMethod": "bank_transfer",
|
|
387
|
+
"expiresAt": "\u2026"
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
"provider": "TRANSAK",
|
|
391
|
+
"transitions": [
|
|
392
|
+
{
|
|
393
|
+
"id": "continue",
|
|
394
|
+
"mode": "SUBMIT",
|
|
395
|
+
"endpoint": "POST /fiat/session",
|
|
396
|
+
"params": {
|
|
397
|
+
"quoteRef": "q_8f2"
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
]
|
|
401
|
+
},
|
|
402
|
+
"screen-02-session-auth": {
|
|
403
|
+
"state": {
|
|
404
|
+
"kind": "SESSION_AUTH",
|
|
405
|
+
"channel": "EMAIL_OTP"
|
|
406
|
+
},
|
|
407
|
+
"provider": "TRANSAK",
|
|
408
|
+
"transitions": [
|
|
409
|
+
{
|
|
410
|
+
"id": "verify",
|
|
411
|
+
"mode": "SUBMIT",
|
|
412
|
+
"endpoint": "POST /fiat/session/verify",
|
|
413
|
+
"params": {
|
|
414
|
+
"quoteRef": "q_8f2"
|
|
415
|
+
},
|
|
416
|
+
"inputs": {
|
|
417
|
+
"code": {
|
|
418
|
+
"type": "TEXT",
|
|
419
|
+
"length": 6
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
]
|
|
424
|
+
},
|
|
425
|
+
"screen-03-kyc-input-required": {
|
|
426
|
+
"state": {
|
|
427
|
+
"kind": "KYC",
|
|
428
|
+
"phase": "INPUT_REQUIRED",
|
|
429
|
+
"form": {
|
|
430
|
+
"formId": "personal_details",
|
|
431
|
+
"fields": [
|
|
432
|
+
{
|
|
433
|
+
"id": "firstName",
|
|
434
|
+
"type": "TEXT",
|
|
435
|
+
"labelFallback": "First name",
|
|
436
|
+
"required": true
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
"id": "dob",
|
|
440
|
+
"type": "DATE",
|
|
441
|
+
"labelFallback": "Date of birth",
|
|
442
|
+
"required": true
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
"id": "country",
|
|
446
|
+
"type": "COUNTRY",
|
|
447
|
+
"labelFallback": "Country",
|
|
448
|
+
"required": true
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
"id": "sourceOfFunds",
|
|
452
|
+
"type": "SELECT",
|
|
453
|
+
"labelFallback": "Source of funds",
|
|
454
|
+
"required": true,
|
|
455
|
+
"options": [
|
|
456
|
+
{
|
|
457
|
+
"id": "salary",
|
|
458
|
+
"labelFallback": "Salary"
|
|
459
|
+
}
|
|
460
|
+
]
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
"id": "idProof",
|
|
464
|
+
"type": "HOSTED_LINK",
|
|
465
|
+
"labelFallback": "Verify your ID",
|
|
466
|
+
"required": true,
|
|
467
|
+
"url": "\u2026"
|
|
468
|
+
}
|
|
469
|
+
]
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
"provider": "TRANSAK",
|
|
473
|
+
"transitions": [
|
|
474
|
+
{
|
|
475
|
+
"id": "submit_round",
|
|
476
|
+
"mode": "SUBMIT",
|
|
477
|
+
"endpoint": "POST /fiat/kyc/form",
|
|
478
|
+
"params": {
|
|
479
|
+
"formId": "personal_details"
|
|
480
|
+
},
|
|
481
|
+
"inputs": {
|
|
482
|
+
"values": {
|
|
483
|
+
"type": "FORM_VALUES"
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
]
|
|
488
|
+
},
|
|
489
|
+
"screen-04-order-review": {
|
|
490
|
+
"state": {
|
|
491
|
+
"kind": "ORDER",
|
|
492
|
+
"phase": "AWAITING_CONFIRMATION",
|
|
493
|
+
"quote": {
|
|
494
|
+
"quoteRef": "q_8f2",
|
|
495
|
+
"fiat": {
|
|
496
|
+
"currency": "GBP",
|
|
497
|
+
"amount": "100.00"
|
|
498
|
+
},
|
|
499
|
+
"crypto": {
|
|
500
|
+
"currency": "USDC",
|
|
501
|
+
"network": "polygon",
|
|
502
|
+
"amount": "98.41"
|
|
503
|
+
},
|
|
504
|
+
"rate": "0.9841",
|
|
505
|
+
"totalFee": "1.59",
|
|
506
|
+
"feeBreakdown": [
|
|
507
|
+
{
|
|
508
|
+
"id": "provider_fee",
|
|
509
|
+
"labelFallback": "Processing fee",
|
|
510
|
+
"amount": "1.09"
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
"id": "network_fee",
|
|
514
|
+
"labelFallback": "Network fee",
|
|
515
|
+
"amount": "0.50"
|
|
516
|
+
}
|
|
517
|
+
],
|
|
518
|
+
"paymentMethod": "bank_transfer",
|
|
519
|
+
"expiresAt": "\u2026"
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
"provider": "TRANSAK",
|
|
523
|
+
"transitions": [
|
|
524
|
+
{
|
|
525
|
+
"id": "confirm",
|
|
526
|
+
"mode": "SUBMIT",
|
|
527
|
+
"endpoint": "POST /fiat/orders",
|
|
528
|
+
"params": {
|
|
529
|
+
"quoteRef": "q_8f2"
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
]
|
|
533
|
+
},
|
|
534
|
+
"screen-05-payment-capture": {
|
|
535
|
+
"state": {
|
|
536
|
+
"kind": "PAYMENT",
|
|
537
|
+
"phase": "CAPTURE",
|
|
538
|
+
"surface": {
|
|
539
|
+
"kind": "PCI_COMPONENT",
|
|
540
|
+
"session": "eyJ\u2026"
|
|
541
|
+
}
|
|
542
|
+
},
|
|
543
|
+
"provider": "TRANSAK",
|
|
544
|
+
"transitions": [
|
|
545
|
+
{
|
|
546
|
+
"id": "capture",
|
|
547
|
+
"mode": "CLIENT_SURFACE",
|
|
548
|
+
"report": {
|
|
549
|
+
"endpoint": "POST /fiat/orders",
|
|
550
|
+
"params": {
|
|
551
|
+
"quoteRef": "q_8f2"
|
|
552
|
+
},
|
|
553
|
+
"expects": [
|
|
554
|
+
"requestId"
|
|
555
|
+
]
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
]
|
|
559
|
+
},
|
|
560
|
+
"screen-09-kyc-in-review": {
|
|
561
|
+
"state": {
|
|
562
|
+
"kind": "KYC",
|
|
563
|
+
"phase": "NO_ACTION_REQUIRED",
|
|
564
|
+
"reason": "IN_REVIEW"
|
|
565
|
+
},
|
|
566
|
+
"provider": "TRANSAK",
|
|
567
|
+
"transitions": [
|
|
568
|
+
{
|
|
569
|
+
"id": "poll",
|
|
570
|
+
"mode": "AWAIT",
|
|
571
|
+
"poll": {
|
|
572
|
+
"endpoint": "GET /fiat/kyc",
|
|
573
|
+
"params": {
|
|
574
|
+
"quoteRef": "q_8f2"
|
|
575
|
+
},
|
|
576
|
+
"intervalMs": 3e3,
|
|
577
|
+
"backoffFactor": 1.5,
|
|
578
|
+
"maxIntervalMs": 1e4,
|
|
579
|
+
"giveUpAfterMs": 9e4
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
]
|
|
583
|
+
},
|
|
584
|
+
"screen-10-kyc-on-hold": {
|
|
585
|
+
"state": {
|
|
586
|
+
"kind": "KYC",
|
|
587
|
+
"phase": "NO_ACTION_REQUIRED",
|
|
588
|
+
"reason": "ON_HOLD",
|
|
589
|
+
"retryAfter": "2026-08-20T09:00:00Z"
|
|
590
|
+
},
|
|
591
|
+
"provider": "TRANSAK",
|
|
592
|
+
"transitions": []
|
|
593
|
+
},
|
|
594
|
+
"screen-11-kyc-rejected-escalation": {
|
|
595
|
+
"state": {
|
|
596
|
+
"kind": "KYC",
|
|
597
|
+
"phase": "NO_ACTION_REQUIRED",
|
|
598
|
+
"reason": "REJECTED"
|
|
599
|
+
},
|
|
600
|
+
"provider": "TRANSAK",
|
|
601
|
+
"transitions": [
|
|
602
|
+
{
|
|
603
|
+
"id": "escalate",
|
|
604
|
+
"mode": "SUBMIT",
|
|
605
|
+
"endpoint": "GET /fiat/kyc",
|
|
606
|
+
"params": {
|
|
607
|
+
"tier": "escalated"
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
]
|
|
611
|
+
},
|
|
612
|
+
"screen-12-pending-order": {
|
|
613
|
+
"state": {
|
|
614
|
+
"kind": "PENDING_ORDER",
|
|
615
|
+
"order": {
|
|
616
|
+
"orderId": "o_31c",
|
|
617
|
+
"fiat": {
|
|
618
|
+
"currency": "GBP",
|
|
619
|
+
"amount": "100.00"
|
|
620
|
+
},
|
|
621
|
+
"crypto": {
|
|
622
|
+
"currency": "USDC",
|
|
623
|
+
"network": "polygon",
|
|
624
|
+
"amount": "98.41"
|
|
625
|
+
},
|
|
626
|
+
"method": "bank_transfer",
|
|
627
|
+
"createdAt": "\u2026",
|
|
628
|
+
"status": "AWAITING_PAYMENT"
|
|
629
|
+
}
|
|
630
|
+
},
|
|
631
|
+
"provider": "TRANSAK",
|
|
632
|
+
"transitions": [
|
|
633
|
+
{
|
|
634
|
+
"id": "resume",
|
|
635
|
+
"mode": "SUBMIT",
|
|
636
|
+
"endpoint": "GET /fiat/orders/o_31c"
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
"id": "cancel",
|
|
640
|
+
"mode": "SUBMIT",
|
|
641
|
+
"endpoint": "POST /fiat/orders/o_31c/cancel"
|
|
642
|
+
}
|
|
643
|
+
]
|
|
644
|
+
},
|
|
645
|
+
"screen-13-order-settled": {
|
|
646
|
+
"state": {
|
|
647
|
+
"kind": "ORDER",
|
|
648
|
+
"phase": "CREATED",
|
|
649
|
+
"order": {
|
|
650
|
+
"orderId": "o_31c",
|
|
651
|
+
"fiat": {
|
|
652
|
+
"currency": "GBP",
|
|
653
|
+
"amount": "100.00"
|
|
654
|
+
},
|
|
655
|
+
"crypto": {
|
|
656
|
+
"currency": "USDC",
|
|
657
|
+
"network": "polygon",
|
|
658
|
+
"amount": "98.41"
|
|
659
|
+
},
|
|
660
|
+
"method": "card",
|
|
661
|
+
"instrument": {
|
|
662
|
+
"brandLabel": "Chase Debit",
|
|
663
|
+
"last4": "4021"
|
|
664
|
+
},
|
|
665
|
+
"createdAt": "\u2026"
|
|
666
|
+
},
|
|
667
|
+
"status": "SETTLED",
|
|
668
|
+
"tx": {
|
|
669
|
+
"chainId": 137,
|
|
670
|
+
"hash": "0x\u2026"
|
|
671
|
+
}
|
|
672
|
+
},
|
|
673
|
+
"provider": "TRANSAK",
|
|
674
|
+
"transitions": []
|
|
675
|
+
},
|
|
676
|
+
"screen-14-order-failed": {
|
|
677
|
+
"state": {
|
|
678
|
+
"kind": "ORDER",
|
|
679
|
+
"phase": "CREATED",
|
|
680
|
+
"order": {
|
|
681
|
+
"orderId": "o_31c",
|
|
682
|
+
"fiat": {
|
|
683
|
+
"currency": "GBP",
|
|
684
|
+
"amount": "100.00"
|
|
685
|
+
},
|
|
686
|
+
"method": "card",
|
|
687
|
+
"createdAt": "\u2026"
|
|
688
|
+
},
|
|
689
|
+
"status": "FAILED",
|
|
690
|
+
"failureReason": {
|
|
691
|
+
"category": "PAYMENT_DECLINED",
|
|
692
|
+
"retryable": true,
|
|
693
|
+
"recovery": "RETRY_PAYMENT"
|
|
694
|
+
}
|
|
695
|
+
},
|
|
696
|
+
"provider": "TRANSAK",
|
|
697
|
+
"transitions": [
|
|
698
|
+
{
|
|
699
|
+
"id": "retry_payment",
|
|
700
|
+
"mode": "SUBMIT",
|
|
701
|
+
"endpoint": "POST /fiat/orders",
|
|
702
|
+
"params": {
|
|
703
|
+
"quoteRef": "q_8f2"
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
]
|
|
707
|
+
},
|
|
708
|
+
"screen-15-order-refunded": {
|
|
709
|
+
"state": {
|
|
710
|
+
"kind": "ORDER",
|
|
711
|
+
"phase": "CREATED",
|
|
712
|
+
"order": {
|
|
713
|
+
"orderId": "o_31c",
|
|
714
|
+
"fiat": {
|
|
715
|
+
"currency": "GBP",
|
|
716
|
+
"amount": "100.00"
|
|
717
|
+
},
|
|
718
|
+
"method": "bank_transfer",
|
|
719
|
+
"createdAt": "\u2026"
|
|
720
|
+
},
|
|
721
|
+
"status": "REFUNDED",
|
|
722
|
+
"refund": {
|
|
723
|
+
"currency": "GBP",
|
|
724
|
+
"amount": "100.00",
|
|
725
|
+
"expectedBy": "\u2026"
|
|
726
|
+
}
|
|
727
|
+
},
|
|
728
|
+
"provider": "TRANSAK",
|
|
729
|
+
"transitions": []
|
|
730
|
+
},
|
|
731
|
+
"screen-16-blocked": {
|
|
732
|
+
"state": {
|
|
733
|
+
"kind": "BLOCKED",
|
|
734
|
+
"reason": "REGION_UNSUPPORTED",
|
|
735
|
+
"retryAfter": null
|
|
736
|
+
},
|
|
737
|
+
"provider": "TRANSAK",
|
|
738
|
+
"transitions": []
|
|
739
|
+
},
|
|
740
|
+
"worked-example-01-quote": {
|
|
741
|
+
"state": {
|
|
742
|
+
"kind": "QUOTE",
|
|
743
|
+
"quote": {
|
|
744
|
+
"quoteRef": "q_8f2",
|
|
745
|
+
"fiat": {
|
|
746
|
+
"currency": "GBP",
|
|
747
|
+
"amount": "100.00"
|
|
748
|
+
},
|
|
749
|
+
"crypto": {
|
|
750
|
+
"currency": "USDC",
|
|
751
|
+
"network": "polygon",
|
|
752
|
+
"amount": "98.41"
|
|
753
|
+
},
|
|
754
|
+
"rate": "0.9841",
|
|
755
|
+
"totalFee": "1.59",
|
|
756
|
+
"feeBreakdown": [
|
|
757
|
+
{
|
|
758
|
+
"id": "provider_fee",
|
|
759
|
+
"labelFallback": "Processing fee",
|
|
760
|
+
"amount": "1.09"
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
"id": "network_fee",
|
|
764
|
+
"labelFallback": "Network fee",
|
|
765
|
+
"amount": "0.50"
|
|
766
|
+
}
|
|
767
|
+
],
|
|
768
|
+
"paymentMethod": "bank_transfer",
|
|
769
|
+
"expiresAt": "2026-08-11T21:33:00Z"
|
|
770
|
+
}
|
|
771
|
+
},
|
|
772
|
+
"transitions": [
|
|
773
|
+
{
|
|
774
|
+
"id": "continue",
|
|
775
|
+
"mode": "SUBMIT",
|
|
776
|
+
"endpoint": "POST /fiat/session",
|
|
777
|
+
"params": {
|
|
778
|
+
"quoteRef": "q_8f2"
|
|
779
|
+
},
|
|
780
|
+
"inputs": {
|
|
781
|
+
"email": {
|
|
782
|
+
"type": "TEXT"
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
]
|
|
787
|
+
},
|
|
788
|
+
"worked-example-02-session-auth": {
|
|
789
|
+
"state": {
|
|
790
|
+
"kind": "SESSION_AUTH",
|
|
791
|
+
"channel": "EMAIL_OTP"
|
|
792
|
+
},
|
|
793
|
+
"transitions": [
|
|
794
|
+
{
|
|
795
|
+
"id": "verify",
|
|
796
|
+
"mode": "SUBMIT",
|
|
797
|
+
"endpoint": "POST /fiat/session/verify",
|
|
798
|
+
"params": {
|
|
799
|
+
"quoteRef": "q_8f2"
|
|
800
|
+
},
|
|
801
|
+
"inputs": {
|
|
802
|
+
"code": {
|
|
803
|
+
"type": "TEXT",
|
|
804
|
+
"length": 6
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
]
|
|
809
|
+
},
|
|
810
|
+
"worked-example-04-payment-instruct": {
|
|
811
|
+
"orderId": "o_3d1",
|
|
812
|
+
"state": {
|
|
813
|
+
"kind": "PAYMENT",
|
|
814
|
+
"phase": "INSTRUCT",
|
|
815
|
+
"instructions": {
|
|
816
|
+
"kind": "BANK_FIELDS",
|
|
817
|
+
"fields": [
|
|
818
|
+
{
|
|
819
|
+
"id": "beneficiary",
|
|
820
|
+
"labelFallback": "Beneficiary",
|
|
821
|
+
"value": "Transak"
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
"id": "sort_code",
|
|
825
|
+
"labelFallback": "Sort code",
|
|
826
|
+
"value": "04-00-04"
|
|
827
|
+
},
|
|
828
|
+
{
|
|
829
|
+
"id": "account_number",
|
|
830
|
+
"labelFallback": "Account number",
|
|
831
|
+
"value": "12345678"
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
"id": "reference",
|
|
835
|
+
"labelFallback": "Reference",
|
|
836
|
+
"value": "o_3d1"
|
|
837
|
+
}
|
|
838
|
+
],
|
|
839
|
+
"expiresAt": "\u2026"
|
|
840
|
+
}
|
|
841
|
+
},
|
|
842
|
+
"transitions": [
|
|
843
|
+
{
|
|
844
|
+
"id": "confirm_paid",
|
|
845
|
+
"mode": "SUBMIT",
|
|
846
|
+
"endpoint": "POST /fiat/orders/o_3d1/confirm-payment"
|
|
847
|
+
}
|
|
848
|
+
]
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
// src/fixtures/index.ts
|
|
853
|
+
var FIXTURES = [
|
|
854
|
+
{
|
|
855
|
+
id: "screen-01-quote",
|
|
856
|
+
file: "screen-01-quote.json",
|
|
857
|
+
source: "FE_DOC",
|
|
858
|
+
docRef: "Screen 1 \xB7 Quote / review",
|
|
859
|
+
screen: 1,
|
|
860
|
+
stateKey: "QUOTE",
|
|
861
|
+
complete: true
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
id: "screen-02-session-auth",
|
|
865
|
+
file: "screen-02-session-auth.json",
|
|
866
|
+
source: "FE_DOC",
|
|
867
|
+
docRef: "Screen 2 \xB7 Verification code (OTP)",
|
|
868
|
+
screen: 2,
|
|
869
|
+
stateKey: "SESSION_AUTH",
|
|
870
|
+
complete: true
|
|
871
|
+
},
|
|
872
|
+
{
|
|
873
|
+
id: "screen-03-kyc-input-required",
|
|
874
|
+
file: "screen-03-kyc-input-required.json",
|
|
875
|
+
source: "FE_DOC",
|
|
876
|
+
docRef: "Screen 3 \xB7 Identity form (KYC)",
|
|
877
|
+
screen: 3,
|
|
878
|
+
stateKey: "KYC/INPUT_REQUIRED",
|
|
879
|
+
complete: true,
|
|
880
|
+
notes: ["Carries the FORM_VALUES input spec \u2014 the no-double-carry case of the accepted split."]
|
|
881
|
+
},
|
|
882
|
+
{
|
|
883
|
+
id: "screen-04-order-review",
|
|
884
|
+
file: "screen-04-order-review.json",
|
|
885
|
+
source: "FE_DOC",
|
|
886
|
+
docRef: "Screen 4 \xB7 Order review (bank rail)",
|
|
887
|
+
screen: 4,
|
|
888
|
+
stateKey: "ORDER/AWAITING_CONFIRMATION",
|
|
889
|
+
complete: true,
|
|
890
|
+
notes: [
|
|
891
|
+
`The screen card publishes this payload by reference \u2014 "quote: <same shape as Screen 1, refreshed>" \u2014 so the quote here is Screen 1's payload verbatim. The numbers are not re-published as refreshed values, so they are unchanged; only the shape is contractual.`
|
|
892
|
+
]
|
|
893
|
+
},
|
|
894
|
+
{
|
|
895
|
+
id: "screen-05-payment-capture",
|
|
896
|
+
file: "screen-05-payment-capture.json",
|
|
897
|
+
source: "FE_DOC",
|
|
898
|
+
docRef: "Screen 5 \xB7 Card payment",
|
|
899
|
+
screen: 5,
|
|
900
|
+
stateKey: "PAYMENT/CAPTURE",
|
|
901
|
+
complete: true,
|
|
902
|
+
notes: [
|
|
903
|
+
"Capture-then-order topology (report \u2192 POST /fiat/orders, expects requestId).",
|
|
904
|
+
"TODO(open-decision): report target per card topology is a Transak ask (contract Flow A)."
|
|
905
|
+
]
|
|
906
|
+
},
|
|
907
|
+
{
|
|
908
|
+
id: "screen-09-kyc-in-review",
|
|
909
|
+
file: "screen-09-kyc-in-review.json",
|
|
910
|
+
source: "FE_DOC",
|
|
911
|
+
docRef: "Screen 9 \xB7 Verifying identity",
|
|
912
|
+
screen: 9,
|
|
913
|
+
stateKey: "KYC/NO_ACTION_REQUIRED:IN_REVIEW",
|
|
914
|
+
complete: true
|
|
915
|
+
},
|
|
916
|
+
{
|
|
917
|
+
id: "screen-10-kyc-on-hold",
|
|
918
|
+
file: "screen-10-kyc-on-hold.json",
|
|
919
|
+
source: "FE_DOC",
|
|
920
|
+
docRef: "Screen 10 \xB7 Verification on hold",
|
|
921
|
+
screen: 10,
|
|
922
|
+
stateKey: "KYC/NO_ACTION_REQUIRED:ON_HOLD",
|
|
923
|
+
complete: true,
|
|
924
|
+
notes: ["transitions: [] and NOT terminal \u2014 the fixture that proves terminality is table data."]
|
|
925
|
+
},
|
|
926
|
+
{
|
|
927
|
+
id: "screen-11-kyc-rejected-escalation",
|
|
928
|
+
file: "screen-11-kyc-rejected-escalation.json",
|
|
929
|
+
source: "FE_DOC",
|
|
930
|
+
docRef: "Screen 11 \xB7 Verification unsuccessful",
|
|
931
|
+
screen: 11,
|
|
932
|
+
stateKey: "KYC/NO_ACTION_REQUIRED:REJECTED",
|
|
933
|
+
complete: true,
|
|
934
|
+
notes: [
|
|
935
|
+
"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).",
|
|
936
|
+
"The terminal REJECTED variant (failureReason + transitions []) is described in prose, not published as JSON \u2014 no fixture."
|
|
937
|
+
]
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
id: "screen-12-pending-order",
|
|
941
|
+
file: "screen-12-pending-order.json",
|
|
942
|
+
source: "FE_DOC",
|
|
943
|
+
docRef: "Screen 12 \xB7 Order in progress",
|
|
944
|
+
screen: 12,
|
|
945
|
+
stateKey: "PENDING_ORDER",
|
|
946
|
+
complete: true,
|
|
947
|
+
notes: [
|
|
948
|
+
"TODO(open-decision): PENDING_ORDER removal proposed; Screen 12 frozen. Kept so 0.1.0 can validate the current shape."
|
|
949
|
+
]
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
id: "screen-13-order-settled",
|
|
953
|
+
file: "screen-13-order-settled.json",
|
|
954
|
+
source: "FE_DOC",
|
|
955
|
+
docRef: "Screen 13 \xB7 Success",
|
|
956
|
+
screen: 13,
|
|
957
|
+
stateKey: "ORDER/CREATED",
|
|
958
|
+
complete: true
|
|
959
|
+
},
|
|
960
|
+
{
|
|
961
|
+
id: "screen-14-order-failed",
|
|
962
|
+
file: "screen-14-order-failed.json",
|
|
963
|
+
source: "FE_DOC",
|
|
964
|
+
docRef: "Screen 14 \xB7 Something went wrong",
|
|
965
|
+
screen: 14,
|
|
966
|
+
stateKey: "ORDER/CREATED",
|
|
967
|
+
complete: true,
|
|
968
|
+
notes: ["Terminal status (FAILED) still carrying a recovery transition \u2014 retryable failureReason."]
|
|
969
|
+
},
|
|
970
|
+
{
|
|
971
|
+
id: "screen-15-order-refunded",
|
|
972
|
+
file: "screen-15-order-refunded.json",
|
|
973
|
+
source: "FE_DOC",
|
|
974
|
+
docRef: "Screen 15 \xB7 Refund on the way",
|
|
975
|
+
screen: 15,
|
|
976
|
+
stateKey: "ORDER/CREATED",
|
|
977
|
+
complete: true
|
|
978
|
+
},
|
|
979
|
+
{
|
|
980
|
+
id: "screen-16-blocked",
|
|
981
|
+
file: "screen-16-blocked.json",
|
|
982
|
+
source: "FE_DOC",
|
|
983
|
+
docRef: "Screen 16 \xB7 Not available",
|
|
984
|
+
screen: 16,
|
|
985
|
+
stateKey: "BLOCKED",
|
|
986
|
+
complete: true,
|
|
987
|
+
notes: ["The only flatly terminal state entry in the table."]
|
|
988
|
+
},
|
|
989
|
+
{
|
|
990
|
+
id: "worked-example-01-quote",
|
|
991
|
+
file: "worked-example-01-quote.json",
|
|
992
|
+
source: "CONTRACT_DOC",
|
|
993
|
+
docRef: "\xA7Worked example, step 1 (method selection)",
|
|
994
|
+
stateKey: "QUOTE",
|
|
995
|
+
complete: false,
|
|
996
|
+
notes: ["Carries inputs {email: {type: TEXT}} \u2014 the collected-spec half of the split."]
|
|
997
|
+
},
|
|
998
|
+
{
|
|
999
|
+
id: "worked-example-02-session-auth",
|
|
1000
|
+
file: "worked-example-02-session-auth.json",
|
|
1001
|
+
source: "CONTRACT_DOC",
|
|
1002
|
+
docRef: "\xA7Worked example, step 2 (session challenge)",
|
|
1003
|
+
stateKey: "SESSION_AUTH",
|
|
1004
|
+
complete: false
|
|
1005
|
+
},
|
|
1006
|
+
{
|
|
1007
|
+
id: "worked-example-04-payment-instruct",
|
|
1008
|
+
file: "worked-example-04-payment-instruct.json",
|
|
1009
|
+
source: "CONTRACT_DOC",
|
|
1010
|
+
docRef: "\xA7Worked example, steps 4\u20135 (order + bank instructions)",
|
|
1011
|
+
stateKey: "PAYMENT/INSTRUCT",
|
|
1012
|
+
complete: false,
|
|
1013
|
+
notes: [
|
|
1014
|
+
"The envelope-sibling `orderId` case. TODO(open-decision): orderId placement (envelope sibling here vs inside state in the FE doc v0)."
|
|
1015
|
+
]
|
|
1016
|
+
}
|
|
1017
|
+
];
|
|
1018
|
+
var FIXTURE_COVERAGE_GAPS = [
|
|
1019
|
+
{
|
|
1020
|
+
stateKey: "FUN_AUTH",
|
|
1021
|
+
reason: "No envelope 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."
|
|
1022
|
+
},
|
|
1023
|
+
{
|
|
1024
|
+
stateKey: "KYC/CAPTURE",
|
|
1025
|
+
reason: "Dropped from v1 (FE Screen 8 tombstone, no UK module coverage); no envelope was ever published. Kind retained as handshake-gated vocabulary."
|
|
1026
|
+
}
|
|
1027
|
+
];
|
|
1028
|
+
var fixtureMeta = (id) => {
|
|
1029
|
+
const meta = FIXTURES.find((f) => f.id === id);
|
|
1030
|
+
if (!meta) throw new Error(`unknown fixture: ${id}`);
|
|
1031
|
+
return meta;
|
|
1032
|
+
};
|
|
1033
|
+
function loadFixture(id) {
|
|
1034
|
+
const meta = fixtureMeta(id);
|
|
1035
|
+
const data = FIXTURE_DATA[meta.id];
|
|
1036
|
+
if (data === void 0) throw new Error(`fixture data missing for ${meta.id} \u2014 run npm run generate:fixtures`);
|
|
1037
|
+
return JSON.parse(JSON.stringify(data));
|
|
1038
|
+
}
|
|
1039
|
+
function loadFixtures() {
|
|
1040
|
+
return FIXTURES.map((meta) => ({ meta, json: loadFixture(meta.id) }));
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// src/assert.ts
|
|
1044
|
+
var ContractViolation = class extends Error {
|
|
1045
|
+
issues;
|
|
1046
|
+
constructor(message, issues = []) {
|
|
1047
|
+
super(issues.length ? `${message}
|
|
1048
|
+
- ${issues.join("\n - ")}` : message);
|
|
1049
|
+
this.name = "ContractViolation";
|
|
1050
|
+
this.issues = issues;
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
var formatIssues = (error) => {
|
|
1054
|
+
const issues = error.issues;
|
|
1055
|
+
if (!issues) return [String(error)];
|
|
1056
|
+
return issues.map((issue) => {
|
|
1057
|
+
const path = (issue.path ?? []).join(".");
|
|
1058
|
+
return path ? `${path}: ${issue.message ?? "invalid"}` : issue.message ?? "invalid";
|
|
1059
|
+
});
|
|
1060
|
+
};
|
|
1061
|
+
function assertEnvelope(json) {
|
|
1062
|
+
const result = StepResponseSchema.safeParse(json);
|
|
1063
|
+
if (!result.success) throw new ContractViolation("invalid envelope", formatIssues(result.error));
|
|
1064
|
+
return result.data;
|
|
1065
|
+
}
|
|
1066
|
+
function assertEnvelopeFragment(json) {
|
|
1067
|
+
const result = StepResponseFragmentSchema.safeParse(json);
|
|
1068
|
+
if (!result.success)
|
|
1069
|
+
throw new ContractViolation("invalid envelope fragment", formatIssues(result.error));
|
|
1070
|
+
return result.data;
|
|
1071
|
+
}
|
|
1072
|
+
function endpointMatches(template, actual) {
|
|
1073
|
+
const pattern = template.split("/").map((segment) => segment.startsWith(":") ? "[^/]+" : escapeRegExp(segment)).join("/");
|
|
1074
|
+
return new RegExp(`^${pattern}$`).test(actual);
|
|
1075
|
+
}
|
|
1076
|
+
var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1077
|
+
var transitionEndpoint = (transition) => {
|
|
1078
|
+
switch (transition.mode) {
|
|
1079
|
+
case "SUBMIT":
|
|
1080
|
+
return transition.endpoint;
|
|
1081
|
+
case "AWAIT":
|
|
1082
|
+
return transition.poll.endpoint;
|
|
1083
|
+
case "CLIENT_SURFACE":
|
|
1084
|
+
return transition.report.endpoint;
|
|
1085
|
+
}
|
|
1086
|
+
};
|
|
1087
|
+
var matchesAllowed = (transition, allowed) => allowed.mode === transition.mode && endpointMatches(allowed.endpoint, transitionEndpoint(transition));
|
|
1088
|
+
function bodyKeyIssues(transition) {
|
|
1089
|
+
const issues = [];
|
|
1090
|
+
const collide = (a, b, left, right) => {
|
|
1091
|
+
for (const key of a) {
|
|
1092
|
+
if (b.includes(key)) issues.push(`transition "${transition.id}": key "${key}" is in both ${left} and ${right}`);
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
if (transition.mode === "SUBMIT") {
|
|
1096
|
+
const params = Object.keys(transition.params ?? {});
|
|
1097
|
+
const inputs = Object.keys(transition.inputs ?? {});
|
|
1098
|
+
collide(params, inputs, "params", "inputs");
|
|
1099
|
+
for (const [key, value] of Object.entries(transition.params ?? {})) {
|
|
1100
|
+
if (looksLikeFieldSpec(value)) {
|
|
1101
|
+
issues.push(
|
|
1102
|
+
`transition "${transition.id}": params.${key} is FieldSpec-shaped \u2014 a spec the client must collect belongs in inputs`
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
if (transition.mode === "CLIENT_SURFACE") {
|
|
1108
|
+
const params = Object.keys(transition.report.params ?? {});
|
|
1109
|
+
collide(params, transition.report.expects ?? [], "report.params", "report.expects");
|
|
1110
|
+
}
|
|
1111
|
+
return issues;
|
|
1112
|
+
}
|
|
1113
|
+
function checkLegalEmission(state, transitions) {
|
|
1114
|
+
const entry = tableEntry(state);
|
|
1115
|
+
const issues = [];
|
|
1116
|
+
if (entry.terminal && transitions.length > 0) {
|
|
1117
|
+
issues.push(
|
|
1118
|
+
`${entry.key} is terminal (table) and must carry transitions: [] \u2014 got ${transitions.length}`
|
|
1119
|
+
);
|
|
1120
|
+
}
|
|
1121
|
+
for (const transition of transitions) {
|
|
1122
|
+
issues.push(...bodyKeyIssues(transition));
|
|
1123
|
+
if (transition.mode === "CLIENT_SURFACE" && !("surface" in state)) {
|
|
1124
|
+
issues.push(
|
|
1125
|
+
`transition "${transition.id}": CLIENT_SURFACE requires the Surface in state (\u03942 single-carry) \u2014 ${entry.key} carries none`
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
if (transition.mode === "SUBMIT" && Object.values(transition.inputs ?? {}).some((spec) => spec.type === "FORM_VALUES") && !("form" in state)) {
|
|
1129
|
+
issues.push(
|
|
1130
|
+
`transition "${transition.id}": inputs {type: FORM_VALUES} collects state.form \u2014 ${entry.key} carries no form`
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
if (entry.docStatus === "SPECIFIED" && !entry.allowedTransitions.some((a) => matchesAllowed(transition, a))) {
|
|
1134
|
+
issues.push(
|
|
1135
|
+
`transition "${transition.id}": ${transition.mode} ${transitionEndpoint(transition)} is not in ${entry.key}'s legal set (${entry.allowedTransitions.map((a) => `${a.mode} ${a.endpoint}`).join(" | ") || "none"})`
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
return {
|
|
1140
|
+
state: entry.key,
|
|
1141
|
+
issues,
|
|
1142
|
+
unjudgeable: entry.docStatus === "UNSPECIFIED"
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
function assertLegalEmission(state, transitions) {
|
|
1146
|
+
const { issues, state: key } = checkLegalEmission(state, transitions);
|
|
1147
|
+
if (issues.length) throw new ContractViolation(`illegal emission for ${key}`, issues);
|
|
1148
|
+
}
|
|
1149
|
+
function assertLegalReturn(from, to) {
|
|
1150
|
+
const entry = tableEntry(from);
|
|
1151
|
+
const target = stateKey(to);
|
|
1152
|
+
if (!entry.mayReturn) return;
|
|
1153
|
+
if (!entry.mayReturn.includes(target)) {
|
|
1154
|
+
throw new ContractViolation(
|
|
1155
|
+
`illegal return: ${entry.key} \u2192 ${target}`,
|
|
1156
|
+
[`${entry.key} may receive back: ${entry.mayReturn.join(" \xB7 ") || "nothing (terminal)"}`]
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
function walkTable(fn) {
|
|
1161
|
+
return STATE_KEYS.map((key) => fn(TRANSITION_TABLE[key], key));
|
|
1162
|
+
}
|
|
1163
|
+
function assertFixture(id) {
|
|
1164
|
+
const meta = fixtureMeta(id);
|
|
1165
|
+
const json = loadFixture(id);
|
|
1166
|
+
const envelope = meta.complete ? assertEnvelope(json) : assertEnvelopeFragment(json);
|
|
1167
|
+
if (stateKey(envelope.state) !== meta.stateKey) {
|
|
1168
|
+
throw new ContractViolation(
|
|
1169
|
+
`fixture ${id}: manifest says ${meta.stateKey}, envelope carries ${stateKey(envelope.state)}`
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
assertLegalEmission(envelope.state, envelope.transitions);
|
|
1173
|
+
return { meta, envelope };
|
|
1174
|
+
}
|
|
1175
|
+
function assertAllFixtures() {
|
|
1176
|
+
return FIXTURES.map((meta) => assertFixture(meta.id));
|
|
1177
|
+
}
|
|
1178
|
+
export {
|
|
1179
|
+
AuthChallengeSchema,
|
|
1180
|
+
BlockedStateSchema,
|
|
1181
|
+
ContractViolation,
|
|
1182
|
+
CryptoAmountSchema,
|
|
1183
|
+
DOCUMENTED_ENDPOINTS,
|
|
1184
|
+
ENDPOINT_PATTERN,
|
|
1185
|
+
EnvelopeSchema,
|
|
1186
|
+
FIELD_SPEC_TYPES,
|
|
1187
|
+
FIXTURES,
|
|
1188
|
+
FIXTURE_COVERAGE_GAPS,
|
|
1189
|
+
FailureReasonSchema,
|
|
1190
|
+
FeeLineSchema,
|
|
1191
|
+
FiatAmountSchema,
|
|
1192
|
+
FiatEndpointSchema,
|
|
1193
|
+
FiatProviderSchema,
|
|
1194
|
+
FieldSpecSchema,
|
|
1195
|
+
FlowStateSchema,
|
|
1196
|
+
FormDescriptorSchema,
|
|
1197
|
+
FormFieldSchema,
|
|
1198
|
+
FormFieldTypeSchema,
|
|
1199
|
+
FunAuthStateSchema,
|
|
1200
|
+
HTTP_VERBS,
|
|
1201
|
+
InstructionFieldSchema,
|
|
1202
|
+
InstructionsSchema,
|
|
1203
|
+
InstrumentSchema,
|
|
1204
|
+
JsonSchema,
|
|
1205
|
+
KycCaptureStateSchema,
|
|
1206
|
+
KycInputRequiredStateSchema,
|
|
1207
|
+
KycNoActionRequiredStateSchema,
|
|
1208
|
+
OrderAwaitingConfirmationStateSchema,
|
|
1209
|
+
OrderCreatedStateSchema,
|
|
1210
|
+
OrderRefSchema,
|
|
1211
|
+
OrderStatusSchema,
|
|
1212
|
+
OrderSummarySchema,
|
|
1213
|
+
PaymentCaptureStateSchema,
|
|
1214
|
+
PaymentInstructStateSchema,
|
|
1215
|
+
PendingOrderStateSchema,
|
|
1216
|
+
PollSpecSchema,
|
|
1217
|
+
QuoteSchema,
|
|
1218
|
+
QuoteStateSchema,
|
|
1219
|
+
RecoverySchema,
|
|
1220
|
+
RefundSchema,
|
|
1221
|
+
ReportSpecSchema,
|
|
1222
|
+
STATE_KEYS,
|
|
1223
|
+
SelectOptionSchema,
|
|
1224
|
+
SessionAuthStateSchema,
|
|
1225
|
+
StatusHistoryEntrySchema,
|
|
1226
|
+
StepResponseFragmentSchema,
|
|
1227
|
+
StepResponseSchema,
|
|
1228
|
+
SurfaceSchema,
|
|
1229
|
+
TABLE_VERSION,
|
|
1230
|
+
TERMINAL_ORDER_STATUSES,
|
|
1231
|
+
TRANSITION_TABLE,
|
|
1232
|
+
TransitionInputsSchema,
|
|
1233
|
+
TransitionParamsSchema,
|
|
1234
|
+
TransitionSchema,
|
|
1235
|
+
TxSchema,
|
|
1236
|
+
assertAllFixtures,
|
|
1237
|
+
assertEnvelope,
|
|
1238
|
+
assertEnvelopeFragment,
|
|
1239
|
+
assertFixture,
|
|
1240
|
+
assertLegalEmission,
|
|
1241
|
+
assertLegalReturn,
|
|
1242
|
+
checkLegalEmission,
|
|
1243
|
+
endpointMatches,
|
|
1244
|
+
fixtureMeta,
|
|
1245
|
+
isTerminal,
|
|
1246
|
+
loadFixture,
|
|
1247
|
+
loadFixtures,
|
|
1248
|
+
looksLikeFieldSpec,
|
|
1249
|
+
stateKey,
|
|
1250
|
+
tableEntry,
|
|
1251
|
+
walkTable
|
|
1252
|
+
};
|
|
1253
|
+
//# sourceMappingURL=index.mjs.map
|