@amos.com/react-amos-js 0.9.15 → 0.9.17

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
@@ -39,7 +39,7 @@ The render token configures the iframe's allowed origin(s), allowed payment meth
39
39
  The following flow is for credit card and bank account payment method types only.
40
40
 
41
41
  1. **Set up prerequisites**: create a `renderToken` (safe for client), and keep `apiKey` and `accountId` server-side only.
42
- 2. **Render your checkout UI** with one of the payment method components (e.g. `AmosCreditCardPaymentMethodForm`) along with the required `onResult` prop. Card and bank forms show a field-shaped skeleton immediately (sized from `appearance`, `additionalFields`, and `billingAddressRequirement`); iframe height is auto-managed.
42
+ 2. **Render your checkout UI** with one of the payment method components (e.g. `AmosCreditCardPaymentMethodForm`) along with the required `onResult` prop. Card and bank forms show a field-shaped skeleton immediately (sized from `appearance`, `additionalFields`, and `billingAddressRequirement`); Google Pay and Apple Pay paint a button-shaped skeleton in the parent document on first render so the 48px slot is reserved before the iframe loads.
43
43
  3. **User clicks "Pay now" button**: call `validateForm({ iframeRef })`, which returns `Promise<true>` if the embedded form is valid and `Promise<false>` otherwise.
44
44
  4. **Create payment intent on your server**: use your server-side Amos client to call `POST /payment_intents`. You may also associate this payment intent with a new or existing customer via `POST /customers`. This must be server-side because it uses your private API key.
45
45
  5. **Return the payment intent token to the browser**: your backend responds with the embed token (`components["schemas"]["EmbedToken"]`) needed for confirmation.
@@ -82,7 +82,7 @@ In short, your app orchestrates the payment flow, while sensitive payment data s
82
82
 
83
83
  ## Appearance
84
84
 
85
- Card and bank components accept an optional `appearance` prop that controls the look of the iframe UI. It contains a `themeVariables` object whose keys are CSS custom-property names and whose values are strings, and an optional `labels` setting for field label placement. You can update this prop after page load to update the iframe appearance. Wallet buttons do not take `appearance`.
85
+ Card and bank components accept an optional `appearance` prop that controls the look of the iframe UI, and of the parent-page **Connect bank account** button when ACH verification is required. It contains a `themeVariables` object whose keys are CSS custom-property names and whose values are strings, and an optional `labels` setting for field label placement. You can update this prop after page load to update the iframe appearance. Wallet buttons do not take `appearance`.
86
86
 
87
87
  ```tsx
88
88
  <AmosCreditCardPaymentMethodForm
@@ -155,7 +155,7 @@ Radio groups (e.g. account type) always use an above-style group label regardles
155
155
  | `--control-gap` | Horizontal gap between side-by-side controls | `0.5rem` |
156
156
  | `--error-font-size` | Font size of field-level error messages | `0.875rem` |
157
157
  | `--radio-size` | Size of radio buttons on the bank account form | `1rem` |
158
- | `--ring` | Focus ring and outline color | `oklch(0.708 0 0)` |
158
+ | `--ring` | Focus ring and outline color (inputs, Connect button) | `oklch(0.708 0 0)` |
159
159
  | `--ring-width` | Focus ring width | `3px` |
160
160
  | `--radius` | Base border-radius (derived into sm/md/lg/xl) | `0.625rem` |
161
161
 
@@ -492,15 +492,33 @@ Renders the secure credit card iframe form. A field-shaped skeleton is shown imm
492
492
 
493
493
  Renders the secure bank account iframe form. A field-shaped skeleton is shown immediately and replaced by the iframe once appearance is applied.
494
494
 
495
+ When the charge meets the merchant’s ACH verification threshold, the SDK hides the routing/account iframe and renders a **Connect bank account** button in the parent page. The button follows the same outline/focus defaults as Amos UI (`--ring`, `--border`, `--radius`, `--input-height`, …): it inherits those CSS variables from the host page when present, and `appearance.themeVariables` overrides them the same way as the iframe. Clicking it asks the iframe to mint a Plaid Link token, then opens [Plaid Link](https://plaid.com/docs/link/web/). Hosts do not proxy Pay API (`GET /merchants`, `POST /plaid_link_tokens`); embed does that with `PAY_API_KEY`. Do not put Plaid secrets in the browser.
496
+
495
497
  **Required props:** same as `AmosCreditCardPaymentMethodForm` — `renderToken`, `onResult`.
496
498
 
497
- **Optional props:** same as `AmosCreditCardPaymentMethodForm` — `appearance`, `billingAddressRequirement`, `onValidityChange`.
499
+ **Optional props:** same as `AmosCreditCardPaymentMethodForm` — `appearance`, `billingAddressRequirement`, `onValidityChange` (`isValid` is also true after Plaid Link returns credentials), plus:
500
+
501
+ - `amount` (`string`, major-currency decimal, e.g. `"50.00"`) — same format as Google Pay / Apple Pay. Compared to the threshold the iframe fetches (cents). Omit to always Connect once a threshold exists (setup-intent save, or when you do not know the charge yet). Pass a new `amount` when the customer changes the charge if small charges should stay on the manual form.
502
+
503
+ Compare locally once the iframe posts `ACH_THRESHOLD`: Plaid when the amount (converted to cents) is `>= achThreshold`, or when `amount` is omitted and a threshold is set. No threshold (or `null`) keeps the manual bank form. If `amount` later drops under the threshold, Plaid credentials are dropped and the iframe form is shown again.
504
+
505
+ ```tsx
506
+ <AmosBankAccountPaymentMethodForm
507
+ renderToken={renderToken}
508
+ amount="50.00" // omit to always Connect
509
+ onResult={onResult}
510
+ />
511
+ ```
512
+
513
+ `validateForm` / `confirmPaymentIntent` / `confirmSetupIntent` stay iframe-based. When Plaid succeeded, confirm sends `payment_method.plaid` (`public_token`, `account_id`) and does not require typed account numbers.
514
+
515
+ **CSP:** the parent page must allow Plaid’s script and frames, for example `script-src https://cdn.plaid.com` and `frame-src https://cdn.plaid.com https://*.plaid.com`. Amos never loads `PLAID_SECRET` / `PLAID_CLIENT_ID` in the SDK or embed iframe.
498
516
 
