@fun-xyz/fiat-contract 0.9.0 → 0.11.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 +44 -3
- package/dist/{chunk-5QGVY5Z4.mjs → chunk-GKKBF7QB.mjs} +14 -8
- package/dist/index.js +201 -33
- package/dist/index.mjs +189 -27
- package/dist/schemas.d.ts +10 -2
- package/dist/table.js +13 -7
- package/dist/table.mjs +1 -1
- package/dist/types.d.ts +135 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -59,10 +59,51 @@ this package is [ENG-5268](https://linear.app/funxyz/issue/ENG-5268).
|
|
|
59
59
|
6. **Surface kind names the renderer; provider selects the adapter.** Sumsub capture is
|
|
60
60
|
`{ kind: 'KYC_SDK', provider: 'SUMSUB' }`. It is not `URL_EMBED`: the client mounts an SDK,
|
|
61
61
|
not a URL or iframe.
|
|
62
|
-
7. **Quote-screen bounds are explicit exact-money strings.** `QUOTE.limits` carries the
|
|
63
|
-
|
|
62
|
+
7. **Quote-screen bounds are explicit exact-money strings.** `QUOTE.limits` carries the bounding
|
|
63
|
+
minimum/maximum plus the exact `serviceableRanges` when a routing set has gaps; clients do not
|
|
64
|
+
infer capacity from provider metadata or floating point.
|
|
64
65
|
8. **Provider auth is capability-shaped.** SMS uses `SMS_OTP`; provider-owned auth uses
|
|
65
66
|
`PROVIDER_RENDERED` with an optional `AUTH_COMPONENT` surface rather than provider-specific channels.
|
|
67
|
+
9. **KYC SDK refresh is a transition.** `KYC{CAPTURE}` may offer `refresh_sdk_token` to
|
|
68
|
+
`POST /fiat/kyc/sdk-token`; a transition-following client does not invent an auxiliary endpoint call.
|
|
69
|
+
10. **SDK launch is not eligibility authority.** A `KYC_SDK_RESULT` report injects either
|
|
70
|
+
`{outcome: 'COMPLETED'}` or `{outcome: 'FAILED', errorType}`. `errorType` is opaque, and raw
|
|
71
|
+
messages/errors are excluded. The backend must still read the provider's authoritative state.
|
|
72
|
+
11. **Client-executed delivery is current-session-only.** A provider-owned surface may declare a
|
|
73
|
+
`CLIENT_EXECUTED` delivery with `source: 'CURRENT_SESSION_INPUTS'` and a value-free `allowedFieldIds`
|
|
74
|
+
allowlist. The delivery shape cannot request or contain backend-stored KYC values.
|
|
75
|
+
12. **Capability skew is explicit.** `BLOCKED{reason: 'CLIENT_UPGRADE_REQUIRED'}` means every
|
|
76
|
+
otherwise-viable route needs a client capability absent from the handshake. It is not a provider
|
|
77
|
+
decline and should render an upgrade path rather than generic retry copy.
|
|
78
|
+
13. **Providerless is terminal-routing-only.** `FiatStepResponse.provider` may be absent only for
|
|
79
|
+
`BLOCKED`, including `ALL_PROVIDERS_DECLINED` when routing selected no provider. Every other state
|
|
80
|
+
remains provider-bound and requires `provider`; provider-specific blocked responses may keep it.
|
|
81
|
+
|
|
82
|
+
## KYC SDK hints, results, and client delivery
|
|
83
|
+
|
|
84
|
+
`KYC_SDK` is a dedicated `Surface` union member. Its optional
|
|
85
|
+
`preferredDocuments.IDENTITY` contains the exact adapter input needed to bias document capture:
|
|
86
|
+
the identity-vendor `idDocType` plus its ISO alpha-3 `country`. The hint changes presentation only;
|
|
87
|
+
it does not establish that the document is accepted or available.
|
|
88
|
+
|
|
89
|
+
When `report.result` is `{type: 'KYC_SDK_RESULT', bodyKey: 'result'}`, the harness injects a
|
|
90
|
+
`KycSdkResultReport` under that body key. Completion means the SDK launch completed, not that KYC passed.
|
|
91
|
+
Failure exposes only the provider's opaque `errorType` (for example `Unauthorized`), never a raw
|
|
92
|
+
error object or message.
|
|
93
|
+
|
|
94
|
+
`surface.delivery` describes a client-side forwarding boundary on a provider-owned surface, not
|
|
95
|
+
transport mechanics. The selected provider adapter may forward only the declared `allowedFieldIds` that the user entered during the
|
|
96
|
+
current mounted session. The schema fixes the source to `CURRENT_SESSION_INPUTS`, requires a unique
|
|
97
|
+
non-empty field allowlist, and contains no values. Provider-specific submission mechanics remain
|
|
98
|
+
adapter-owned and are intentionally unresolved by this provider-neutral contract.
|
|
99
|
+
|
|
100
|
+
“Declarative KYC collection” means the backend sends a `FormDescriptor` listing canonical field
|
|
101
|
+
IDs, input kinds, labels, validation, and select options. The frontend renders that description,
|
|
102
|
+
collects values, and submits them through the transition's single `FORM_VALUES` input; it does not
|
|
103
|
+
hard-code a separate screen or endpoint for source of funds, occupation, purpose, nationality,
|
|
104
|
+
country of birth, employment status, or phone. Field IDs and type-specific properties are closed
|
|
105
|
+
unions, so misspelled IDs and impossible combinations such as `options` on a `TEXT` field fail
|
|
106
|
+
validation.
|
|
66
107
|
|
|
67
108
|
## Versioning — the package version IS the table version
|
|
68
109
|
|
|
@@ -449,7 +490,7 @@ The current documented shape is what ships; none of these are settled here.
|
|
|
449
490
|
| `orderId` placement | Both: optional on `PAYMENT{INSTRUCT}` (FE doc v0) **and** optional beside `state` (contract worked example). One fixture of each. |
|
|
450
491
|
| `PENDING_ORDER` removal | Kind ships, with the removal proposal flagged on the type, the table entry, and the fixture. Screen 12 stays frozen. |
|
|
451
492
|
| `[OQ7]` failure enumeration | The published `FailureReason` taxonomy only. Expired instructions, partial payment, and per-rail cancel eligibility remain unenumerated; KYC rejection is terminal only for an explicit non-retryable outcome. |
|
|
452
|
-
| Pre-order KYC sequencing | KYC states carry their quote; SDK submission reports to `POST /fiat/kyc/capture`; pending review polls the quote resource. There is no `GET /fiat/kyc`. |
|
|
493
|
+
| Pre-order KYC sequencing | KYC states carry their quote; SDK submission reports to `POST /fiat/kyc/capture`; expired SDK credentials refresh through the explicit `POST /fiat/kyc/sdk-token` transition; pending review polls the quote resource. There is no `GET /fiat/kyc`. |
|
|
453
494
|
| Card capture report target | Both topologies are legal in the table (`POST /fiat/orders` for capture-then-order; `POST /fiat/orders/:id/surface-result` otherwise) pending the Transak answer. |
|
|
454
495
|
| Cancel placement | `cancel` is legal on `PAYMENT{INSTRUCT}` and `ORDER{CREATED}`, marked conditional on the placement decision. |
|
|
455
496
|
| `FUN_AUTH` shape | `challenge: Record<string, JsonValue>`; the table entry is `docStatus: 'UNSPECIFIED'`, so `assertLegalEmission` reports it unjudgeable instead of guessing. |
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// package.json
|
|
2
|
-
var version = "0.
|
|
2
|
+
var version = "0.11.0";
|
|
3
3
|
|
|
4
4
|
// src/table.ts
|
|
5
5
|
var TABLE_VERSION = version;
|
|
@@ -114,7 +114,8 @@ var TRANSITION_TABLE = {
|
|
|
114
114
|
terminal: false,
|
|
115
115
|
notes: [
|
|
116
116
|
"A bad code re-enters this same state with error {category: USER_ERROR, code: BAD_CODE} \u2014 banner, not a screen change.",
|
|
117
|
-
"No resend affordance is specified in either doc (known gap, FE doc Screen 2)."
|
|
117
|
+
"No resend affordance is specified in either doc (known gap, FE doc Screen 2).",
|
|
118
|
+
"A provider-owned surface may declare client-executed delivery from allowlisted CURRENT_SESSION_INPUTS only."
|
|
118
119
|
]
|
|
119
120
|
},
|
|
120
121
|
"KYC/CAPTURE": {
|
|
@@ -124,6 +125,12 @@ var TRANSITION_TABLE = {
|
|
|
124
125
|
screens: [8],
|
|
125
126
|
docStatus: "SPECIFIED",
|
|
126
127
|
allowedTransitions: [
|
|
128
|
+
{
|
|
129
|
+
id: "refresh_sdk_token",
|
|
130
|
+
mode: "SUBMIT",
|
|
131
|
+
endpoint: "POST /fiat/kyc/sdk-token",
|
|
132
|
+
note: "Refreshes an expired SDK credential and returns a new KYC/CAPTURE state."
|
|
133
|
+
},
|
|
127
134
|
{
|
|
128
135
|
id: "capture",
|
|
129
136
|
mode: "CLIENT_SURFACE",
|
|
@@ -143,7 +150,10 @@ var TRANSITION_TABLE = {
|
|
|
143
150
|
"ORDER/AWAITING_CONFIRMATION"
|
|
144
151
|
],
|
|
145
152
|
terminal: false,
|
|
146
|
-
notes: [
|
|
153
|
+
notes: [
|
|
154
|
+
"Carries the handshake-gated KYC_SDK surface; provider selects the identity-vendor adapter.",
|
|
155
|
+
"SDK completion or failure is only a launch report; eligibility is re-read authoritatively by the backend."
|
|
156
|
+
]
|
|
147
157
|
},
|
|
148
158
|
"KYC/INPUT_REQUIRED": {
|
|
149
159
|
key: "KYC/INPUT_REQUIRED",
|
|
@@ -399,10 +409,6 @@ var DOCUMENTED_ENDPOINTS = [
|
|
|
399
409
|
"POST /fiat/orders/:id/surface-result"
|
|
400
410
|
];
|
|
401
411
|
var UNOFFERED_ENDPOINTS = [
|
|
402
|
-
{
|
|
403
|
-
endpoint: "POST /fiat/kyc/sdk-token",
|
|
404
|
-
reason: "SDK token refresh is adapter-driven rather than a flow transition."
|
|
405
|
-
},
|
|
406
412
|
{
|
|
407
413
|
endpoint: "GET /fiat/payment-methods",
|
|
408
414
|
reason: "Discovery, called before any flow state exists \u2014 there is no state to hang it off."
|
|
@@ -500,4 +506,4 @@ export {
|
|
|
500
506
|
tableEntry,
|
|
501
507
|
isTerminal
|
|
502
508
|
};
|
|
503
|
-
//# sourceMappingURL=chunk-
|
|
509
|
+
//# sourceMappingURL=chunk-GKKBF7QB.mjs.map
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ __export(index_exports, {
|
|
|
25
25
|
BlockedStateSchema: () => BlockedStateSchema,
|
|
26
26
|
COUNTRY_NAMES: () => COUNTRY_NAMES,
|
|
27
27
|
COUNTRY_TO_CURRENCY: () => COUNTRY_TO_CURRENCY,
|
|
28
|
+
ClientExecutedDeliverySchema: () => ClientExecutedDeliverySchema,
|
|
28
29
|
ContractViolation: () => ContractViolation,
|
|
29
30
|
CountryCode: () => CountryCode,
|
|
30
31
|
CryptoAmountSchema: () => CryptoAmountSchema,
|
|
@@ -53,9 +54,11 @@ __export(index_exports, {
|
|
|
53
54
|
InstrumentSchema: () => InstrumentSchema,
|
|
54
55
|
JsonSchema: () => JsonSchema,
|
|
55
56
|
KYCProviderSchema: () => KYCProviderSchema,
|
|
57
|
+
KYC_ACKNOWLEDGEMENT_FIELD_IDS: () => KYC_ACKNOWLEDGEMENT_FIELD_IDS,
|
|
56
58
|
KycCaptureStateSchema: () => KycCaptureStateSchema,
|
|
57
59
|
KycInputRequiredStateSchema: () => KycInputRequiredStateSchema,
|
|
58
60
|
KycNoActionRequiredStateSchema: () => KycNoActionRequiredStateSchema,
|
|
61
|
+
KycSdkResultReportSchema: () => KycSdkResultReportSchema,
|
|
59
62
|
OrderAwaitingConfirmationStateSchema: () => OrderAwaitingConfirmationStateSchema,
|
|
60
63
|
OrderCreatedStateSchema: () => OrderCreatedStateSchema,
|
|
61
64
|
OrderRefSchema: () => OrderRefSchema,
|
|
@@ -66,12 +69,15 @@ __export(index_exports, {
|
|
|
66
69
|
PaymentInstructStateSchema: () => PaymentInstructStateSchema,
|
|
67
70
|
PendingOrderStateSchema: () => PendingOrderStateSchema,
|
|
68
71
|
PollSpecSchema: () => PollSpecSchema,
|
|
72
|
+
PreferredIdentityDocumentSchema: () => PreferredIdentityDocumentSchema,
|
|
73
|
+
QuoteLimitRangeSchema: () => QuoteLimitRangeSchema,
|
|
69
74
|
QuoteLimitsSchema: () => QuoteLimitsSchema,
|
|
70
75
|
QuoteSchema: () => QuoteSchema,
|
|
71
76
|
QuoteStateSchema: () => QuoteStateSchema,
|
|
72
77
|
RecoverySchema: () => RecoverySchema,
|
|
73
78
|
RefundSchema: () => RefundSchema,
|
|
74
79
|
ReportSpecSchema: () => ReportSpecSchema,
|
|
80
|
+
STANDARD_KYC_FIELD_IDS: () => STANDARD_KYC_FIELD_IDS,
|
|
75
81
|
STATE_KEYS: () => STATE_KEYS,
|
|
76
82
|
SelectOptionSchema: () => SelectOptionSchema,
|
|
77
83
|
SessionAuthStateSchema: () => SessionAuthStateSchema,
|
|
@@ -82,6 +88,7 @@ __export(index_exports, {
|
|
|
82
88
|
TABLE_VERSION: () => TABLE_VERSION,
|
|
83
89
|
TERMINAL_ORDER_STATUSES: () => TERMINAL_ORDER_STATUSES,
|
|
84
90
|
TRANSITION_TABLE: () => TRANSITION_TABLE,
|
|
91
|
+
TermsAcceptanceSchema: () => TermsAcceptanceSchema,
|
|
85
92
|
TransitionInputsSchema: () => TransitionInputsSchema,
|
|
86
93
|
TransitionParamsSchema: () => TransitionParamsSchema,
|
|
87
94
|
TransitionSchema: () => TransitionSchema,
|
|
@@ -464,7 +471,13 @@ var $quote = obj({
|
|
|
464
471
|
expiresAt: import_zod.z.string()
|
|
465
472
|
});
|
|
466
473
|
var QuoteSchema = $quote;
|
|
467
|
-
var $
|
|
474
|
+
var $quoteLimitRange = obj({ min: import_zod.z.string(), max: import_zod.z.string() });
|
|
475
|
+
var QuoteLimitRangeSchema = $quoteLimitRange;
|
|
476
|
+
var $quoteLimits = obj({
|
|
477
|
+
min: import_zod.z.string(),
|
|
478
|
+
max: import_zod.z.string(),
|
|
479
|
+
serviceableRanges: import_zod.z.array($quoteLimitRange).min(1).optional()
|
|
480
|
+
});
|
|
468
481
|
var QuoteLimitsSchema = $quoteLimits;
|
|
469
482
|
var $instrument = obj({ brandLabel: import_zod.z.string(), last4: import_zod.z.string() });
|
|
470
483
|
var InstrumentSchema = $instrument;
|
|
@@ -505,8 +518,7 @@ var $surfaceKind = import_zod.z.enum([
|
|
|
505
518
|
"AUTH_COMPONENT"
|
|
506
519
|
]);
|
|
507
520
|
var SurfaceKindSchema = $surfaceKind;
|
|
508
|
-
var $
|
|
509
|
-
kind: $surfaceKind,
|
|
521
|
+
var $surfaceBase = {
|
|
510
522
|
provider: $surfaceProvider,
|
|
511
523
|
/**
|
|
512
524
|
* The one deliberate non-strict leaf in the contract. Provider inputs are the adapter's
|
|
@@ -518,7 +530,84 @@ var $surface = obj({
|
|
|
518
530
|
*/
|
|
519
531
|
params: import_zod.z.record(import_zod.z.string(), JsonSchema),
|
|
520
532
|
expiresAt: import_zod.z.string().optional()
|
|
533
|
+
};
|
|
534
|
+
var $preferredIdentityDocument = obj({
|
|
535
|
+
idDocType: import_zod.z.enum(["PASSPORT", "ID_CARD", "DRIVERS", "RESIDENCE_PERMIT", "VISA"]),
|
|
536
|
+
country: import_zod.z.string().regex(/^[A-Z]{3}$/)
|
|
521
537
|
});
|
|
538
|
+
var PreferredIdentityDocumentSchema = $preferredIdentityDocument;
|
|
539
|
+
var STANDARD_KYC_FIELD_IDS = [
|
|
540
|
+
"LEGAL_FIRST_NAME",
|
|
541
|
+
"LEGAL_MIDDLE_NAME",
|
|
542
|
+
"LEGAL_LAST_NAME",
|
|
543
|
+
"DATE_OF_BIRTH",
|
|
544
|
+
"COUNTRY_OF_BIRTH",
|
|
545
|
+
"CITY_OF_BIRTH",
|
|
546
|
+
"NATIONALITY",
|
|
547
|
+
"EMAIL_ADDRESS",
|
|
548
|
+
"EMAIL_OWNERSHIP_VERIFICATION",
|
|
549
|
+
"PHONE_NUMBER",
|
|
550
|
+
"PHONE_OWNERSHIP_VERIFICATION",
|
|
551
|
+
"RESIDENTIAL_ADDRESS_LINE_1",
|
|
552
|
+
"RESIDENTIAL_ADDRESS_LINE_2",
|
|
553
|
+
"RESIDENTIAL_ADDRESS_CITY",
|
|
554
|
+
"RESIDENTIAL_ADDRESS_REGION",
|
|
555
|
+
"RESIDENTIAL_ADDRESS_POSTAL_CODE",
|
|
556
|
+
"RESIDENTIAL_ADDRESS_COUNTRY",
|
|
557
|
+
"TAX_IDENTIFIER",
|
|
558
|
+
"TAX_RESIDENCE_COUNTRY",
|
|
559
|
+
"EMPLOYMENT_STATUS",
|
|
560
|
+
"OCCUPATION_INDUSTRY",
|
|
561
|
+
"OCCUPATION_TITLE",
|
|
562
|
+
"SOURCE_OF_FUNDS_DECLARATION",
|
|
563
|
+
"SOURCE_OF_INCOME_DECLARATION",
|
|
564
|
+
"SOURCE_OF_WEALTH_DECLARATION",
|
|
565
|
+
"PURPOSE_OF_SERVICE",
|
|
566
|
+
"PURPOSE_OF_TRANSACTION",
|
|
567
|
+
"CUSTOMER_CATEGORY"
|
|
568
|
+
];
|
|
569
|
+
var KYC_ACKNOWLEDGEMENT_FIELD_IDS = [
|
|
570
|
+
"RISK_ACKNOWLEDGEMENT",
|
|
571
|
+
"COOLING_OFF_ACKNOWLEDGEMENT",
|
|
572
|
+
"TERMS_ACCEPTANCE"
|
|
573
|
+
];
|
|
574
|
+
var $questionnaireKycFieldId = import_zod.z.custom(
|
|
575
|
+
(value) => typeof value === "string" && /^QUESTIONNAIRE_RESPONSE:[^:]+:[^:]+$/.test(value),
|
|
576
|
+
"Questionnaire field ids must identify exactly one policy and question"
|
|
577
|
+
);
|
|
578
|
+
var $kycFieldId = import_zod.z.union([
|
|
579
|
+
import_zod.z.enum(STANDARD_KYC_FIELD_IDS),
|
|
580
|
+
import_zod.z.enum(KYC_ACKNOWLEDGEMENT_FIELD_IDS),
|
|
581
|
+
$questionnaireKycFieldId
|
|
582
|
+
]);
|
|
583
|
+
var $clientExecutedDelivery = obj({
|
|
584
|
+
channel: import_zod.z.literal("CLIENT_EXECUTED"),
|
|
585
|
+
source: import_zod.z.literal("CURRENT_SESSION_INPUTS"),
|
|
586
|
+
allowedFieldIds: import_zod.z.array($kycFieldId).min(1)
|
|
587
|
+
}).superRefine((delivery, context) => {
|
|
588
|
+
if (new Set(delivery.allowedFieldIds).size !== delivery.allowedFieldIds.length) {
|
|
589
|
+
context.addIssue({
|
|
590
|
+
code: "custom",
|
|
591
|
+
path: ["allowedFieldIds"],
|
|
592
|
+
message: "allowedFieldIds must be unique"
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
var ClientExecutedDeliverySchema = $clientExecutedDelivery;
|
|
597
|
+
var $surface = import_zod.z.union([
|
|
598
|
+
obj({
|
|
599
|
+
...$surfaceBase,
|
|
600
|
+
kind: import_zod.z.literal("KYC_SDK"),
|
|
601
|
+
provider: $kycProvider,
|
|
602
|
+
preferredDocuments: obj({ IDENTITY: $preferredIdentityDocument }).optional()
|
|
603
|
+
}),
|
|
604
|
+
obj({
|
|
605
|
+
...$surfaceBase,
|
|
606
|
+
kind: import_zod.z.enum(["URL_REDIRECT", "PCI_COMPONENT", "PAY_SHEET", "ACH_COMPONENT", "AUTH_COMPONENT"]),
|
|
607
|
+
provider: $provider,
|
|
608
|
+
delivery: $clientExecutedDelivery.optional()
|
|
609
|
+
})
|
|
610
|
+
]);
|
|
522
611
|
var SurfaceSchema = $surface;
|
|
523
612
|
var $instructionField = obj({
|
|
524
613
|
id: import_zod.z.string(),
|
|
@@ -541,15 +630,15 @@ var $selectOption = obj({ id: import_zod.z.string(), labelFallback: import_zod.z
|
|
|
541
630
|
var SelectOptionSchema = $selectOption;
|
|
542
631
|
var $formFieldType = import_zod.z.enum(["TEXT", "DATE", "SELECT", "COUNTRY", "FILE", "HOSTED_LINK"]);
|
|
543
632
|
var FormFieldTypeSchema = $formFieldType;
|
|
544
|
-
var $
|
|
545
|
-
|
|
546
|
-
type:
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
633
|
+
var $formFieldBase = { id: $kycFieldId, labelFallback: import_zod.z.string(), required: import_zod.z.boolean() };
|
|
634
|
+
var $formField = import_zod.z.union([
|
|
635
|
+
obj({ ...$formFieldBase, type: import_zod.z.literal("TEXT"), regex: import_zod.z.string().optional() }),
|
|
636
|
+
obj({ ...$formFieldBase, type: import_zod.z.literal("DATE") }),
|
|
637
|
+
obj({ ...$formFieldBase, type: import_zod.z.literal("SELECT"), options: import_zod.z.array($selectOption) }),
|
|
638
|
+
obj({ ...$formFieldBase, type: import_zod.z.literal("COUNTRY") }),
|
|
639
|
+
obj({ ...$formFieldBase, type: import_zod.z.literal("FILE") }),
|
|
640
|
+
obj({ ...$formFieldBase, type: import_zod.z.literal("HOSTED_LINK"), url: import_zod.z.string() })
|
|
641
|
+
]);
|
|
553
642
|
var FormFieldSchema = $formField;
|
|
554
643
|
var $formDescriptor = obj({ formId: import_zod.z.string(), fields: import_zod.z.array($formField) });
|
|
555
644
|
var FormDescriptorSchema = $formDescriptor;
|
|
@@ -560,7 +649,8 @@ var FIELD_SPEC_TYPES = [
|
|
|
560
649
|
"FILE",
|
|
561
650
|
"SELECT",
|
|
562
651
|
"HOSTED_LINK",
|
|
563
|
-
"FORM_VALUES"
|
|
652
|
+
"FORM_VALUES",
|
|
653
|
+
"TERMS_ACCEPTANCE"
|
|
564
654
|
];
|
|
565
655
|
var $fieldSpec = import_zod.z.union([
|
|
566
656
|
obj({
|
|
@@ -578,9 +668,19 @@ var $fieldSpec = import_zod.z.union([
|
|
|
578
668
|
labelFallback: import_zod.z.string().optional()
|
|
579
669
|
}),
|
|
580
670
|
obj({ type: import_zod.z.literal("HOSTED_LINK"), url: import_zod.z.string(), labelFallback: import_zod.z.string().optional() }),
|
|
581
|
-
obj({ type: import_zod.z.literal("FORM_VALUES") })
|
|
671
|
+
obj({ type: import_zod.z.literal("FORM_VALUES") }),
|
|
672
|
+
obj({
|
|
673
|
+
type: import_zod.z.literal("TERMS_ACCEPTANCE"),
|
|
674
|
+
documentUrl: import_zod.z.string(),
|
|
675
|
+
version: import_zod.z.string(),
|
|
676
|
+
labelFallback: import_zod.z.string().optional()
|
|
677
|
+
})
|
|
582
678
|
]);
|
|
583
679
|
var FieldSpecSchema = $fieldSpec;
|
|
680
|
+
var TermsAcceptanceSchema = obj({
|
|
681
|
+
version: import_zod.z.string(),
|
|
682
|
+
acceptedAt: import_zod.z.string()
|
|
683
|
+
});
|
|
584
684
|
var looksLikeFieldSpec = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.type === "string" && FIELD_SPEC_TYPES.includes(value.type);
|
|
585
685
|
var $params = import_zod.z.record(import_zod.z.string(), JsonSchema).superRefine((params, ctx) => {
|
|
586
686
|
for (const [key, value] of Object.entries(params)) {
|
|
@@ -627,9 +727,15 @@ var PollSpecSchema = $pollSpec;
|
|
|
627
727
|
var $reportSpec = obj({
|
|
628
728
|
endpoint: $endpoint,
|
|
629
729
|
params: $params.optional(),
|
|
630
|
-
expects: import_zod.z.array(import_zod.z.string()).optional()
|
|
730
|
+
expects: import_zod.z.array(import_zod.z.string()).optional(),
|
|
731
|
+
result: obj({ type: import_zod.z.literal("KYC_SDK_RESULT"), bodyKey: import_zod.z.string().trim().min(1) }).optional()
|
|
631
732
|
});
|
|
632
733
|
var ReportSpecSchema = $reportSpec;
|
|
734
|
+
var $kycSdkResult = import_zod.z.union([
|
|
735
|
+
obj({ outcome: import_zod.z.literal("COMPLETED") }),
|
|
736
|
+
obj({ outcome: import_zod.z.literal("FAILED"), errorType: import_zod.z.string().trim().min(1) })
|
|
737
|
+
]);
|
|
738
|
+
var KycSdkResultReportSchema = $kycSdkResult;
|
|
633
739
|
var $transition = import_zod.z.union([
|
|
634
740
|
obj({
|
|
635
741
|
id: import_zod.z.string(),
|
|
@@ -704,7 +810,8 @@ var $blockedReason = import_zod.z.enum([
|
|
|
704
810
|
"REGION_UNSUPPORTED",
|
|
705
811
|
"ASSET_UNSUPPORTED",
|
|
706
812
|
"ALL_PROVIDERS_DECLINED",
|
|
707
|
-
"AMOUNT_UNSERVABLE"
|
|
813
|
+
"AMOUNT_UNSERVABLE",
|
|
814
|
+
"CLIENT_UPGRADE_REQUIRED"
|
|
708
815
|
]);
|
|
709
816
|
var BlockedReasonSchema = $blockedReason;
|
|
710
817
|
var $blockedState = obj({
|
|
@@ -748,7 +855,7 @@ var $orderCreatedState = obj({
|
|
|
748
855
|
error: $failureReason.optional()
|
|
749
856
|
});
|
|
750
857
|
var OrderCreatedStateSchema = $orderCreatedState;
|
|
751
|
-
var $
|
|
858
|
+
var $providerBoundFlowState = import_zod.z.union([
|
|
752
859
|
$quoteState,
|
|
753
860
|
$funAuthState,
|
|
754
861
|
$sessionAuthState,
|
|
@@ -756,23 +863,30 @@ var $flowState = import_zod.z.union([
|
|
|
756
863
|
$kycInputRequiredState,
|
|
757
864
|
$kycNoActionRequiredState,
|
|
758
865
|
$pendingOrderState,
|
|
759
|
-
$blockedState,
|
|
760
866
|
$paymentCaptureState,
|
|
761
867
|
$paymentInstructState,
|
|
762
868
|
$orderAwaitingConfirmationState,
|
|
763
869
|
$orderCreatedState
|
|
764
870
|
]);
|
|
871
|
+
var $flowState = import_zod.z.union([$providerBoundFlowState, $blockedState]);
|
|
765
872
|
var FlowStateSchema = $flowState;
|
|
766
|
-
var $
|
|
767
|
-
state: $
|
|
873
|
+
var $providerBoundStepResponse = obj({
|
|
874
|
+
state: $providerBoundFlowState,
|
|
768
875
|
provider: $provider,
|
|
769
876
|
transitions: import_zod.z.array($transition),
|
|
770
877
|
orderId: import_zod.z.string().optional()
|
|
771
878
|
});
|
|
879
|
+
var $blockedStepResponse = obj({
|
|
880
|
+
state: $blockedState,
|
|
881
|
+
provider: $provider.optional(),
|
|
882
|
+
transitions: import_zod.z.array($transition),
|
|
883
|
+
orderId: import_zod.z.string().optional()
|
|
884
|
+
});
|
|
885
|
+
var $stepResponse = import_zod.z.union([$providerBoundStepResponse, $blockedStepResponse]);
|
|
772
886
|
var FiatStepResponseSchema = $stepResponse;
|
|
773
887
|
|
|
774
888
|
// package.json
|
|
775
|
-
var version = "0.
|
|
889
|
+
var version = "0.11.0";
|
|
776
890
|
|
|
777
891
|
// src/table.ts
|
|
778
892
|
var TABLE_VERSION = version;
|
|
@@ -887,7 +1001,8 @@ var TRANSITION_TABLE = {
|
|
|
887
1001
|
terminal: false,
|
|
888
1002
|
notes: [
|
|
889
1003
|
"A bad code re-enters this same state with error {category: USER_ERROR, code: BAD_CODE} \u2014 banner, not a screen change.",
|
|
890
|
-
"No resend affordance is specified in either doc (known gap, FE doc Screen 2)."
|
|
1004
|
+
"No resend affordance is specified in either doc (known gap, FE doc Screen 2).",
|
|
1005
|
+
"A provider-owned surface may declare client-executed delivery from allowlisted CURRENT_SESSION_INPUTS only."
|
|
891
1006
|
]
|
|
892
1007
|
},
|
|
893
1008
|
"KYC/CAPTURE": {
|
|
@@ -897,6 +1012,12 @@ var TRANSITION_TABLE = {
|
|
|
897
1012
|
screens: [8],
|
|
898
1013
|
docStatus: "SPECIFIED",
|
|
899
1014
|
allowedTransitions: [
|
|
1015
|
+
{
|
|
1016
|
+
id: "refresh_sdk_token",
|
|
1017
|
+
mode: "SUBMIT",
|
|
1018
|
+
endpoint: "POST /fiat/kyc/sdk-token",
|
|
1019
|
+
note: "Refreshes an expired SDK credential and returns a new KYC/CAPTURE state."
|
|
1020
|
+
},
|
|
900
1021
|
{
|
|
901
1022
|
id: "capture",
|
|
902
1023
|
mode: "CLIENT_SURFACE",
|
|
@@ -916,7 +1037,10 @@ var TRANSITION_TABLE = {
|
|
|
916
1037
|
"ORDER/AWAITING_CONFIRMATION"
|
|
917
1038
|
],
|
|
918
1039
|
terminal: false,
|
|
919
|
-
notes: [
|
|
1040
|
+
notes: [
|
|
1041
|
+
"Carries the handshake-gated KYC_SDK surface; provider selects the identity-vendor adapter.",
|
|
1042
|
+
"SDK completion or failure is only a launch report; eligibility is re-read authoritatively by the backend."
|
|
1043
|
+
]
|
|
920
1044
|
},
|
|
921
1045
|
"KYC/INPUT_REQUIRED": {
|
|
922
1046
|
key: "KYC/INPUT_REQUIRED",
|
|
@@ -1172,10 +1296,6 @@ var DOCUMENTED_ENDPOINTS = [
|
|
|
1172
1296
|
"POST /fiat/orders/:id/surface-result"
|
|
1173
1297
|
];
|
|
1174
1298
|
var UNOFFERED_ENDPOINTS = [
|
|
1175
|
-
{
|
|
1176
|
-
endpoint: "POST /fiat/kyc/sdk-token",
|
|
1177
|
-
reason: "SDK token refresh is adapter-driven rather than a flow transition."
|
|
1178
|
-
},
|
|
1179
1299
|
{
|
|
1180
1300
|
endpoint: "GET /fiat/payment-methods",
|
|
1181
1301
|
reason: "Discovery, called before any flow state exists \u2014 there is no state to hang it off."
|
|
@@ -1266,7 +1386,17 @@ var FIXTURE_DATA = {
|
|
|
1266
1386
|
"kind": "QUOTE",
|
|
1267
1387
|
"limits": {
|
|
1268
1388
|
"min": "1.00",
|
|
1269
|
-
"max": "3000.00"
|
|
1389
|
+
"max": "3000.00",
|
|
1390
|
+
"serviceableRanges": [
|
|
1391
|
+
{
|
|
1392
|
+
"min": "1.00",
|
|
1393
|
+
"max": "500.00"
|
|
1394
|
+
},
|
|
1395
|
+
{
|
|
1396
|
+
"min": "1000.00",
|
|
1397
|
+
"max": "3000.00"
|
|
1398
|
+
}
|
|
1399
|
+
]
|
|
1270
1400
|
},
|
|
1271
1401
|
"quote": {
|
|
1272
1402
|
"quoteRef": "q_8f2",
|
|
@@ -1314,7 +1444,13 @@ var FIXTURE_DATA = {
|
|
|
1314
1444
|
"kind": "QUOTE",
|
|
1315
1445
|
"limits": {
|
|
1316
1446
|
"min": "1.00",
|
|
1317
|
-
"max": "3000.00"
|
|
1447
|
+
"max": "3000.00",
|
|
1448
|
+
"serviceableRanges": [
|
|
1449
|
+
{
|
|
1450
|
+
"min": "1.00",
|
|
1451
|
+
"max": "3000.00"
|
|
1452
|
+
}
|
|
1453
|
+
]
|
|
1318
1454
|
},
|
|
1319
1455
|
"quote": {
|
|
1320
1456
|
"quoteRef": "q_8f2",
|
|
@@ -1425,13 +1561,13 @@ var FIXTURE_DATA = {
|
|
|
1425
1561
|
"formId": "kyc_q_8f2_1",
|
|
1426
1562
|
"fields": [
|
|
1427
1563
|
{
|
|
1428
|
-
"id": "
|
|
1564
|
+
"id": "TAX_IDENTIFIER",
|
|
1429
1565
|
"type": "TEXT",
|
|
1430
1566
|
"labelFallback": "Tax ID",
|
|
1431
1567
|
"required": true
|
|
1432
1568
|
},
|
|
1433
1569
|
{
|
|
1434
|
-
"id": "
|
|
1570
|
+
"id": "RESIDENTIAL_ADDRESS_LINE_1",
|
|
1435
1571
|
"type": "TEXT",
|
|
1436
1572
|
"labelFallback": "Home address",
|
|
1437
1573
|
"required": true
|
|
@@ -1565,11 +1701,26 @@ var FIXTURE_DATA = {
|
|
|
1565
1701
|
"params": {
|
|
1566
1702
|
"accessToken": "eyJ\u2026"
|
|
1567
1703
|
},
|
|
1704
|
+
"preferredDocuments": {
|
|
1705
|
+
"IDENTITY": {
|
|
1706
|
+
"idDocType": "PASSPORT",
|
|
1707
|
+
"country": "GBR"
|
|
1708
|
+
}
|
|
1709
|
+
},
|
|
1568
1710
|
"expiresAt": "\u2026"
|
|
1569
1711
|
}
|
|
1570
1712
|
},
|
|
1571
1713
|
"provider": "TRANSAK",
|
|
1572
1714
|
"transitions": [
|
|
1715
|
+
{
|
|
1716
|
+
"id": "refresh_sdk_token",
|
|
1717
|
+
"mode": "SUBMIT",
|
|
1718
|
+
"endpoint": "POST /fiat/kyc/sdk-token",
|
|
1719
|
+
"params": {
|
|
1720
|
+
"quoteRef": "q_8f2"
|
|
1721
|
+
},
|
|
1722
|
+
"labelFallback": "Retry verification"
|
|
1723
|
+
},
|
|
1573
1724
|
{
|
|
1574
1725
|
"id": "capture",
|
|
1575
1726
|
"mode": "CLIENT_SURFACE",
|
|
@@ -1577,6 +1728,10 @@ var FIXTURE_DATA = {
|
|
|
1577
1728
|
"endpoint": "POST /fiat/kyc/capture",
|
|
1578
1729
|
"params": {
|
|
1579
1730
|
"quoteRef": "q_8f2"
|
|
1731
|
+
},
|
|
1732
|
+
"result": {
|
|
1733
|
+
"type": "KYC_SDK_RESULT",
|
|
1734
|
+
"bodyKey": "result"
|
|
1580
1735
|
}
|
|
1581
1736
|
}
|
|
1582
1737
|
}
|
|
@@ -1821,7 +1976,6 @@ var FIXTURE_DATA = {
|
|
|
1821
1976
|
"reason": "REGION_UNSUPPORTED",
|
|
1822
1977
|
"retryAfter": null
|
|
1823
1978
|
},
|
|
1824
|
-
"provider": "TRANSAK",
|
|
1825
1979
|
"transitions": []
|
|
1826
1980
|
},
|
|
1827
1981
|
"worked-example-01-quote": {
|
|
@@ -1829,7 +1983,13 @@ var FIXTURE_DATA = {
|
|
|
1829
1983
|
"kind": "QUOTE",
|
|
1830
1984
|
"limits": {
|
|
1831
1985
|
"min": "1.00",
|
|
1832
|
-
"max": "3000.00"
|
|
1986
|
+
"max": "3000.00",
|
|
1987
|
+
"serviceableRanges": [
|
|
1988
|
+
{
|
|
1989
|
+
"min": "1.00",
|
|
1990
|
+
"max": "3000.00"
|
|
1991
|
+
}
|
|
1992
|
+
]
|
|
1833
1993
|
},
|
|
1834
1994
|
"quote": {
|
|
1835
1995
|
"quoteRef": "q_8f2",
|
|
@@ -2193,6 +2353,9 @@ function bodyKeyIssues(transition) {
|
|
|
2193
2353
|
if (transition.mode === "CLIENT_SURFACE") {
|
|
2194
2354
|
const params = Object.keys(transition.report.params ?? {});
|
|
2195
2355
|
collide(params, transition.report.expects ?? [], "report.params", "report.expects");
|
|
2356
|
+
const resultKeys = transition.report.result ? [transition.report.result.bodyKey] : [];
|
|
2357
|
+
collide(params, resultKeys, "report.params", "report.result");
|
|
2358
|
+
collide(transition.report.expects ?? [], resultKeys, "report.expects", "report.result");
|
|
2196
2359
|
}
|
|
2197
2360
|
return issues;
|
|
2198
2361
|
}
|
|
@@ -2221,6 +2384,11 @@ function checkLegalEmission(state, transitions) {
|
|
|
2221
2384
|
`transition "${transition.id}": CLIENT_SURFACE requires the Surface in state (\u03942 single-carry) \u2014 ${entry.key} carries none`
|
|
2222
2385
|
);
|
|
2223
2386
|
}
|
|
2387
|
+
if (transition.mode === "CLIENT_SURFACE" && transition.report.result?.type === "KYC_SDK_RESULT" && (!("surface" in state) || state.surface?.kind !== "KYC_SDK")) {
|
|
2388
|
+
issues.push(
|
|
2389
|
+
`transition "${transition.id}": KYC_SDK_RESULT requires a KYC_SDK surface in state`
|
|
2390
|
+
);
|
|
2391
|
+
}
|
|
2224
2392
|
if (transition.mode === "SUBMIT" && Object.values(transition.inputs ?? {}).some((spec) => spec.type === "FORM_VALUES") && !("form" in state)) {
|
|
2225
2393
|
issues.push(
|
|
2226
2394
|
`transition "${transition.id}": inputs {type: FORM_VALUES} collects state.form \u2014 ${entry.key} carries no form`
|
package/dist/index.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
tableEntry,
|
|
13
13
|
unofferedEndpoints,
|
|
14
14
|
unreachableStates
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-GKKBF7QB.mjs";
|
|
16
16
|
|
|
17
17
|
// src/codes.ts
|
|
18
18
|
var COUNTRY_CURRENCY_LIST = [
|
|
@@ -370,7 +370,13 @@ var $quote = obj({
|
|
|
370
370
|
expiresAt: z.string()
|
|
371
371
|
});
|
|
372
372
|
var QuoteSchema = $quote;
|
|
373
|
-
var $
|
|
373
|
+
var $quoteLimitRange = obj({ min: z.string(), max: z.string() });
|
|
374
|
+
var QuoteLimitRangeSchema = $quoteLimitRange;
|
|
375
|
+
var $quoteLimits = obj({
|
|
376
|
+
min: z.string(),
|
|
377
|
+
max: z.string(),
|
|
378
|
+
serviceableRanges: z.array($quoteLimitRange).min(1).optional()
|
|
379
|
+
});
|
|
374
380
|
var QuoteLimitsSchema = $quoteLimits;
|
|
375
381
|
var $instrument = obj({ brandLabel: z.string(), last4: z.string() });
|
|
376
382
|
var InstrumentSchema = $instrument;
|
|
@@ -411,8 +417,7 @@ var $surfaceKind = z.enum([
|
|
|
411
417
|
"AUTH_COMPONENT"
|
|
412
418
|
]);
|
|
413
419
|
var SurfaceKindSchema = $surfaceKind;
|
|
414
|
-
var $
|
|
415
|
-
kind: $surfaceKind,
|
|
420
|
+
var $surfaceBase = {
|
|
416
421
|
provider: $surfaceProvider,
|
|
417
422
|
/**
|
|
418
423
|
* The one deliberate non-strict leaf in the contract. Provider inputs are the adapter's
|
|
@@ -424,7 +429,84 @@ var $surface = obj({
|
|
|
424
429
|
*/
|
|
425
430
|
params: z.record(z.string(), JsonSchema),
|
|
426
431
|
expiresAt: z.string().optional()
|
|
432
|
+
};
|
|
433
|
+
var $preferredIdentityDocument = obj({
|
|
434
|
+
idDocType: z.enum(["PASSPORT", "ID_CARD", "DRIVERS", "RESIDENCE_PERMIT", "VISA"]),
|
|
435
|
+
country: z.string().regex(/^[A-Z]{3}$/)
|
|
427
436
|
});
|
|
437
|
+
var PreferredIdentityDocumentSchema = $preferredIdentityDocument;
|
|
438
|
+
var STANDARD_KYC_FIELD_IDS = [
|
|
439
|
+
"LEGAL_FIRST_NAME",
|
|
440
|
+
"LEGAL_MIDDLE_NAME",
|
|
441
|
+
"LEGAL_LAST_NAME",
|
|
442
|
+
"DATE_OF_BIRTH",
|
|
443
|
+
"COUNTRY_OF_BIRTH",
|
|
444
|
+
"CITY_OF_BIRTH",
|
|
445
|
+
"NATIONALITY",
|
|
446
|
+
"EMAIL_ADDRESS",
|
|
447
|
+
"EMAIL_OWNERSHIP_VERIFICATION",
|
|
448
|
+
"PHONE_NUMBER",
|
|
449
|
+
"PHONE_OWNERSHIP_VERIFICATION",
|
|
450
|
+
"RESIDENTIAL_ADDRESS_LINE_1",
|
|
451
|
+
"RESIDENTIAL_ADDRESS_LINE_2",
|
|
452
|
+
"RESIDENTIAL_ADDRESS_CITY",
|
|
453
|
+
"RESIDENTIAL_ADDRESS_REGION",
|
|
454
|
+
"RESIDENTIAL_ADDRESS_POSTAL_CODE",
|
|
455
|
+
"RESIDENTIAL_ADDRESS_COUNTRY",
|
|
456
|
+
"TAX_IDENTIFIER",
|
|
457
|
+
"TAX_RESIDENCE_COUNTRY",
|
|
458
|
+
"EMPLOYMENT_STATUS",
|
|
459
|
+
"OCCUPATION_INDUSTRY",
|
|
460
|
+
"OCCUPATION_TITLE",
|
|
461
|
+
"SOURCE_OF_FUNDS_DECLARATION",
|
|
462
|
+
"SOURCE_OF_INCOME_DECLARATION",
|
|
463
|
+
"SOURCE_OF_WEALTH_DECLARATION",
|
|
464
|
+
"PURPOSE_OF_SERVICE",
|
|
465
|
+
"PURPOSE_OF_TRANSACTION",
|
|
466
|
+
"CUSTOMER_CATEGORY"
|
|
467
|
+
];
|
|
468
|
+
var KYC_ACKNOWLEDGEMENT_FIELD_IDS = [
|
|
469
|
+
"RISK_ACKNOWLEDGEMENT",
|
|
470
|
+
"COOLING_OFF_ACKNOWLEDGEMENT",
|
|
471
|
+
"TERMS_ACCEPTANCE"
|
|
472
|
+
];
|
|
473
|
+
var $questionnaireKycFieldId = z.custom(
|
|
474
|
+
(value) => typeof value === "string" && /^QUESTIONNAIRE_RESPONSE:[^:]+:[^:]+$/.test(value),
|
|
475
|
+
"Questionnaire field ids must identify exactly one policy and question"
|
|
476
|
+
);
|
|
477
|
+
var $kycFieldId = z.union([
|
|
478
|
+
z.enum(STANDARD_KYC_FIELD_IDS),
|
|
479
|
+
z.enum(KYC_ACKNOWLEDGEMENT_FIELD_IDS),
|
|
480
|
+
$questionnaireKycFieldId
|
|
481
|
+
]);
|
|
482
|
+
var $clientExecutedDelivery = obj({
|
|
483
|
+
channel: z.literal("CLIENT_EXECUTED"),
|
|
484
|
+
source: z.literal("CURRENT_SESSION_INPUTS"),
|
|
485
|
+
allowedFieldIds: z.array($kycFieldId).min(1)
|
|
486
|
+
}).superRefine((delivery, context) => {
|
|
487
|
+
if (new Set(delivery.allowedFieldIds).size !== delivery.allowedFieldIds.length) {
|
|
488
|
+
context.addIssue({
|
|
489
|
+
code: "custom",
|
|
490
|
+
path: ["allowedFieldIds"],
|
|
491
|
+
message: "allowedFieldIds must be unique"
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
var ClientExecutedDeliverySchema = $clientExecutedDelivery;
|
|
496
|
+
var $surface = z.union([
|
|
497
|
+
obj({
|
|
498
|
+
...$surfaceBase,
|
|
499
|
+
kind: z.literal("KYC_SDK"),
|
|
500
|
+
provider: $kycProvider,
|
|
501
|
+
preferredDocuments: obj({ IDENTITY: $preferredIdentityDocument }).optional()
|
|
502
|
+
}),
|
|
503
|
+
obj({
|
|
504
|
+
...$surfaceBase,
|
|
505
|
+
kind: z.enum(["URL_REDIRECT", "PCI_COMPONENT", "PAY_SHEET", "ACH_COMPONENT", "AUTH_COMPONENT"]),
|
|
506
|
+
provider: $provider,
|
|
507
|
+
delivery: $clientExecutedDelivery.optional()
|
|
508
|
+
})
|
|
509
|
+
]);
|
|
428
510
|
var SurfaceSchema = $surface;
|
|
429
511
|
var $instructionField = obj({
|
|
430
512
|
id: z.string(),
|
|
@@ -447,15 +529,15 @@ var $selectOption = obj({ id: z.string(), labelFallback: z.string() });
|
|
|
447
529
|
var SelectOptionSchema = $selectOption;
|
|
448
530
|
var $formFieldType = z.enum(["TEXT", "DATE", "SELECT", "COUNTRY", "FILE", "HOSTED_LINK"]);
|
|
449
531
|
var FormFieldTypeSchema = $formFieldType;
|
|
450
|
-
var $
|
|
451
|
-
|
|
452
|
-
type:
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
532
|
+
var $formFieldBase = { id: $kycFieldId, labelFallback: z.string(), required: z.boolean() };
|
|
533
|
+
var $formField = z.union([
|
|
534
|
+
obj({ ...$formFieldBase, type: z.literal("TEXT"), regex: z.string().optional() }),
|
|
535
|
+
obj({ ...$formFieldBase, type: z.literal("DATE") }),
|
|
536
|
+
obj({ ...$formFieldBase, type: z.literal("SELECT"), options: z.array($selectOption) }),
|
|
537
|
+
obj({ ...$formFieldBase, type: z.literal("COUNTRY") }),
|
|
538
|
+
obj({ ...$formFieldBase, type: z.literal("FILE") }),
|
|
539
|
+
obj({ ...$formFieldBase, type: z.literal("HOSTED_LINK"), url: z.string() })
|
|
540
|
+
]);
|
|
459
541
|
var FormFieldSchema = $formField;
|
|
460
542
|
var $formDescriptor = obj({ formId: z.string(), fields: z.array($formField) });
|
|
461
543
|
var FormDescriptorSchema = $formDescriptor;
|
|
@@ -466,7 +548,8 @@ var FIELD_SPEC_TYPES = [
|
|
|
466
548
|
"FILE",
|
|
467
549
|
"SELECT",
|
|
468
550
|
"HOSTED_LINK",
|
|
469
|
-
"FORM_VALUES"
|
|
551
|
+
"FORM_VALUES",
|
|
552
|
+
"TERMS_ACCEPTANCE"
|
|
470
553
|
];
|
|
471
554
|
var $fieldSpec = z.union([
|
|
472
555
|
obj({
|
|
@@ -484,9 +567,19 @@ var $fieldSpec = z.union([
|
|
|
484
567
|
labelFallback: z.string().optional()
|
|
485
568
|
}),
|
|
486
569
|
obj({ type: z.literal("HOSTED_LINK"), url: z.string(), labelFallback: z.string().optional() }),
|
|
487
|
-
obj({ type: z.literal("FORM_VALUES") })
|
|
570
|
+
obj({ type: z.literal("FORM_VALUES") }),
|
|
571
|
+
obj({
|
|
572
|
+
type: z.literal("TERMS_ACCEPTANCE"),
|
|
573
|
+
documentUrl: z.string(),
|
|
574
|
+
version: z.string(),
|
|
575
|
+
labelFallback: z.string().optional()
|
|
576
|
+
})
|
|
488
577
|
]);
|
|
489
578
|
var FieldSpecSchema = $fieldSpec;
|
|
579
|
+
var TermsAcceptanceSchema = obj({
|
|
580
|
+
version: z.string(),
|
|
581
|
+
acceptedAt: z.string()
|
|
582
|
+
});
|
|
490
583
|
var looksLikeFieldSpec = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.type === "string" && FIELD_SPEC_TYPES.includes(value.type);
|
|
491
584
|
var $params = z.record(z.string(), JsonSchema).superRefine((params, ctx) => {
|
|
492
585
|
for (const [key, value] of Object.entries(params)) {
|
|
@@ -533,9 +626,15 @@ var PollSpecSchema = $pollSpec;
|
|
|
533
626
|
var $reportSpec = obj({
|
|
534
627
|
endpoint: $endpoint,
|
|
535
628
|
params: $params.optional(),
|
|
536
|
-
expects: z.array(z.string()).optional()
|
|
629
|
+
expects: z.array(z.string()).optional(),
|
|
630
|
+
result: obj({ type: z.literal("KYC_SDK_RESULT"), bodyKey: z.string().trim().min(1) }).optional()
|
|
537
631
|
});
|
|
538
632
|
var ReportSpecSchema = $reportSpec;
|
|
633
|
+
var $kycSdkResult = z.union([
|
|
634
|
+
obj({ outcome: z.literal("COMPLETED") }),
|
|
635
|
+
obj({ outcome: z.literal("FAILED"), errorType: z.string().trim().min(1) })
|
|
636
|
+
]);
|
|
637
|
+
var KycSdkResultReportSchema = $kycSdkResult;
|
|
539
638
|
var $transition = z.union([
|
|
540
639
|
obj({
|
|
541
640
|
id: z.string(),
|
|
@@ -610,7 +709,8 @@ var $blockedReason = z.enum([
|
|
|
610
709
|
"REGION_UNSUPPORTED",
|
|
611
710
|
"ASSET_UNSUPPORTED",
|
|
612
711
|
"ALL_PROVIDERS_DECLINED",
|
|
613
|
-
"AMOUNT_UNSERVABLE"
|
|
712
|
+
"AMOUNT_UNSERVABLE",
|
|
713
|
+
"CLIENT_UPGRADE_REQUIRED"
|
|
614
714
|
]);
|
|
615
715
|
var BlockedReasonSchema = $blockedReason;
|
|
616
716
|
var $blockedState = obj({
|
|
@@ -654,7 +754,7 @@ var $orderCreatedState = obj({
|
|
|
654
754
|
error: $failureReason.optional()
|
|
655
755
|
});
|
|
656
756
|
var OrderCreatedStateSchema = $orderCreatedState;
|
|
657
|
-
var $
|
|
757
|
+
var $providerBoundFlowState = z.union([
|
|
658
758
|
$quoteState,
|
|
659
759
|
$funAuthState,
|
|
660
760
|
$sessionAuthState,
|
|
@@ -662,19 +762,26 @@ var $flowState = z.union([
|
|
|
662
762
|
$kycInputRequiredState,
|
|
663
763
|
$kycNoActionRequiredState,
|
|
664
764
|
$pendingOrderState,
|
|
665
|
-
$blockedState,
|
|
666
765
|
$paymentCaptureState,
|
|
667
766
|
$paymentInstructState,
|
|
668
767
|
$orderAwaitingConfirmationState,
|
|
669
768
|
$orderCreatedState
|
|
670
769
|
]);
|
|
770
|
+
var $flowState = z.union([$providerBoundFlowState, $blockedState]);
|
|
671
771
|
var FlowStateSchema = $flowState;
|
|
672
|
-
var $
|
|
673
|
-
state: $
|
|
772
|
+
var $providerBoundStepResponse = obj({
|
|
773
|
+
state: $providerBoundFlowState,
|
|
674
774
|
provider: $provider,
|
|
675
775
|
transitions: z.array($transition),
|
|
676
776
|
orderId: z.string().optional()
|
|
677
777
|
});
|
|
778
|
+
var $blockedStepResponse = obj({
|
|
779
|
+
state: $blockedState,
|
|
780
|
+
provider: $provider.optional(),
|
|
781
|
+
transitions: z.array($transition),
|
|
782
|
+
orderId: z.string().optional()
|
|
783
|
+
});
|
|
784
|
+
var $stepResponse = z.union([$providerBoundStepResponse, $blockedStepResponse]);
|
|
678
785
|
var FiatStepResponseSchema = $stepResponse;
|
|
679
786
|
|
|
680
787
|
// src/fixtures/data.ts
|
|
@@ -684,7 +791,17 @@ var FIXTURE_DATA = {
|
|
|
684
791
|
"kind": "QUOTE",
|
|
685
792
|
"limits": {
|
|
686
793
|
"min": "1.00",
|
|
687
|
-
"max": "3000.00"
|
|
794
|
+
"max": "3000.00",
|
|
795
|
+
"serviceableRanges": [
|
|
796
|
+
{
|
|
797
|
+
"min": "1.00",
|
|
798
|
+
"max": "500.00"
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
"min": "1000.00",
|
|
802
|
+
"max": "3000.00"
|
|
803
|
+
}
|
|
804
|
+
]
|
|
688
805
|
},
|
|
689
806
|
"quote": {
|
|
690
807
|
"quoteRef": "q_8f2",
|
|
@@ -732,7 +849,13 @@ var FIXTURE_DATA = {
|
|
|
732
849
|
"kind": "QUOTE",
|
|
733
850
|
"limits": {
|
|
734
851
|
"min": "1.00",
|
|
735
|
-
"max": "3000.00"
|
|
852
|
+
"max": "3000.00",
|
|
853
|
+
"serviceableRanges": [
|
|
854
|
+
{
|
|
855
|
+
"min": "1.00",
|
|
856
|
+
"max": "3000.00"
|
|
857
|
+
}
|
|
858
|
+
]
|
|
736
859
|
},
|
|
737
860
|
"quote": {
|
|
738
861
|
"quoteRef": "q_8f2",
|
|
@@ -843,13 +966,13 @@ var FIXTURE_DATA = {
|
|
|
843
966
|
"formId": "kyc_q_8f2_1",
|
|
844
967
|
"fields": [
|
|
845
968
|
{
|
|
846
|
-
"id": "
|
|
969
|
+
"id": "TAX_IDENTIFIER",
|
|
847
970
|
"type": "TEXT",
|
|
848
971
|
"labelFallback": "Tax ID",
|
|
849
972
|
"required": true
|
|
850
973
|
},
|
|
851
974
|
{
|
|
852
|
-
"id": "
|
|
975
|
+
"id": "RESIDENTIAL_ADDRESS_LINE_1",
|
|
853
976
|
"type": "TEXT",
|
|
854
977
|
"labelFallback": "Home address",
|
|
855
978
|
"required": true
|
|
@@ -983,11 +1106,26 @@ var FIXTURE_DATA = {
|
|
|
983
1106
|
"params": {
|
|
984
1107
|
"accessToken": "eyJ\u2026"
|
|
985
1108
|
},
|
|
1109
|
+
"preferredDocuments": {
|
|
1110
|
+
"IDENTITY": {
|
|
1111
|
+
"idDocType": "PASSPORT",
|
|
1112
|
+
"country": "GBR"
|
|
1113
|
+
}
|
|
1114
|
+
},
|
|
986
1115
|
"expiresAt": "\u2026"
|
|
987
1116
|
}
|
|
988
1117
|
},
|
|
989
1118
|
"provider": "TRANSAK",
|
|
990
1119
|
"transitions": [
|
|
1120
|
+
{
|
|
1121
|
+
"id": "refresh_sdk_token",
|
|
1122
|
+
"mode": "SUBMIT",
|
|
1123
|
+
"endpoint": "POST /fiat/kyc/sdk-token",
|
|
1124
|
+
"params": {
|
|
1125
|
+
"quoteRef": "q_8f2"
|
|
1126
|
+
},
|
|
1127
|
+
"labelFallback": "Retry verification"
|
|
1128
|
+
},
|
|
991
1129
|
{
|
|
992
1130
|
"id": "capture",
|
|
993
1131
|
"mode": "CLIENT_SURFACE",
|
|
@@ -995,6 +1133,10 @@ var FIXTURE_DATA = {
|
|
|
995
1133
|
"endpoint": "POST /fiat/kyc/capture",
|
|
996
1134
|
"params": {
|
|
997
1135
|
"quoteRef": "q_8f2"
|
|
1136
|
+
},
|
|
1137
|
+
"result": {
|
|
1138
|
+
"type": "KYC_SDK_RESULT",
|
|
1139
|
+
"bodyKey": "result"
|
|
998
1140
|
}
|
|
999
1141
|
}
|
|
1000
1142
|
}
|
|
@@ -1239,7 +1381,6 @@ var FIXTURE_DATA = {
|
|
|
1239
1381
|
"reason": "REGION_UNSUPPORTED",
|
|
1240
1382
|
"retryAfter": null
|
|
1241
1383
|
},
|
|
1242
|
-
"provider": "TRANSAK",
|
|
1243
1384
|
"transitions": []
|
|
1244
1385
|
},
|
|
1245
1386
|
"worked-example-01-quote": {
|
|
@@ -1247,7 +1388,13 @@ var FIXTURE_DATA = {
|
|
|
1247
1388
|
"kind": "QUOTE",
|
|
1248
1389
|
"limits": {
|
|
1249
1390
|
"min": "1.00",
|
|
1250
|
-
"max": "3000.00"
|
|
1391
|
+
"max": "3000.00",
|
|
1392
|
+
"serviceableRanges": [
|
|
1393
|
+
{
|
|
1394
|
+
"min": "1.00",
|
|
1395
|
+
"max": "3000.00"
|
|
1396
|
+
}
|
|
1397
|
+
]
|
|
1251
1398
|
},
|
|
1252
1399
|
"quote": {
|
|
1253
1400
|
"quoteRef": "q_8f2",
|
|
@@ -1611,6 +1758,9 @@ function bodyKeyIssues(transition) {
|
|
|
1611
1758
|
if (transition.mode === "CLIENT_SURFACE") {
|
|
1612
1759
|
const params = Object.keys(transition.report.params ?? {});
|
|
1613
1760
|
collide(params, transition.report.expects ?? [], "report.params", "report.expects");
|
|
1761
|
+
const resultKeys = transition.report.result ? [transition.report.result.bodyKey] : [];
|
|
1762
|
+
collide(params, resultKeys, "report.params", "report.result");
|
|
1763
|
+
collide(transition.report.expects ?? [], resultKeys, "report.expects", "report.result");
|
|
1614
1764
|
}
|
|
1615
1765
|
return issues;
|
|
1616
1766
|
}
|
|
@@ -1639,6 +1789,11 @@ function checkLegalEmission(state, transitions) {
|
|
|
1639
1789
|
`transition "${transition.id}": CLIENT_SURFACE requires the Surface in state (\u03942 single-carry) \u2014 ${entry.key} carries none`
|
|
1640
1790
|
);
|
|
1641
1791
|
}
|
|
1792
|
+
if (transition.mode === "CLIENT_SURFACE" && transition.report.result?.type === "KYC_SDK_RESULT" && (!("surface" in state) || state.surface?.kind !== "KYC_SDK")) {
|
|
1793
|
+
issues.push(
|
|
1794
|
+
`transition "${transition.id}": KYC_SDK_RESULT requires a KYC_SDK surface in state`
|
|
1795
|
+
);
|
|
1796
|
+
}
|
|
1642
1797
|
if (transition.mode === "SUBMIT" && Object.values(transition.inputs ?? {}).some((spec) => spec.type === "FORM_VALUES") && !("form" in state)) {
|
|
1643
1798
|
issues.push(
|
|
1644
1799
|
`transition "${transition.id}": inputs {type: FORM_VALUES} collects state.form \u2014 ${entry.key} carries no form`
|
|
@@ -1695,6 +1850,7 @@ export {
|
|
|
1695
1850
|
BlockedStateSchema,
|
|
1696
1851
|
COUNTRY_NAMES,
|
|
1697
1852
|
COUNTRY_TO_CURRENCY,
|
|
1853
|
+
ClientExecutedDeliverySchema,
|
|
1698
1854
|
ContractViolation,
|
|
1699
1855
|
CountryCode,
|
|
1700
1856
|
CryptoAmountSchema,
|
|
@@ -1723,9 +1879,11 @@ export {
|
|
|
1723
1879
|
InstrumentSchema,
|
|
1724
1880
|
JsonSchema,
|
|
1725
1881
|
KYCProviderSchema,
|
|
1882
|
+
KYC_ACKNOWLEDGEMENT_FIELD_IDS,
|
|
1726
1883
|
KycCaptureStateSchema,
|
|
1727
1884
|
KycInputRequiredStateSchema,
|
|
1728
1885
|
KycNoActionRequiredStateSchema,
|
|
1886
|
+
KycSdkResultReportSchema,
|
|
1729
1887
|
OrderAwaitingConfirmationStateSchema,
|
|
1730
1888
|
OrderCreatedStateSchema,
|
|
1731
1889
|
OrderRefSchema,
|
|
@@ -1736,12 +1894,15 @@ export {
|
|
|
1736
1894
|
PaymentInstructStateSchema,
|
|
1737
1895
|
PendingOrderStateSchema,
|
|
1738
1896
|
PollSpecSchema,
|
|
1897
|
+
PreferredIdentityDocumentSchema,
|
|
1898
|
+
QuoteLimitRangeSchema,
|
|
1739
1899
|
QuoteLimitsSchema,
|
|
1740
1900
|
QuoteSchema,
|
|
1741
1901
|
QuoteStateSchema,
|
|
1742
1902
|
RecoverySchema,
|
|
1743
1903
|
RefundSchema,
|
|
1744
1904
|
ReportSpecSchema,
|
|
1905
|
+
STANDARD_KYC_FIELD_IDS,
|
|
1745
1906
|
STATE_KEYS,
|
|
1746
1907
|
SelectOptionSchema,
|
|
1747
1908
|
SessionAuthStateSchema,
|
|
@@ -1752,6 +1913,7 @@ export {
|
|
|
1752
1913
|
TABLE_VERSION,
|
|
1753
1914
|
TERMINAL_ORDER_STATUSES,
|
|
1754
1915
|
TRANSITION_TABLE,
|
|
1916
|
+
TermsAcceptanceSchema,
|
|
1755
1917
|
TransitionInputsSchema,
|
|
1756
1918
|
TransitionParamsSchema,
|
|
1757
1919
|
TransitionSchema,
|
package/dist/schemas.d.ts
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
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 { BlockedReason, CryptoAmount, FailureReason, FeeLine, FiatEndpoint, FieldSpec, FlowState, FormDescriptor, FormField, FormFieldType,
|
|
30
|
+
import type { BlockedReason, CryptoAmount, FailureReason, FeeLine, FiatAmount, FiatEndpoint, FiatProvider, FiatStepResponse, FieldSpec, FlowState, FormDescriptor, FormField, FormFieldType, InstructionField, Instructions, Instrument, JsonValue, KYCProvider, OrderRef, OrderStatus, OrderSummary, PollSpec, Quote, QuoteLimitRange, QuoteLimits, Recovery, Refund, ReportSpec, SelectOption, StatusHistoryEntry, Surface, PreferredIdentityDocument, ClientExecutedDelivery, KycSdkResultReport, SurfaceKind, SurfaceProvider, TermsAcceptance, 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. */
|
|
@@ -43,6 +43,7 @@ export declare const FiatAmountSchema: z.ZodType<FiatAmount>;
|
|
|
43
43
|
export declare const CryptoAmountSchema: z.ZodType<CryptoAmount>;
|
|
44
44
|
export declare const FeeLineSchema: z.ZodType<FeeLine>;
|
|
45
45
|
export declare const QuoteSchema: z.ZodType<Quote>;
|
|
46
|
+
export declare const QuoteLimitRangeSchema: z.ZodType<QuoteLimitRange>;
|
|
46
47
|
export declare const QuoteLimitsSchema: z.ZodType<QuoteLimits>;
|
|
47
48
|
export declare const InstrumentSchema: z.ZodType<Instrument>;
|
|
48
49
|
export declare const OrderRefSchema: z.ZodType<OrderRef>;
|
|
@@ -51,6 +52,10 @@ export declare const StatusHistoryEntrySchema: z.ZodType<StatusHistoryEntry>;
|
|
|
51
52
|
export declare const TxSchema: z.ZodType<Tx>;
|
|
52
53
|
export declare const RefundSchema: z.ZodType<Refund>;
|
|
53
54
|
export declare const SurfaceKindSchema: z.ZodType<SurfaceKind>;
|
|
55
|
+
export declare const PreferredIdentityDocumentSchema: z.ZodType<PreferredIdentityDocument>;
|
|
56
|
+
export declare const STANDARD_KYC_FIELD_IDS: readonly ["LEGAL_FIRST_NAME", "LEGAL_MIDDLE_NAME", "LEGAL_LAST_NAME", "DATE_OF_BIRTH", "COUNTRY_OF_BIRTH", "CITY_OF_BIRTH", "NATIONALITY", "EMAIL_ADDRESS", "EMAIL_OWNERSHIP_VERIFICATION", "PHONE_NUMBER", "PHONE_OWNERSHIP_VERIFICATION", "RESIDENTIAL_ADDRESS_LINE_1", "RESIDENTIAL_ADDRESS_LINE_2", "RESIDENTIAL_ADDRESS_CITY", "RESIDENTIAL_ADDRESS_REGION", "RESIDENTIAL_ADDRESS_POSTAL_CODE", "RESIDENTIAL_ADDRESS_COUNTRY", "TAX_IDENTIFIER", "TAX_RESIDENCE_COUNTRY", "EMPLOYMENT_STATUS", "OCCUPATION_INDUSTRY", "OCCUPATION_TITLE", "SOURCE_OF_FUNDS_DECLARATION", "SOURCE_OF_INCOME_DECLARATION", "SOURCE_OF_WEALTH_DECLARATION", "PURPOSE_OF_SERVICE", "PURPOSE_OF_TRANSACTION", "CUSTOMER_CATEGORY"];
|
|
57
|
+
export declare const KYC_ACKNOWLEDGEMENT_FIELD_IDS: readonly ["RISK_ACKNOWLEDGEMENT", "COOLING_OFF_ACKNOWLEDGEMENT", "TERMS_ACCEPTANCE"];
|
|
58
|
+
export declare const ClientExecutedDeliverySchema: z.ZodType<ClientExecutedDelivery>;
|
|
54
59
|
export declare const SurfaceSchema: z.ZodType<Surface>;
|
|
55
60
|
export declare const InstructionFieldSchema: z.ZodType<InstructionField>;
|
|
56
61
|
export declare const InstructionsSchema: z.ZodType<Instructions>;
|
|
@@ -58,8 +63,10 @@ export declare const SelectOptionSchema: z.ZodType<SelectOption>;
|
|
|
58
63
|
export declare const FormFieldTypeSchema: z.ZodType<FormFieldType>;
|
|
59
64
|
export declare const FormFieldSchema: z.ZodType<FormField>;
|
|
60
65
|
export declare const FormDescriptorSchema: z.ZodType<FormDescriptor>;
|
|
61
|
-
export declare const FIELD_SPEC_TYPES: readonly ["TEXT", "DATE", "COUNTRY", "FILE", "SELECT", "HOSTED_LINK", "FORM_VALUES"];
|
|
66
|
+
export declare const FIELD_SPEC_TYPES: readonly ["TEXT", "DATE", "COUNTRY", "FILE", "SELECT", "HOSTED_LINK", "FORM_VALUES", "TERMS_ACCEPTANCE"];
|
|
62
67
|
export declare const FieldSpecSchema: z.ZodType<FieldSpec>;
|
|
68
|
+
/** The value a `TERMS_ACCEPTANCE` input yields. */
|
|
69
|
+
export declare const TermsAcceptanceSchema: z.ZodType<TermsAcceptance>;
|
|
63
70
|
/**
|
|
64
71
|
* True when a `params` value is wearing a `FieldSpec`'s clothes — i.e. the exact defect the
|
|
65
72
|
* accepted split exists to kill (a spec object spread into a request body as if it were a value).
|
|
@@ -69,6 +76,7 @@ export declare const TransitionParamsSchema: z.ZodType<TransitionParams>;
|
|
|
69
76
|
export declare const TransitionInputsSchema: z.ZodType<TransitionInputs>;
|
|
70
77
|
export declare const PollSpecSchema: z.ZodType<PollSpec>;
|
|
71
78
|
export declare const ReportSpecSchema: z.ZodType<ReportSpec>;
|
|
79
|
+
export declare const KycSdkResultReportSchema: z.ZodType<KycSdkResultReport>;
|
|
72
80
|
export declare const TransitionSchema: z.ZodType<Transition>;
|
|
73
81
|
export declare const AuthChallengeSchema: z.ZodType<Record<string, JsonValue>>;
|
|
74
82
|
/** `Extract` keeps each per-state export pinned to the union member it validates. */
|
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.
|
|
39
|
+
var version = "0.11.0";
|
|
40
40
|
|
|
41
41
|
// src/table.ts
|
|
42
42
|
var TABLE_VERSION = version;
|
|
@@ -151,7 +151,8 @@ var TRANSITION_TABLE = {
|
|
|
151
151
|
terminal: false,
|
|
152
152
|
notes: [
|
|
153
153
|
"A bad code re-enters this same state with error {category: USER_ERROR, code: BAD_CODE} \u2014 banner, not a screen change.",
|
|
154
|
-
"No resend affordance is specified in either doc (known gap, FE doc Screen 2)."
|
|
154
|
+
"No resend affordance is specified in either doc (known gap, FE doc Screen 2).",
|
|
155
|
+
"A provider-owned surface may declare client-executed delivery from allowlisted CURRENT_SESSION_INPUTS only."
|
|
155
156
|
]
|
|
156
157
|
},
|
|
157
158
|
"KYC/CAPTURE": {
|
|
@@ -161,6 +162,12 @@ var TRANSITION_TABLE = {
|
|
|
161
162
|
screens: [8],
|
|
162
163
|
docStatus: "SPECIFIED",
|
|
163
164
|
allowedTransitions: [
|
|
165
|
+
{
|
|
166
|
+
id: "refresh_sdk_token",
|
|
167
|
+
mode: "SUBMIT",
|
|
168
|
+
endpoint: "POST /fiat/kyc/sdk-token",
|
|
169
|
+
note: "Refreshes an expired SDK credential and returns a new KYC/CAPTURE state."
|
|
170
|
+
},
|
|
164
171
|
{
|
|
165
172
|
id: "capture",
|
|
166
173
|
mode: "CLIENT_SURFACE",
|
|
@@ -180,7 +187,10 @@ var TRANSITION_TABLE = {
|
|
|
180
187
|
"ORDER/AWAITING_CONFIRMATION"
|
|
181
188
|
],
|
|
182
189
|
terminal: false,
|
|
183
|
-
notes: [
|
|
190
|
+
notes: [
|
|
191
|
+
"Carries the handshake-gated KYC_SDK surface; provider selects the identity-vendor adapter.",
|
|
192
|
+
"SDK completion or failure is only a launch report; eligibility is re-read authoritatively by the backend."
|
|
193
|
+
]
|
|
184
194
|
},
|
|
185
195
|
"KYC/INPUT_REQUIRED": {
|
|
186
196
|
key: "KYC/INPUT_REQUIRED",
|
|
@@ -436,10 +446,6 @@ var DOCUMENTED_ENDPOINTS = [
|
|
|
436
446
|
"POST /fiat/orders/:id/surface-result"
|
|
437
447
|
];
|
|
438
448
|
var UNOFFERED_ENDPOINTS = [
|
|
439
|
-
{
|
|
440
|
-
endpoint: "POST /fiat/kyc/sdk-token",
|
|
441
|
-
reason: "SDK token refresh is adapter-driven rather than a flow transition."
|
|
442
|
-
},
|
|
443
449
|
{
|
|
444
450
|
endpoint: "GET /fiat/payment-methods",
|
|
445
451
|
reason: "Discovery, called before any flow state exists \u2014 there is no state to hang it off."
|
package/dist/table.mjs
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -151,10 +151,23 @@ export interface Quote {
|
|
|
151
151
|
paymentMethod: PaymentMethodCategory;
|
|
152
152
|
expiresAt: string;
|
|
153
153
|
}
|
|
154
|
-
/**
|
|
154
|
+
/** One inclusive amount interval the current routing set can actually serve. */
|
|
155
|
+
export interface QuoteLimitRange {
|
|
156
|
+
min: string;
|
|
157
|
+
max: string;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Input bounds for the quote screen. Decimal strings in the quote currency.
|
|
161
|
+
*
|
|
162
|
+
* `min`/`max` remain the bounding envelope for clients on contract <=0.9. A routing set can have
|
|
163
|
+
* gaps (one path serves a low interval and another a higher interval), so new emitters also carry
|
|
164
|
+
* the exact sorted, non-overlapping intervals in `serviceableRanges`. The field is optional only so
|
|
165
|
+
* an upgraded client can cold-render an older recorded response during a rolling deployment.
|
|
166
|
+
*/
|
|
155
167
|
export interface QuoteLimits {
|
|
156
168
|
min: string;
|
|
157
169
|
max: string;
|
|
170
|
+
serviceableRanges?: QuoteLimitRange[];
|
|
158
171
|
}
|
|
159
172
|
/** Deferred to the credit-card iteration (FE doc port ledger) — optional wherever it appears. */
|
|
160
173
|
export interface Instrument {
|
|
@@ -228,8 +241,7 @@ export type SurfaceProvider = FiatProvider | KYCProvider;
|
|
|
228
241
|
* `params` values are secrets by default. Client redaction defaults every key closed and opens
|
|
229
242
|
* only names that are safe under *any* provider's vocabulary.
|
|
230
243
|
*/
|
|
231
|
-
|
|
232
|
-
kind: SurfaceKind;
|
|
244
|
+
interface SurfaceBase {
|
|
233
245
|
provider: SurfaceProvider;
|
|
234
246
|
params: Record<string, JsonValue>;
|
|
235
247
|
/**
|
|
@@ -238,6 +250,49 @@ export interface Surface {
|
|
|
238
250
|
*/
|
|
239
251
|
expiresAt?: string;
|
|
240
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Identity-document kinds the Sumsub adapter may prefer for the `IDENTITY` step.
|
|
255
|
+
*
|
|
256
|
+
* These are the Sumsub identity spellings understood by the capture adapter. Adding a new kind is
|
|
257
|
+
* a contract change: the client adapter must know how to pass it to the identity SDK and every
|
|
258
|
+
* relying provider must either map or explicitly decline it.
|
|
259
|
+
*/
|
|
260
|
+
export type KycIdentityDocumentType = 'PASSPORT' | 'ID_CARD' | 'DRIVERS' | 'RESIDENCE_PERMIT' | 'VISA';
|
|
261
|
+
/** Provider-native document definition understood by the selected KYC adapter. */
|
|
262
|
+
export interface PreferredIdentityDocument {
|
|
263
|
+
idDocType: KycIdentityDocumentType;
|
|
264
|
+
/** ISO 3166-1 alpha-3 country code expected by the identity-vendor SDK. */
|
|
265
|
+
country: string;
|
|
266
|
+
}
|
|
267
|
+
/** Canonical KYC values a declarative form or current-session delivery may name. */
|
|
268
|
+
export type StandardKycFieldId = 'LEGAL_FIRST_NAME' | 'LEGAL_MIDDLE_NAME' | 'LEGAL_LAST_NAME' | 'DATE_OF_BIRTH' | 'COUNTRY_OF_BIRTH' | 'CITY_OF_BIRTH' | 'NATIONALITY' | 'EMAIL_ADDRESS' | 'EMAIL_OWNERSHIP_VERIFICATION' | 'PHONE_NUMBER' | 'PHONE_OWNERSHIP_VERIFICATION' | 'RESIDENTIAL_ADDRESS_LINE_1' | 'RESIDENTIAL_ADDRESS_LINE_2' | 'RESIDENTIAL_ADDRESS_CITY' | 'RESIDENTIAL_ADDRESS_REGION' | 'RESIDENTIAL_ADDRESS_POSTAL_CODE' | 'RESIDENTIAL_ADDRESS_COUNTRY' | 'TAX_IDENTIFIER' | 'TAX_RESIDENCE_COUNTRY' | 'EMPLOYMENT_STATUS' | 'OCCUPATION_INDUSTRY' | 'OCCUPATION_TITLE' | 'SOURCE_OF_FUNDS_DECLARATION' | 'SOURCE_OF_INCOME_DECLARATION' | 'SOURCE_OF_WEALTH_DECLARATION' | 'PURPOSE_OF_SERVICE' | 'PURPOSE_OF_TRANSACTION' | 'CUSTOMER_CATEGORY';
|
|
269
|
+
export type KycAcknowledgementFieldId = 'RISK_ACKNOWLEDGEMENT' | 'COOLING_OFF_ACKNOWLEDGEMENT' | 'TERMS_ACCEPTANCE';
|
|
270
|
+
export type QuestionnaireKycFieldId = `QUESTIONNAIRE_RESPONSE:${string}:${string}`;
|
|
271
|
+
export type KycFieldId = StandardKycFieldId | KycAcknowledgementFieldId | QuestionnaireKycFieldId;
|
|
272
|
+
export interface ClientExecutedDelivery {
|
|
273
|
+
channel: 'CLIENT_EXECUTED';
|
|
274
|
+
/**
|
|
275
|
+
* A hard security boundary: only values entered during this mounted session may be selected.
|
|
276
|
+
* Backend-stored KYC values are never returned to the client through this contract.
|
|
277
|
+
*/
|
|
278
|
+
source: 'CURRENT_SESSION_INPUTS';
|
|
279
|
+
/** Server-declared canonical field ids the adapter may select; this carries no field values. */
|
|
280
|
+
allowedFieldIds: KycFieldId[];
|
|
281
|
+
}
|
|
282
|
+
export type Surface = (SurfaceBase & {
|
|
283
|
+
kind: 'KYC_SDK';
|
|
284
|
+
provider: KYCProvider;
|
|
285
|
+
/** Credentials and adapter configuration only; never backend-stored KYC field values. */
|
|
286
|
+
params: Record<string, JsonValue>;
|
|
287
|
+
preferredDocuments?: {
|
|
288
|
+
IDENTITY: PreferredIdentityDocument;
|
|
289
|
+
};
|
|
290
|
+
}) | (SurfaceBase & {
|
|
291
|
+
kind: Exclude<SurfaceKind, 'KYC_SDK'>;
|
|
292
|
+
provider: FiatProvider;
|
|
293
|
+
/** Adapter-owned submission mechanics; the shared contract declares only the safe source. */
|
|
294
|
+
delivery?: ClientExecutedDelivery;
|
|
295
|
+
});
|
|
241
296
|
export interface InstructionField {
|
|
242
297
|
id: string;
|
|
243
298
|
labelFallback: string;
|
|
@@ -275,17 +330,28 @@ export interface SelectOption {
|
|
|
275
330
|
}
|
|
276
331
|
/** Field types inside a KYC form round. A new field TYPE is a client release (handshake-gated). */
|
|
277
332
|
export type FormFieldType = 'TEXT' | 'DATE' | 'SELECT' | 'COUNTRY' | 'FILE' | 'HOSTED_LINK';
|
|
278
|
-
|
|
279
|
-
id:
|
|
280
|
-
type: FormFieldType;
|
|
333
|
+
interface FormFieldBase {
|
|
334
|
+
id: KycFieldId;
|
|
281
335
|
labelFallback: string;
|
|
282
336
|
required: boolean;
|
|
283
|
-
regex?: string;
|
|
284
|
-
/** SELECT only. */
|
|
285
|
-
options?: SelectOption[];
|
|
286
|
-
/** HOSTED_LINK only. */
|
|
287
|
-
url?: string;
|
|
288
337
|
}
|
|
338
|
+
/** Invalid property combinations (for example `options` on TEXT) are unrepresentable. */
|
|
339
|
+
export type FormField = (FormFieldBase & {
|
|
340
|
+
type: 'TEXT';
|
|
341
|
+
regex?: string;
|
|
342
|
+
}) | (FormFieldBase & {
|
|
343
|
+
type: 'DATE';
|
|
344
|
+
}) | (FormFieldBase & {
|
|
345
|
+
type: 'SELECT';
|
|
346
|
+
options: SelectOption[];
|
|
347
|
+
}) | (FormFieldBase & {
|
|
348
|
+
type: 'COUNTRY';
|
|
349
|
+
}) | (FormFieldBase & {
|
|
350
|
+
type: 'FILE';
|
|
351
|
+
}) | (FormFieldBase & {
|
|
352
|
+
type: 'HOSTED_LINK';
|
|
353
|
+
url: string;
|
|
354
|
+
});
|
|
289
355
|
/** Payload of `KYC{INPUT_REQUIRED}`. One uniform renderer walks `fields`. */
|
|
290
356
|
export interface FormDescriptor {
|
|
291
357
|
formId: string;
|
|
@@ -297,7 +363,7 @@ export interface FormDescriptor {
|
|
|
297
363
|
* transition never re-carries the descriptor (the double-carry disease Δ2 killed).
|
|
298
364
|
*
|
|
299
365
|
* Note: the contract's illustrative list reads `'TEXT' | 'NUMBER' | …`; the conformance-package
|
|
300
|
-
* spec closes the set at the
|
|
366
|
+
* spec closes the set at the eight below, so `NUMBER` is not declared here.
|
|
301
367
|
*/
|
|
302
368
|
export type FieldSpec = {
|
|
303
369
|
type: 'TEXT';
|
|
@@ -323,8 +389,28 @@ export type FieldSpec = {
|
|
|
323
389
|
labelFallback?: string;
|
|
324
390
|
} | {
|
|
325
391
|
type: 'FORM_VALUES';
|
|
392
|
+
} | {
|
|
393
|
+
type: 'TERMS_ACCEPTANCE';
|
|
394
|
+
/** The document being accepted. The client shows it; the user cannot accept what they cannot read. */
|
|
395
|
+
documentUrl: string;
|
|
396
|
+
/**
|
|
397
|
+
* Which revision of that document. Opaque to the client and echoed back verbatim, so a later
|
|
398
|
+
* dispute names the text the user saw rather than whatever is at `documentUrl` today.
|
|
399
|
+
*/
|
|
400
|
+
version: string;
|
|
401
|
+
labelFallback?: string;
|
|
326
402
|
};
|
|
327
403
|
export type FieldSpecType = FieldSpec['type'];
|
|
404
|
+
/**
|
|
405
|
+
* What a `TERMS_ACCEPTANCE` input collects. A bare timestamp answers "when" and nothing else; the
|
|
406
|
+
* version is what ties the moment to the words, which is the only part a dispute turns on.
|
|
407
|
+
*/
|
|
408
|
+
export interface TermsAcceptance {
|
|
409
|
+
/** Echoed from the `FieldSpec`, unmodified. */
|
|
410
|
+
version: string;
|
|
411
|
+
/** ISO-8601, stamped by the client when the user accepted. Never defaulted server-side. */
|
|
412
|
+
acceptedAt: string;
|
|
413
|
+
}
|
|
328
414
|
/** Server literals — spread into the body verbatim, opaque to the client. */
|
|
329
415
|
export type TransitionParams = Record<string, JsonValue>;
|
|
330
416
|
/** Client-collected specs — the harness renders + locally validates, then merges values in. */
|
|
@@ -378,7 +464,22 @@ export interface ReportSpec {
|
|
|
378
464
|
* Declared so conformance can assert the surface actually yields them.
|
|
379
465
|
*/
|
|
380
466
|
expects?: string[];
|
|
467
|
+
/** Typed SDK result injected under `bodyKey`; mutually exclusive outcome shapes are below. */
|
|
468
|
+
result?: {
|
|
469
|
+
type: 'KYC_SDK_RESULT';
|
|
470
|
+
bodyKey: string;
|
|
471
|
+
};
|
|
381
472
|
}
|
|
473
|
+
/**
|
|
474
|
+
* Client report for a KYC SDK launch. This reports SDK completion, not KYC eligibility: only the
|
|
475
|
+
* backend's authoritative provider read can advance eligibility.
|
|
476
|
+
*/
|
|
477
|
+
export type KycSdkResultReport = {
|
|
478
|
+
outcome: 'COMPLETED';
|
|
479
|
+
} | {
|
|
480
|
+
outcome: 'FAILED';
|
|
481
|
+
errorType: string;
|
|
482
|
+
};
|
|
382
483
|
export type TransitionMode = 'SUBMIT' | 'AWAIT' | 'CLIENT_SURFACE';
|
|
383
484
|
/**
|
|
384
485
|
* Body assembly, harness-owned, one rule:
|
|
@@ -531,16 +632,33 @@ export type FlowState =
|
|
|
531
632
|
error?: FailureReason;
|
|
532
633
|
};
|
|
533
634
|
export type FlowStateKind = FlowState['kind'];
|
|
534
|
-
export type BlockedReason = 'REGION_UNSUPPORTED' | 'ASSET_UNSUPPORTED' | 'ALL_PROVIDERS_DECLINED' | 'AMOUNT_UNSERVABLE'
|
|
635
|
+
export type BlockedReason = 'REGION_UNSUPPORTED' | 'ASSET_UNSUPPORTED' | 'ALL_PROVIDERS_DECLINED' | 'AMOUNT_UNSERVABLE'
|
|
636
|
+
/** The installed client did not declare a capability required by every otherwise-viable route. */
|
|
637
|
+
| 'CLIENT_UPGRADE_REQUIRED';
|
|
535
638
|
/**
|
|
536
639
|
* Every `/fiat/*` response. `transitions: []` = nothing the wizard can do; whether the *flow*
|
|
537
640
|
* is over is read from the table (`terminal`), never from array emptiness (ON_HOLD is the proof).
|
|
538
641
|
*/
|
|
539
|
-
|
|
540
|
-
state: FlowState;
|
|
541
|
-
provider: FiatProvider;
|
|
642
|
+
type FiatStepResponseBase = {
|
|
542
643
|
transitions: Transition[];
|
|
543
644
|
/** TODO(open-decision): `orderId` placement — see `PAYMENT{INSTRUCT}.orderId` above. */
|
|
544
645
|
orderId?: string;
|
|
545
|
-
}
|
|
646
|
+
};
|
|
647
|
+
/**
|
|
648
|
+
* Provider is absent only when routing ended before a provider could be selected. A BLOCKED response
|
|
649
|
+
* may still name a provider for compatibility with provider-specific terminal decisions; every other
|
|
650
|
+
* state is provider-bound and therefore requires one.
|
|
651
|
+
*/
|
|
652
|
+
export type FiatStepResponse = (FiatStepResponseBase & {
|
|
653
|
+
state: Extract<FlowState, {
|
|
654
|
+
kind: 'BLOCKED';
|
|
655
|
+
}>;
|
|
656
|
+
provider?: FiatProvider;
|
|
657
|
+
}) | (FiatStepResponseBase & {
|
|
658
|
+
state: Exclude<FlowState, {
|
|
659
|
+
kind: 'BLOCKED';
|
|
660
|
+
}>;
|
|
661
|
+
provider: FiatProvider;
|
|
662
|
+
});
|
|
663
|
+
export {};
|
|
546
664
|
//# sourceMappingURL=types.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fun-xyz/fiat-contract",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.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": {
|