@amos.com/react-amos-js 0.9.14 → 0.9.16

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.
@@ -247,58 +247,109 @@ function CheckoutForm() {
247
247
  }
248
248
  ```
249
249
 
250
- ### Rendering Google Pay within your checkout flow
250
+ ### Rendering Google Pay and Apple Pay within your checkout flow
251
251
 
252
252
  ```tsx
253
253
  import { useState } from "react";
254
- import { AmosGooglePayButton } from "@amos.com/react-amos-js";
254
+ import {
255
+ AmosApplePayButton,
256
+ AmosGooglePayButton,
257
+ type ConfirmationResult,
258
+ } from "@amos.com/react-amos-js";
259
+ import type { components } from "@amos.com/node";
260
+
261
+ async function createPaymentIntentToken({
262
+ paymentIntentCreateAttributes,
263
+ customerCreateAttributes,
264
+ }: {
265
+ paymentIntentCreateAttributes: components["schemas"]["CreatePaymentIntentInput"];
266
+ customerCreateAttributes: components["schemas"]["CreateCustomerInput"];
267
+ }): Promise<string> {
268
+ const response = await fetch("/api/payment-intents", {
269
+ method: "POST",
270
+ headers: { "Content-Type": "application/json" },
271
+ body: JSON.stringify({
272
+ customer: customerCreateAttributes,
273
+ paymentIntent: paymentIntentCreateAttributes,
274
+ }),
275
+ });
276
+ if (!response.ok) {
277
+ throw new Error("Failed to create payment intent.");
278
+ }
279
+ const { token } = (await response.json()) as { token: string };
280
+ return token;
281
+ }
255
282
 