499
517
  **Also accepts:** standard iframe props.
500
518
 
501
519
  ### `AmosGooglePayButton`
502
520
 
503
- Renders the secure Google Pay iframe button (express checkout flow).
521
+ Renders the secure Google Pay iframe button (express checkout flow). A button-shaped skeleton is shown immediately and replaced by the iframe once appearance is applied.
504
522
 
505
523
  **Required props:**
506
524
 
@@ -527,7 +545,7 @@ Renders the secure Google Pay iframe button (express checkout flow).
527
545
 
528
546
  ### `AmosApplePayButton`
529
547
 
530
- Renders the secure Apple Pay iframe button (express checkout flow). Same required props and callbacks as `AmosGooglePayButton`.
548
+ Renders the secure Apple Pay iframe button (express checkout flow). Same required props and callbacks as `AmosGooglePayButton`. A button-shaped skeleton is shown immediately and replaced by the iframe once appearance is applied.
531
549
 
532
550
  **Optional visual props:**
533
551
 
@@ -572,7 +590,8 @@ Re-exports of the same advanced helpers exposed by `@amos.com/amos-js`. Most int
572
590
  - **`ref` / `iframeRef`**: for card and bank forms, pass `ref={iframeRef}` to the form component. The same `iframeRef` must be used when calling `validateForm`, `confirmPaymentIntent`, `confirmSetupIntent`, or `resetForm`. The component forwards the ref to the inner iframe.
573
591
  - **`onResult` is not settlement proof**: `onResult` tells you when to stop waiting (e.g. dismiss a spinner). Verify payment or setup success on your backend via webhooks. On `status: "incomplete"`, unlock your UI — the customer can fix fields in the iframe and retry. Use `result.reason` (`"field_errors"` or `"validation_failed"`) to distinguish recoverable states.
574
592
  - **Same components for payment vs setup intents**: `AmosCreditCardPaymentMethodForm` and `AmosBankAccountPaymentMethodForm` support both payment intents and setup intents. The flow differs only by which server call you make and which confirmation function you use (`confirmPaymentIntent` vs `confirmSetupIntent`). Handle both payment and setup outcomes via `onResult`.
575
- - **Amount format**: for `AmosGooglePayButton` and `AmosApplePayButton`, `amount` is a major-currency decimal string (e.g. `"50.00"` for $50.00). For `components["schemas"]["CreatePaymentIntentInput"]` on the server (card/bank create, and the object the wallet iframe sends to `onInitiatePaymentIntentRequest`), `amount` is a number in cents (e.g. `5000`).
593
+ - **Amount format**: for `AmosGooglePayButton`, `AmosApplePayButton`, and `AmosBankAccountPaymentMethodForm`, `amount` is a major-currency decimal string (e.g. `"50.00"` for $50.00). For `components["schemas"]["CreatePaymentIntentInput"]` on the server (card/bank create, and the object the wallet iframe sends to `onInitiatePaymentIntentRequest`), `amount` is a number in cents (e.g. `5000`).
594
+ - **Plaid Link (ACH verification)**: load `cdn.plaid.com` from the **parent** document (see CSP on `AmosBankAccountPaymentMethodForm`). Merchants do not proxy Pay API; the bank iframe fetches the ACH threshold and mints link tokens. Confirm still goes through the bank iframe so Amos can attach `plaid` to the payment method.
576
595
  - **Apple Pay waiting overlay**: on browsers where Apple's QR handoff opens in a popup (non-Safari), `AmosApplePayButton` shows a fixed full-viewport overlay on the host page until payment completes, the popup closes, or the user clicks **Cancel payment**. Avoid stacking other fixed UI above it.
577
596
  - **Going framework-free**: if you need to use Amos outside of React (vanilla JS, another framework, etc.), use [`@amos.com/amos-js`](../amos-js) directly.
578
597
 
package/dist/index.d.ts CHANGED
@@ -63,14 +63,23 @@ type AmosBankAccountPaymentMethodFormProps = IframePassthroughProps & {
63
63
  onResult: (result: ConfirmationResult) => void;
64
64
  /**
65
65
  * Called when form validity changes. `isValid` is true when all
66
- * required fields are present and valid. Does not include PCI data.
66
+ * required fields are present and valid, or when Plaid Link has
67
+ * returned credentials. Does not include PCI data.
67
68
  */
68
69
  onValidityChange?: (event: {
69
70
  isValid: boolean;
70
71
  }) => void;
71
72
  billingAddressRequirement?: BillingAddressRequirement;
73
+ /**
74
+ * Charge amount as a major-currency decimal string (e.g. `"50.00"`
75
+ * for $50.00), the same format as Google Pay / Apple Pay. Compared
76
+ * to the merchant ACH threshold fetched by the iframe. Omit for
77
+ * setup intents or when the charge is unknown — if a threshold is
78
+ * set, Plaid is required.
79
+ */
80
+ amount?: string;
72
81
  };
