@fun-xyz/fiat-contract 0.18.0 → 0.20.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 CHANGED
@@ -13,13 +13,14 @@ Four things, zero runtime logic beyond validation:
13
13
  | `src/table.ts` | the transition table **as data**: per state, its legal transition set, the states any call from it may return, and `terminal: boolean` |
14
14
  | `src/assert.ts` + `src/fixtures/` | `assertFiatStepResponse` · `assertLegalEmission` · `assertLegalReturn` · `walkTable` · fixture loader + 18 recorded step responses (inlined as data — no filesystem, so React Native can bundle it) |
15
15
 
16
- ## Four entry points — production vs test-time
16
+ ## Five entry points — production vs test-time
17
17
 
18
18
  | Import | Weight | Contains | Used by |
19
19
  | --- | --- | --- | --- |
20
20
  | `@fun-xyz/fiat-contract/types` | **0.1 KB** (types erase) | every type; no runtime values | production, both repos |
21
21
  | `@fun-xyz/fiat-contract/table` | **13.8 KB**, zero deps | `TRANSITION_TABLE`, `stateKey`, `tableEntry`, `isTerminal`, `walkTable`, `TABLE_VERSION`, `TERMINAL_ORDER_STATUSES`, `DOCUMENTED_ENDPOINTS`, `UNOFFERED_ENDPOINTS`, `UNREACHABLE_STATES`, `unofferedEndpoints`, `unreachableStates` | **production frontend** + backend |
22
- | `@fun-xyz/fiat-contract/moonpay` | zero deps | the MoonPay provider-owned flow: request, response, standing and fault types, and `MOONPAY_ENDPOINTS` | **production frontend** (MoonPay adapter) + backend |
22
+ | `@fun-xyz/fiat-contract/moonpay` | zero deps | the MoonPay provider-owned flow: `intent` request kinds, response statuses and fault types, and `MOONPAY_ENDPOINTS` | **production frontend** (MoonPay adapter) + backend |
23
+ | `@fun-xyz/fiat-contract/terms` | zero deps | the platform terms route: `TermsDocument`, `FiatTermsRequest`, `FiatTermsResponse`, fault types, and `FIAT_TERMS_ENDPOINT` | **production frontend** + backend |
23
24
  | `@fun-xyz/fiat-contract` | 47.6 KB, needs zod | the above + 46 zod schemas + assertions + 18 fixtures | tests, and backend dev/test guards |
24
25
 
25
26
  `./table` is not a micro-optimisation. Terminality is table data a **shipped** client must read
