@movmo_app/payments 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -39,6 +39,8 @@ declare interface CardFieldValidity {
39
39
  cvv: boolean;
40
40
  }
41
41
 
42
+ export declare type CardFormField = 'number' | 'expiry' | 'cvv' | 'zip' | 'name';
43
+
42
44
  export declare type CardFormStatus = 'idle' | 'loading' | 'ready' | 'tokenizing' | 'saving' | 'error';
43
45
 
44
46
  export declare interface CardholderTokenizeData {
@@ -52,6 +54,18 @@ export declare interface CardholderTokenizeData {
52
54
  zip?: string;
53
55
  }
54
56
 
57
+ /**
58
+ * PII-free validation snapshot: field KEYS only, never values. Lets a parent
59
+ * (e.g. `PaymentMethodsManager`) turn touched-invalid streaks into a
60
+ * consumer-side friction signal without the SDK importing an analytics
61
+ * library or leaking anything the traveler typed.
62
+ */
63
+ export declare interface CardValidationState {
64
+ valid: boolean;
65
+ missing: CardFormField[];
66
+ touchedInvalid: CardFormField[];
67
+ }
68
+
55
69
  /**
56
70
  * Builds an auth-aware `fetch` that the payments package (or any other
57
71
  * consumer that registers it via `setPaymentsConfig({ fetch })`) can call.
@@ -59,41 +73,6 @@ export declare interface CardholderTokenizeData {
59
73
  */
60
74
  export declare const createMovmoAuthFetch: (options: CreateMovmoAuthFetchOptions) => typeof fetch;
61
75
 
62
- /**
63
- * Auth-aware `fetch` wrapper for `@movmo_app/payments`. The package's API
64
- * helpers call through `getPaymentsConfig().fetch`, bypassing whatever axios
65
- * interceptor stack the consumer wires up. Without this wrapper:
66
- *
67
- * - Session cookie expiry → silent 401/403 (no refresh, no logout).
68
- * - State-mutating endpoints → 403 because the CSRF cookie isn't echoed.
69
- * - Local-dev / multi-tenant flows → missing per-request headers like
70
- * `X-Movmo-Proxy-Auth` or `movmo-customer-id`.
71
- *
72
- * This factory replaces a ~95-line `paymentsFetch.ts` copy that was
73
- * duplicated across consumer apps. The contract:
74
- *
75
- * 1. CSRF token from `csrfCookieName` is set on every request as
76
- * `X-CSRF-Token` (matching the axios interceptor's unconditional set).
77
- * 2. `extraHeaders(input, init)` is invoked per-request to allow dynamic
78
- * headers (proxy-auth, customer id, etc.). Headers the caller already
79
- * set on `init.headers` win — `extraHeaders` only fills gaps.
80
- * 3. `credentials: 'include'` is forced so the session cookie travels.
81
- * 4. On a 401/403 from a non-auth endpoint, the wrapper calls
82
- * `refreshSession()` once (concurrent failures share one in-flight
83
- * promise) and retries the original request. If refresh ITSELF fails,
84
- * `onRefreshFailed(err)` fires so the consumer can drive a sign-out —
85
- * silently swallowing here would strand the user on a broken page.
86
- * Failures on the *retry* fetch are logged and the original response is
87
- * returned so the SDK still sees a Response to render an error from.
88
- * 5. The original fetch's network errors (offline, CORS, DNS) propagate.
89
- * Only retry-path failures are caught; pre-refresh errors are left for
90
- * the consumer to surface.
91
- *
92
- * The duplicated copies that this replaces both silently swallowed refresh
93
- * failure (see review issue #1 on both PR #47 + PR #180). Making
94
- * `onRefreshFailed` *required* enforces at the type level that consumers
95
- * wire up a logout path.
96
- */
97
76
  export declare interface CreateMovmoAuthFetchOptions {
98
77
  /**
99
78
  * Refreshes the session cookie. Called once on the first 401/403; concurrent
@@ -129,6 +108,11 @@ export declare interface CreateMovmoAuthFetchOptions {
129
108
  * infinite recursion when refresh returns 401. Defaults to `/v1/auth/`.
130
109
  */
131
110
  authPathFragment?: string;
111
+ /**
112
+ * 403 response `code`s that skip refresh + retry (the response is returned
113
+ * as-is). Defaults to {@link DEFAULT_NO_REFRESH_CODES}.
114
+ */
115
+ noRefreshCodes?: ReadonlySet<string>;
132
116
  /**
133
117
  * Underlying fetch impl. Defaults to `globalThis.fetch.bind(globalThis)`.
134
118
  * Override only for tests or non-browser environments.
@@ -141,6 +125,9 @@ export declare interface CreateMovmoAuthFetchOptions {
141
125
  readCookie?: (name: string) => string | null;
142
126
  }
143
127
 
128
+ /** 403 error codes that mean "refreshing will not help"; the wrapper returns them untouched. */
129
+ export declare const DEFAULT_NO_REFRESH_CODES: ReadonlySet<string>;
130
+
144
131
  /**
145
132
  * The cardholder/zip subset of {@link UpdatePaymentMethodPatch} that the Edit
146
133
  * flow (MOVMO-381) can mutate. Derived via `Pick` so it stays in sync if the
@@ -154,7 +141,7 @@ export declare const getPaymentsConfig: () => InternalPaymentsConfig;
154
141
  /* Excluded from this release type: InternalPaymentsConfig */
155
142
 
156
143
  export declare const MovmoCardForm: {
157
- ({ userId, onSuccess, onError, onErrorDetail, isDefault, className, defaultCardholderName, defaultCardholderFirstName, defaultCardholderLastName, autoSave, formId, hideInternalSaveButton, onCanSubmitChange, onSavingChange, autoFocus, }: MovmoCardFormProps): JSX_2.Element;
144
+ ({ userId, onSuccess, onError, onErrorDetail, isDefault, className, defaultCardholderName, defaultCardholderFirstName, defaultCardholderLastName, autoSave, formId, hideInternalSaveButton, onCanSubmitChange, onSavingChange, autoFocus, onValidationChange, }: MovmoCardFormProps): JSX_2.Element;
158
145
  displayName: string;
159
146
  };
160
147
 
@@ -221,6 +208,14 @@ export declare interface MovmoCardFormProps {
221
208
  * accounts-ui add-card modal so the user can start typing immediately.
222
209
  */
223
210
  autoFocus?: 'number' | 'cvv';
211
+ /**
212
+ * Fires whenever the validation snapshot changes (missing fields or
213
+ * touched-invalid fields). PII-free by design — carries field KEYS only,
214
+ * never values — so a parent can turn a touched-invalid streak into an
215
+ * analytics-neutral friction event without the SDK importing an analytics
216
+ * library.
217
+ */
218
+ onValidationChange?: (state: CardValidationState) => void;
224
219
  }
225
220
 
226
221
  /**
@@ -254,6 +249,12 @@ export declare interface PaymentManagerError {
254
249
  source: PaymentErrorSource;
255
250
  message: string;
256
251
  cause?: unknown;
252
+ /** HTTP status when the failure was an API response. */
253
+ status?: number;
254
+ /** Server error code (e.g. `recovery_cooldown`) when present. */
255
+ code?: string;
256
+ /** RFC3339 UTC end of a `recovery_cooldown`, when the server sent it. */
257
+ until?: string;
257
258
  }
258
259
 
259
260
  /**
@@ -270,8 +271,15 @@ export declare interface PaymentManagerError {
270
271
  * - `edited` — the cardholder/zip PUT succeeded.
271
272
  * - `deleted` — the delete request succeeded.
272
273
  * - `set_default` — the set-default PUT succeeded.
274
+ * - `validation_error` — a touched card-form field is invalid (fires once per
275
+ * invalid streak, not per keystroke). This is a friction
276
+ * signal, not a failure — it's the analytics-neutral
277
+ * counterpart to `onError`'s `card-tokenize` source, meant
278
+ * to surface which fields trip people up. Props are
279
+ * `field_count` and `fields` (touched-invalid field KEYS
280
+ * only, e.g. `'zip'` — never card values).
273
281
  */
274
- export declare type PaymentMethodEvent = 'autoselected' | 'selected' | 'added' | 'edited' | 'deleted' | 'set_default';
282
+ export declare type PaymentMethodEvent = 'autoselected' | 'selected' | 'added' | 'edited' | 'deleted' | 'set_default' | 'validation_error';
275
283
 
276
284
  /**
277
285
  * Compact preview of a single saved card: brand icon + label. Used by
@@ -415,10 +423,26 @@ export declare interface PaymentMethodSummary {
415
423
  vault?: 'stripe' | 'spreedly';
416
424
  /** Current cardholder name (MOVMO-381). Empty/absent for legacy rows. */
417
425
  cardholderName?: string;
418
- /** Current billing ZIP (MOVMO-381). Empty/absent for legacy rows. */
426
+ /** Current billing postal code (MOVMO-381). Empty/absent for legacy rows. */
419
427
  zip?: string;
420
428
  }
421
429
 
430
+ /**
431
+ * Error thrown by every API helper on a non-2xx response. `code`/`until` come
432
+ * from the server's JSON error body (e.g. `recovery_cooldown`); `message` is
433
+ * user-facing (cool-down copy, the server `message`, or the helper's fallback).
434
+ */
435
+ export declare class PaymentsApiError extends Error {
436
+ readonly status: number;
437
+ readonly code?: string;
438
+ readonly until?: string;
439
+ constructor(message: string, opts: {
440
+ status: number;
441
+ code?: string;
442
+ until?: string;
443
+ });
444
+ }
445
+
422
446
  export declare interface PaymentsConfig {
423
447
  baseUrl: string;
424
448
  /**