73
- export declare function AmosBankAccountPaymentMethodForm({ ref, renderToken, appearance, onResult, onValidityChange, billingAddressRequirement, style, ...rest }: AmosBankAccountPaymentMethodFormProps): import("react").JSX.Element;
82
+ export declare function AmosBankAccountPaymentMethodForm({ ref, renderToken, appearance, onResult, onValidityChange, billingAddressRequirement, amount, style, ...rest }: AmosBankAccountPaymentMethodFormProps): import("react").JSX.Element;
74
83
  type AmosGooglePayButtonProps = {
75
84
  ref?: ForwardedIframeRef;
76
85
  renderToken: string;
@@ -101,6 +110,11 @@ type AmosGooglePayButtonProps = {
101
110
  }) => Promise<components["schemas"]["EmbedToken"]["token"]>;
102
111
  onResult: (result: ConfirmationResult) => void;
103
112
  };
113
+ /**
114
+ * Renders the secure Google Pay iframe button. A button-shaped
115
+ * skeleton is painted in the parent document on first render (including
116
+ * SSR) so the slot height is reserved before the iframe loads.
117
+ */
104
118
  export declare function AmosGooglePayButton({ ref, renderToken, amount, merchantName, height, buttonProps, iframeProps, onInitiatePaymentIntentRequest, onResult, }: AmosGooglePayButtonProps): import("react").JSX.Element;
105
119
  type AmosApplePayButtonProps = {
106
120
  ref?: ForwardedIframeRef;
@@ -132,4 +146,9 @@ type AmosApplePayButtonProps = {
132
146
  }) => Promise<components["schemas"]["EmbedToken"]["token"]>;
133
147
  onResult: (result: ConfirmationResult) => void;
134
148
  };