@@ -98,18 +99,48 @@ this package is [ENG-5268](https://linear.app/funxyz/issue/ENG-5268).
98
99
 
99
100
  A provider can run its own flow instead of the shared step machine. `POST /fiat/quote` still routes
100
101
  and prices. When the request's `handoffs` lists the winning provider, the response carries
101
- `handoff: { provider }` and no transitions, and the client calls that provider's routes under
102
- `POST /fiat/providers/{provider}/…`. The schema refuses a handoff to a provider other than the
103
- response's own, and a handoff that still offers transitions.
104
-
105
- MoonPay is the first (`./moonpay`). Its routes are `start`, `intent`, `kyc`, and `complete`, in
106
- `MOONPAY_ENDPOINTS`. Only `intent` decides: it answers with a `MoonPayStanding` (`READY`, `KYC`,
107
- `PENDING`, or `REFUSED`). `kyc` answers `{ status: 'ACCEPTED' }`, and the client then calls `intent`.
108
- `complete` answers with the same step response as `GET /fiat/orders/:orderId`. A fault is a 4xx or
109
- 5xx with a `MoonPayErrorBody`: the shared `errorCode`, `errorMsg` and `reqId`, plus `reason`,
110
- `recovery`, and `fields` on a rejected KYC value. `recovery` is `RETRY` (send the same request),
111
- `REFRESH` (call `intent` for a new standing, as after a stale form), `REQUOTE`, or `NONE`. The zod
112
- schemas (`MoonPay*Schema`) are exported from the root only. A handoff appears only on a `QUOTE`.
102
+ `handoff: { provider, terms }` and no transitions, and the client calls that provider's
103
+ `POST /fiat/providers/{provider}/intent`. `terms` lists the `CHECKOUT` documents this login has not
104
+ accepted at the current version; it is empty for a returning buyer. The schema refuses a handoff on
105
+ any state but `QUOTE`, a handoff to a provider other than the response's own, a handoff that still
106
+ offers transitions, and a `terms` entry that is not a unique `CHECKOUT` document.
107
+
108
+ **Terms (`./terms`).** Every provider records acceptances through the platform `POST /fiat/terms`
109
+ (`FIAT_TERMS_ENDPOINT`), with an `Idempotency-Key`. A `TermsDocument` has an `id`, a `kind`
110
+ (`FUN_TERMS`, `PROVIDER_TERMS`, `FUN_KYC_COLLECTION`, `PROVIDER_KYC_SHARING`), a `stage` (`CHECKOUT` or
111
+ `KYC`), an opaque `version` and `contentDigest`, and at least one `https` link. `FiatTermsRequest` is
112
+ `{ provider, acceptances: [{ documentId, version, contentDigest, acceptedAt }] }`, each document at
113
+ most once; `FiatTermsResponse` is `{ recorded }`, the recorded document ids. A fault is a
114
+ `FiatTermsErrorBody`: the shared `errorCode`, `errorMsg` and `reqId`, plus `reason` (`STALE_TERMS`,
115
+ `IN_PROGRESS`, `IDEMPOTENCY_KEY_REUSED`, `INVALID_REQUEST`, `RATE_LIMITED`) and `recovery`.
116
+
117
+ **MoonPay (`./moonpay`).** One route, `MOONPAY_ENDPOINTS.intent`. A `MoonPayIntentRequest` is one of
118
+ three kinds:
119
+
120
+ | `kind` | Carries | `Idempotency-Key` |
121
+ | --- | --- | --- |
122
+ | `observe` | `checkout`, and `credentials` (`customerId`, `accessToken`) once the SDK has them | ignored |
123
+ | `kyc` | `checkout`, `credentials`, `values` keyed by field id (no form id) | required |
124
+ | `order` | `orderId`, `transactionId` | required |
125
+
126
+ A `MoonPayIntentResponse` says what the backend needs next:
127
+
128
+ | `status` | Carries | The client then |
129
+ | --- | --- | --- |
130
+ | `TERMS` | `stage`, `documents` (at least one, all at `stage`) | sends `POST /fiat/terms`, then `observe` |
131
+ | `SDK_AUTH` | `sessionToken`, `quoteInput` | runs the MoonPay connection check, then `observe` with credentials |
132
+ | `KYC` | `fields`, `fieldErrors?` (each naming a field in `fields`) | shows the shared form, then sends `kyc` |
133
+ | `PENDING` | `retryAfterMs` (at most 60 s) | sends the same request again after the delay |
134
+ | `READY` | `orderId`, `quoteInput` | mounts Apple Pay, then sends `order` after the charge |
135
+ | `DONE` | `order`, the `GET /fiat/orders/:orderId` response | shows the shared order status |
136
+ | `REFUSED` | `reason` (`NOT_ELIGIBLE`, `DOCUMENTS_REQUIRED`, `FINAL_REJECTION`, `CUSTOMER_MISMATCH`), `recovery: REQUOTE \| NONE` | exits the provider flow |
137
+
138
+ A fault is a 4xx or 5xx with a `MoonPayErrorBody`: the shared `errorCode`, `errorMsg` and `reqId`,
139
+ plus `reason` (`SESSION_BUDGET_SPENT`, `RATE_LIMITED`, `PROVIDER_UNAVAILABLE`, `IN_PROGRESS`,
140
+ `IDEMPOTENCY_KEY_REUSED`, `INVALID_REQUEST`, `TRANSACTION_MISMATCH`, `WALLET_MISMATCH`) and `recovery`:
141
+ `RETRY` (send the same request, with the same key), `REQUOTE`, or `NONE`. A field error is a `KYC`
142
+ response, not a fault. The zod schemas (`MoonPay*Schema`, `TermsDocumentSchema`, `FiatTerms*Schema`)
143
+ are exported from the root only.
113
144
 
114
145
  ## KYC SDK hints, results, and client delivery
115
146
 
@@ -461,8 +492,8 @@ Dual CJS + ESM, same shape as `@funkit/fun-relay`, because the two consumers loa
461
492
  | --- | --- | --- |
462
493
  | `require()` must work | `fun-backend/apps/api-server` compiles `module: commonjs` and runs plain node — no bundler | esbuild emits `dist/index.js` (CJS) + `dist/index.mjs` (ESM); `exports` maps `require`/`import`; no `"type": "module"` |
463
494
  | Zero Node builtins | `connect-core` is React Native — Metro cannot resolve `node:fs` | fixtures are inlined as generated TS data (`src/fixtures/data.ts`), so nothing touches the filesystem; `platform: browser`, every bare import external |
464
- | Subpath imports must resolve without `exports` support | Metro only reads `package.json#exports` from RN 0.79 on, and `@funkit/connect-rn` accepts `react-native: >=0.74` | root compat stubs `table.js` / `types.js` (+ `.d.ts`) that re-export `dist/`. Without them Metro fails with *"Unable to resolve module"* — verified, not theorised. Resolvers that do read `exports` never see the stubs |
465
- | Production code must not pull zod or fixtures | `connect-core` ships to React Native, where Metro does not tree-shake unused exports by default | four entry points: `./types` (erased), `./table` (13.8 KB, zero deps), `./moonpay` (zero deps), root (schemas + fixtures, test-time). CI asserts `require('.../table')` and `require('.../moonpay')` never load zod |
495
+ | Subpath imports must resolve without `exports` support | Metro only reads `package.json#exports` from RN 0.79 on, and `@funkit/connect-rn` accepts `react-native: >=0.74` | root compat stubs `table.js` / `types.js` / `moonpay.js` / `terms.js` (+ `.d.ts`) that re-export `dist/`. Without them Metro fails with *"Unable to resolve module"* — verified, not theorised. Resolvers that do read `exports` never see the stubs |
496
+ | Production code must not pull zod or fixtures | `connect-core` ships to React Native, where Metro does not tree-shake unused exports by default | five entry points: `./types` (erased), `./table` (13.8 KB, zero deps), `./moonpay` and `./terms` (zero deps), root (schemas + fixtures, test-time). CI asserts `require('.../table')`, `require('.../moonpay')` and `require('.../terms')` never load zod |
466
497
  | `.d.ts` must not lock a zod major | Both repos run `skipLibCheck: true`, which turns a broken declaration into a silent `any` | every exported schema is annotated `z.ZodType<T>`, so declarations name only `z.ZodType`; the structural drift checks stay module-private |
467
498
 
468
499
  Declarations come from `tsc --emitDeclarationOnly`; esbuild only emits JS. The fixture `.json` files
@@ -0,0 +1,7 @@
1
+ // src/terms.ts
2
+ var FIAT_TERMS_ENDPOINT = "POST /fiat/terms";
3
+
4
+ export {
5
+ FIAT_TERMS_ENDPOINT
6
+ };
7
+ //# sourceMappingURL=chunk-QXJ54JWX.mjs.map
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "0.18.0";
2
+ var version = "0.20.0";
3
3
 
4
4
  // src/table.ts
5
5
  var TABLE_VERSION = version;
@@ -514,4 +514,4 @@ export {
514
514
  tableEntry,
515
515
  isTerminal
516
516
  };
517
- //# sourceMappingURL=chunk-7KI7DJZ2.mjs.map
517
+ //# sourceMappingURL=chunk-TV6IWKTB.mjs.map
@@ -0,0 +1,9 @@
1
+ // src/providers/moonpay.ts
2
+ var MOONPAY_ENDPOINTS = {
3
+ intent: "POST /fiat/providers/moonpay/intent"
4
+ };
5
+
6
+ export {
7
+ MOONPAY_ENDPOINTS
8
+ };
9
+ //# sourceMappingURL=chunk-UN3WTIMI.mjs.map
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ export * from './codes';
10
10
  export * from './schemas';
11
11
  export * from './table';
12
12
  export * from './assert';
13
+ export * from './terms';
13
14
  export * from './providers/moonpay';
14
15
  export * from './providers/moonpay.schemas';
15
16
  export { FIXTURES, FIXTURE_COVERAGE_GAPS, fixtureMeta, loadFixture, loadFixtures, type FixtureMeta, type FixtureSource, type LoadedFixture, } from './fixtures/index';
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ __export(index_exports, {
32
32
  DOCUMENTED_ENDPOINTS: () => DOCUMENTED_ENDPOINTS,
33
33
  ENDPOINT_PATTERN: () => ENDPOINT_PATTERN,
34
34
  FIAT_CURRENCY_NAMES: () => FIAT_CURRENCY_NAMES,
35
+ FIAT_TERMS_ENDPOINT: () => FIAT_TERMS_ENDPOINT,
35
36
  FIELD_SPEC_TYPES: () => FIELD_SPEC_TYPES,
36
37
  FIXTURES: () => FIXTURES,
37
38
  FIXTURE_COVERAGE_GAPS: () => FIXTURE_COVERAGE_GAPS,
@@ -42,6 +43,9 @@ __export(index_exports, {
42
43
  FiatEndpointSchema: () => FiatEndpointSchema,
43
44
  FiatProviderSchema: () => FiatProviderSchema,
44
45
  FiatStepResponseSchema: () => FiatStepResponseSchema,
46
+ FiatTermsErrorBodySchema: () => FiatTermsErrorBodySchema,
47
+ FiatTermsRequestSchema: () => FiatTermsRequestSchema,
48
+ FiatTermsResponseSchema: () => FiatTermsResponseSchema,
45
49
  FieldSpecSchema: () => FieldSpecSchema,
46
50
  FlowStateSchema: () => FlowStateSchema,
47
51
  FormDescriptorSchema: () => FormDescriptorSchema,
@@ -60,17 +64,13 @@ __export(index_exports, {
60
64
  KycNoActionRequiredStateSchema: () => KycNoActionRequiredStateSchema,
61
65
  KycSdkResultReportSchema: () => KycSdkResultReportSchema,
62
66
  MOONPAY_ENDPOINTS: () => MOONPAY_ENDPOINTS,
63
- MoonPayCompleteRequestSchema: () => MoonPayCompleteRequestSchema,
64
- MoonPayCompleteResponseSchema: () => MoonPayCompleteResponseSchema,
67
+ MoonPayCheckoutSchema: () => MoonPayCheckoutSchema,
65
68
  MoonPayErrorBodySchema: () => MoonPayErrorBodySchema,
66
69
  MoonPayFaultSchema: () => MoonPayFaultSchema,
67
70
  MoonPayIntentRequestSchema: () => MoonPayIntentRequestSchema,
68
71
  MoonPayIntentResponseSchema: () => MoonPayIntentResponseSchema,
69
- MoonPayKycRequestSchema: () => MoonPayKycRequestSchema,
70
- MoonPayKycResponseSchema: () => MoonPayKycResponseSchema,
71
- MoonPayStandingSchema: () => MoonPayStandingSchema,
72
- MoonPayStartRequestSchema: () => MoonPayStartRequestSchema,
73
- MoonPayStartResponseSchema: () => MoonPayStartResponseSchema,
72
+ MoonPayQuoteInputSchema: () => MoonPayQuoteInputSchema,
73
+ MoonPaySdkCredentialsSchema: () => MoonPaySdkCredentialsSchema,
74
74
  OrderAwaitingConfirmationStateSchema: () => OrderAwaitingConfirmationStateSchema,
75
75
  OrderCreatedStateSchema: () => OrderCreatedStateSchema,
76
76
  OrderRefSchema: () => OrderRefSchema,
@@ -103,6 +103,8 @@ __export(index_exports, {
103
103
  TERMINAL_ORDER_STATUSES: () => TERMINAL_ORDER_STATUSES,
104
104
  TRANSITION_TABLE: () => TRANSITION_TABLE,
105
105
  TermsAcceptanceSchema: () => TermsAcceptanceSchema,
106
+ TermsDocumentAcceptanceSchema: () => TermsDocumentAcceptanceSchema,
107
+ TermsDocumentSchema: () => TermsDocumentSchema,
106
108
  TransitionInputsSchema: () => TransitionInputsSchema,
107
109
  TransitionParamsSchema: () => TransitionParamsSchema,
108
110
  TransitionSchema: () => TransitionSchema,
@@ -965,12 +967,50 @@ var $providerBoundFlowState = import_zod.z.union([
965
967
  ]);
966
968
  var $flowState = import_zod.z.union([$providerBoundFlowState, $blockedState]);
967
969
  var FlowStateSchema = $flowState;
970
+ var $nonBlank = import_zod.z.string().refine((value) => value.trim().length > 0, "must not be blank");
971
+ var $termsStage = import_zod.z.enum(["CHECKOUT", "KYC"]);
972
+ var $termsDocument = obj({
973
+ id: $nonBlank,
974
+ kind: import_zod.z.enum(["FUN_TERMS", "PROVIDER_TERMS", "FUN_KYC_COLLECTION", "PROVIDER_KYC_SHARING"]),
975
+ stage: $termsStage,
976
+ version: $nonBlank,
977
+ contentDigest: $nonBlank,
978
+ links: import_zod.z.array(obj({ label: $nonBlank, url: import_zod.z.string().regex(/^https:\/\/\S+$/, "must be an https URL") })).min(1)
979
+ });
980
+ var TermsDocumentSchema = $termsDocument;
981
+ var hasUniqueIds = (ids) => new Set(ids).size === ids.length;
982
+ var $termsDocuments = (stage) => import_zod.z.array($termsDocument).refine((documents) => hasUniqueIds(documents.map((document) => document.id)), "document ids must be unique").refine(
983
+ (documents) => stage === void 0 || documents.every((document) => document.stage === stage),
984
+ `every document must be at stage ${stage}`
985
+ );
986
+ var $termsAcceptance = obj({
987
+ documentId: $nonBlank,
988
+ version: $nonBlank,
989
+ contentDigest: $nonBlank,
990
+ acceptedAt: import_zod.z.string().datetime({ offset: true })
991
+ });
992
+ var TermsDocumentAcceptanceSchema = $termsAcceptance;
993
+ var $termsRequest = obj({
994
+ provider: $provider,
995
+ acceptances: import_zod.z.array($termsAcceptance).min(1).refine((acceptances) => hasUniqueIds(acceptances.map((acceptance) => acceptance.documentId)), "each document is accepted once")
996
+ });
997
+ var FiatTermsRequestSchema = $termsRequest;
998
+ var $termsResponse = obj({ recorded: import_zod.z.array($nonBlank) });
999
+ var FiatTermsResponseSchema = $termsResponse;
1000
+ var $termsErrorBody = obj({
1001
+ errorCode: $nonBlank,
1002
+ errorMsg: import_zod.z.string(),
1003
+ reqId: import_zod.z.string().optional(),
1004
+ reason: import_zod.z.enum(["STALE_TERMS", "IN_PROGRESS", "IDEMPOTENCY_KEY_REUSED", "INVALID_REQUEST", "RATE_LIMITED"]),
1005
+ recovery: import_zod.z.enum(["RETRY", "REQUOTE", "NONE"])
1006
+ });
1007
+ var FiatTermsErrorBodySchema = $termsErrorBody;
968
1008
  var $providerBoundStepResponse = obj({
969
1009
  state: $providerBoundFlowState,
970
1010
  provider: $provider,
971
1011
  transitions: import_zod.z.array($transition),
972
1012
  preparedPayment: $preparedPayment.optional(),
973
- handoff: obj({ provider: $provider }).optional(),
1013
+ handoff: obj({ provider: $provider, terms: $termsDocuments("CHECKOUT") }).optional(),
974
1014
  routingSessionRef: import_zod.z.string().optional(),
975
1015
  providerAuthToken: obj({
976
1016
  clientSecret: import_zod.z.string(),
@@ -1024,7 +1064,7 @@ var $stepResponse = import_zod.z.union([$providerBoundStepResponse, $blockedStep
1024
1064
  var FiatStepResponseSchema = $stepResponse;
1025
1065
 
1026
1066
  // package.json
1027
- var version = "0.18.0";
1067
+ var version = "0.20.0";
1028
1068
 
1029
1069
  // src/table.ts
1030
1070
  var TABLE_VERSION = version;
@@ -2590,12 +2630,12 @@ function assertAllFixtures() {
2590
2630
  return FIXTURES.map((meta) => assertFixture(meta.id));
2591
2631
  }
2592
2632
 
2633
+ // src/terms.ts
2634
+ var FIAT_TERMS_ENDPOINT = "POST /fiat/terms";
2635
+
2593
2636
  // src/providers/moonpay.ts
2594
2637
  var MOONPAY_ENDPOINTS = {
2595
- start: "POST /fiat/providers/moonpay/start",
2596
- intent: "POST /fiat/providers/moonpay/intent",
2597
- kyc: "POST /fiat/providers/moonpay/kyc",
2598
- complete: "POST /fiat/providers/moonpay/complete"
2638
+ intent: "POST /fiat/providers/moonpay/intent"
2599
2639
  };
2600
2640
 
2601
2641
  // src/providers/moonpay.schemas.ts
@@ -2603,14 +2643,27 @@ var import_zod2 = require("zod");
2603
2643
  var FIAT_CURRENCY_CODES2 = Object.keys(FIAT_CURRENCY_NAMES);
2604
2644
  var obj2 = (shape) => import_zod2.z.object(shape).strict();
2605
2645
  var $nonEmpty = import_zod2.z.string().refine((value) => value.trim().length > 0, "must not be blank");
2606
- var $startRequest = obj2({
2646
+ var hasUniqueIds2 = (ids) => new Set(ids).size === ids.length;
2647
+ var $checkout = obj2({
2607
2648
  amount: FiatAmountSchema,
2608
2649
  destination: obj2({ asset: $nonEmpty, network: $nonEmpty }),
2609
2650
  paymentMethod: import_zod2.z.literal("apple_pay"),
2610
- geo: obj2({ alpha2: import_zod2.z.string().regex(/^[A-Z]{2}$/), region: $nonEmpty.optional(), ip: $nonEmpty.optional() }),
2611
- termsAcceptance: obj2({ version: $nonEmpty, acceptedAt: import_zod2.z.string().datetime({ offset: true }) })
2651
+ geo: obj2({ alpha2: import_zod2.z.string().regex(/^[A-Z]{2}$/), region: $nonEmpty.optional(), ip: $nonEmpty.optional() })
2612
2652
  });
2613
- var MoonPayStartRequestSchema = $startRequest;
2653
+ var MoonPayCheckoutSchema = $checkout;
2654
+ var $credentials = obj2({ customerId: $nonEmpty, accessToken: $nonEmpty });
2655
+ var MoonPaySdkCredentialsSchema = $credentials;
2656
+ var $intentRequest = import_zod2.z.discriminatedUnion("kind", [
2657
+ obj2({ kind: import_zod2.z.literal("observe"), checkout: $checkout, credentials: $credentials.optional() }),
2658
+ obj2({
2659
+ kind: import_zod2.z.literal("kyc"),
2660
+ checkout: $checkout,
2661
+ credentials: $credentials,
2662
+ values: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.string()).refine((values) => Object.keys(values).length > 0, "values must not be empty")
2663
+ }),
2664
+ obj2({ kind: import_zod2.z.literal("order"), orderId: $nonEmpty, transactionId: $nonEmpty })
2665
+ ]);
2666
+ var MoonPayIntentRequestSchema = $intentRequest;
2614
2667
  var $quoteInput = obj2({
2615
2668
  source: obj2({ asset: obj2({ code: import_zod2.z.enum(FIAT_CURRENCY_CODES2) }), amount: $nonEmpty }),
2616
2669
  destination: obj2({ asset: obj2({ code: $nonEmpty }) }),
@@ -2618,46 +2671,53 @@ var $quoteInput = obj2({
2618
2671
  paymentMethod: obj2({ type: import_zod2.z.literal("apple_pay") }),
2619
2672
  feeBehavior: import_zod2.z.literal("inclusive")
2620
2673
  });
2621
- var $startResponse = obj2({ sessionToken: $nonEmpty, quoteInput: $quoteInput });
2622
- var MoonPayStartResponseSchema = $startResponse;
2623
- var $intentRequest = obj2({ customerId: $nonEmpty, accessToken: $nonEmpty });
2624
- var MoonPayIntentRequestSchema = $intentRequest;
2625
- var $refusal = import_zod2.z.enum(["DOCUMENTS_REQUIRED", "FINAL_REJECTION", "CUSTOMER_MISMATCH"]);
2626
- var $standing = import_zod2.z.discriminatedUnion("status", [
2627
- obj2({ status: import_zod2.z.literal("READY"), orderId: $nonEmpty }),
2628
- obj2({ status: import_zod2.z.literal("KYC"), form: FormDescriptorSchema }),
2674
+ var MoonPayQuoteInputSchema = $quoteInput;
2675
+ var $refusal = import_zod2.z.enum(["NOT_ELIGIBLE", "DOCUMENTS_REQUIRED", "FINAL_REJECTION", "CUSTOMER_MISMATCH"]);
2676
+ var $fieldError = obj2({ fieldId: $nonEmpty, message: $nonEmpty });
2677
+ var $intentStatus = import_zod2.z.discriminatedUnion("status", [
2678
+ obj2({ status: import_zod2.z.literal("TERMS"), stage: import_zod2.z.enum(["CHECKOUT", "KYC"]), documents: import_zod2.z.array(TermsDocumentSchema).min(1) }),
2679
+ obj2({ status: import_zod2.z.literal("SDK_AUTH"), sessionToken: $nonEmpty, quoteInput: $quoteInput }),
2680
+ obj2({ status: import_zod2.z.literal("KYC"), fields: import_zod2.z.array(FormFieldSchema).min(1), fieldErrors: import_zod2.z.array($fieldError).min(1).optional() }),
2629
2681
  obj2({ status: import_zod2.z.literal("PENDING"), retryAfterMs: import_zod2.z.number().int().positive().max(6e4) }),
2682
+ obj2({ status: import_zod2.z.literal("READY"), orderId: $nonEmpty, quoteInput: $quoteInput }),
2683
+ obj2({ status: import_zod2.z.literal("DONE"), order: FiatStepResponseSchema }),
2630
2684
  obj2({ status: import_zod2.z.literal("REFUSED"), reason: $refusal, recovery: import_zod2.z.enum(["REQUOTE", "NONE"]) })
2631
2685
  ]);
2632
- var MoonPayStandingSchema = $standing;
2633
- var MoonPayIntentResponseSchema = $standing;
2634
- var $kycRequest = obj2({ formId: $nonEmpty, values: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.string()) });
2635
- var MoonPayKycRequestSchema = $kycRequest;
2636
- var $kycResponse = obj2({ status: import_zod2.z.literal("ACCEPTED") });
2637
- var MoonPayKycResponseSchema = $kycResponse;
2638
- var $completeRequest = obj2({ orderId: $nonEmpty, transactionId: $nonEmpty });
2639
- var MoonPayCompleteRequestSchema = $completeRequest;
2640
- var MoonPayCompleteResponseSchema = FiatStepResponseSchema;
2641
- var $fieldError = obj2({ fieldId: $nonEmpty, message: $nonEmpty });
2686
+ var $intentResponse = $intentStatus.superRefine((response, context) => {
2687
+ if (response.status === "TERMS") {
2688
+ if (!hasUniqueIds2(response.documents.map((document) => document.id))) {
2689
+ context.addIssue({ code: "custom", path: ["documents"], message: "document ids must be unique" });
2690
+ }
2691
+ if (response.documents.some((document) => document.stage !== response.stage)) {
2692
+ context.addIssue({ code: "custom", path: ["documents"], message: "every document must be at the response stage" });
2693
+ }
2694
+ }
2695
+ if (response.status === "KYC") {
2696
+ const fieldIds = response.fields.map((field) => field.id);
2697
+ if (!hasUniqueIds2(fieldIds)) {
2698
+ context.addIssue({ code: "custom", path: ["fields"], message: "field ids must be unique" });
2699
+ }
2700
+ if (response.fieldErrors?.some((error) => !fieldIds.includes(error.fieldId))) {
2701
+ context.addIssue({ code: "custom", path: ["fieldErrors"], message: "a field error must name a field in this response" });
2702
+ }
2703
+ }
2704
+ });
2705
+ var MoonPayIntentResponseSchema = $intentResponse;
2642
2706
  var $faultFields = {
2643
2707
  reason: import_zod2.z.enum([
2644
- "NOT_ELIGIBLE",
2645
2708
  "SESSION_BUDGET_SPENT",
2709
+ "RATE_LIMITED",
2646
2710
  "PROVIDER_UNAVAILABLE",
2647
- "INVALID_VALUE",
2648
- "STALE_FORM",
2649
- "TRANSACTION_NOT_VISIBLE",
2711
+ "IN_PROGRESS",
2712
+ "IDEMPOTENCY_KEY_REUSED",
2713
+ "INVALID_REQUEST",
2714
+ "TRANSACTION_MISMATCH",
2650
2715
  "WALLET_MISMATCH"
2651
2716
  ]),
2652
- recovery: import_zod2.z.enum(["RETRY", "REFRESH", "REQUOTE", "NONE"]),
2653
- fields: import_zod2.z.array($fieldError).min(1).optional()
2717
+ recovery: import_zod2.z.enum(["RETRY", "REQUOTE", "NONE"])
2654
2718
  };
2655
- var fieldsOnlyOnInvalidValue = (fault) => fault.reason === "INVALID_VALUE" === (fault.fields !== void 0);
2656
- var $fault = obj2($faultFields).refine(fieldsOnlyOnInvalidValue, "fields is present exactly when reason is INVALID_VALUE");
2719
+ var $fault = obj2($faultFields);
2657
2720
  var MoonPayFaultSchema = $fault;
2658
- var $errorBody = obj2({ ...$faultFields, errorCode: $nonEmpty, errorMsg: import_zod2.z.string(), reqId: import_zod2.z.string().optional() }).refine(
2659
- fieldsOnlyOnInvalidValue,
2660
- "fields is present exactly when reason is INVALID_VALUE"
2661
- );
2721
+ var $errorBody = obj2({ ...$faultFields, errorCode: $nonEmpty, errorMsg: import_zod2.z.string(), reqId: import_zod2.z.string().optional() });
2662
2722
  var MoonPayErrorBodySchema = $errorBody;
2663
2723
  //# sourceMappingURL=index.js.map
package/dist/index.mjs CHANGED
@@ -12,10 +12,13 @@ import {
12
12
  tableEntry,
13
13
  unofferedEndpoints,
14
14
  unreachableStates
15
- } from "./chunk-7KI7DJZ2.mjs";
15
+ } from "./chunk-TV6IWKTB.mjs";
16
16
  import {
17
17
  MOONPAY_ENDPOINTS
18
- } from "./chunk-YWKS7MMT.mjs";
18
+ } from "./chunk-UN3WTIMI.mjs";
19
+ import {
20
+ FIAT_TERMS_ENDPOINT
21
+ } from "./chunk-QXJ54JWX.mjs";
19
22
 
20
23
  // src/codes.ts
21
24
  var US_STATE_CODES = [
@@ -851,12 +854,50 @@ var $providerBoundFlowState = z.union([
851
854
  ]);
852
855
  var $flowState = z.union([$providerBoundFlowState, $blockedState]);
853
856
  var FlowStateSchema = $flowState;
857
+ var $nonBlank = z.string().refine((value) => value.trim().length > 0, "must not be blank");
858
+ var $termsStage = z.enum(["CHECKOUT", "KYC"]);
859
+ var $termsDocument = obj({
860
+ id: $nonBlank,
861
+ kind: z.enum(["FUN_TERMS", "PROVIDER_TERMS", "FUN_KYC_COLLECTION", "PROVIDER_KYC_SHARING"]),
862
+ stage: $termsStage,
863
+ version: $nonBlank,
864
+ contentDigest: $nonBlank,
865
+ links: z.array(obj({ label: $nonBlank, url: z.string().regex(/^https:\/\/\S+$/, "must be an https URL") })).min(1)
866
+ });
867
+ var TermsDocumentSchema = $termsDocument;
868
+ var hasUniqueIds = (ids) => new Set(ids).size === ids.length;
869
+ var $termsDocuments = (stage) => z.array($termsDocument).refine((documents) => hasUniqueIds(documents.map((document) => document.id)), "document ids must be unique").refine(
870
+ (documents) => stage === void 0 || documents.every((document) => document.stage === stage),
871
+ `every document must be at stage ${stage}`
872
+ );
873
+ var $termsAcceptance = obj({
874
+ documentId: $nonBlank,
875
+ version: $nonBlank,
876
+ contentDigest: $nonBlank,
877
+ acceptedAt: z.string().datetime({ offset: true })
878
+ });
879
+ var TermsDocumentAcceptanceSchema = $termsAcceptance;
880
+ var $termsRequest = obj({
881
+ provider: $provider,
882
+ acceptances: z.array($termsAcceptance).min(1).refine((acceptances) => hasUniqueIds(acceptances.map((acceptance) => acceptance.documentId)), "each document is accepted once")
883
+ });
884
+ var FiatTermsRequestSchema = $termsRequest;
885
+ var $termsResponse = obj({ recorded: z.array($nonBlank) });
886
+ var FiatTermsResponseSchema = $termsResponse;
887
+ var $termsErrorBody = obj({
888
+ errorCode: $nonBlank,
889
+ errorMsg: z.string(),
890
+ reqId: z.string().optional(),
891
+ reason: z.enum(["STALE_TERMS", "IN_PROGRESS", "IDEMPOTENCY_KEY_REUSED", "INVALID_REQUEST", "RATE_LIMITED"]),
892
+ recovery: z.enum(["RETRY", "REQUOTE", "NONE"])
893
+ });
894
+ var FiatTermsErrorBodySchema = $termsErrorBody;
854
895
  var $providerBoundStepResponse = obj({
855
896
  state: $providerBoundFlowState,
856
897
  provider: $provider,
857
898
  transitions: z.array($transition),
858
899
  preparedPayment: $preparedPayment.optional(),
859
- handoff: obj({ provider: $provider }).optional(),
900
+ handoff: obj({ provider: $provider, terms: $termsDocuments("CHECKOUT") }).optional(),
860
901
  routingSessionRef: z.string().optional(),
861
902
  providerAuthToken: obj({
862
903
  clientSecret: z.string(),
@@ -1979,14 +2020,27 @@ import { z as z2 } from "zod";
1979
2020
  var FIAT_CURRENCY_CODES2 = Object.keys(FIAT_CURRENCY_NAMES);
1980
2021
  var obj2 = (shape) => z2.object(shape).strict();
1981
2022
  var $nonEmpty = z2.string().refine((value) => value.trim().length > 0, "must not be blank");
1982
- var $startRequest = obj2({
2023
+ var hasUniqueIds2 = (ids) => new Set(ids).size === ids.length;
2024
+ var $checkout = obj2({
1983
2025
  amount: FiatAmountSchema,
1984
2026
  destination: obj2({ asset: $nonEmpty, network: $nonEmpty }),
1985
2027
  paymentMethod: z2.literal("apple_pay"),
1986
- geo: obj2({ alpha2: z2.string().regex(/^[A-Z]{2}$/), region: $nonEmpty.optional(), ip: $nonEmpty.optional() }),
1987
- termsAcceptance: obj2({ version: $nonEmpty, acceptedAt: z2.string().datetime({ offset: true }) })
2028
+ geo: obj2({ alpha2: z2.string().regex(/^[A-Z]{2}$/), region: $nonEmpty.optional(), ip: $nonEmpty.optional() })
1988
2029
  });
1989
- var MoonPayStartRequestSchema = $startRequest;
2030
+ var MoonPayCheckoutSchema = $checkout;
2031
+ var $credentials = obj2({ customerId: $nonEmpty, accessToken: $nonEmpty });
2032
+ var MoonPaySdkCredentialsSchema = $credentials;
2033
+ var $intentRequest = z2.discriminatedUnion("kind", [
2034
+ obj2({ kind: z2.literal("observe"), checkout: $checkout, credentials: $credentials.optional() }),
2035
+ obj2({
2036
+ kind: z2.literal("kyc"),
2037
+ checkout: $checkout,
2038
+ credentials: $credentials,
2039
+ values: z2.record(z2.string(), z2.string()).refine((values) => Object.keys(values).length > 0, "values must not be empty")
2040
+ }),
2041
+ obj2({ kind: z2.literal("order"), orderId: $nonEmpty, transactionId: $nonEmpty })
2042
+ ]);
2043
+ var MoonPayIntentRequestSchema = $intentRequest;
1990
2044
  var $quoteInput = obj2({
1991
2045
  source: obj2({ asset: obj2({ code: z2.enum(FIAT_CURRENCY_CODES2) }), amount: $nonEmpty }),
1992
2046
  destination: obj2({ asset: obj2({ code: $nonEmpty }) }),
@@ -1994,47 +2048,54 @@ var $quoteInput = obj2({
1994
2048
  paymentMethod: obj2({ type: z2.literal("apple_pay") }),
1995
2049
  feeBehavior: z2.literal("inclusive")
1996
2050
  });
1997
- var $startResponse = obj2({ sessionToken: $nonEmpty, quoteInput: $quoteInput });
1998
- var MoonPayStartResponseSchema = $startResponse;
1999
- var $intentRequest = obj2({ customerId: $nonEmpty, accessToken: $nonEmpty });
2000
- var MoonPayIntentRequestSchema = $intentRequest;
2001
- var $refusal = z2.enum(["DOCUMENTS_REQUIRED", "FINAL_REJECTION", "CUSTOMER_MISMATCH"]);
2002
- var $standing = z2.discriminatedUnion("status", [
2003
- obj2({ status: z2.literal("READY"), orderId: $nonEmpty }),
2004
- obj2({ status: z2.literal("KYC"), form: FormDescriptorSchema }),
2051
+ var MoonPayQuoteInputSchema = $quoteInput;
2052
+ var $refusal = z2.enum(["NOT_ELIGIBLE", "DOCUMENTS_REQUIRED", "FINAL_REJECTION", "CUSTOMER_MISMATCH"]);
2053
+ var $fieldError = obj2({ fieldId: $nonEmpty, message: $nonEmpty });
2054
+ var $intentStatus = z2.discriminatedUnion("status", [
2055
+ obj2({ status: z2.literal("TERMS"), stage: z2.enum(["CHECKOUT", "KYC"]), documents: z2.array(TermsDocumentSchema).min(1) }),
2056
+ obj2({ status: z2.literal("SDK_AUTH"), sessionToken: $nonEmpty, quoteInput: $quoteInput }),
2057
+ obj2({ status: z2.literal("KYC"), fields: z2.array(FormFieldSchema).min(1), fieldErrors: z2.array($fieldError).min(1).optional() }),
2005
2058
  obj2({ status: z2.literal("PENDING"), retryAfterMs: z2.number().int().positive().max(6e4) }),
2059
+ obj2({ status: z2.literal("READY"), orderId: $nonEmpty, quoteInput: $quoteInput }),
2060
+ obj2({ status: z2.literal("DONE"), order: FiatStepResponseSchema }),
2006
2061
  obj2({ status: z2.literal("REFUSED"), reason: $refusal, recovery: z2.enum(["REQUOTE", "NONE"]) })
2007
2062
  ]);
2008
- var MoonPayStandingSchema = $standing;
2009
- var MoonPayIntentResponseSchema = $standing;
2010
- var $kycRequest = obj2({ formId: $nonEmpty, values: z2.record(z2.string(), z2.string()) });
2011
- var MoonPayKycRequestSchema = $kycRequest;
2012
- var $kycResponse = obj2({ status: z2.literal("ACCEPTED") });
2013
- var MoonPayKycResponseSchema = $kycResponse;
2014
- var $completeRequest = obj2({ orderId: $nonEmpty, transactionId: $nonEmpty });
2015
- var MoonPayCompleteRequestSchema = $completeRequest;
2016
- var MoonPayCompleteResponseSchema = FiatStepResponseSchema;
2017
- var $fieldError = obj2({ fieldId: $nonEmpty, message: $nonEmpty });
2063
+ var $intentResponse = $intentStatus.superRefine((response, context) => {
2064
+ if (response.status === "TERMS") {
2065
+ if (!hasUniqueIds2(response.documents.map((document) => document.id))) {
2066
+ context.addIssue({ code: "custom", path: ["documents"], message: "document ids must be unique" });
2067
+ }
2068
+ if (response.documents.some((document) => document.stage !== response.stage)) {
2069
+ context.addIssue({ code: "custom", path: ["documents"], message: "every document must be at the response stage" });
2070
+ }
2071
+ }
2072
+ if (response.status === "KYC") {
2073
+ const fieldIds = response.fields.map((field) => field.id);
2074
+ if (!hasUniqueIds2(fieldIds)) {
2075
+ context.addIssue({ code: "custom", path: ["fields"], message: "field ids must be unique" });
2076
+ }
2077
+ if (response.fieldErrors?.some((error) => !fieldIds.includes(error.fieldId))) {
2078
+ context.addIssue({ code: "custom", path: ["fieldErrors"], message: "a field error must name a field in this response" });
2079
+ }
2080
+ }
2081
+ });
2082
+ var MoonPayIntentResponseSchema = $intentResponse;
2018
2083
  var $faultFields = {
2019
2084
  reason: z2.enum([
2020
- "NOT_ELIGIBLE",
2021
2085
  "SESSION_BUDGET_SPENT",
2086
+ "RATE_LIMITED",
2022
2087
  "PROVIDER_UNAVAILABLE",
2023
- "INVALID_VALUE",
2024
- "STALE_FORM",
2025
- "TRANSACTION_NOT_VISIBLE",
2088
+ "IN_PROGRESS",
2089
+ "IDEMPOTENCY_KEY_REUSED",
2090
+ "INVALID_REQUEST",
2091
+ "TRANSACTION_MISMATCH",
2026
2092
  "WALLET_MISMATCH"
2027
2093
  ]),
2028
- recovery: z2.enum(["RETRY", "REFRESH", "REQUOTE", "NONE"]),
2029
- fields: z2.array($fieldError).min(1).optional()
2094
+ recovery: z2.enum(["RETRY", "REQUOTE", "NONE"])
2030
2095
  };
2031
- var fieldsOnlyOnInvalidValue = (fault) => fault.reason === "INVALID_VALUE" === (fault.fields !== void 0);
2032
- var $fault = obj2($faultFields).refine(fieldsOnlyOnInvalidValue, "fields is present exactly when reason is INVALID_VALUE");
2096
+ var $fault = obj2($faultFields);
2033
2097
  var MoonPayFaultSchema = $fault;
2034
- var $errorBody = obj2({ ...$faultFields, errorCode: $nonEmpty, errorMsg: z2.string(), reqId: z2.string().optional() }).refine(
2035
- fieldsOnlyOnInvalidValue,
2036
- "fields is present exactly when reason is INVALID_VALUE"
2037
- );
2098
+ var $errorBody = obj2({ ...$faultFields, errorCode: $nonEmpty, errorMsg: z2.string(), reqId: z2.string().optional() });
2038
2099
  var MoonPayErrorBodySchema = $errorBody;
2039
2100
  export {
2040
2101
  AuthChallengeSchema,
@@ -2049,6 +2110,7 @@ export {
2049
2110
  DOCUMENTED_ENDPOINTS,
2050
2111
  ENDPOINT_PATTERN,
2051
2112
  FIAT_CURRENCY_NAMES,
2113
+ FIAT_TERMS_ENDPOINT,
2052
2114
  FIELD_SPEC_TYPES,
2053
2115
  FIXTURES,
2054
2116
  FIXTURE_COVERAGE_GAPS,
@@ -2059,6 +2121,9 @@ export {
2059
2121
  FiatEndpointSchema,
2060
2122
  FiatProviderSchema,
2061
2123
  FiatStepResponseSchema,
2124
+ FiatTermsErrorBodySchema,
2125
+ FiatTermsRequestSchema,
2126
+ FiatTermsResponseSchema,
2062
2127
  FieldSpecSchema,
2063
2128
  FlowStateSchema,
2064
2129
  FormDescriptorSchema,
@@ -2077,17 +2142,13 @@ export {
2077
2142
  KycNoActionRequiredStateSchema,
2078
2143
  KycSdkResultReportSchema,
2079
2144
  MOONPAY_ENDPOINTS,
2080
- MoonPayCompleteRequestSchema,
2081
- MoonPayCompleteResponseSchema,
2145
+ MoonPayCheckoutSchema,
2082
2146
  MoonPayErrorBodySchema,
2083
2147
  MoonPayFaultSchema,
2084
2148
  MoonPayIntentRequestSchema,
2085
2149
  MoonPayIntentResponseSchema,
2086
- MoonPayKycRequestSchema,
2087
- MoonPayKycResponseSchema,
2088
- MoonPayStandingSchema,
2089
- MoonPayStartRequestSchema,
2090
- MoonPayStartResponseSchema,
2150
+ MoonPayQuoteInputSchema,
2151
+ MoonPaySdkCredentialsSchema,
2091
2152
  OrderAwaitingConfirmationStateSchema,
2092
2153
  OrderCreatedStateSchema,
2093
2154
  OrderRefSchema,
@@ -2120,6 +2181,8 @@ export {
2120
2181
  TERMINAL_ORDER_STATUSES,
2121
2182
  TRANSITION_TABLE,
2122
2183
  TermsAcceptanceSchema,
2184
+ TermsDocumentAcceptanceSchema,
2185
+ TermsDocumentSchema,
2123
2186
  TransitionInputsSchema,
2124
2187
  TransitionParamsSchema,
2125
2188
  TransitionSchema,
@@ -1,25 +1,28 @@
1
1
  /**
2
- * The MoonPay provider-owned flow: `POST /fiat/providers/moonpay/{start,intent,kyc,complete}`.
2
+ * The MoonPay provider-owned flow: one route, `POST /fiat/providers/moonpay/intent`.
3
3
  *
4
- * Only `intent` decides what happens next. `kyc` confirms a write or rejects it with field errors,
5
- * and the client then calls `intent` again. Identity and the deposit wallet always come from the
4
+ * Each request carries evidence or a user action (`kind`), and each response says what the backend
5
+ * needs next (`status`). The client repeats until `DONE` or `REFUSED`. Terms and consents go through
6
+ * the platform `POST /fiat/terms` (`./terms`). Identity and the deposit wallet always come from the
6
7
  * verified partner assertion, never from a request body.
7
8
  *
8
- * A fault is a 4xx/5xx whose body is the shared error body plus `MoonPayFault`. A decision the user
9
- * can act on is a 200 `MoonPayStanding`.
9
+ * `kyc` and `order` require an `Idempotency-Key` header; `observe` ignores it. A repeated key skips
10
+ * the effect and still returns the current response.
11
+ *
12
+ * A fault is a 4xx/5xx whose body is `MoonPayErrorBody`. Anything the user can act on is a 200
13
+ * `MoonPayIntentResponse`.
10
14
  *
11
15
  * Zero runtime dependencies, so production React Native code can import it. The zod schemas are in
12
16
  * `./moonpay.schemas`, exported from the package root.
13
17
  */
14
18
  import type { FiatCurrencyCode } from '../codes';
15
- import type { FiatAmount, FiatStepResponse, FormDescriptor, TermsAcceptance } from '../types';
19
+ import type { TermsDocument, TermsStage } from '../terms';
20
+ import type { FiatAmount, FiatStepResponse, FormField } from '../types';
16
21
  export declare const MOONPAY_ENDPOINTS: {
17
- readonly start: "POST /fiat/providers/moonpay/start";
18
22
  readonly intent: "POST /fiat/providers/moonpay/intent";
19
- readonly kyc: "POST /fiat/providers/moonpay/kyc";
20
- readonly complete: "POST /fiat/providers/moonpay/complete";
21
23
  };
22
- export interface MoonPayStartRequest {
24
+ /** The checkout terms. The client holds them and sends them on every `observe` and `kyc`. */
25
+ export interface MoonPayCheckout {
23
26
  amount: FiatAmount;
24
27
  /** The quote's `crypto.currency` and `crypto.network`, unchanged. */
25
28
  destination: {
@@ -33,9 +36,33 @@ export interface MoonPayStartRequest {
33
36
  region?: string;
34
37
  ip?: string;
35
38
  };
36
- /** The MoonPay terms notice on the amount screen; the tap is the acceptance. */
37
- termsAcceptance: TermsAcceptance;
38
39
  }
40
+ /** From the SDK's `active` connection. `accessToken` is a credential: the backend never logs it. */
41
+ export interface MoonPaySdkCredentials {
42
+ customerId: string;
43
+ accessToken: string;
44
+ }
45
+ export type MoonPayIntentRequest =
46
+ /** No effect. Without credentials, the answer past the gates is `SDK_AUTH`. */
47
+ {
48
+ kind: 'observe';
49
+ checkout: MoonPayCheckout;
50
+ credentials?: MoonPaySdkCredentials;
51
+ }
52
+ /** Only after a `KYC` response. `values` is keyed by field id; there is no form id. */
53
+ | {
54
+ kind: 'kyc';
55
+ checkout: MoonPayCheckout;
56
+ credentials: MoonPaySdkCredentials;
57
+ values: Record<string, string>;
58
+ }
59
+ /** After the Apple Pay sheet. `transactionId` is from the Apple Pay completion event. */
60
+ | {
61
+ kind: 'order';
62
+ orderId: string;
63
+ transactionId: string;
64
+ };
65
+ export type MoonPayIntentKind = MoonPayIntentRequest['kind'];
39
66
  /**
40
67
  * The parameters for the device `client.getQuote`, not a quote. The backend builds them because the
41
68
  * wallet must be the verified one and MoonPay's currency codes differ by environment.
@@ -60,69 +87,71 @@ export interface MoonPayQuoteInput {
60
87
  };
61
88
  feeBehavior: 'inclusive';
62
89
  }
63
- export interface MoonPayStartResponse {
64
- /** The one SDK session, for the connection check, the auth frames, and the Apple Pay button. */
90
+ export type MoonPayRefusal = 'NOT_ELIGIBLE' | 'DOCUMENTS_REQUIRED' | 'FINAL_REJECTION' | 'CUSTOMER_MISMATCH';
91
+ /** A reason on one field of a `KYC` response. `fieldId` names a field in that response. */
92
+ export interface MoonPayFieldError {
93
+ fieldId: string;
94
+ message: string;
95
+ }
96
+ export type MoonPayIntentResponse =
97
+ /** Show `documents` (every one at `stage`), send `POST /fiat/terms`, then `observe`. */
98
+ {
99
+ status: 'TERMS';
100
+ stage: TermsStage;
101
+ documents: TermsDocument[];
102
+ }
103
+ /** Run the MoonPay connection check with `sessionToken`, then `observe` with the credentials. */
104
+ | {
105
+ status: 'SDK_AUTH';
65
106
  sessionToken: string;
66
107
  quoteInput: MoonPayQuoteInput;
67
108
  }
68
- export interface MoonPayIntentRequest {
69
- /** From the SDK's `active` connection. */
70
- customerId: string;
71
- /** From the SDK's `active` connection. A credential: the backend strips it from every log. */
72
- accessToken: string;
73
- }
74
- export type MoonPayRefusal = 'DOCUMENTS_REQUIRED' | 'FINAL_REJECTION' | 'CUSTOMER_MISMATCH';
75
- export type MoonPayStanding =
76
- /** `orderId` is the `externalTransactionId` for the Apple Pay button. */
77
- {
78
- status: 'READY';
79
- orderId: string;
80
- } | {
109
+ /** Show the shared form, then send `kyc`. */
110
+ | {
81
111
  status: 'KYC';
82
- form: FormDescriptor;
83
- } | {
112
+ fields: FormField[];
113
+ fieldErrors?: MoonPayFieldError[];
114
+ }
115
+ /** Send the same request again after the delay. */
116
+ | {
84
117
  status: 'PENDING';
85
118
  retryAfterMs: number;
86
- } | {
119
+ }
120
+ /** Mount Apple Pay with `externalTransactionId = orderId`, then send `order` after the charge. */
121
+ | {
122
+ status: 'READY';
123
+ orderId: string;
124
+ quoteInput: MoonPayQuoteInput;
125
+ }
126
+ /** The `GET /fiat/orders/:orderId` response; the shared order screen renders it. */
127
+ | {
128
+ status: 'DONE';
129
+ order: FiatStepResponse;
130
+ }
131
+ /** Exit the provider flow. `REQUOTE` goes back to `POST /fiat/quote`; `NONE` ends the flow. */
132
+ | {
87
133
  status: 'REFUSED';
88
134
  reason: MoonPayRefusal;
89
135
  recovery: 'REQUOTE' | 'NONE';
90
136
  };
91
- export type MoonPayIntentResponse = MoonPayStanding;
92
- export interface MoonPayKycRequest {
93
- /** From the `KYC` standing. A stale one is a 409 `STALE_FORM` with recovery `REFRESH`. */
94
- formId: string;
95
- /** One string per form field id. */
96
- values: Record<string, string>;
97
- }
98
- /** The write landed. The client calls `intent` next. */
99
- export interface MoonPayKycResponse {
100
- status: 'ACCEPTED';
101
- }
102
- export interface MoonPayCompleteRequest {
103
- orderId: string;
104
- /** From the Apple Pay completion event. */
105
- transactionId: string;
106
- }
107
- /** The same response as `GET /fiat/orders/:orderId`, so the shared order screen renders it. */
108
- export type MoonPayCompleteResponse = FiatStepResponse;
137
+ export type MoonPayIntentStatus = MoonPayIntentResponse['status'];
138
+ /** `RETRY` sends the same request again, with the same key. `REQUOTE` goes back to `POST /fiat/quote`. `NONE` ends the flow. */
139
+ export type MoonPayRecovery = 'RETRY' | 'REQUOTE' | 'NONE';
109
140
  /**
110
- * `RETRY` sends the same request again. `REFRESH` calls `intent` for a new standing. `REQUOTE` goes
111
- * back to `POST /fiat/quote`. `NONE` ends the flow.
141
+ * `SESSION_BUDGET_SPENT`: the per-login SDK session mint budget (429). `RATE_LIMITED`: the kind's
142
+ * request budget (429). `PROVIDER_UNAVAILABLE`: MoonPay or Sumsub is down, or a charge cannot be
143
+ * recorded. `IN_PROGRESS`: the same key is still running (409). `IDEMPOTENCY_KEY_REUSED`: the same
144
+ * key with a different body (422). `INVALID_REQUEST`: a bad body or a missing key (400).
145
+ * `TRANSACTION_MISMATCH`: the transaction is not this buyer's (400). `WALLET_MISMATCH`: the
146
+ * transaction pays another wallet.
112
147
  */
113
- export type MoonPayRecovery = 'RETRY' | 'REFRESH' | 'REQUOTE' | 'NONE';
114
- export type MoonPayFaultReason = 'NOT_ELIGIBLE' | 'SESSION_BUDGET_SPENT' | 'PROVIDER_UNAVAILABLE' | 'INVALID_VALUE' | 'STALE_FORM' | 'TRANSACTION_NOT_VISIBLE' | 'WALLET_MISMATCH';
115
- export interface MoonPayFieldError {
116
- fieldId: string;
117
- message: string;
118
- }
119
- /** The fields a MoonPay route adds to the shared error body. `fields` is only on `INVALID_VALUE`. */
148
+ export type MoonPayFaultReason = 'SESSION_BUDGET_SPENT' | 'RATE_LIMITED' | 'PROVIDER_UNAVAILABLE' | 'IN_PROGRESS' | 'IDEMPOTENCY_KEY_REUSED' | 'INVALID_REQUEST' | 'TRANSACTION_MISMATCH' | 'WALLET_MISMATCH';
149
+ /** The fields a MoonPay fault adds to the shared error body. */
120
150
  export interface MoonPayFault {
121
151
  reason: MoonPayFaultReason;
122
152
  recovery: MoonPayRecovery;
123
- fields?: MoonPayFieldError[];
124
153
  }
125
- /** A MoonPay route's 4xx/5xx body: the shared error body plus `MoonPayFault`. */
154
+ /** The route's 4xx/5xx body: the shared error body plus `MoonPayFault`. */
126
155
  export type MoonPayErrorBody = MoonPayFault & {
127
156
  errorCode: string;
128
157
  errorMsg: string;
@@ -24,9 +24,6 @@ __export(moonpay_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(moonpay_exports);
26
26
  var MOONPAY_ENDPOINTS = {
27
- start: "POST /fiat/providers/moonpay/start",
28
- intent: "POST /fiat/providers/moonpay/intent",
29
- kyc: "POST /fiat/providers/moonpay/kyc",
30
- complete: "POST /fiat/providers/moonpay/complete"
27
+ intent: "POST /fiat/providers/moonpay/intent"
31
28
  };
32
29
  //# sourceMappingURL=moonpay.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MOONPAY_ENDPOINTS
3
- } from "../chunk-YWKS7MMT.mjs";
3
+ } from "../chunk-UN3WTIMI.mjs";
4
4
  export {
5
5
  MOONPAY_ENDPOINTS
6
6
  };
@@ -3,16 +3,12 @@
3
3
  * the package root, never from `./moonpay`, so the production entry stays free of zod.
4
4
  */
5
5
  import { z } from 'zod';
6
- import type { MoonPayCompleteRequest, MoonPayCompleteResponse, MoonPayErrorBody, MoonPayFault, MoonPayIntentRequest, MoonPayIntentResponse, MoonPayKycRequest, MoonPayKycResponse, MoonPayStanding, MoonPayStartRequest, MoonPayStartResponse } from './moonpay';
7
- export declare const MoonPayStartRequestSchema: z.ZodType<MoonPayStartRequest>;
8
- export declare const MoonPayStartResponseSchema: z.ZodType<MoonPayStartResponse>;
6
+ import type { MoonPayCheckout, MoonPayErrorBody, MoonPayFault, MoonPayIntentRequest, MoonPayIntentResponse, MoonPayQuoteInput, MoonPaySdkCredentials } from './moonpay';
7
+ export declare const MoonPayCheckoutSchema: z.ZodType<MoonPayCheckout>;
8
+ export declare const MoonPaySdkCredentialsSchema: z.ZodType<MoonPaySdkCredentials>;
9
9
  export declare const MoonPayIntentRequestSchema: z.ZodType<MoonPayIntentRequest>;
10
- export declare const MoonPayStandingSchema: z.ZodType<MoonPayStanding>;
10
+ export declare const MoonPayQuoteInputSchema: z.ZodType<MoonPayQuoteInput>;
11
11
  export declare const MoonPayIntentResponseSchema: z.ZodType<MoonPayIntentResponse>;
12
- export declare const MoonPayKycRequestSchema: z.ZodType<MoonPayKycRequest>;
13
- export declare const MoonPayKycResponseSchema: z.ZodType<MoonPayKycResponse>;
14
- export declare const MoonPayCompleteRequestSchema: z.ZodType<MoonPayCompleteRequest>;
15
- export declare const MoonPayCompleteResponseSchema: z.ZodType<MoonPayCompleteResponse>;
16
12
  export declare const MoonPayFaultSchema: z.ZodType<MoonPayFault>;
17
13
  export declare const MoonPayErrorBodySchema: z.ZodType<MoonPayErrorBody>;
18
14
  //# sourceMappingURL=moonpay.schemas.d.ts.map
package/dist/schemas.d.ts CHANGED
@@ -29,6 +29,7 @@
29
29
  import { z } from 'zod';
30
30
  import type { UsStateCode } from './codes';
31
31
  import type { BlockedReason, CryptoAmount, FailureReason, FeeLine, FiatAmount, FiatEndpoint, FiatProvider, FiatStepResponse, FieldSpec, FlowState, FormDescriptor, FormField, FormFieldType, InstructionField, Instructions, Instrument, JsonValue, KYCProvider, OrderRef, OrderStatus, OrderSummary, PollSpec, PreparedPayment, PaymentSurface, Quote, QuoteLimitRange, QuoteLimits, Recovery, Refund, ReportSpec, SelectOption, StatusHistoryEntry, Surface, PreferredIdentityDocument, ClientExecutedDelivery, KycSdkResultReport, SurfaceKind, SurfaceProvider, TermsAcceptance, Transition, TransitionInputs, TransitionParams, Tx } from './types';
32
+ import type { FiatTermsErrorBody, FiatTermsRequest, FiatTermsResponse, TermsDocument, TermsDocumentAcceptance } from './terms';
32
33
  export declare const JsonSchema: z.ZodType<JsonValue>;
33
34
  export declare const HTTP_VERBS: readonly ["GET", "POST"];
34
35
  /** `"POST /fiat/session/verify"`, `"GET /fiat/orders/o_31c"` — the verb rides the string. */
@@ -104,6 +105,11 @@ export declare const PaymentInstructStateSchema: z.ZodType<State<'PAYMENT', 'INS
104
105
  export declare const OrderAwaitingConfirmationStateSchema: z.ZodType<State<'ORDER', 'AWAITING_CONFIRMATION'>>;
105
106
  export declare const OrderCreatedStateSchema: z.ZodType<State<'ORDER', 'CREATED'>>;
106
107
  export declare const FlowStateSchema: z.ZodType<FlowState>;
108
+ export declare const TermsDocumentSchema: z.ZodType<TermsDocument>;
109
+ export declare const TermsDocumentAcceptanceSchema: z.ZodType<TermsDocumentAcceptance>;
110
+ export declare const FiatTermsRequestSchema: z.ZodType<FiatTermsRequest>;
111
+ export declare const FiatTermsResponseSchema: z.ZodType<FiatTermsResponse>;
112
+ export declare const FiatTermsErrorBodySchema: z.ZodType<FiatTermsErrorBody>;
107
113
  export declare const FiatStepResponseSchema: z.ZodType<FiatStepResponse>;
108
114
  export {};
109
115
  //# sourceMappingURL=schemas.d.ts.map
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.18.0";
39
+ var version = "0.20.0";
40
40
 
41
41
  // src/table.ts
42
42
  var TABLE_VERSION = version;
package/dist/table.mjs CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  tableEntry,
12
12
  unofferedEndpoints,
13
13
  unreachableStates
14
- } from "./chunk-7KI7DJZ2.mjs";
14
+ } from "./chunk-TV6IWKTB.mjs";
15
15
  export {
16
16
  DOCUMENTED_ENDPOINTS,
17
17
  STATE_KEYS,
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The platform terms route: `POST /fiat/terms`, shared by every provider.
3
+ *
4
+ * A checkout shows the user documents to accept. `CHECKOUT` documents arrive in `handoff.terms` on
5
+ * a `QUOTE`; `KYC` documents arrive in a provider flow's `TERMS` response. The user's Continue tap
6
+ * is the acceptance, and the client records it here, with an `Idempotency-Key`, before the next
7
+ * provider call.
8
+ *
9
+ * Zero runtime dependencies, so production React Native code can import it. The zod schemas are
10
+ * exported from the package root.
11
+ */
12
+ import type { FiatProvider } from './types';
13
+ export declare const FIAT_TERMS_ENDPOINT: "POST /fiat/terms";
14
+ export type TermsDocumentKind = 'FUN_TERMS' | 'PROVIDER_TERMS' | 'FUN_KYC_COLLECTION' | 'PROVIDER_KYC_SHARING';
15
+ /** `CHECKOUT` documents gate the provider session; `KYC` documents gate the first KYC write. */
16
+ export type TermsStage = 'CHECKOUT' | 'KYC';
17
+ export interface TermsDocument {
18
+ id: string;
19
+ kind: TermsDocumentKind;
20
+ stage: TermsStage;
21
+ /** Opaque to the client; echoed in the acceptance. */
22
+ version: string;
23
+ /** Opaque to the client; echoed in the acceptance, so a dispute names the text the user saw. */
24
+ contentDigest: string;
25
+ /** What the screen links to. At least one, each an `https` URL. */
26
+ links: {
27
+ label: string;
28
+ url: string;
29
+ }[];
30
+ }
31
+ export interface TermsDocumentAcceptance {
32
+ documentId: string;
33
+ version: string;
34
+ contentDigest: string;
35
+ /** ISO-8601 with an offset, stamped by the client at the tap. The backend refuses one older than 5 minutes. */
36
+ acceptedAt: string;
37
+ }
38
+ export interface FiatTermsRequest {
39
+ provider: FiatProvider;
40
+ /** One per document the screen showed, each document at most once. */
41
+ acceptances: TermsDocumentAcceptance[];
42
+ }
43
+ /** The recorded document ids. The client then calls the provider's `intent`. */
44
+ export interface FiatTermsResponse {
45
+ recorded: string[];
46
+ }
47
+ /**
48
+ * `STALE_TERMS`: a version, digest, or `acceptedAt` the backend no longer accepts; the client
49
+ * fetches the documents again. `IN_PROGRESS`: the same key is still running (409).
50
+ * `IDEMPOTENCY_KEY_REUSED`: the same key with a different body (422). `INVALID_REQUEST`: a bad body
51
+ * or a missing key (400). `RATE_LIMITED`: the per-login terms budget is spent (429).
52
+ */
53
+ export type FiatTermsFaultReason = 'STALE_TERMS' | 'IN_PROGRESS' | 'IDEMPOTENCY_KEY_REUSED' | 'INVALID_REQUEST' | 'RATE_LIMITED';
54
+ /** `RETRY` sends the same request, with the same key. `REQUOTE` goes back to `POST /fiat/quote`. `NONE` ends the flow. */
55
+ export type FiatTermsRecovery = 'RETRY' | 'REQUOTE' | 'NONE';
56
+ /** The 4xx/5xx body of `POST /fiat/terms`: the shared error body plus `reason` and `recovery`. */
57
+ export interface FiatTermsErrorBody {
58
+ errorCode: string;
59
+ errorMsg: string;
60
+ reqId?: string;
61
+ reason: FiatTermsFaultReason;
62
+ recovery: FiatTermsRecovery;
63
+ }
64
+ //# sourceMappingURL=terms.d.ts.map
package/dist/terms.js ADDED
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/terms.ts
21
+ var terms_exports = {};
22
+ __export(terms_exports, {
23
+ FIAT_TERMS_ENDPOINT: () => FIAT_TERMS_ENDPOINT
24
+ });
25
+ module.exports = __toCommonJS(terms_exports);
26
+ var FIAT_TERMS_ENDPOINT = "POST /fiat/terms";
27
+ //# sourceMappingURL=terms.js.map
package/dist/terms.mjs ADDED
@@ -0,0 +1,7 @@
1
+ import {
2
+ FIAT_TERMS_ENDPOINT
3
+ } from "./chunk-QXJ54JWX.mjs";
4
+ export {
5
+ FIAT_TERMS_ENDPOINT
6
+ };
7
+ //# sourceMappingURL=terms.mjs.map
package/dist/types.d.ts CHANGED
@@ -19,6 +19,7 @@
19
19
  * - terminality comes from the table (`src/table.ts`), never from `transitions.length`
20
20
  */
21
21
  import type { FiatCurrencyCode, PaymentMethodCategory } from './codes';
22
+ import type { TermsDocument } from './terms';
22
23
  /**
23
24
  * JSON value. `params` carry these — opaque literals the client spreads verbatim.
24
25
  * Named `JsonValue` rather than `Json` because this package's surface is re-exported wholesale;
@@ -715,11 +716,17 @@ export type FiatStepResponse = (FiatStepResponseBase & {
715
716
  /** Optional future payment artifact; never one of this response's currently active transitions. */
716
717
  preparedPayment?: PreparedPayment;
717
718
  /**
718
- * The provider runs its own flow from here (`POST /fiat/providers/{provider}/…`). Present only
719
- * when the request's `handoffs` named this provider; the response then carries no transitions.
719
+ * The provider runs its own flow from here (`POST /fiat/providers/{provider}/intent`). Present
720
+ * only on a `QUOTE`, when the request's `handoffs` named this provider; the response then
721
+ * carries no transitions.
720
722
  */
721
723
  handoff?: {
722
724
  provider: FiatProvider;
725
+ /**
726
+ * The `CHECKOUT` documents this login has not accepted at the current version, shown next to
727
+ * Continue and recorded with `POST /fiat/terms` on the tap. Empty for a returning buyer.
728
+ */
729
+ terms: TermsDocument[];
723
730
  };
724
731
  });
725
732
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fun-xyz/fiat-contract",
3
- "version": "0.18.0",
3
+ "version": "0.20.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": {
@@ -33,6 +33,11 @@
33
33
  "import": "./dist/providers/moonpay.mjs",
34
34
  "require": "./dist/providers/moonpay.js"
35
35
  },
36
+ "./terms": {
37
+ "types": "./dist/terms.d.ts",
38
+ "import": "./dist/terms.mjs",
39
+ "require": "./dist/terms.js"
40
+ },
36
41
  "./package.json": "./package.json"
37
42
  },
38
43
  "files": [
@@ -43,6 +48,8 @@
43
48
  "types.d.ts",
44
49
  "moonpay.js",
45
50
  "moonpay.d.ts",
51
+ "terms.js",
52
+ "terms.d.ts",
46
53
  "README.md",
47
54
  "!dist/**/*.map"
48
55
  ],
@@ -85,6 +92,9 @@
85
92
  ],
86
93
  "moonpay": [
87
94
  "./dist/providers/moonpay.d.ts"
95
+ ],
96
+ "terms": [
97
+ "./dist/terms.d.ts"
88
98
  ]
89
99
  }
90
100
  }
package/terms.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './dist/terms';
package/terms.js ADDED
@@ -0,0 +1,2 @@
1
+ // Compat stub — see table.js. Metro before RN 0.79 resolves `@fun-xyz/fiat-contract/terms` by path.
2
+ module.exports = require('./dist/terms.js');
@@ -1,12 +0,0 @@
1
- // src/providers/moonpay.ts
2
- var MOONPAY_ENDPOINTS = {
3
- start: "POST /fiat/providers/moonpay/start",
4
- intent: "POST /fiat/providers/moonpay/intent",
5
- kyc: "POST /fiat/providers/moonpay/kyc",
6
- complete: "POST /fiat/providers/moonpay/complete"
7
- };
8
-
9
- export {
10
- MOONPAY_ENDPOINTS
11
- };
12
- //# sourceMappingURL=chunk-YWKS7MMT.mjs.map