256
- function CheckoutGooglePay() {
283
+ function CheckoutWallets({ renderToken }: { renderToken: string }) {
257
284
  const [error, setError] = useState<string | null>(null);
258
285
 
286
+ function handleResult(result: ConfirmationResult) {
287
+ if (result.status === "succeeded") {
288
+ console.log("Confirm returned:", result);
289
+ } else if (result.status === "failed") {
290
+ setError(result.errorMessage);
291
+ } else if (result.status === "incomplete") {
292
+ console.log("Recoverable:", result.reason);
293
+ }
294
+ }
295
+
259
296
  return (
260
297
  <>
261
- <AmosGooglePayButton
262
- renderToken="the-render-token-that-you-created-on-dashboard.amos.com"
263
- amount="5000" // $50.00 in cents, as a string
264
- merchantName="your-user-facing-merchant-name"
265
- onInitiatePaymentIntentRequest={async ({
266
- paymentIntentCreateAttributes,
267
- customerCreateAttributes,
268
- }) => {
269
- const response = await fetch("/api/payment-intents", {
270
- method: "POST",
271
- headers: { "Content-Type": "application/json" },
272
- body: JSON.stringify({
273
- customer: customerCreateAttributes,
274
- paymentIntent: paymentIntentCreateAttributes,
275
- }),
276
- });
277
-
278
- if (!response.ok) {
279
- throw new Error("Failed to create payment intent.");
280
- }
281
-
282
- const { token } = await response.json();
283
- return token;
284
- }}
285
- onResult={(result) => {
286
- if (result.status === "succeeded") {
287
- console.log("Confirm returned:", result);
288
- } else if (result.status === "failed") {
289
- console.error("Confirm failed:", result.errorMessage);
290
- } else if (result.status === "incomplete") {
291
- console.log("Recoverable:", result.reason);
292
- }
293
- }}
294
- />
295
- {error ? <p>{error}</p> : null}
298
+ <div style={{ display: "flex", gap: "12px" }}>
299
+ <div style={{ flex: "1 1 0", minWidth: 0 }}>
300
+ <AmosGooglePayButton
301
+ renderToken={renderToken}
302
+ amount="50.00"
303
+ merchantName="Example Store"
304
+ onInitiatePaymentIntentRequest={createPaymentIntentToken}
305
+ onResult={handleResult}
306
+ />
307
+ </div>
308
+ <div style={{ flex: "1 1 0", minWidth: 0 }}>
309
+ <AmosApplePayButton
310
+ renderToken={renderToken}
311
+ amount="50.00"
312
+ merchantName="Example Store"
313
+ onInitiatePaymentIntentRequest={createPaymentIntentToken}
314
+ onResult={handleResult}
315
+ />
316
+ </div>
317
+ </div>
318
+ {error ? <p role="alert">{error}</p> : null}
296
319
  </>
297
320
  );
298
321
  }
299
322
  ```
300
323
 
301
- `AmosApplePayButton` uses the same props and express-checkout callbacks. Drop it in the same place (or alongside Google Pay) with the same `amount`, `merchantName`, and `onInitiatePaymentIntentRequest` wiring. On Safari, the native payment sheet is used; on other browsers, Apple's QR handoff opens in a popup (`pay.apple.com`). While that popup is open, the SDK shows a waiting overlay on your page with a **Cancel payment** button.
324
+ Do not call `validateForm` or `confirmPaymentIntent` return the embed token from `onInitiatePaymentIntentRequest` and the SDK confirms. Size the mount slot; omitted `buttonProps` keep paint defaults and fill the iframe.
325
+
326
+ On Safari, Apple Pay uses the native payment sheet. On other browsers, Apple's QR handoff opens in a popup (`pay.apple.com`); while that popup is open, the SDK shows a waiting overlay with **Cancel payment**.
327
+
328
+ Optional visuals:
329
+
330
+ ```tsx
331
+ <AmosGooglePayButton
332
+ renderToken={renderToken}
333
+ amount="50.00"
334
+ merchantName="Example Store"
335
+ height="48px"
336
+ buttonProps={{ buttonType: "donate", buttonBorderType: "no_border" }}
337
+ iframeProps={{ style: { borderRadius: "8px" } }}
338
+ onInitiatePaymentIntentRequest={createPaymentIntentToken}
339
+ onResult={handleResult}
340
+ />
341
+
342
+ <AmosApplePayButton
343
+ renderToken={renderToken}
344
+ amount="50.00"
345
+ merchantName="Example Store"
346
+ height="48px"
347
+ buttonProps={{ type: "donate" }}
348
+ iframeProps={{ style: { borderRadius: "8px" } }}
349
+ onInitiatePaymentIntentRequest={createPaymentIntentToken}
350
+ onResult={handleResult}
351
+ />
352
+ ```
302
353
 
303
354
  ### Saving a payment method with setup intent (credit card)
304
355
 
@@ -449,12 +500,12 @@ Renders the secure bank account iframe form. A field-shaped skeleton is shown im
449
500
 
450
501
  ### `AmosGooglePayButton`
451
502
 
452
- Renders the secure Google Pay iframe button (express checkout flow).
503
+ 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.
453
504
 
454
505
  **Required props:**
455
506
 
456
507
  - `renderToken` (`string`)
457
- - `amount` (`string`)
508
+ - `amount` (`string`) — major-currency decimal string shown in the wallet sheet (e.g. `"50.00"` for $50.00). The iframe converts this to cents in `paymentIntentCreateAttributes.amount`.
458
509
  - `merchantName` (`string`)
459
510
  - `onInitiatePaymentIntentRequest` (callback receiving `{ paymentIntentCreateAttributes: components["schemas"]["CreatePaymentIntentInput"]; customerCreateAttributes: components["schemas"]["CreateCustomerInput"] }`, returns `Promise<components["schemas"]["EmbedToken"]["token"]>` — the embed JWT string for confirmation)
460
511
 
@@ -476,7 +527,7 @@ Renders the secure Google Pay iframe button (express checkout flow).
476
527
 
477
528
  ### `AmosApplePayButton`
478
529
 
479
- Renders the secure Apple Pay iframe button (express checkout flow). Same required props and callbacks as `AmosGooglePayButton`.
530
+ 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.
480
531
 
481
532
  **Optional visual props:**
482
533
 
@@ -521,7 +572,7 @@ Re-exports of the same advanced helpers exposed by `@amos.com/amos-js`. Most int
521
572
  - **`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.
522
573
  - **`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.
523
574
  - **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`.
524
- - **Amount format**: for `AmosGooglePayButton` and `AmosApplePayButton`, `amount` is a string (e.g. `"5000"` for $50.00). For `components["schemas"]["CreatePaymentIntentInput"]` on the server, `amount` is a number in cents (e.g. `5000`).
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`).
525
576
  - **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.
526
577
  - **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.
527
578
 
package/dist/index.d.ts CHANGED
@@ -74,6 +74,11 @@ export declare function AmosBankAccountPaymentMethodForm({ ref, renderToken, app
74
74
  type AmosGooglePayButtonProps = {
75
75
  ref?: ForwardedIframeRef;
76
76
  renderToken: string;
77
+ /**
78
+ * Major-currency decimal string shown in the Google Pay sheet
79
+ * (e.g. `"50.00"` for $50.00). Converted to cents in
80
+ * `paymentIntentCreateAttributes.amount`.
81
+ */
77
82
  amount: string;
78
83
  merchantName: string;
79
84
  /**
@@ -96,10 +101,20 @@ type AmosGooglePayButtonProps = {
96
101
  }) => Promise<components["schemas"]["EmbedToken"]["token"]>;
97
102
  onResult: (result: ConfirmationResult) => void;
98
103
  };
104
+ /**
105
+ * Renders the secure Google Pay iframe button. A button-shaped
106
+ * skeleton is painted in the parent document on first render (including
107
+ * SSR) so the slot height is reserved before the iframe loads.
108
+ */
99
109
  export declare function AmosGooglePayButton({ ref, renderToken, amount, merchantName, height, buttonProps, iframeProps, onInitiatePaymentIntentRequest, onResult, }: AmosGooglePayButtonProps): import("react").JSX.Element;
100
110
  type AmosApplePayButtonProps = {
101
111
  ref?: ForwardedIframeRef;
102
112
  renderToken: string;
113
+ /**
114
+ * Major-currency decimal string shown in the Apple Pay sheet
115
+ * (e.g. `"50.00"` for $50.00). Converted to cents in
116
+ * `paymentIntentCreateAttributes.amount`.
117
+ */
103
118
  amount: string;
104
119
  merchantName: string;
105
120
  /**
@@ -122,4 +137,9 @@ type AmosApplePayButtonProps = {
122
137
  }) => Promise<components["schemas"]["EmbedToken"]["token"]>;
123
138
  onResult: (result: ConfirmationResult) => void;
124
139
  };
140
+ /**
141
+ * Renders the secure Apple Pay iframe button. A button-shaped
142
+ * skeleton is painted in the parent document on first render (including
143
+ * SSR) so the slot height is reserved before the iframe loads.
144
+ */
125
145
  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`,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 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,28 +105,28 @@ 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", style: s, ...c }) {
120
+ let l = f(null);
121
+ return S({
122
+ containerRef: l,
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
+ onResult: r,
93
130
  onValidityChange: a
94
131
  },
95
132
  remountDeps: [t, o],
@@ -100,25 +137,28 @@ function b({ ref: e, renderToken: t, appearance: n, onResult: i, onValidityChang
100
137
  updateDeps: [
101
138
  n,
102
139
  o,
103
- i,
140
+ r,
104
141
  a
105
142
  ]
106
- }), /* @__PURE__ */ u("div", { ref: d });
143
+ }), /* @__PURE__ */ p("div", { ref: l });
107
144
  }
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,
145
+ function D({ ref: e, renderToken: t, amount: n, merchantName: r, height: i = "48px", buttonProps: a, iframeProps: s, onInitiatePaymentIntentRequest: l, onResult: u }) {
146
+ let d = f(null), m = c({
147
+ iframeStyle: s?.style,
148
+ buttonProps: a
149
+ });
150
+ return S({
151
+ containerRef: d,
112
152
  iframeRef: e,
113
- mount: a,
153
+ mount: o,
114
154
  options: {
115
155
  renderToken: t,
116
156
  amount: n,
117
157
  merchantName: r,
118
158
  height: i,
119
- buttonProps: o,
120
- onInitiatePaymentIntentRequest: c,
121
- onResult: d
159
+ buttonProps: a,
160
+ onInitiatePaymentIntentRequest: l,
161
+ onResult: u
122
162
  },
123
163
  remountDeps: [t],
124
164
  iframePassthrough: s ?? {},
@@ -126,38 +166,49 @@ function x({ ref: e, renderToken: t, amount: n, merchantName: r, height: i = "48
126
166
  n,
127
167
  r,
128
168
  i,
129
- o,
130
- c,
131
- d
169
+ a,
170
+ l,
171
+ u
132
172
  ]
133
- }), /* @__PURE__ */ u("div", { ref: f });
173
+ }), /* @__PURE__ */ p(w, {
174
+ height: i,
175
+ borderRadius: m,
176
+ containerRef: d
177
+ });
134
178
  }
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,
179
+ function O({ ref: e, renderToken: t, amount: n, merchantName: i, height: a = "48px", buttonProps: o, iframeProps: s, onInitiatePaymentIntentRequest: l, onResult: u }) {
180
+ let d = f(null), m = c({
181
+ iframeStyle: s?.style,
182
+ buttonProps: o
183
+ });
184
+ return S({
185
+ containerRef: d,
139
186
  iframeRef: e,
140
- mount: n,
187
+ mount: r,
141
188
  options: {
142
189
  renderToken: t,
143
- amount: r,
190
+ amount: n,
144
191
  merchantName: i,
145
192
  height: a,
146
193
  buttonProps: o,
147
- onInitiatePaymentIntentRequest: c,
148
- onResult: d
194
+ onInitiatePaymentIntentRequest: l,
195
+ onResult: u
149
196
  },
150
197
  remountDeps: [t],
151
198
  iframePassthrough: s ?? {},
152
199
  updateDeps: [
153
- r,
200
+ n,
154
201
  i,
155
202
  a,
156
203
  o,
157
- c,
158
- d
204
+ l,
205
+ u
159
206
  ]
160
- }), /* @__PURE__ */ u("div", { ref: f });
207
+ }), /* @__PURE__ */ p(w, {
208
+ height: a,
209
+ borderRadius: m,
210
+ containerRef: d
211
+ });
161
212
  }
