@amos.com/amos-js 0.9.16 → 0.9.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -7
- package/dist/form-skeleton.d.ts +31 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +112 -2
- package/dist/index.mjs +543 -234
- package/dist/messaging.d.ts +7 -0
- package/dist/mount.d.ts +29 -3
- package/dist/plaid-bank-ui.d.ts +21 -0
- package/dist/plaid-session.d.ts +12 -0
- package/dist/plaid.d.ts +82 -0
- package/dist/types.d.ts +28 -2
- package/package.json +1 -1
- package/src/form-skeleton.ts +77 -2
- package/src/index.ts +27 -2
- package/src/messaging.ts +107 -16
- package/src/mount.ts +222 -44
- package/src/payment-method-form.ts +4 -0
- package/src/plaid-bank-ui.ts +421 -0
- package/src/plaid-session.ts +41 -0
- package/src/plaid.ts +215 -0
- package/src/types.ts +31 -2
package/dist/messaging.d.ts
CHANGED
|
@@ -68,6 +68,13 @@ export declare function updateMerchantName({ iframe, merchantName, }: {
|
|
|
68
68
|
export declare function validateForm({ iframe }: {
|
|
69
69
|
iframe: Iframe;
|
|
70
70
|
}): Promise<boolean>;
|
|
71
|
+
/**
|
|
72
|
+
* Ask the bank iframe to mint a Plaid Link token (`POST /plaid_link_tokens`
|
|
73
|
+
* on embed). Resolves with `link_token`, or rejects on error / timeout.
|
|
74
|
+
*/
|
|
75
|
+
export declare function requestPlaidLinkToken({ iframe, }: {
|
|
76
|
+
iframe: Iframe;
|
|
77
|
+
}): Promise<string>;
|
|
71
78
|
/**
|
|
72
79
|
* Clear all field values and API errors in the embedded credit-card or
|
|
73
80
|
* bank-account iframe form.
|
package/dist/mount.d.ts
CHANGED
|
@@ -40,6 +40,14 @@ export type AmosPaymentMethodFormMountController = PaymentMethodFormController &
|
|
|
40
40
|
*/
|
|
41
41
|
iframe: HTMLIFrameElement;
|
|
42
42
|
};
|
|
43
|
+
/**
|
|
44
|
+
* Controller returned by {@link mountAmosBankAccountPaymentMethodForm}.
|
|
45
|
+
* `update()` also accepts `amount` (major-currency decimal string) when
|
|
46
|
+
* the charge changes.
|
|
47
|
+
*/
|
|
48
|
+
export type AmosBankAccountPaymentMethodFormMountController = Omit<AmosPaymentMethodFormMountController, "update"> & {
|
|
49
|
+
update: (patch: Partial<AmosBankAccountPaymentMethodFormOptions>) => void;
|
|
50
|
+
};
|
|
43
51
|
/**
|
|
44
52
|
* Mount the secure credit-card payment method form into a container
|
|
45
53
|
* element. Returns a controller exposing the underlying iframe, an
|
|
@@ -70,20 +78,32 @@ export type AmosBankAccountPaymentMethodFormOptions = PaymentMethodFormListenerO
|
|
|
70
78
|
* @default "country"
|
|
71
79
|
*/
|
|
72
80
|
billingAddressRequirement?: BillingAddressRequirement;
|
|
81
|
+
/**
|
|
82
|
+
* Charge amount as a major-currency decimal string (e.g. `"50.00"`
|
|
83
|
+
* for $50.00), the same format as Google Pay / Apple Pay. Compared
|
|
84
|
+
* to the merchant ACH threshold fetched by the iframe. Omit for
|
|
85
|
+
* setup intents or when the charge is unknown — if a threshold is
|
|
86
|
+
* set, Plaid is required.
|
|
87
|
+
*/
|
|
88
|
+
amount?: string;
|
|
73
89
|
};
|
|
74
90
|
/**
|
|
75
91
|
* Mount the secure bank-account payment method form into a container
|
|
76
92
|
* element. Returns a controller exposing the underlying iframe, an
|
|
77
93
|
* `update()` method, and a `destroy()` method.
|
|
78
94
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
95
|
+
* When the iframe reports an ACH threshold and `amount` meets it (or
|
|
96
|
+
* `amount` is omitted), a Connect bank button is rendered in the parent
|
|
97
|
+
* document and Plaid Link is opened on click. The button uses the same
|
|
98
|
+
* `appearance.themeVariables` as the iframe (and inherits host-page
|
|
99
|
+
* tokens when those variables are unset). Otherwise a field-shaped
|
|
100
|
+
* skeleton is shown and replaced by the iframe once appearance is applied.
|
|
81
101
|
*
|
|
82
102
|
* Use the returned `controller.iframe` when calling
|
|
83
103
|
* {@link validateForm}, {@link confirmPaymentIntent}, or
|
|
84
104
|
* {@link confirmSetupIntent}.
|
|
85
105
|
*/
|
|
86
|
-
export declare function mountAmosBankAccountPaymentMethodForm(container: Container, options: AmosBankAccountPaymentMethodFormOptions):
|
|
106
|
+
export declare function mountAmosBankAccountPaymentMethodForm(container: Container, options: AmosBankAccountPaymentMethodFormOptions): AmosBankAccountPaymentMethodFormMountController;
|
|
87
107
|
/**
|
|
88
108
|
* Options accepted by {@link mountAmosGooglePayButton}.
|
|
89
109
|
*/
|
|
@@ -116,6 +136,9 @@ export type AmosGooglePayButtonMountController = GooglePayButtonController & {
|
|
|
116
136
|
* Mount the secure Google Pay button (express checkout) into a
|
|
117
137
|
* container element. Returns a controller exposing the underlying
|
|
118
138
|
* iframe, an `update()` method, and a `destroy()` method.
|
|
139
|
+
*
|
|
140
|
+
* A button-shaped skeleton is shown immediately and replaced by the
|
|
141
|
+
* iframe once appearance is applied.
|
|
119
142
|
*/
|
|
120
143
|
export declare function mountAmosGooglePayButton(container: Container, options: AmosGooglePayButtonOptions): AmosGooglePayButtonMountController;
|
|
121
144
|
/**
|
|
@@ -150,6 +173,9 @@ export type AmosApplePayButtonMountController = ApplePayButtonController & {
|
|
|
150
173
|
* Mount the secure Apple Pay button (express checkout) into a
|
|
151
174
|
* container element. Returns a controller exposing the underlying
|
|
152
175
|
* iframe, an `update()` method, and a `destroy()` method.
|
|
176
|
+
*
|
|
177
|
+
* A button-shaped skeleton is shown immediately and replaced by the
|
|
178
|
+
* iframe once appearance is applied.
|
|
153
179
|
*/
|
|
154
180
|
export declare function mountAmosApplePayButton(container: Container, options: AmosApplePayButtonOptions): AmosApplePayButtonMountController;
|
|
155
181
|
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { PaymentMethodFormListenerOptions } from './payment-method-form';
|
|
2
|
+
export type PlaidBankUiOptions = {
|
|
3
|
+
amount?: string;
|
|
4
|
+
appearance?: PaymentMethodFormListenerOptions["appearance"];
|
|
5
|
+
onValidityChange?: PaymentMethodFormListenerOptions["onValidityChange"];
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Parent-page Connect bank UI. Outline/ghost controls match `@amos/ui`
|
|
9
|
+
* (including `:focus-visible` `--ring`). Unset theme variables inherit
|
|
10
|
+
* from the host document, then fall back to {@link ThemeVariable}
|
|
11
|
+
* defaults; `appearance.themeVariables` overrides with the same replace
|
|
12
|
+
* model as the iframe.
|
|
13
|
+
*/
|
|
14
|
+
export declare function attachPlaidBankUi({ host, iframe, options, }: {
|
|
15
|
+
host: HTMLElement;
|
|
16
|
+
iframe: HTMLIFrameElement;
|
|
17
|
+
options: PlaidBankUiOptions;
|
|
18
|
+
}): {
|
|
19
|
+
update: (patch: Partial<PlaidBankUiOptions>) => void;
|
|
20
|
+
destroy: () => void;
|
|
21
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PlaidCredentials } from './plaid';
|
|
2
|
+
type BankPlaidSession = {
|
|
3
|
+
requiresVerification: boolean;
|
|
4
|
+
plaid?: PlaidCredentials;
|
|
5
|
+
/** Drop linked Plaid credentials and restore the Connect button. */
|
|
6
|
+
clearLinked?: () => void;
|
|
7
|
+
};
|
|
8
|
+
export declare function setBankPlaidSession(iframe: HTMLIFrameElement, session: BankPlaidSession): void;
|
|
9
|
+
export declare function getBankPlaidSession(iframe: HTMLIFrameElement | null | undefined): BankPlaidSession | undefined;
|
|
10
|
+
export declare function clearBankPlaidSession(iframe: HTMLIFrameElement | null | undefined): void;
|
|
11
|
+
export declare function getBankPlaidCredentials(iframe: HTMLIFrameElement | null | undefined): PlaidCredentials | undefined;
|
|
12
|
+
export {};
|
package/dist/plaid.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { components } from '@amos.com/node';
|
|
2
|
+
export type PlaidCredentials = components["schemas"]["PlaidCredentialsInput"];
|
|
3
|
+
export type PlaidLinkAccount = {
|
|
4
|
+
id?: string;
|
|
5
|
+
mask?: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
subtype?: string | null;
|
|
8
|
+
type?: string;
|
|
9
|
+
};
|
|
10
|
+
export type PlaidLinkOnSuccessMetadata = {
|
|
11
|
+
institution?: {
|
|
12
|
+
name?: string;
|
|
13
|
+
} | null;
|
|
14
|
+
accounts?: Array<PlaidLinkAccount>;
|
|
15
|
+
account?: PlaidLinkAccount;
|
|
16
|
+
account_id?: string;
|
|
17
|
+
};
|
|
18
|
+
type PlaidLinkHandler = {
|
|
19
|
+
open: () => void;
|
|
20
|
+
exit: (options?: {
|
|
21
|
+
force?: boolean;
|
|
22
|
+
}) => void;
|
|
23
|
+
destroy: () => void;
|
|
24
|
+
};
|
|
25
|
+
type PlaidCreateConfig = {
|
|
26
|
+
token: string;
|
|
27
|
+
onSuccess: (publicToken: string, metadata: PlaidLinkOnSuccessMetadata) => void;
|
|
28
|
+
onExit?: (error: {
|
|
29
|
+
error_code?: string;
|
|
30
|
+
error_message?: string;
|
|
31
|
+
} | null, metadata: unknown) => void;
|
|
32
|
+
};
|
|
33
|
+
declare global {
|
|
34
|
+
interface Window {
|
|
35
|
+
Plaid?: {
|
|
36
|
+
create: (config: PlaidCreateConfig) => PlaidLinkHandler;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Whether the bank form should show Plaid Link instead of routing/account
|
|
42
|
+
* fields.
|
|
43
|
+
*
|
|
44
|
+
* - No `achThreshold`: always manual ACH (backward compatible).
|
|
45
|
+
* - `amount` omitted: Plaid (setup / unknown future charge).
|
|
46
|
+
* - Otherwise: Plaid when `amount >= achThreshold`.
|
|
47
|
+
*
|
|
48
|
+
* `amount` and `achThreshold` are integer minor units (cents).
|
|
49
|
+
*/
|
|
50
|
+
export declare function requiresAchVerification({ amount, achThreshold, }: {
|
|
51
|
+
amount?: number;
|
|
52
|
+
achThreshold?: number;
|
|
53
|
+
}): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Convert a major-currency decimal string (e.g. `"50.00"`) to integer
|
|
56
|
+
* cents. Empty / invalid values are omitted.
|
|
57
|
+
*/
|
|
58
|
+
export declare function majorAmountToMinorUnits(amount?: string): number | undefined;
|
|
59
|
+
export declare function plaidAccountIdFromMetadata(metadata: PlaidLinkOnSuccessMetadata): string | undefined;
|
|
60
|
+
export declare function linkedBankLabelFromMetadata(metadata: PlaidLinkOnSuccessMetadata): {
|
|
61
|
+
bankName: string;
|
|
62
|
+
last4: string;
|
|
63
|
+
};
|
|
64
|
+
export declare function loadPlaidScript(): Promise<void>;
|
|
65
|
+
export type OpenPlaidLinkInput = {
|
|
66
|
+
token: string;
|
|
67
|
+
onSuccess: (publicToken: string, metadata: PlaidLinkOnSuccessMetadata) => void;
|
|
68
|
+
onExit?: (error: {
|
|
69
|
+
error_code?: string;
|
|
70
|
+
} | null) => void;
|
|
71
|
+
/**
|
|
72
|
+
* When aborted (e.g. the bank form was unmounted), skip opening Link
|
|
73
|
+
* and destroy the handler if it was already created.
|
|
74
|
+
*/
|
|
75
|
+
signal?: AbortSignal;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Load Plaid Link (if needed) and open it with the given `link_token`.
|
|
79
|
+
* Returns a destroy function for the Link handler.
|
|
80
|
+
*/
|
|
81
|
+
export declare function openPlaidLink({ token, onSuccess, onExit, signal, }: OpenPlaidLinkInput): Promise<() => void>;
|
|
82
|
+
export {};
|
package/dist/types.d.ts
CHANGED
|
@@ -185,10 +185,16 @@ export type Message = {
|
|
|
185
185
|
} | ({
|
|
186
186
|
/** Parent → embed: confirm a payment intent with an embed token. */
|
|
187
187
|
type: "CONFIRM_PAYMENT_INTENT";
|
|
188
|
-
} & Pick<components["schemas"]["PaymentIntent"], "id"> & Pick<components["schemas"]["EmbedToken"], "token">
|
|
188
|
+
} & Pick<components["schemas"]["PaymentIntent"], "id"> & Pick<components["schemas"]["EmbedToken"], "token"> & {
|
|
189
|
+
/** Present when ACH verification completed via Plaid Link in the parent. */
|
|
190
|
+
plaid?: components["schemas"]["PlaidCredentialsInput"];
|
|
191
|
+
}) | ({
|
|
189
192
|
/** Parent → embed: confirm a setup intent with an embed token. */
|
|
190
193
|
type: "CONFIRM_SETUP_INTENT";
|
|
191
|
-
} & Pick<components["schemas"]["SetupIntent"], "id"> & Pick<components["schemas"]["EmbedToken"], "token">
|
|
194
|
+
} & Pick<components["schemas"]["SetupIntent"], "id"> & Pick<components["schemas"]["EmbedToken"], "token"> & {
|
|
195
|
+
/** Present when ACH verification completed via Plaid Link in the parent. */
|
|
196
|
+
plaid?: components["schemas"]["PlaidCredentialsInput"];
|
|
197
|
+
}) | {
|
|
192
198
|
/**
|
|
193
199
|
* Embed → parent: the interactive confirmation flow finished.
|
|
194
200
|
* Parent → embed: express-checkout initiation failed on the host.
|
|
@@ -220,6 +226,26 @@ export type Message = {
|
|
|
220
226
|
*/
|
|
221
227
|
type: "FORM_VALIDITY_CHANGE";
|
|
222
228
|
isValid: boolean;
|
|
229
|
+
} | {
|
|
230
|
+
/**
|
|
231
|
+
* Embed → parent: merchant ACH verification threshold for this
|
|
232
|
+
* render token. `achThreshold` is cents, or `null` when the
|
|
233
|
+
* merchant has no threshold (manual ACH). `requireVerification`
|
|
234
|
+
* is true when the fetch failed in production (fail closed).
|
|
235
|
+
*/
|
|
236
|
+
type: "ACH_THRESHOLD";
|
|
237
|
+
achThreshold?: number | null;
|
|
238
|
+
requireVerification?: boolean;
|
|
239
|
+
} | {
|
|
240
|
+
/** Parent → embed: mint a Plaid Link token for Connect. */
|
|
241
|
+
type: "CREATE_PLAID_LINK_TOKEN";
|
|
242
|
+
requestId: string;
|
|
243
|
+
} | {
|
|
244
|
+
/** Embed → parent: minted Plaid Link token, or `error`. */
|
|
245
|
+
type: "PLAID_LINK_TOKEN";
|
|
246
|
+
requestId: string;
|
|
247
|
+
link_token?: string;
|
|
248
|
+
error?: string;
|
|
223
249
|
};
|
|
224
250
|
/**
|
|
225
251
|
* Identity helper that brands an object as a typed `Message`.
|
package/package.json
CHANGED
package/src/form-skeleton.ts
CHANGED
|
@@ -16,7 +16,7 @@ const SKELETON_THEME_DEFAULTS: Record<string, string> = {
|
|
|
16
16
|
"--label-font-size": "0.875rem",
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
const SKELETON_STYLES = `
|
|
19
|
+
export const SKELETON_STYLES = `
|
|
20
20
|
.amos-js-form-skeleton {
|
|
21
21
|
box-sizing: border-box;
|
|
22
22
|
container-type: inline-size;
|
|
@@ -71,6 +71,10 @@ const SKELETON_STYLES = `
|
|
|
71
71
|
gap: var(--control-gap);
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
+
.amos-js-wallet-skeleton {
|
|
75
|
+
flex: none;
|
|
76
|
+
width: 100%;
|
|
77
|
+
}
|
|
74
78
|
@keyframes amos-js-skeleton-pulse {
|
|
75
79
|
50% { opacity: 0.5; }
|
|
76
80
|
}
|
|
@@ -81,7 +85,7 @@ const SKELETON_STYLES = `
|
|
|
81
85
|
}
|
|
82
86
|
`;
|
|
83
87
|
|
|
84
|
-
function ensureSkeletonStyles(): void {
|
|
88
|
+
export function ensureSkeletonStyles(): void {
|
|
85
89
|
if (document.getElementById(STYLE_ID)) {
|
|
86
90
|
return;
|
|
87
91
|
}
|
|
@@ -242,3 +246,74 @@ export function createPaymentMethodFormSkeleton(
|
|
|
242
246
|
update: render,
|
|
243
247
|
};
|
|
244
248
|
}
|
|
249
|
+
|
|
250
|
+
export type WalletButtonSkeletonOptions = {
|
|
251
|
+
/** Painted height of the wallet button slot (CSS length). */
|
|
252
|
+
height: string;
|
|
253
|
+
/** Corner radius matching the iframe / native button. */
|
|
254
|
+
borderRadius?: string;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
export type WalletButtonSkeleton = {
|
|
258
|
+
element: HTMLElement;
|
|
259
|
+
update: (options: WalletButtonSkeletonOptions) => void;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Host-page placeholder for Google Pay / Apple Pay: a pulsing bar at
|
|
264
|
+
* the button's height. Shown immediately while the iframe loads, then
|
|
265
|
+
* removed when appearance is ready.
|
|
266
|
+
*/
|
|
267
|
+
export function createWalletButtonSkeleton(
|
|
268
|
+
options: WalletButtonSkeletonOptions,
|
|
269
|
+
): WalletButtonSkeleton {
|
|
270
|
+
ensureSkeletonStyles();
|
|
271
|
+
const element = div("amos-js-form-skeleton-input amos-js-wallet-skeleton");
|
|
272
|
+
element.setAttribute("aria-hidden", "true");
|
|
273
|
+
|
|
274
|
+
function render(next: WalletButtonSkeletonOptions): void {
|
|
275
|
+
applyTheme(element, undefined);
|
|
276
|
+
element.style.height = next.height;
|
|
277
|
+
element.style.borderRadius = next.borderRadius ?? "4px";
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
render(options);
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
element,
|
|
284
|
+
update: render,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function cssLength(value: unknown): string | undefined {
|
|
289
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
290
|
+
return `${value}px`;
|
|
291
|
+
}
|
|
292
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
293
|
+
return value.trim();
|
|
294
|
+
}
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Corner radius for a wallet-button skeleton. Prefers host iframe
|
|
300
|
+
* chrome, then native Google / Apple button radius, then 4px.
|
|
301
|
+
*/
|
|
302
|
+
export function resolveWalletButtonSkeletonBorderRadius({
|
|
303
|
+
iframeStyle,
|
|
304
|
+
buttonProps,
|
|
305
|
+
}: {
|
|
306
|
+
iframeStyle?: { borderRadius?: string | number };
|
|
307
|
+
buttonProps?: {
|
|
308
|
+
buttonRadius?: number;
|
|
309
|
+
style?: Record<string, string | number | undefined>;
|
|
310
|
+
};
|
|
311
|
+
}): string {
|
|
312
|
+
return (
|
|
313
|
+
cssLength(iframeStyle?.borderRadius) ??
|
|
314
|
+
cssLength(buttonProps?.buttonRadius) ??
|
|
315
|
+
cssLength(buttonProps?.style?.["borderRadius"]) ??
|
|
316
|
+
cssLength(buttonProps?.style?.["--apple-pay-button-border-radius"]) ??
|
|
317
|
+
"4px"
|
|
318
|
+
);
|
|
319
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -9,7 +9,20 @@ export {
|
|
|
9
9
|
getApplePayButtonInitialHeight,
|
|
10
10
|
getApplePayButtonSrc,
|
|
11
11
|
} from "./apple-pay";
|
|
12
|
-
|
|
12
|
+
export type {
|
|
13
|
+
PaymentMethodFormSkeleton,
|
|
14
|
+
PaymentMethodFormSkeletonKind,
|
|
15
|
+
PaymentMethodFormSkeletonOptions,
|
|
16
|
+
WalletButtonSkeleton,
|
|
17
|
+
WalletButtonSkeletonOptions,
|
|
18
|
+
} from "./form-skeleton";
|
|
19
|
+
export {
|
|
20
|
+
createPaymentMethodFormSkeleton,
|
|
21
|
+
createWalletButtonSkeleton,
|
|
22
|
+
ensureSkeletonStyles,
|
|
23
|
+
resolveWalletButtonSkeletonBorderRadius,
|
|
24
|
+
SKELETON_STYLES,
|
|
25
|
+
} from "./form-skeleton";
|
|
13
26
|
export type {
|
|
14
27
|
FormattedGooglePayPaymentData,
|
|
15
28
|
GooglePayButtonController,
|
|
@@ -21,7 +34,6 @@ export {
|
|
|
21
34
|
getGooglePayButtonInitialHeight,
|
|
22
35
|
getGooglePayButtonSrc,
|
|
23
36
|
} from "./google-pay";
|
|
24
|
-
|
|
25
37
|
export { decodeJwt, getEmbedOrigin } from "./jwt";
|
|
26
38
|
|
|
27
39
|
export {
|
|
@@ -40,6 +52,7 @@ export {
|
|
|
40
52
|
export type {
|
|
41
53
|
AmosApplePayButtonMountController,
|
|
42
54
|
AmosApplePayButtonOptions,
|
|
55
|
+
AmosBankAccountPaymentMethodFormMountController,
|
|
43
56
|
AmosBankAccountPaymentMethodFormOptions,
|
|
44
57
|
AmosCreditCardPaymentMethodFormOptions,
|
|
45
58
|
AmosGooglePayButtonMountController,
|
|
@@ -65,6 +78,18 @@ export {
|
|
|
65
78
|
getCreditCardFormInitialHeight,
|
|
66
79
|
getCreditCardFormSrc,
|
|
67
80
|
} from "./payment-method-form";
|
|
81
|
+
export type {
|
|
82
|
+
OpenPlaidLinkInput,
|
|
83
|
+
PlaidCredentials,
|
|
84
|
+
PlaidLinkOnSuccessMetadata,
|
|
85
|
+
} from "./plaid";
|
|
86
|
+
export {
|
|
87
|
+
linkedBankLabelFromMetadata,
|
|
88
|
+
loadPlaidScript,
|
|
89
|
+
openPlaidLink,
|
|
90
|
+
plaidAccountIdFromMetadata,
|
|
91
|
+
requiresAchVerification,
|
|
92
|
+
} from "./plaid";
|
|
68
93
|
export type {
|
|
69
94
|
Appearance,
|
|
70
95
|
AppearanceLabels,
|
package/src/messaging.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { components } from "@amos.com/node";
|
|
2
2
|
import { decodeJwt } from "./jwt";
|
|
3
|
+
import { getBankPlaidSession } from "./plaid-session";
|
|
3
4
|
import {
|
|
4
5
|
type Appearance,
|
|
5
6
|
type ApplePayButtonElementProps,
|
|
@@ -163,6 +164,11 @@ export function updateMerchantName({
|
|
|
163
164
|
* `false` if the iframe does not respond within 5 seconds.
|
|
164
165
|
*/
|
|
165
166
|
export function validateForm({ iframe }: { iframe: Iframe }): Promise<boolean> {
|
|
167
|
+
const session = getBankPlaidSession(iframe);
|
|
168
|
+
if (session?.requiresVerification) {
|
|
169
|
+
return Promise.resolve(Boolean(session.plaid));
|
|
170
|
+
}
|
|
171
|
+
|
|
166
172
|
const requestId = crypto.randomUUID();
|
|
167
173
|
|
|
168
174
|
return new Promise((resolve) => {
|
|
@@ -193,11 +199,71 @@ export function validateForm({ iframe }: { iframe: Iframe }): Promise<boolean> {
|
|
|
193
199
|
});
|
|
194
200
|
}
|
|
195
201
|
|
|
202
|
+
const PLAID_LINK_TOKEN_TIMEOUT_MS = 10_000;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Ask the bank iframe to mint a Plaid Link token (`POST /plaid_link_tokens`
|
|
206
|
+
* on embed). Resolves with `link_token`, or rejects on error / timeout.
|
|
207
|
+
*/
|
|
208
|
+
export function requestPlaidLinkToken({
|
|
209
|
+
iframe,
|
|
210
|
+
}: {
|
|
211
|
+
iframe: Iframe;
|
|
212
|
+
}): Promise<string> {
|
|
213
|
+
const requestId = crypto.randomUUID();
|
|
214
|
+
|
|
215
|
+
return new Promise((resolve, reject) => {
|
|
216
|
+
if (!iframe?.contentWindow) {
|
|
217
|
+
reject(new Error("Bank form is not ready."));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const contentWindow = iframe.contentWindow;
|
|
222
|
+
contentWindow.postMessage(
|
|
223
|
+
createMessage({ type: "CREATE_PLAID_LINK_TOKEN", requestId }),
|
|
224
|
+
getIframeTargetOrigin(iframe),
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const timeoutId = setTimeout(() => {
|
|
228
|
+
window.removeEventListener("message", handleMessage);
|
|
229
|
+
reject(new Error("Timed out waiting for Plaid Link token."));
|
|
230
|
+
}, PLAID_LINK_TOKEN_TIMEOUT_MS);
|
|
231
|
+
|
|
232
|
+
function handleMessage(event: MessageEvent<Message>) {
|
|
233
|
+
if (event.source !== contentWindow) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (
|
|
237
|
+
event.data.type !== "PLAID_LINK_TOKEN" ||
|
|
238
|
+
event.data.requestId !== requestId
|
|
239
|
+
) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
window.removeEventListener("message", handleMessage);
|
|
243
|
+
clearTimeout(timeoutId);
|
|
244
|
+
if (event.data.link_token) {
|
|
245
|
+
resolve(event.data.link_token);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
reject(
|
|
249
|
+
new Error(event.data.error ?? "Could not create Plaid Link token."),
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
window.addEventListener("message", handleMessage);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
196
257
|
/**
|
|
197
258
|
* Clear all field values and API errors in the embedded credit-card or
|
|
198
259
|
* bank-account iframe form.
|
|
199
260
|
*/
|
|
200
261
|
export function resetForm({ iframe }: { iframe: Iframe }): void {
|
|
262
|
+
getBankPlaidSession(iframe)?.clearLinked?.();
|
|
263
|
+
resetIframeFields(iframe);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function resetIframeFields(iframe: Iframe): void {
|
|
201
267
|
if (!iframe?.contentWindow) {
|
|
202
268
|
return;
|
|
203
269
|
}
|
|
@@ -208,6 +274,35 @@ export function resetForm({ iframe }: { iframe: Iframe }): void {
|
|
|
208
274
|
);
|
|
209
275
|
}
|
|
210
276
|
|
|
277
|
+
function postConfirmIntent({
|
|
278
|
+
iframe,
|
|
279
|
+
type,
|
|
280
|
+
token,
|
|
281
|
+
id,
|
|
282
|
+
}: {
|
|
283
|
+
iframe: HTMLIFrameElement;
|
|
284
|
+
type: "CONFIRM_PAYMENT_INTENT" | "CONFIRM_SETUP_INTENT";
|
|
285
|
+
token: string | undefined;
|
|
286
|
+
id: string | undefined;
|
|
287
|
+
}): void {
|
|
288
|
+
const session = getBankPlaidSession(iframe);
|
|
289
|
+
const plaid = session?.plaid;
|
|
290
|
+
// Plaid mode: do not confirm leftover routing/account numbers.
|
|
291
|
+
if (session?.requiresVerification && !plaid) {
|
|
292
|
+
resetIframeFields(iframe);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
iframe.contentWindow?.postMessage(
|
|
296
|
+
createMessage({
|
|
297
|
+
type,
|
|
298
|
+
token,
|
|
299
|
+
id,
|
|
300
|
+
...(plaid ? { plaid } : {}),
|
|
301
|
+
}),
|
|
302
|
+
getIframeTargetOrigin(iframe),
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
211
306
|
/**
|
|
212
307
|
* Confirm a payment intent in the embedded iframe flow.
|
|
213
308
|
*
|
|
@@ -227,14 +322,12 @@ export function confirmPaymentIntent({
|
|
|
227
322
|
|
|
228
323
|
const { payment_intent_id: id }: components["schemas"]["EmbedTokenJwt"] =
|
|
229
324
|
decodeJwt(token).payload;
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
getIframeTargetOrigin(iframe),
|
|
237
|
-
);
|
|
325
|
+
postConfirmIntent({
|
|
326
|
+
iframe,
|
|
327
|
+
type: "CONFIRM_PAYMENT_INTENT",
|
|
328
|
+
token,
|
|
329
|
+
id: id ?? undefined,
|
|
330
|
+
});
|
|
238
331
|
}
|
|
239
332
|
|
|
240
333
|
/**
|
|
@@ -256,14 +349,12 @@ export function confirmSetupIntent({
|
|
|
256
349
|
|
|
257
350
|
const { setup_intent_id: id }: components["schemas"]["EmbedTokenJwt"] =
|
|
258
351
|
decodeJwt(token).payload;
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
getIframeTargetOrigin(iframe),
|
|
266
|
-
);
|
|
352
|
+
postConfirmIntent({
|
|
353
|
+
iframe,
|
|
354
|
+
type: "CONFIRM_SETUP_INTENT",
|
|
355
|
+
token,
|
|
356
|
+
id: id ?? undefined,
|
|
357
|
+
});
|
|
267
358
|
}
|
|
268
359
|
|
|
269
360
|
/**
|