149
+ /**
150
+ * Renders the secure Apple Pay iframe button. A button-shaped
151
+ * skeleton is painted in the parent document on first render (including
152
+ * SSR) so the slot height is reserved before the iframe loads.
153
+ */
135
154
  export declare function AmosApplePayButton({ ref, renderToken, amount, merchantName, height, buttonProps, iframeProps, onInitiatePaymentIntentRequest, onResult, }: AmosApplePayButtonProps): import("react").JSX.Element;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@amos.com/amos-js"),t=require("react"),n=require("react/jsx-runtime");function r(e){return e?e.current??null:null}function i({iframeRef:t}){return(0,e.validateForm)({iframe:r(t)})}function a({iframeRef:t,token:n}){(0,e.confirmPaymentIntent)({iframe:r(t),token:n})}function o({iframeRef:t,token:n}){(0,e.confirmSetupIntent)({iframe:r(t),token:n})}function s({iframeRef:t}){(0,e.resetForm)({iframe:r(t)})}function c(e,t){typeof e==`function`?e(t):e&&(e.current=t)}function l(e,{style:t,className:n,id:r,...i}){n!=null&&(e.className=n),r!=null&&(e.id=r),Object.assign(e.style,t);for(let[t,n]of Object.entries(i))n!=null&&(t in e?Reflect.set(e,t,n):e.setAttribute(t,String(n)))}function u({containerRef:e,iframeRef:n,mount:r,options:i,remountDeps:a,iframePassthrough:o,updateDeps:s}){let u=(0,t.useRef)(null);(0,t.useEffect)(()=>{let t=e.current;if(!t)return;let a=r(t,i);return u.current=a,c(n,a.iframe),l(a.iframe,o),()=>{a.destroy(),u.current=null,c(n,null)}},[...a]),(0,t.useEffect)(()=>{u.current?.update(i)},[...s]),(0,t.useEffect)(()=>{let e=u.current?.iframe;e&&l(e,o)})}function d({ref:r,renderToken:i,appearance:a,onResult:o,onValidityChange:s,additionalFields:c={cardholderName:!1},billingAddressRequirement:l=`country`,style:d,...f}){let p=(0,t.useRef)(null);return u({containerRef:p,iframeRef:r,mount:e.mountAmosCreditCardPaymentMethodForm,options:{renderToken:i,appearance:a,additionalFields:c,billingAddressRequirement:l,onResult:o,onValidityChange:s},remountDeps:[i,c.cardholderName,l],iframePassthrough:{style:d,...f},updateDeps:[a,c,l,o,s]}),(0,n.jsx)(`div`,{ref:p})}function f({ref:r,renderToken:i,appearance:a,onResult:o,onValidityChange:s,billingAddressRequirement:c=`country`,style:l,...d}){let f=(0,t.useRef)(null);return u({containerRef:f,iframeRef:r,mount:e.mountAmosBankAccountPaymentMethodForm,options:{renderToken:i,appearance:a,billingAddressRequirement:c,onResult:o,onValidityChange:s},remountDeps:[i,c],iframePassthrough:{style:l,...d},updateDeps:[a,c,o,s]}),(0,n.jsx)(`div`,{ref:f})}function p({ref:r,renderToken:i,amount:a,merchantName:o,height:s=`48px`,buttonProps:c,iframeProps:l,onInitiatePaymentIntentRequest:d,onResult:f}){let p=(0,t.useRef)(null);return u({containerRef:p,iframeRef:r,mount:e.mountAmosGooglePayButton,options:{renderToken:i,amount:a,merchantName:o,height:s,buttonProps:c,onInitiatePaymentIntentRequest:d,onResult:f},remountDeps:[i],iframePassthrough:l??{},updateDeps:[a,o,s,c,d,f]}),(0,n.jsx)(`div`,{ref:p})}function m({ref:r,renderToken:i,amount:a,merchantName:o,height:s=`48px`,buttonProps:c,iframeProps:l,onInitiatePaymentIntentRequest:d,onResult:f}){let p=(0,t.useRef)(null);return u({containerRef:p,iframeRef:r,mount:e.mountAmosApplePayButton,options:{renderToken:i,amount:a,merchantName:o,height:s,buttonProps:c,onInitiatePaymentIntentRequest:d,onResult:f},remountDeps:[i],iframePassthrough:l??{},updateDeps:[a,o,s,c,d,f]}),(0,n.jsx)(`div`,{ref:p})}exports.AmosApplePayButton=m,exports.AmosBankAccountPaymentMethodForm=f,exports.AmosCreditCardPaymentMethodForm=d,exports.AmosGooglePayButton=p,exports.confirmPaymentIntent=a,exports.confirmSetupIntent=o,exports.resetForm=s,exports.validateForm=i,Object.keys(e).forEach(function(t){t!=="default"&&!Object.prototype.hasOwnProperty.call(exports,t)&&Object.defineProperty(exports,t,{enumerable:!0,get:function(){return e[t]}})});
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@amos.com/amos-js"),t=require("react"),n=require("react/jsx-runtime");function r(e){return e?e.current??null:null}function i({iframeRef:t}){return(0,e.validateForm)({iframe:r(t)})}function a({iframeRef:t,token:n}){(0,e.confirmPaymentIntent)({iframe:r(t),token:n})}function o({iframeRef:t,token:n}){(0,e.confirmSetupIntent)({iframe:r(t),token:n})}function s({iframeRef:t}){(0,e.resetForm)({iframe:r(t)})}function c(e,t){typeof e==`function`?e(t):e&&(e.current=t)}function l(e,{style:t,className:n,id:r,...i}){n!=null&&(e.className=n),r!=null&&(e.id=r),Object.assign(e.style,t);for(let[t,n]of Object.entries(i))n!=null&&(t in e?Reflect.set(e,t,n):e.setAttribute(t,String(n)))}function u({containerRef:e,iframeRef:n,mount:r,options:i,remountDeps:a,iframePassthrough:o,updateDeps:s}){let u=(0,t.useRef)(null);(0,t.useLayoutEffect)(()=>{let t=e.current;if(!t)return;let a=r(t,i);return u.current=a,c(n,a.iframe),l(a.iframe,o),()=>{a.destroy(),u.current=null,c(n,null)}},[...a]),(0,t.useEffect)(()=>{u.current?.update(i)},[...s]),(0,t.useEffect)(()=>{let e=u.current?.iframe;e&&l(e,o)})}var d=`oklch(0.97 0 0)`;function f({height:r,borderRadius:i,containerRef:a}){return(0,t.useLayoutEffect)(()=>{(0,e.ensureSkeletonStyles)()},[]),(0,n.jsxs)(`div`,{style:{boxSizing:`border-box`,position:`relative`,width:`100%`,height:r,minHeight:r,overflow:`hidden`},children:[(0,n.jsx)(`div`,{className:`amos-js-form-skeleton-input amos-js-wallet-skeleton`,style:{position:`absolute`,inset:0,height:r,borderRadius:i,background:d,pointerEvents:`none`,zIndex:0},"aria-hidden":!0}),(0,n.jsx)(`div`,{ref:a,style:{position:`absolute`,inset:0,zIndex:1,width:`100%`,height:`100%`}})]})}function p({ref:r,renderToken:i,appearance:a,onResult:o,onValidityChange:s,additionalFields:c={cardholderName:!1},billingAddressRequirement:l=`country`,style:d,...f}){let p=(0,t.useRef)(null);return u({containerRef:p,iframeRef:r,mount:e.mountAmosCreditCardPaymentMethodForm,options:{renderToken:i,appearance:a,additionalFields:c,billingAddressRequirement:l,onResult:o,onValidityChange:s},remountDeps:[i,c.cardholderName,l],iframePassthrough:{style:d,...f},updateDeps:[a,c,l,o,s]}),(0,n.jsx)(`div`,{ref:p})}function m({ref:r,renderToken:i,appearance:a,onResult:o,onValidityChange:s,billingAddressRequirement:c=`country`,amount:l,style:d,...f}){let p=(0,t.useRef)(null);return u({containerRef:p,iframeRef:r,mount:e.mountAmosBankAccountPaymentMethodForm,options:{renderToken:i,appearance:a,billingAddressRequirement:c,amount:l,onResult:o,onValidityChange:s},remountDeps:[i,c],iframePassthrough:{style:d,...f},updateDeps:[a,c,l,o,s]}),(0,n.jsx)(`div`,{ref:p})}function h({ref:r,renderToken:i,amount:a,merchantName:o,height:s=`48px`,buttonProps:c,iframeProps:l,onInitiatePaymentIntentRequest:d,onResult:p}){let m=(0,t.useRef)(null),h=(0,e.resolveWalletButtonSkeletonBorderRadius)({iframeStyle:l?.style,buttonProps:c});return u({containerRef:m,iframeRef:r,mount:e.mountAmosGooglePayButton,options:{renderToken:i,amount:a,merchantName:o,height:s,buttonProps:c,onInitiatePaymentIntentRequest:d,onResult:p},remountDeps:[i],iframePassthrough:l??{},updateDeps:[a,o,s,c,d,p]}),(0,n.jsx)(f,{height:s,borderRadius:h,containerRef:m})}function g({ref:r,renderToken:i,amount:a,merchantName:o,height:s=`48px`,buttonProps:c,iframeProps:l,onInitiatePaymentIntentRequest:d,onResult:p}){let m=(0,t.useRef)(null),h=(0,e.resolveWalletButtonSkeletonBorderRadius)({iframeStyle:l?.style,buttonProps:c});return u({containerRef:m,iframeRef:r,mount:e.mountAmosApplePayButton,options:{renderToken:i,amount:a,merchantName:o,height:s,buttonProps:c,onInitiatePaymentIntentRequest:d,onResult:p},remountDeps:[i],iframePassthrough:l??{},updateDeps:[a,o,s,c,d,p]}),(0,n.jsx)(f,{height:s,borderRadius:h,containerRef:m})}exports.AmosApplePayButton=g,exports.AmosBankAccountPaymentMethodForm=m,exports.AmosCreditCardPaymentMethodForm=p,exports.AmosGooglePayButton=h,exports.confirmPaymentIntent=a,exports.confirmSetupIntent=o,exports.resetForm=s,exports.validateForm=i,Object.keys(e).forEach(function(t){t!=="default"&&!Object.prototype.hasOwnProperty.call(exports,t)&&Object.defineProperty(exports,t,{enumerable:!0,get:function(){return e[t]}})});
package/dist/index.mjs CHANGED
@@ -1,65 +1,102 @@
1
- import { confirmPaymentIntent as e, confirmSetupIntent as t, mountAmosApplePayButton as n, mountAmosBankAccountPaymentMethodForm as r, mountAmosCreditCardPaymentMethodForm as i, mountAmosGooglePayButton as a, resetForm as o, validateForm as s } from "@amos.com/amos-js";
2
- import { useEffect as c, useRef as l } from "react";
3
- import { jsx as u } from "react/jsx-runtime";
1
+ import { confirmPaymentIntent as e, confirmSetupIntent as t, ensureSkeletonStyles as n, mountAmosApplePayButton as r, mountAmosBankAccountPaymentMethodForm as i, mountAmosCreditCardPaymentMethodForm as a, mountAmosGooglePayButton as o, resetForm as s, resolveWalletButtonSkeletonBorderRadius as c, validateForm as l } from "@amos.com/amos-js";
2
+ import { useEffect as u, useLayoutEffect as d, useRef as f } from "react";
3
+ import { jsx as p, jsxs as m } from "react/jsx-runtime";
4
4
  export * from "@amos.com/amos-js";
