@amos.com/amos-js 0.9.17 → 0.10.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 +32 -4
- package/dist/index.d.ts +4 -2
- package/dist/index.js +108 -2
- package/dist/index.mjs +425 -170
- package/dist/messaging.d.ts +7 -0
- package/dist/mount.d.ts +23 -3
- package/dist/payment-method-form.d.ts +8 -1
- 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 +48 -2
- package/package.json +1 -1
- package/src/index.ts +15 -0
- package/src/messaging.ts +107 -16
- package/src/mount.ts +52 -4
- package/src/payment-method-form.ts +16 -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 +60 -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
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Appearance, ConfirmationResult, PaymentMethodFormValidityChangeEvent } from './types';
|
|
1
|
+
import { Appearance, ConfirmationResult, PaymentMethodFormCardBrandChangeEvent, PaymentMethodFormValidityChangeEvent } from './types';
|
|
2
2
|
/**
|
|
3
3
|
* The additional fields beyond the standard card number, expiration
|
|
4
4
|
* date, CVV, and billing address fields that are required to be filled
|
|
@@ -65,6 +65,13 @@ export type PaymentMethodFormListenerOptions = {
|
|
|
65
65
|
* PCI data. Use this to enable or disable a host checkout button.
|
|
66
66
|
*/
|
|
67
67
|
onValidityChange?: (event: PaymentMethodFormValidityChangeEvent) => void;
|
|
68
|
+
/**
|
|
69
|
+
* Called when the detected card brand changes. `brand` is the matched
|
|
70
|
+
* network, or `null` when the field is empty or the digits do not
|
|
71
|
+
* match a known brand. Does not include PCI data. Credit-card form
|
|
72
|
+
* only — never fired for bank account.
|
|
73
|
+
*/
|
|
74
|
+
onCardBrandChanged?: (event: PaymentMethodFormCardBrandChangeEvent) => void;
|
|
68
75
|
/**
|
|
69
76
|
* Called when the interactive confirmation flow finishes (success or
|
|
70
77
|
* terminal failure). Not settlement proof — verify via webhooks.
|
|
@@ -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
|
@@ -7,6 +7,18 @@ import { components } from '@amos.com/node';
|
|
|
7
7
|
export type PaymentMethodFormValidityChangeEvent = {
|
|
8
8
|
isValid: boolean;
|
|
9
9
|
};
|
|
10
|
+
/**
|
|
11
|
+
* Detected card network from the PAN prefix inside the iframe.
|
|
12
|
+
* `null` when the field is empty or the digits do not match a known brand.
|
|
13
|
+
*/
|
|
14
|
+
export type CardBrand = "visa" | "mastercard" | "amex" | "discover" | "diners" | "jcb";
|
|
15
|
+
/**
|
|
16
|
+
* PCI-safe card brand update from the credit-card iframe. Does not
|
|
17
|
+
* include the PAN, last4, or BIN.
|
|
18
|
+
*/
|
|
19
|
+
export type PaymentMethodFormCardBrandChangeEvent = {
|
|
20
|
+
brand: CardBrand | null;
|
|
21
|
+
};
|
|
10
22
|
/**
|
|
11
23
|
* Why an interactive confirmation attempt ended with `status: "incomplete"`.
|
|
12
24
|
*
|
|
@@ -185,10 +197,16 @@ export type Message = {
|
|
|
185
197
|
} | ({
|
|
186
198
|
/** Parent → embed: confirm a payment intent with an embed token. */
|
|
187
199
|
type: "CONFIRM_PAYMENT_INTENT";
|
|
188
|
-
} & Pick<components["schemas"]["PaymentIntent"], "id"> & Pick<components["schemas"]["EmbedToken"], "token">
|
|
200
|
+
} & Pick<components["schemas"]["PaymentIntent"], "id"> & Pick<components["schemas"]["EmbedToken"], "token"> & {
|
|
201
|
+
/** Present when ACH verification completed via Plaid Link in the parent. */
|
|
202
|
+
plaid?: components["schemas"]["PlaidCredentialsInput"];
|
|
203
|
+
}) | ({
|
|
189
204
|
/** Parent → embed: confirm a setup intent with an embed token. */
|
|
190
205
|
type: "CONFIRM_SETUP_INTENT";
|
|
191
|
-
} & Pick<components["schemas"]["SetupIntent"], "id"> & Pick<components["schemas"]["EmbedToken"], "token">
|
|
206
|
+
} & Pick<components["schemas"]["SetupIntent"], "id"> & Pick<components["schemas"]["EmbedToken"], "token"> & {
|
|
207
|
+
/** Present when ACH verification completed via Plaid Link in the parent. */
|
|
208
|
+
plaid?: components["schemas"]["PlaidCredentialsInput"];
|
|
209
|
+
}) | {
|
|
192
210
|
/**
|
|
193
211
|
* Embed → parent: the interactive confirmation flow finished.
|
|
194
212
|
* Parent → embed: express-checkout initiation failed on the host.
|
|
@@ -220,6 +238,34 @@ export type Message = {
|
|
|
220
238
|
*/
|
|
221
239
|
type: "FORM_VALIDITY_CHANGE";
|
|
222
240
|
isValid: boolean;
|
|
241
|
+
} | {
|
|
242
|
+
/**
|
|
243
|
+
* Embed → parent: detected card brand changed. `brand` is the
|
|
244
|
+
* matched network, or `null` when the field is empty or the
|
|
245
|
+
* digits do not match a known brand. Does not include PCI data.
|
|
246
|
+
*/
|
|
247
|
+
type: "CARD_BRAND_CHANGE";
|
|
248
|
+
brand: CardBrand | null;
|
|
249
|
+
} | {
|
|
250
|
+
/**
|
|
251
|
+
* Embed → parent: merchant ACH verification threshold for this
|
|
252
|
+
* render token. `achThreshold` is cents, or `null` when the
|
|
253
|
+
* merchant has no threshold (manual ACH). `requireVerification`
|
|
254
|
+
* is true when the fetch failed in production (fail closed).
|
|
255
|
+
*/
|
|
256
|
+
type: "ACH_THRESHOLD";
|
|
257
|
+
achThreshold?: number | null;
|
|
258
|
+
requireVerification?: boolean;
|
|
259
|
+
} | {
|
|
260
|
+
/** Parent → embed: mint a Plaid Link token for Connect. */
|
|
261
|
+
type: "CREATE_PLAID_LINK_TOKEN";
|
|
262
|
+
requestId: string;
|
|
263
|
+
} | {
|
|
264
|
+
/** Embed → parent: minted Plaid Link token, or `error`. */
|
|
265
|
+
type: "PLAID_LINK_TOKEN";
|
|
266
|
+
requestId: string;
|
|
267
|
+
link_token?: string;
|
|
268
|
+
error?: string;
|
|
223
269
|
};
|
|
224
270
|
/**
|
|
225
271
|
* Identity helper that brands an object as a typed `Message`.
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -52,6 +52,7 @@ export {
|
|
|
52
52
|
export type {
|
|
53
53
|
AmosApplePayButtonMountController,
|
|
54
54
|
AmosApplePayButtonOptions,
|
|
55
|
+
AmosBankAccountPaymentMethodFormMountController,
|
|
55
56
|
AmosBankAccountPaymentMethodFormOptions,
|
|
56
57
|
AmosCreditCardPaymentMethodFormOptions,
|
|
57
58
|
AmosGooglePayButtonMountController,
|
|
@@ -77,14 +78,28 @@ export {
|
|
|
77
78
|
getCreditCardFormInitialHeight,
|
|
78
79
|
getCreditCardFormSrc,
|
|
79
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";
|
|
80
93
|
export type {
|
|
81
94
|
Appearance,
|
|
82
95
|
AppearanceLabels,
|
|
83
96
|
ApplePayButtonElementProps,
|
|
97
|
+
CardBrand,
|
|
84
98
|
ConfirmationIncompleteReason,
|
|
85
99
|
ConfirmationResult,
|
|
86
100
|
GooglePayButtonElementProps,
|
|
87
101
|
Message,
|
|
102
|
+
PaymentMethodFormCardBrandChangeEvent,
|
|
88
103
|
PaymentMethodFormValidityChangeEvent,
|
|
89
104
|
ThemeVariable,
|
|
90
105
|
} from "./types";
|
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
|
/**
|
package/src/mount.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
type PaymentMethodFormController,
|
|
32
32
|
type PaymentMethodFormListenerOptions,
|
|
33
33
|
} from "./payment-method-form";
|
|
34
|
+
import { attachPlaidBankUi } from "./plaid-bank-ui";
|
|
34
35
|
|
|
35
36
|
type Container = HTMLElement | string;
|
|
36
37
|
|
|
@@ -425,6 +426,18 @@ export type AmosPaymentMethodFormMountController =
|
|
|
425
426
|
iframe: HTMLIFrameElement;
|
|
426
427
|
};
|
|
427
428
|
|
|
429
|
+
/**
|
|
430
|
+
* Controller returned by {@link mountAmosBankAccountPaymentMethodForm}.
|
|
431
|
+
* `update()` also accepts `amount` (major-currency decimal string) when
|
|
432
|
+
* the charge changes.
|
|
433
|
+
*/
|
|
434
|
+
export type AmosBankAccountPaymentMethodFormMountController = Omit<
|
|
435
|
+
AmosPaymentMethodFormMountController,
|
|
436
|
+
"update"
|
|
437
|
+
> & {
|
|
438
|
+
update: (patch: Partial<AmosBankAccountPaymentMethodFormOptions>) => void;
|
|
439
|
+
};
|
|
440
|
+
|
|
428
441
|
/**
|
|
429
442
|
* Mount the secure credit-card payment method form into a container
|
|
430
443
|
* element. Returns a controller exposing the underlying iframe, an
|
|
@@ -494,6 +507,14 @@ export type AmosBankAccountPaymentMethodFormOptions =
|
|
|
494
507
|
* @default "country"
|
|
495
508
|
*/
|
|
496
509
|
billingAddressRequirement?: BillingAddressRequirement;
|
|
510
|
+
/**
|
|
511
|
+
* Charge amount as a major-currency decimal string (e.g. `"50.00"`
|
|
512
|
+
* for $50.00), the same format as Google Pay / Apple Pay. Compared
|
|
513
|
+
* to the merchant ACH threshold fetched by the iframe. Omit for
|
|
514
|
+
* setup intents or when the charge is unknown — if a threshold is
|
|
515
|
+
* set, Plaid is required.
|
|
516
|
+
*/
|
|
517
|
+
amount?: string;
|
|
497
518
|
};
|
|
498
519
|
|
|
499
520
|
/**
|
|
@@ -501,8 +522,12 @@ export type AmosBankAccountPaymentMethodFormOptions =
|
|
|
501
522
|
* element. Returns a controller exposing the underlying iframe, an
|
|
502
523
|
* `update()` method, and a `destroy()` method.
|
|
503
524
|
*
|
|
504
|
-
*
|
|
505
|
-
*
|
|
525
|
+
* When the iframe reports an ACH threshold and `amount` meets it (or
|
|
526
|
+
* `amount` is omitted), a Connect bank button is rendered in the parent
|
|
527
|
+
* document and Plaid Link is opened on click. The button uses the same
|
|
528
|
+
* `appearance.themeVariables` as the iframe (and inherits host-page
|
|
529
|
+
* tokens when those variables are unset). Otherwise a field-shaped
|
|
530
|
+
* skeleton is shown and replaced by the iframe once appearance is applied.
|
|
506
531
|
*
|
|
507
532
|
* Use the returned `controller.iframe` when calling
|
|
508
533
|
* {@link validateForm}, {@link confirmPaymentIntent}, or
|
|
@@ -511,11 +536,12 @@ export type AmosBankAccountPaymentMethodFormOptions =
|
|
|
511
536
|
export function mountAmosBankAccountPaymentMethodForm(
|
|
512
537
|
container: Container,
|
|
513
538
|
options: AmosBankAccountPaymentMethodFormOptions,
|
|
514
|
-
):
|
|
539
|
+
): AmosBankAccountPaymentMethodFormMountController {
|
|
515
540
|
const host = resolveContainer(container);
|
|
516
541
|
const {
|
|
517
542
|
renderToken,
|
|
518
543
|
billingAddressRequirement = "country",
|
|
544
|
+
amount,
|
|
519
545
|
...listenerOptions
|
|
520
546
|
} = options;
|
|
521
547
|
|
|
@@ -526,7 +552,7 @@ export function mountAmosBankAccountPaymentMethodForm(
|
|
|
526
552
|
height: getBankAccountFormInitialHeight(billingAddressRequirement),
|
|
527
553
|
});
|
|
528
554
|
|
|
529
|
-
|
|
555
|
+
const iframeMount = mountPaymentMethodFormWithSkeleton({
|
|
530
556
|
host,
|
|
531
557
|
iframe,
|
|
532
558
|
listenerOptions,
|
|
@@ -536,6 +562,28 @@ export function mountAmosBankAccountPaymentMethodForm(
|
|
|
536
562
|
billingAddressRequirement,
|
|
537
563
|
},
|
|
538
564
|
});
|
|
565
|
+
|
|
566
|
+
const plaidUi = attachPlaidBankUi({
|
|
567
|
+
host,
|
|
568
|
+
iframe,
|
|
569
|
+
options: {
|
|
570
|
+
amount,
|
|
571
|
+
appearance: listenerOptions.appearance,
|
|
572
|
+
onValidityChange: listenerOptions.onValidityChange,
|
|
573
|
+
},
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
return {
|
|
577
|
+
iframe,
|
|
578
|
+
update(patch) {
|
|
579
|
+
iframeMount.update(patch as Partial<PaymentMethodFormListenerOptions>);
|
|
580
|
+
plaidUi.update(patch);
|
|
581
|
+
},
|
|
582
|
+
destroy() {
|
|
583
|
+
plaidUi.destroy();
|
|
584
|
+
iframeMount.destroy();
|
|
585
|
+
},
|
|
586
|
+
};
|
|
539
587
|
}
|
|
540
588
|
|
|
541
589
|
/**
|
|
@@ -3,10 +3,12 @@ import {
|
|
|
3
3
|
sendParentReadyMessage,
|
|
4
4
|
updateAppearance as sendUpdateAppearance,
|
|
5
5
|
} from "./messaging";
|
|
6
|
+
import { getBankPlaidSession } from "./plaid-session";
|
|
6
7
|
import type {
|
|
7
8
|
Appearance,
|
|
8
9
|
ConfirmationResult,
|
|
9
10
|
Message,
|
|
11
|
+
PaymentMethodFormCardBrandChangeEvent,
|
|
10
12
|
PaymentMethodFormValidityChangeEvent,
|
|
11
13
|
} from "./types";
|
|
12
14
|
|
|
@@ -137,6 +139,13 @@ export type PaymentMethodFormListenerOptions = {
|
|
|
137
139
|
* PCI data. Use this to enable or disable a host checkout button.
|
|
138
140
|
*/
|
|
139
141
|
onValidityChange?: (event: PaymentMethodFormValidityChangeEvent) => void;
|
|
142
|
+
/**
|
|
143
|
+
* Called when the detected card brand changes. `brand` is the matched
|
|
144
|
+
* network, or `null` when the field is empty or the digits do not
|
|
145
|
+
* match a known brand. Does not include PCI data. Credit-card form
|
|
146
|
+
* only — never fired for bank account.
|
|
147
|
+
*/
|
|
148
|
+
onCardBrandChanged?: (event: PaymentMethodFormCardBrandChangeEvent) => void;
|
|
140
149
|
/**
|
|
141
150
|
* Called when the interactive confirmation flow finishes (success or
|
|
142
151
|
* terminal failure). Not settlement proof — verify via webhooks.
|
|
@@ -203,9 +212,16 @@ export function attachPaymentMethodFormListeners(
|
|
|
203
212
|
break;
|
|
204
213
|
|
|
205
214
|
case "FORM_VALIDITY_CHANGE":
|
|
215
|
+
if (getBankPlaidSession(iframe)?.requiresVerification) {
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
206
218
|
current.onValidityChange?.({ isValid: event.data.isValid });
|
|
207
219
|
break;
|
|
208
220
|
|
|
221
|
+
case "CARD_BRAND_CHANGE":
|
|
222
|
+
current.onCardBrandChanged?.({ brand: event.data.brand });
|
|
223
|
+
break;
|
|
224
|
+
|
|
209
225
|
case "CONFIRMATION_RESULT":
|
|
210
226
|
current.onResult(event.data.result);
|
|
211
227
|
break;
|