162
213
  //#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 };
214
+ 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.14",
3
+ "version": "0.9.16",
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.15",
51
+ "@amos.com/amos-js": "0.9.17",
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;
@@ -295,6 +351,11 @@ export function AmosBankAccountPaymentMethodForm({
295
351
  type AmosGooglePayButtonProps = {
296
352
  ref?: ForwardedIframeRef;
297
353
  renderToken: string;
354
+ /**
355
+ * Major-currency decimal string shown in the Google Pay sheet
356
+ * (e.g. `"50.00"` for $50.00). Converted to cents in
357
+ * `paymentIntentCreateAttributes.amount`.
358
+ */
298
359
  amount: string;
299
360
  merchantName: string;
300
361
  /**
@@ -321,6 +382,11 @@ type AmosGooglePayButtonProps = {
321
382
  onResult: (result: ConfirmationResult) => void;
322
383
  };
323
384
 
385
+ /**
386
+ * Renders the secure Google Pay iframe button. A button-shaped
387
+ * skeleton is painted in the parent document on first render (including
388
+ * SSR) so the slot height is reserved before the iframe loads.
389
+ */
324
390
  export function AmosGooglePayButton({
325
391
  ref,
326
392
  renderToken,
@@ -333,6 +399,10 @@ export function AmosGooglePayButton({
333
399
  onResult,
334
400
  }: AmosGooglePayButtonProps) {
335
401
  const containerRef = useRef<HTMLDivElement | null>(null);
402
+ const borderRadius = resolveWalletButtonSkeletonBorderRadius({
403
+ iframeStyle: iframeProps?.style as { borderRadius?: string | number },
404
+ buttonProps,
405
+ });
336
406
 
337
407
  useAmosEmbed({
338
408
  containerRef,
@@ -359,12 +429,23 @@ export function AmosGooglePayButton({
359
429
  ],
360
430
  });
361
431
 
362
- return <div ref={containerRef} />;
432
+ return (
433
+ <WalletButtonSlot
434
+ height={height}
435
+ borderRadius={borderRadius}
436
+ containerRef={containerRef}
437
+ />
438
+ );
363
439
  }
364
440
 
365
441
  type AmosApplePayButtonProps = {
366
442
  ref?: ForwardedIframeRef;
367
443
  renderToken: string;
444
+ /**
445
+ * Major-currency decimal string shown in the Apple Pay sheet
446
+ * (e.g. `"50.00"` for $50.00). Converted to cents in
447
+ * `paymentIntentCreateAttributes.amount`.
448
+ */
368
449
  amount: string;
369
450
  merchantName: string;
370
451
  /**
@@ -391,6 +472,11 @@ type AmosApplePayButtonProps = {
391
472
  onResult: (result: ConfirmationResult) => void;
392
473
  };
393
474
 
475
+ /**
476
+ * Renders the secure Apple Pay iframe button. A button-shaped
477
+ * skeleton is painted in the parent document on first render (including
478
+ * SSR) so the slot height is reserved before the iframe loads.
479
+ */
394
480
  export function AmosApplePayButton({
395
481
  ref,
396
482
  renderToken,
@@ -403,6 +489,10 @@ export function AmosApplePayButton({
403
489
  onResult,
404
490
  }: AmosApplePayButtonProps) {
405
491
  const containerRef = useRef<HTMLDivElement | null>(null);
492
+ const borderRadius = resolveWalletButtonSkeletonBorderRadius({
493
+ iframeStyle: iframeProps?.style as { borderRadius?: string | number },
494
+ buttonProps,
495
+ });
406
496
 
407
497
  useAmosEmbed({
408
498
  containerRef,
@@ -429,5 +519,11 @@ export function AmosApplePayButton({
429
519
  ],
430
520
  });
431
521
 
432
- return <div ref={containerRef} />;
522
+ return (
523
+ <WalletButtonSlot
524
+ height={height}
525
+ borderRadius={borderRadius}
526
+ containerRef={containerRef}
527
+ />
528
+ );
433
529
  }