5
5
  //#region src/index.tsx
6
- function d(e) {
6
+ function h(e) {
7
7
  return e ? e.current ?? null : null;
8
8
  }
9
- function f({ iframeRef: e }) {
10
- return s({ iframe: d(e) });
9
+ function g({ iframeRef: e }) {
10
+ return l({ iframe: h(e) });
11
11
  }
12
- function p({ iframeRef: t, token: n }) {
12
+ function _({ iframeRef: t, token: n }) {
13
13
  e({
14
- iframe: d(t),
14
+ iframe: h(t),
15
15
  token: n
16
16
  });
17
17
  }
18
- function m({ iframeRef: e, token: n }) {
18
+ function v({ iframeRef: e, token: n }) {
19
19
  t({
20
- iframe: d(e),
20
+ iframe: h(e),
21
21
  token: n
22
22
  });
23
23
  }
24
- function h({ iframeRef: e }) {
25
- o({ iframe: d(e) });
24
+ function y({ iframeRef: e }) {
25
+ s({ iframe: h(e) });
26
26
  }
27
- function g(e, t) {
27
+ function b(e, t) {
28
28
  typeof e == "function" ? e(t) : e && (e.current = t);
29
29
  }
30
- function _(e, { style: t, className: n, id: r, ...i }) {
30
+ function x(e, { style: t, className: n, id: r, ...i }) {
31
31
  n != null && (e.className = n), r != null && (e.id = r), Object.assign(e.style, t);
32
32
  for (let [t, n] of Object.entries(i)) n != null && (t in e ? Reflect.set(e, t, n) : e.setAttribute(t, String(n)));
33
33
  }
34
- function v({ containerRef: e, iframeRef: t, mount: n, options: r, remountDeps: i, iframePassthrough: a, updateDeps: o }) {
35
- let s = l(null);
36
- c(() => {
34
+ function S({ containerRef: e, iframeRef: t, mount: n, options: r, remountDeps: i, iframePassthrough: a, updateDeps: o }) {
35
+ let s = f(null);
36
+ d(() => {
37
37
  let i = e.current;
38
38
  if (!i) return;
39
39
  let o = n(i, r);
40
- return s.current = o, g(t, o.iframe), _(o.iframe, a), () => {
41
- o.destroy(), s.current = null, g(t, null);
40
+ return s.current = o, b(t, o.iframe), x(o.iframe, a), () => {
41
+ o.destroy(), s.current = null, b(t, null);
42
42
  };
43
- }, [...i]), c(() => {
43
+ }, [...i]), u(() => {
44
44
  s.current?.update(r);
45
- }, [...o]), c(() => {
45
+ }, [...o]), u(() => {
46
46
  let e = s.current?.iframe;
47
- e && _(e, a);
47
+ e && x(e, a);
48
48
  });
49
49
  }
50
- function y({ ref: e, renderToken: t, appearance: n, onResult: r, onValidityChange: a, additionalFields: o = { cardholderName: !1 }, billingAddressRequirement: s = "country", style: c, ...d }) {
51
- let f = l(null);
52
- return v({
53
- containerRef: f,
50
+ var C = "oklch(0.97 0 0)";
51
+ function w({ height: e, borderRadius: t, containerRef: r }) {
52
+ return d(() => {
53
+ n();
54
+ }, []), /* @__PURE__ */ m("div", {
55
+ style: {
56
+ boxSizing: "border-box",
57
+ position: "relative",
58
+ width: "100%",
59
+ height: e,
60
+ minHeight: e,
61
+ overflow: "hidden"
62
+ },
63
+ children: [/* @__PURE__ */ p("div", {
64
+ className: "amos-js-form-skeleton-input amos-js-wallet-skeleton",
65
+ style: {
66
+ position: "absolute",
67
+ inset: 0,
68
+ height: e,
69
+ borderRadius: t,
70
+ background: C,
71
+ pointerEvents: "none",
72
+ zIndex: 0
73
+ },
74
+ "aria-hidden": !0
75
+ }), /* @__PURE__ */ p("div", {
76
+ ref: r,
77
+ style: {
78
+ position: "absolute",
79
+ inset: 0,
80
+ zIndex: 1,
81
+ width: "100%",
82
+ height: "100%"
83
+ }
84
+ })]
85
+ });
86
+ }
87
+ function T({ ref: e, renderToken: t, appearance: n, onResult: r, onValidityChange: i, additionalFields: o = { cardholderName: !1 }, billingAddressRequirement: s = "country", style: c, ...l }) {
88
+ let u = f(null);
89
+ return S({
90
+ containerRef: u,
54
91
  iframeRef: e,
55
- mount: i,
92
+ mount: a,
56
93
  options: {
57
94
  renderToken: t,
58
95
  appearance: n,
59
96
  additionalFields: o,
60
97
  billingAddressRequirement: s,
61
98
  onResult: r,
62
- onValidityChange: a
99
+ onValidityChange: i
63
100
  },
64
101
  remountDeps: [
65
102
  t,
@@ -68,57 +105,62 @@ function y({ ref: e, renderToken: t, appearance: n, onResult: r, onValidityChang
68
105
  ],
69
106
  iframePassthrough: {
70
107
  style: c,
71
- ...d
108
+ ...l
72
109
  },
73
110
  updateDeps: [
74
111
  n,
75
112
  o,
76
113
  s,
77
114
  r,
78
- a
115
+ i
79
116
  ]
80
- }), /* @__PURE__ */ u("div", { ref: f });
117
+ }), /* @__PURE__ */ p("div", { ref: u });
81
118
  }
82
- function b({ ref: e, renderToken: t, appearance: n, onResult: i, onValidityChange: a, billingAddressRequirement: o = "country", style: s, ...c }) {
83
- let d = l(null);
84
- return v({
85
- containerRef: d,
119
+ function E({ ref: e, renderToken: t, appearance: n, onResult: r, onValidityChange: a, billingAddressRequirement: o = "country", amount: s, style: c, ...l }) {
120
+ let u = f(null);
121
+ return S({
122
+ containerRef: u,
86
123
  iframeRef: e,
87
- mount: r,
124
+ mount: i,
88
125
  options: {
89
126
  renderToken: t,
90
127
  appearance: n,
91
128
  billingAddressRequirement: o,
92
- onResult: i,
129
+ amount: s,
130
+ onResult: r,
93
131
  onValidityChange: a
94
132
  },
95
133
  remountDeps: [t, o],
96
134
  iframePassthrough: {
97
- style: s,
98
- ...c
135
+ style: c,
136
+ ...l
99
137
  },
100
138
  updateDeps: [
101
139
  n,
102
140
  o,
103
- i,
141
+ s,
142
+ r,
104
143
  a
105
144
  ]
106
- }), /* @__PURE__ */ u("div", { ref: d });
145
+ }), /* @__PURE__ */ p("div", { ref: u });
107
146
  }
108
- function x({ ref: e, renderToken: t, amount: n, merchantName: r, height: i = "48px", buttonProps: o, iframeProps: s, onInitiatePaymentIntentRequest: c, onResult: d }) {
109
- let f = l(null);
110
- return v({
111
- containerRef: f,
147
+ function D({ ref: e, renderToken: t, amount: n, merchantName: r, height: i = "48px", buttonProps: a, iframeProps: s, onInitiatePaymentIntentRequest: l, onResult: u }) {
148
+ let d = f(null), m = c({
149
+ iframeStyle: s?.style,
150
+ buttonProps: a
151
+ });
152
+ return S({
153
+ containerRef: d,
112
154
  iframeRef: e,
113
- mount: a,
155
+ mount: o,
114
156
  options: {
115
157
  renderToken: t,
116
158
  amount: n,
117
159
  merchantName: r,
118
160
  height: i,
119
- buttonProps: o,
120
- onInitiatePaymentIntentRequest: c,
121
- onResult: d
161
+ buttonProps: a,
162
+ onInitiatePaymentIntentRequest: l,
163
+ onResult: u
122
164
  },
123
165
  remountDeps: [t],
124
166
  iframePassthrough: s ?? {},
@@ -126,38 +168,49 @@ function x({ ref: e, renderToken: t, amount: n, merchantName: r, height: i = "48
126
168
  n,
127
169
  r,
128
170
  i,
129
- o,
130
- c,
131
- d
171
+ a,
172
+ l,
173
+ u
132
174
  ]
133
- }), /* @__PURE__ */ u("div", { ref: f });
175
+ }), /* @__PURE__ */ p(w, {
176
+ height: i,
177
+ borderRadius: m,
178
+ containerRef: d
179
+ });
134
180
  }
135
- function S({ ref: e, renderToken: t, amount: r, merchantName: i, height: a = "48px", buttonProps: o, iframeProps: s, onInitiatePaymentIntentRequest: c, onResult: d }) {
136
- let f = l(null);
137
- return v({
138
- containerRef: f,
181
+ function O({ ref: e, renderToken: t, amount: n, merchantName: i, height: a = "48px", buttonProps: o, iframeProps: s, onInitiatePaymentIntentRequest: l, onResult: u }) {
182
+ let d = f(null), m = c({
183
+ iframeStyle: s?.style,
184
+ buttonProps: o
185
+ });
186
+ return S({
187
+ containerRef: d,
139
188
  iframeRef: e,
140
- mount: n,
189
+ mount: r,
141
190
  options: {
142
191
  renderToken: t,
143
- amount: r,
192
+ amount: n,
144
193
  merchantName: i,
145
194
  height: a,
146
195
  buttonProps: o,
147
- onInitiatePaymentIntentRequest: c,
148
- onResult: d
196
+ onInitiatePaymentIntentRequest: l,
197
+ onResult: u
149
198
  },
150
199
  remountDeps: [t],
151
200
  iframePassthrough: s ?? {},
152
201
  updateDeps: [
153
- r,
202
+ n,
154
203
  i,
155
204
  a,
156
205
  o,
157
- c,
158
- d
206
+ l,
207
+ u
159
208
  ]
160
- }), /* @__PURE__ */ u("div", { ref: f });
209
+ }), /* @__PURE__ */ p(w, {
210
+ height: a,
211
+ borderRadius: m,
212
+ containerRef: d
213
+ });
161
214
  }
162
215
  //#endregion
163
- export { S as AmosApplePayButton, b as AmosBankAccountPaymentMethodForm, y as AmosCreditCardPaymentMethodForm, x as AmosGooglePayButton, p as confirmPaymentIntent, m as confirmSetupIntent, h as resetForm, f as validateForm };
216
+ export { O as AmosApplePayButton, E as AmosBankAccountPaymentMethodForm, T as AmosCreditCardPaymentMethodForm, D as AmosGooglePayButton, _ as confirmPaymentIntent, v as confirmSetupIntent, y as resetForm, g as validateForm };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amos.com/react-amos-js",
3
- "version": "0.9.15",
3
+ "version": "0.9.17",
4
4
  "main": "dist/index.js",
5
5
  "repository": {
6
6
  "type": "git",
@@ -48,7 +48,7 @@
48
48
  "vite-plugin-dts": "5.0.3"
49
49
  },
50
50
  "dependencies": {
51
- "@amos.com/amos-js": "0.9.16",
51
+ "@amos.com/amos-js": "0.9.18",
52
52
  "@types/googlepay": "0.7.11"
53
53
  },
54
54
  "peerDependencies": {
package/src/index.tsx CHANGED
@@ -10,11 +10,13 @@ import {
10
10
  type BillingAddressRequirement,
11
11
  type ConfirmationResult,
12
12
  type CreditCardAdditionalFields,
13
+ ensureSkeletonStyles,
13
14
  type GooglePayButtonElementProps,
14
15
  mountAmosApplePayButton,
15
16
  mountAmosBankAccountPaymentMethodForm,
16
17
  mountAmosCreditCardPaymentMethodForm,
17
18
  mountAmosGooglePayButton,
19
+ resolveWalletButtonSkeletonBorderRadius,
18
20
  } from "@amos.com/amos-js";
19
21
  import type { components } from "@amos.com/node";
20
22
  import {
@@ -22,6 +24,7 @@ import {
22
24
  type Ref,
23
25
  type RefObject,
24
26
  useEffect,
27
+ useLayoutEffect,
25
28
  useRef,
26
29
  } from "react";
27
30
 
@@ -157,7 +160,7 @@ function useAmosEmbed<TOptions extends Record<string, unknown>>({
157
160
  const controllerRef = useRef<AmosEmbedController | null>(null);
158
161
 
159
162
  // biome-ignore lint/correctness/useExhaustiveDependencies: remount only when remountDeps change
160
- useEffect(() => {
163
+ useLayoutEffect(() => {
161
164
  const container = containerRef.current;
162
165
  if (!container) {
163
166
  return;
@@ -188,6 +191,59 @@ function useAmosEmbed<TOptions extends Record<string, unknown>>({
188
191
  });
189
192
  }
190
193
 
194
+ const SKELETON_ACCENT = "oklch(0.97 0 0)";
195
+
196
+ function WalletButtonSlot({
197
+ height,
198
+ borderRadius,
199
+ containerRef,
200
+ }: {
201
+ height: string;
202
+ borderRadius: string;
203
+ containerRef: RefObject<HTMLDivElement | null>;
204
+ }) {
205
+ useLayoutEffect(() => {
206
+ ensureSkeletonStyles();
207
+ }, []);
208
+
209
+ return (
210
+ <div
211
+ style={{
212
+ boxSizing: "border-box",
213
+ position: "relative",
214
+ width: "100%",
215
+ height,
216
+ minHeight: height,
217
+ overflow: "hidden",
218
+ }}
219
+ >
220
+ <div
221
+ className="amos-js-form-skeleton-input amos-js-wallet-skeleton"
222
+ style={{
223
+ position: "absolute",
224
+ inset: 0,
225
+ height,
226
+ borderRadius,
227
+ background: SKELETON_ACCENT,
228
+ pointerEvents: "none",
229
+ zIndex: 0,
230
+ }}
231
+ aria-hidden
232
+ />
233
+ <div
234
+ ref={containerRef}
235
+ style={{
236
+ position: "absolute",
237
+ inset: 0,
238
+ zIndex: 1,
239
+ width: "100%",
240
+ height: "100%",
241
+ }}
242
+ />
243
+ </div>
244
+ );
245
+ }
246
+
191
247
  type AmosCreditCardPaymentMethodFormProps = IframePassthroughProps & {
192
248
  renderToken: string;
193
249
  appearance?: Appearance;
@@ -250,10 +306,19 @@ type AmosBankAccountPaymentMethodFormProps = IframePassthroughProps & {
250
306
  onResult: (result: ConfirmationResult) => void;
251
307
  /**
252
308
  * Called when form validity changes. `isValid` is true when all
253
- * required fields are present and valid. Does not include PCI data.
309
+ * required fields are present and valid, or when Plaid Link has
310
+ * returned credentials. Does not include PCI data.
254
311
  */
255
312
  onValidityChange?: (event: { isValid: boolean }) => void;
256
313
  billingAddressRequirement?: BillingAddressRequirement;
314
+ /**
315
+ * Charge amount as a major-currency decimal string (e.g. `"50.00"`
316
+ * for $50.00), the same format as Google Pay / Apple Pay. Compared
317
+ * to the merchant ACH threshold fetched by the iframe. Omit for
318
+ * setup intents or when the charge is unknown — if a threshold is
319
+ * set, Plaid is required.
320
+ */
321
+ amount?: string;
257
322
  };
258
323
 
259
324
  export function AmosBankAccountPaymentMethodForm({
@@ -263,6 +328,7 @@ export function AmosBankAccountPaymentMethodForm({
263
328
  onResult,
264
329
  onValidityChange,
265
330
  billingAddressRequirement = "country",
331
+ amount,
266
332
  style,
267
333
  ...rest
268
334
  }: AmosBankAccountPaymentMethodFormProps) {
@@ -276,6 +342,7 @@ export function AmosBankAccountPaymentMethodForm({
276
342
  renderToken,
277
343
  appearance,
278
344
  billingAddressRequirement,
345
+ amount,
279
346
  onResult,
280
347
  onValidityChange,
281
348
  },
@@ -284,6 +351,7 @@ export function AmosBankAccountPaymentMethodForm({
284
351
  updateDeps: [
285
352
  appearance,
286
353
  billingAddressRequirement,
354
+ amount,
287
355
  onResult,
288
356
  onValidityChange,
289
357
  ],
@@ -326,6 +394,11 @@ type AmosGooglePayButtonProps = {
326
394
  onResult: (result: ConfirmationResult) => void;
327
395
  };
328
396
 
397
+ /**
398
+ * Renders the secure Google Pay iframe button. A button-shaped
399
+ * skeleton is painted in the parent document on first render (including
400
+ * SSR) so the slot height is reserved before the iframe loads.
401
+ */
329
402
  export function AmosGooglePayButton({
330
403
  ref,
331
404
  renderToken,
@@ -338,6 +411,10 @@ export function AmosGooglePayButton({
338
411
  onResult,
339
412
  }: AmosGooglePayButtonProps) {
340
413
  const containerRef = useRef<HTMLDivElement | null>(null);
414
+ const borderRadius = resolveWalletButtonSkeletonBorderRadius({
415
+ iframeStyle: iframeProps?.style as { borderRadius?: string | number },
416
+ buttonProps,
417
+ });
341
418
 
342
419
  useAmosEmbed({
343
420
  containerRef,
@@ -364,7 +441,13 @@ export function AmosGooglePayButton({
364
441
  ],
365
442
  });
366
443
 
367
- return <div ref={containerRef} />;
444
+ return (
445
+ <WalletButtonSlot
446
+ height={height}
447
+ borderRadius={borderRadius}
448
+ containerRef={containerRef}
449
+ />
450
+ );
368
451
  }
369
452
 
370
453
  type AmosApplePayButtonProps = {
@@ -401,6 +484,11 @@ type AmosApplePayButtonProps = {
401
484
  onResult: (result: ConfirmationResult) => void;
402
485
  };
403
486
 
487
+ /**
488
+ * Renders the secure Apple Pay iframe button. A button-shaped
489
+ * skeleton is painted in the parent document on first render (including
490
+ * SSR) so the slot height is reserved before the iframe loads.
491
+ */
404
492
  export function AmosApplePayButton({
405
493
  ref,
406
494
  renderToken,
@@ -413,6 +501,10 @@ export function AmosApplePayButton({
413
501
  onResult,
414
502
  }: AmosApplePayButtonProps) {
415
503
  const containerRef = useRef<HTMLDivElement | null>(null);
504
+ const borderRadius = resolveWalletButtonSkeletonBorderRadius({
505
+ iframeStyle: iframeProps?.style as { borderRadius?: string | number },
506
+ buttonProps,
507
+ });
416
508
 
417
509
  useAmosEmbed({
418
510
  containerRef,
@@ -439,5 +531,11 @@ export function AmosApplePayButton({
439
531
  ],
440
532
  });
441
533
 
442
- return <div ref={containerRef} />;
534
+ return (
535
+ <WalletButtonSlot
536
+ height={height}
537
+ borderRadius={borderRadius}
538
+ containerRef={containerRef}
539
+ />
540
+ );
443
541
  }