@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/src/mount.ts
CHANGED
|
@@ -7,7 +7,11 @@ import {
|
|
|
7
7
|
} from "./apple-pay";
|
|
8
8
|
import {
|
|
9
9
|
createPaymentMethodFormSkeleton,
|
|
10
|
+
createWalletButtonSkeleton,
|
|
10
11
|
type PaymentMethodFormSkeletonOptions,
|
|
12
|
+
resolveWalletButtonSkeletonBorderRadius,
|
|
13
|
+
type WalletButtonSkeleton,
|
|
14
|
+
type WalletButtonSkeletonOptions,
|
|
11
15
|
} from "./form-skeleton";
|
|
12
16
|
import {
|
|
13
17
|
attachGooglePayButtonListeners,
|
|
@@ -27,6 +31,7 @@ import {
|
|
|
27
31
|
type PaymentMethodFormController,
|
|
28
32
|
type PaymentMethodFormListenerOptions,
|
|
29
33
|
} from "./payment-method-form";
|
|
34
|
+
import { attachPlaidBankUi } from "./plaid-bank-ui";
|
|
30
35
|
|
|
31
36
|
type Container = HTMLElement | string;
|
|
32
37
|
|
|
@@ -59,6 +64,19 @@ const WALLET_IFRAME_STYLE: Partial<CSSStyleDeclaration> = {
|
|
|
59
64
|
border: "0",
|
|
60
65
|
};
|
|
61
66
|
|
|
67
|
+
const WALLET_SKELETON_IFRAME_STYLE: Partial<CSSStyleDeclaration> = {
|
|
68
|
+
position: "absolute",
|
|
69
|
+
top: "0",
|
|
70
|
+
left: "0",
|
|
71
|
+
width: "100%",
|
|
72
|
+
height: "100%",
|
|
73
|
+
margin: "0",
|
|
74
|
+
opacity: "0",
|
|
75
|
+
// Do not interpolate opacity while the skeleton is showing.
|
|
76
|
+
transition: "none",
|
|
77
|
+
pointerEvents: "none",
|
|
78
|
+
};
|
|
79
|
+
|
|
62
80
|
const SKELETON_IFRAME_STYLE: Partial<CSSStyleDeclaration> = {
|
|
63
81
|
position: "absolute",
|
|
64
82
|
top: "0",
|
|
@@ -231,6 +249,141 @@ function mountPaymentMethodFormWithSkeleton({
|
|
|
231
249
|
};
|
|
232
250
|
}
|
|
233
251
|
|
|
252
|
+
type WalletListenerOptions = {
|
|
253
|
+
height?: string;
|
|
254
|
+
onHeightChange?: (height: string) => void;
|
|
255
|
+
onAppearanceReady?: () => void;
|
|
256
|
+
buttonProps?: {
|
|
257
|
+
buttonRadius?: number;
|
|
258
|
+
style?: Record<string, string | number | undefined>;
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
type WalletListenerController<TOptions> = {
|
|
263
|
+
update: (patch: Partial<TOptions>) => void;
|
|
264
|
+
destroy: () => void;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
function mountWalletButtonWithSkeleton<TOptions extends WalletListenerOptions>({
|
|
268
|
+
host,
|
|
269
|
+
iframe,
|
|
270
|
+
listenerOptions,
|
|
271
|
+
iframeStyle,
|
|
272
|
+
attachListeners,
|
|
273
|
+
}: {
|
|
274
|
+
host: HTMLElement;
|
|
275
|
+
iframe: HTMLIFrameElement;
|
|
276
|
+
listenerOptions: TOptions;
|
|
277
|
+
iframeStyle?: Partial<CSSStyleDeclaration>;
|
|
278
|
+
attachListeners: (
|
|
279
|
+
iframe: HTMLIFrameElement,
|
|
280
|
+
options: TOptions,
|
|
281
|
+
) => WalletListenerController<TOptions>;
|
|
282
|
+
}): {
|
|
283
|
+
iframe: HTMLIFrameElement;
|
|
284
|
+
update: (patch: Partial<TOptions>) => void;
|
|
285
|
+
destroy: () => void;
|
|
286
|
+
} {
|
|
287
|
+
const initialHeight = listenerOptions.height ?? "48px";
|
|
288
|
+
let skeletonOptions: WalletButtonSkeletonOptions = {
|
|
289
|
+
height: initialHeight,
|
|
290
|
+
borderRadius: resolveWalletButtonSkeletonBorderRadius({
|
|
291
|
+
iframeStyle,
|
|
292
|
+
buttonProps: listenerOptions.buttonProps,
|
|
293
|
+
}),
|
|
294
|
+
};
|
|
295
|
+
const skeleton: WalletButtonSkeleton =
|
|
296
|
+
createWalletButtonSkeleton(skeletonOptions);
|
|
297
|
+
|
|
298
|
+
const wrapper = document.createElement("div");
|
|
299
|
+
wrapper.style.position = "relative";
|
|
300
|
+
wrapper.style.width = "100%";
|
|
301
|
+
wrapper.style.height = skeletonOptions.height;
|
|
302
|
+
wrapper.style.overflow = "hidden";
|
|
303
|
+
wrapper.setAttribute("aria-busy", "true");
|
|
304
|
+
|
|
305
|
+
Object.assign(iframe.style, WALLET_SKELETON_IFRAME_STYLE);
|
|
306
|
+
iframe.style.height = "100%";
|
|
307
|
+
|
|
308
|
+
wrapper.append(skeleton.element, iframe);
|
|
309
|
+
host.appendChild(wrapper);
|
|
310
|
+
|
|
311
|
+
let revealed = false;
|
|
312
|
+
let appearanceReady = false;
|
|
313
|
+
let fallbackRevealTimer: ReturnType<typeof setTimeout> | undefined;
|
|
314
|
+
let currentOptions = listenerOptions;
|
|
315
|
+
|
|
316
|
+
function reveal(): void {
|
|
317
|
+
if (revealed) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
revealed = true;
|
|
321
|
+
if (fallbackRevealTimer !== undefined) {
|
|
322
|
+
clearTimeout(fallbackRevealTimer);
|
|
323
|
+
fallbackRevealTimer = undefined;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
iframe.style.opacity = "1";
|
|
327
|
+
iframe.style.pointerEvents = "";
|
|
328
|
+
skeleton.element.remove();
|
|
329
|
+
wrapper.removeAttribute("aria-busy");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function tryReveal(): void {
|
|
333
|
+
if (revealed || !appearanceReady) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
reveal();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const controller = attachListeners(iframe, {
|
|
340
|
+
...listenerOptions,
|
|
341
|
+
onHeightChange: (height) => {
|
|
342
|
+
currentOptions.onHeightChange?.(height);
|
|
343
|
+
},
|
|
344
|
+
onAppearanceReady: () => {
|
|
345
|
+
appearanceReady = true;
|
|
346
|
+
tryReveal();
|
|
347
|
+
currentOptions.onAppearanceReady?.();
|
|
348
|
+
},
|
|
349
|
+
} as TOptions);
|
|
350
|
+
|
|
351
|
+
fallbackRevealTimer = setTimeout(() => {
|
|
352
|
+
reveal();
|
|
353
|
+
}, 1500);
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
iframe,
|
|
357
|
+
update(patch) {
|
|
358
|
+
currentOptions = { ...currentOptions, ...patch };
|
|
359
|
+
// Keep the wrapped reveal listeners. Forwarding these would replace
|
|
360
|
+
// them and leave the iframe at opacity 0.
|
|
361
|
+
const rest = { ...patch };
|
|
362
|
+
delete rest.onAppearanceReady;
|
|
363
|
+
delete rest.onHeightChange;
|
|
364
|
+
controller.update(rest);
|
|
365
|
+
skeletonOptions = {
|
|
366
|
+
height: currentOptions.height ?? skeletonOptions.height,
|
|
367
|
+
borderRadius: resolveWalletButtonSkeletonBorderRadius({
|
|
368
|
+
iframeStyle,
|
|
369
|
+
buttonProps: currentOptions.buttonProps,
|
|
370
|
+
}),
|
|
371
|
+
};
|
|
372
|
+
wrapper.style.height = skeletonOptions.height;
|
|
373
|
+
if (!revealed) {
|
|
374
|
+
skeleton.update(skeletonOptions);
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
destroy() {
|
|
378
|
+
if (fallbackRevealTimer !== undefined) {
|
|
379
|
+
clearTimeout(fallbackRevealTimer);
|
|
380
|
+
}
|
|
381
|
+
controller.destroy();
|
|
382
|
+
wrapper.remove();
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
234
387
|
/**
|
|
235
388
|
* Options accepted by {@link mountAmosCreditCardPaymentMethodForm}.
|
|
236
389
|
*/
|
|
@@ -273,6 +426,18 @@ export type AmosPaymentMethodFormMountController =
|
|
|
273
426
|
iframe: HTMLIFrameElement;
|
|
274
427
|
};
|
|
275
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
|
+
|
|
276
441
|
/**
|
|
277
442
|
* Mount the secure credit-card payment method form into a container
|
|
278
443
|
* element. Returns a controller exposing the underlying iframe, an
|
|
@@ -342,6 +507,14 @@ export type AmosBankAccountPaymentMethodFormOptions =
|
|
|
342
507
|
* @default "country"
|
|
343
508
|
*/
|
|
344
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;
|
|
345
518
|
};
|
|
346
519
|
|
|
347
520
|
/**
|
|
@@ -349,8 +522,12 @@ export type AmosBankAccountPaymentMethodFormOptions =
|
|
|
349
522
|
* element. Returns a controller exposing the underlying iframe, an
|
|
350
523
|
* `update()` method, and a `destroy()` method.
|
|
351
524
|
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
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.
|
|
354
531
|
*
|
|
355
532
|
* Use the returned `controller.iframe` when calling
|
|
356
533
|
* {@link validateForm}, {@link confirmPaymentIntent}, or
|
|
@@ -359,11 +536,12 @@ export type AmosBankAccountPaymentMethodFormOptions =
|
|
|
359
536
|
export function mountAmosBankAccountPaymentMethodForm(
|
|
360
537
|
container: Container,
|
|
361
538
|
options: AmosBankAccountPaymentMethodFormOptions,
|
|
362
|
-
):
|
|
539
|
+
): AmosBankAccountPaymentMethodFormMountController {
|
|
363
540
|
const host = resolveContainer(container);
|
|
364
541
|
const {
|
|
365
542
|
renderToken,
|
|
366
543
|
billingAddressRequirement = "country",
|
|
544
|
+
amount,
|
|
367
545
|
...listenerOptions
|
|
368
546
|
} = options;
|
|
369
547
|
|
|
@@ -374,7 +552,7 @@ export function mountAmosBankAccountPaymentMethodForm(
|
|
|
374
552
|
height: getBankAccountFormInitialHeight(billingAddressRequirement),
|
|
375
553
|
});
|
|
376
554
|
|
|
377
|
-
|
|
555
|
+
const iframeMount = mountPaymentMethodFormWithSkeleton({
|
|
378
556
|
host,
|
|
379
557
|
iframe,
|
|
380
558
|
listenerOptions,
|
|
@@ -384,6 +562,28 @@ export function mountAmosBankAccountPaymentMethodForm(
|
|
|
384
562
|
billingAddressRequirement,
|
|
385
563
|
},
|
|
386
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
|
+
};
|
|
387
587
|
}
|
|
388
588
|
|
|
389
589
|
/**
|
|
@@ -420,6 +620,9 @@ export type AmosGooglePayButtonMountController = GooglePayButtonController & {
|
|
|
420
620
|
* Mount the secure Google Pay button (express checkout) into a
|
|
421
621
|
* container element. Returns a controller exposing the underlying
|
|
422
622
|
* iframe, an `update()` method, and a `destroy()` method.
|
|
623
|
+
*
|
|
624
|
+
* A button-shaped skeleton is shown immediately and replaced by the
|
|
625
|
+
* iframe once appearance is applied.
|
|
423
626
|
*/
|
|
424
627
|
export function mountAmosGooglePayButton(
|
|
425
628
|
container: Container,
|
|
@@ -439,28 +642,14 @@ export function mountAmosGooglePayButton(
|
|
|
439
642
|
style: WALLET_IFRAME_STYLE,
|
|
440
643
|
});
|
|
441
644
|
Object.assign(iframe.style, iframeStyle);
|
|
442
|
-
host.appendChild(iframe);
|
|
443
645
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
onHeightChange: (height) => {
|
|
447
|
-
iframe.style.height = height;
|
|
448
|
-
listenerOptions.onHeightChange?.(height);
|
|
449
|
-
},
|
|
450
|
-
onAppearanceReady: () => {
|
|
451
|
-
iframe.style.opacity = "1";
|
|
452
|
-
listenerOptions.onAppearanceReady?.();
|
|
453
|
-
},
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
return {
|
|
646
|
+
return mountWalletButtonWithSkeleton({
|
|
647
|
+
host,
|
|
457
648
|
iframe,
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
},
|
|
463
|
-
};
|
|
649
|
+
listenerOptions,
|
|
650
|
+
iframeStyle,
|
|
651
|
+
attachListeners: attachGooglePayButtonListeners,
|
|
652
|
+
});
|
|
464
653
|
}
|
|
465
654
|
|
|
466
655
|
/**
|
|
@@ -497,6 +686,9 @@ export type AmosApplePayButtonMountController = ApplePayButtonController & {
|
|
|
497
686
|
* Mount the secure Apple Pay button (express checkout) into a
|
|
498
687
|
* container element. Returns a controller exposing the underlying
|
|
499
688
|
* iframe, an `update()` method, and a `destroy()` method.
|
|
689
|
+
*
|
|
690
|
+
* A button-shaped skeleton is shown immediately and replaced by the
|
|
691
|
+
* iframe once appearance is applied.
|
|
500
692
|
*/
|
|
501
693
|
export function mountAmosApplePayButton(
|
|
502
694
|
container: Container,
|
|
@@ -516,26 +708,12 @@ export function mountAmosApplePayButton(
|
|
|
516
708
|
style: WALLET_IFRAME_STYLE,
|
|
517
709
|
});
|
|
518
710
|
Object.assign(iframe.style, iframeStyle);
|
|
519
|
-
host.appendChild(iframe);
|
|
520
|
-
|
|
521
|
-
const controller = attachApplePayButtonListeners(iframe, {
|
|
522
|
-
...listenerOptions,
|
|
523
|
-
onHeightChange: (height) => {
|
|
524
|
-
iframe.style.height = height;
|
|
525
|
-
listenerOptions.onHeightChange?.(height);
|
|
526
|
-
},
|
|
527
|
-
onAppearanceReady: () => {
|
|
528
|
-
iframe.style.opacity = "1";
|
|
529
|
-
listenerOptions.onAppearanceReady?.();
|
|
530
|
-
},
|
|
531
|
-
});
|
|
532
711
|
|
|
533
|
-
return {
|
|
712
|
+
return mountWalletButtonWithSkeleton({
|
|
713
|
+
host,
|
|
534
714
|
iframe,
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
},
|
|
540
|
-
};
|
|
715
|
+
listenerOptions,
|
|
716
|
+
iframeStyle,
|
|
717
|
+
attachListeners: attachApplePayButtonListeners,
|
|
718
|
+
});
|
|
541
719
|
}
|
|
@@ -3,6 +3,7 @@ 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,
|
|
@@ -203,6 +204,9 @@ export function attachPaymentMethodFormListeners(
|
|
|
203
204
|
break;
|
|
204
205
|
|
|
205
206
|
case "FORM_VALIDITY_CHANGE":
|
|
207
|
+
if (getBankPlaidSession(iframe)?.requiresVerification) {
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
206
210
|
current.onValidityChange?.({ isValid: event.data.isValid });
|
|
207
211
|
break;
|
|
208
212
|
|