@amos.com/amos-js 0.5.0 → 0.6.1

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.
@@ -73,6 +73,9 @@ export type ApplePayButtonController = {
73
73
  * The iframe is expected to have already been added to the DOM with the
74
74
  * correct `src` (see {@link getApplePayButtonSrc}).
75
75
  *
76
- * Uses the same postMessage protocol as Google Pay express checkout.
76
+ * The Apple Pay button and `ApplePaySession` run inside the Amos embed
77
+ * iframe (so only Amos domains need Apple merchant registration). On
78
+ * `EXPAND_IFRAME`, the parent temporarily overlays that iframe
79
+ * full-viewport for Chrome's in-iframe QR handoff UI.
77
80
  */
78
81
  export declare function attachApplePayButtonListeners(iframe: HTMLIFrameElement, options: ApplePayButtonListenerOptions): ApplePayButtonController;
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ export { attachApplePayButtonListeners, getApplePayButtonInitialHeight, getApple
4
4
  export type { FormattedGooglePayPaymentData, GooglePayButtonController, GooglePayButtonListenerOptions, } from './google-pay';
5
5
  export { attachGooglePayButtonListeners, formatGooglePayPaymentData, getGooglePayButtonInitialHeight, getGooglePayButtonSrc, } from './google-pay';
6
6
  export { decodeJwt, getEmbedOrigin } from './jwt';
7
- export { confirmPaymentIntent, confirmSetupIntent, sendConfirmationFailed, sendParentReadyMessage, updateAmount, updateAppearance, updateMerchantName, validateForm, } from './messaging';
7
+ export { confirmPaymentIntent, confirmSetupIntent, hasNativeApplePaySession, sendConfirmationFailed, sendParentReadyMessage, updateAmount, updateAppearance, updateMerchantName, updateNativeApplePaySession, validateForm, } from './messaging';
8
8
  export type { AmosApplePayButtonMountController, AmosApplePayButtonOptions, AmosBankAccountPaymentMethodFormOptions, AmosCreditCardPaymentMethodFormOptions, AmosGooglePayButtonMountController, AmosGooglePayButtonOptions, AmosPaymentMethodFormMountController, } from './mount';
9
9
  export { mountAmosApplePayButton, mountAmosBankAccountPaymentMethodForm, mountAmosCreditCardPaymentMethodForm, mountAmosGooglePayButton, } from './mount';
10
10
  export type { BillingAddressRequirement, CreditCardAdditionalFields, PaymentMethodFormController, PaymentMethodFormListenerOptions, } from './payment-method-form';
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){let[t=``,n=``,r=``]=e?.split(`.`)??[],i=typeof atob==`function`?atob:e=>Buffer.from(e,`base64`).toString(`utf8`);return{header:JSON.parse(i(t)),payload:JSON.parse(i(n)),signature:r}}function t(t){let{env:n=`sandbox`}=e(t).payload;switch(n){case`production`:return`https://embed.amos.com`;case`sandbox`:return`https://embed-sandbox.amos.com`;default:return`https://embed-sandbox.amos.com`}}function n(e){return e}function r(e){e?.contentWindow?.postMessage(n({type:`PARENT_ACKNOWLEDGED_IFRAME_READY`}),`*`)}function i({iframe:e,appearance:t={}}){e?.contentWindow?.postMessage(n({type:`UPDATE_APPEARANCE`,appearance:t}),`*`)}function a({iframe:e,amount:t}){e?.contentWindow?.postMessage(n({type:`UPDATE_AMOUNT`,amount:t}),`*`)}function o({iframe:e,merchantName:t}){e?.contentWindow?.postMessage(n({type:`UPDATE_MERCHANT_NAME`,merchantName:t}),`*`)}function s({iframe:e}){let t=crypto.randomUUID();return new Promise(r=>{e?.contentWindow?.postMessage(n({type:`VALIDATE_FORM`,requestId:t}),`*`);let i=setTimeout(()=>{window.removeEventListener(`message`,a),r(!1)},5e3);function a(e){e.data.type===`VALIDATE_FORM`&&e.data.requestId===t&&(window.removeEventListener(`message`,a),clearTimeout(i),r(e.data.isValid??!1))}window.addEventListener(`message`,a)})}function c({iframe:t,token:r}){let{payment_intent_id:i}=e(r).payload;t?.contentWindow?.postMessage(n({type:`CONFIRM_PAYMENT_INTENT`,token:r,id:i??void 0}),`*`)}function l({iframe:t,token:r}){let{setup_intent_id:i}=e(r).payload;t?.contentWindow?.postMessage(n({type:`CONFIRM_SETUP_INTENT`,token:r,id:i??void 0}),`*`)}function u({iframe:e,errorMessage:t}){e?.contentWindow?.postMessage(n({type:`CONFIRMATION_FAILED`,errorMessage:t}),`*`)}function d(e){return`${t(e)}/iframe/apple-pay?token=${e}`}function f(){return`40px`}function p(e,t){let n={...t};function s(){a({iframe:e,amount:n.amount})}function l(){o({iframe:e,merchantName:n.merchantName})}function d(t){switch(t.data.type){case`IFRAME_READY`:r(e),i({iframe:e,appearance:n.appearance}),s(),l();break;case`UPDATE_HEIGHT`:n.onHeightChange?.(t.data.height);break;case`UPDATE_APPEARANCE`:i({iframe:e,appearance:t.data.appearance});break;case`UPDATED_APPEARANCE`:n.onAppearanceReady?.();break;case`CREATE_PAYMENT_INTENT`:n.onInitiatePaymentIntentRequest({paymentIntentCreateAttributes:t.data.paymentIntentCreateAttributes,customerCreateAttributes:t.data.customerCreateAttributes}).then(t=>{c({iframe:e,token:t})}).catch(t=>{u({iframe:e,errorMessage:t instanceof Error?t.message:`Unknown error`})});break;case`PAYMENT_INTENT_CONFIRMATION_SUCCEEDED`:n.onPaymentIntentConfirmationSucceeded(t.data.paymentIntent);break;case`CONFIRMATION_FAILED`:n.onConfirmationFailed(t.data.errorMessage);break}}return window.addEventListener(`message`,d),{update(t){let r=`appearance`in t,a=`amount`in t,o=`merchantName`in t;n={...n,...t},r&&i({iframe:e,appearance:n.appearance}),a&&s(),o&&l()},destroy(){window.removeEventListener(`message`,d)}}}function m(e){return`${t(e)}/iframe/google-pay?token=${e}`}function h(){return`40px`}function g(e,t){let n={...t};function s(){a({iframe:e,amount:n.amount})}function l(){o({iframe:e,merchantName:n.merchantName})}function d(t){switch(t.data.type){case`IFRAME_READY`:r(e),i({iframe:e,appearance:n.appearance}),s(),l();break;case`UPDATE_HEIGHT`:n.onHeightChange?.(t.data.height);break;case`UPDATE_APPEARANCE`:i({iframe:e,appearance:t.data.appearance});break;case`UPDATED_APPEARANCE`:n.onAppearanceReady?.();break;case`CREATE_PAYMENT_INTENT`:n.onInitiatePaymentIntentRequest({paymentIntentCreateAttributes:t.data.paymentIntentCreateAttributes,customerCreateAttributes:t.data.customerCreateAttributes}).then(t=>{c({iframe:e,token:t})}).catch(t=>{u({iframe:e,errorMessage:t instanceof Error?t.message:`Unknown error`})});break;case`PAYMENT_INTENT_CONFIRMATION_SUCCEEDED`:n.onPaymentIntentConfirmationSucceeded(t.data.paymentIntent);break;case`CONFIRMATION_FAILED`:n.onConfirmationFailed(t.data.errorMessage);break}}return window.addEventListener(`message`,d),{update(t){let r=`appearance`in t,a=`amount`in t,o=`merchantName`in t;n={...n,...t},r&&i({iframe:e,appearance:n.appearance}),a&&s(),o&&l()},destroy(){window.removeEventListener(`message`,d)}}}function _({paymentData:e}){return{paymentMethod:{type:`googlepay`,billing_address_attributes:{name:e.shippingAddress?.name,address_line1:e.shippingAddress?.address1,address_line2:e.shippingAddress?.address2,city:e.shippingAddress?.locality,state:e.shippingAddress?.administrativeArea,postal_code:e.shippingAddress?.postalCode,country:e.shippingAddress?.countryCode,email:e.email,phone:e.shippingAddress?.phoneNumber},card_profile_attributes:{wallet_provider:`googlepay`,wallet_payload:e.paymentMethodData.tokenizationData.token,wallet_last4:e.paymentMethodData.info?.cardDetails,wallet_brand:(()=>{switch(e.paymentMethodData.info?.cardNetwork){case`AMEX`:return`american_express`;case`VISA`:return`visa`;case`MASTERCARD`:return`master`;case`DISCOVER`:return`discover`;default:return}})()}}}}var v={country:212,full:452},y=80,b={country:400,full:640};function x(e,n={cardholderName:!1},r=`country`){let i=Object.entries(n).filter(([,e])=>e).map(([e])=>e).join(`,`),a=new URLSearchParams({token:e,additionalFields:i,billingAddressRequirement:r});return`${t(e)}/iframe/card?${a}`}function S(e,n=`country`){let r=new URLSearchParams({token:e,billingAddressRequirement:n});return`${t(e)}/iframe/bank?${r}`}function C(e={cardholderName:!1},t=`country`){return`${(v[t]??v.country)+(e.cardholderName?y:0)}px`}function w(e=`country`){return`${b[e]??b.country}px`}function T(e,t){let n={...t};function a(t){switch(t.data.type){case`IFRAME_READY`:r(e),i({iframe:e,appearance:n.appearance});break;case`UPDATE_HEIGHT`:n.onHeightChange?.(t.data.height);break;case`UPDATE_APPEARANCE`:i({iframe:e,appearance:t.data.appearance});break;case`UPDATED_APPEARANCE`:n.onAppearanceReady?.();break;case`PAYMENT_INTENT_CONFIRMATION_SUCCEEDED`:n.onPaymentIntentConfirmationSucceeded?.(t.data.paymentIntent);break;case`SETUP_INTENT_CONFIRMATION_SUCCEEDED`:n.onSetupIntentConfirmationSucceeded?.(t.data.setupIntent);break;case`CONFIRMATION_FAILED`:n.onConfirmationFailed(t.data.errorMessage);break}}return window.addEventListener(`message`,a),{update(t){let r=`appearance`in t;n={...n,...t},r&&i({iframe:e,appearance:n.appearance})},destroy(){window.removeEventListener(`message`,a)}}}function E(e){if(typeof e==`string`){let t=document.querySelector(e);if(!(t instanceof HTMLElement))throw Error(`[amos-js] Container "${e}" did not match any HTMLElement.`);return t}return e}var D={width:`calc(100% + 8px)`,transition:`opacity 150ms ease-in, height 200ms ease-in-out`,margin:`0 -4px`,opacity:`0`,border:`0`};function O({src:e,title:t,name:n,height:r,allow:i}){let a=document.createElement(`iframe`);return a.src=e,a.title=t,a.name=n,a.setAttribute(`role`,`presentation`),a.scrolling=`no`,i&&(a.allow=i),Object.assign(a.style,D,{height:r}),a}function k(e,t){let n=E(e),{renderToken:r,additionalFields:i={cardholderName:!1},billingAddressRequirement:a=`country`,...o}=t,s=O({src:x(r,i,a),title:`Secure credit card payment method form powered by Amos`,name:`amos-credit-card-payment-method-form`,height:C(i,a)});n.appendChild(s);let c=T(s,{...o,onHeightChange:e=>{s.style.height=e,o.onHeightChange?.(e)},onAppearanceReady:()=>{s.style.opacity=`1`,o.onAppearanceReady?.()}});return{iframe:s,update:c.update,destroy(){c.destroy(),s.remove()}}}function A(e,t){let n=E(e),{renderToken:r,billingAddressRequirement:i=`country`,...a}=t,o=O({src:S(r,i),title:`Secure bank account payment method form powered by Amos`,name:`amos-bank-account-payment-method-form`,height:w(i)});n.appendChild(o);let s=T(o,{...a,onHeightChange:e=>{o.style.height=e,a.onHeightChange?.(e)},onAppearanceReady:()=>{o.style.opacity=`1`,a.onAppearanceReady?.()}});return{iframe:o,update:s.update,destroy(){s.destroy(),o.remove()}}}function j(e,t){let n=E(e),{renderToken:r,...i}=t,a=O({src:m(r),title:`Secure Google Pay button powered by Amos`,name:`amos-google-pay-button`,height:h(),allow:`payment`});n.appendChild(a);let o=g(a,{...i,onHeightChange:e=>{a.style.height=e,i.onHeightChange?.(e)},onAppearanceReady:()=>{a.style.opacity=`1`,i.onAppearanceReady?.()}});return{iframe:a,update:o.update,destroy(){o.destroy(),a.remove()}}}function M(e,t){let n=E(e),{renderToken:r,...i}=t,a=O({src:d(r),title:`Secure Apple Pay button powered by Amos`,name:`amos-apple-pay-button`,height:f(),allow:`payment`});n.appendChild(a);let o=p(a,{...i,onHeightChange:e=>{a.style.height=e,i.onHeightChange?.(e)},onAppearanceReady:()=>{a.style.opacity=`1`,i.onAppearanceReady?.()}});return{iframe:a,update:o.update,destroy(){o.destroy(),a.remove()}}}exports.attachApplePayButtonListeners=p,exports.attachGooglePayButtonListeners=g,exports.attachPaymentMethodFormListeners=T,exports.confirmPaymentIntent=c,exports.confirmSetupIntent=l,exports.createMessage=n,exports.decodeJwt=e,exports.formatGooglePayPaymentData=_,exports.getApplePayButtonInitialHeight=f,exports.getApplePayButtonSrc=d,exports.getBankAccountFormInitialHeight=w,exports.getBankAccountFormSrc=S,exports.getCreditCardFormInitialHeight=C,exports.getCreditCardFormSrc=x,exports.getEmbedOrigin=t,exports.getGooglePayButtonInitialHeight=h,exports.getGooglePayButtonSrc=m,exports.mountAmosApplePayButton=M,exports.mountAmosBankAccountPaymentMethodForm=A,exports.mountAmosCreditCardPaymentMethodForm=k,exports.mountAmosGooglePayButton=j,exports.sendConfirmationFailed=u,exports.sendParentReadyMessage=r,exports.updateAmount=a,exports.updateAppearance=i,exports.updateMerchantName=o,exports.validateForm=s;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){let[t=``,n=``,r=``]=e?.split(`.`)??[],i=typeof atob==`function`?atob:e=>Buffer.from(e,`base64`).toString(`utf8`);return{header:JSON.parse(i(t)),payload:JSON.parse(i(n)),signature:r}}function t(t){let{env:n=`sandbox`}=e(t).payload;switch(n){case`production`:return`https://embed.amos.com`;case`sandbox`:return`https://embed-sandbox.amos.com`;default:return`https://embed-sandbox.amos.com`}}function n(e){return e}function r(e){e?.contentWindow?.postMessage(n({type:`PARENT_ACKNOWLEDGED_IFRAME_READY`}),`*`)}function i({iframe:e,appearance:t={}}){e?.contentWindow?.postMessage(n({type:`UPDATE_APPEARANCE`,appearance:t}),`*`)}function a({iframe:e,amount:t}){e?.contentWindow?.postMessage(n({type:`UPDATE_AMOUNT`,amount:t}),`*`)}function o({iframe:e,merchantName:t}){e?.contentWindow?.postMessage(n({type:`UPDATE_MERCHANT_NAME`,merchantName:t}),`*`)}function s(){return`ApplePaySession`in globalThis}function c({iframe:e,hasNativeApplePaySession:t}){e?.contentWindow?.postMessage(n({type:`UPDATE_NATIVE_APPLE_PAY_SESSION`,hasNativeApplePaySession:t}),`*`)}function l({iframe:e}){let t=crypto.randomUUID();return new Promise(r=>{e?.contentWindow?.postMessage(n({type:`VALIDATE_FORM`,requestId:t}),`*`);let i=setTimeout(()=>{window.removeEventListener(`message`,a),r(!1)},5e3);function a(e){e.data.type===`VALIDATE_FORM`&&e.data.requestId===t&&(window.removeEventListener(`message`,a),clearTimeout(i),r(e.data.isValid??!1))}window.addEventListener(`message`,a)})}function u({iframe:t,token:r}){let{payment_intent_id:i}=e(r).payload;t?.contentWindow?.postMessage(n({type:`CONFIRM_PAYMENT_INTENT`,token:r,id:i??void 0}),`*`)}function d({iframe:t,token:r}){let{setup_intent_id:i}=e(r).payload;t?.contentWindow?.postMessage(n({type:`CONFIRM_SETUP_INTENT`,token:r,id:i??void 0}),`*`)}function f({iframe:e,errorMessage:t}){e?.contentWindow?.postMessage(n({type:`CONFIRMATION_FAILED`,errorMessage:t}),`*`)}function p(e){return`${t(e)}/iframe/apple-pay?token=${e}`}function m(){return`40px`}function h(e){let t={cssText:e.style.cssText,bodyOverflow:document.body.style.overflow};return document.body.style.overflow=`hidden`,Object.assign(e.style,{position:`fixed`,inset:`0px`,width:`100vw`,height:`100vh`,maxWidth:`none`,maxHeight:`none`,margin:`0`,border:`0`,zIndex:`2147483647`,opacity:`1`,background:`#ffffff`}),t}function g(e,t){t&&(document.body.style.overflow=t.bodyOverflow,e.style.cssText=t.cssText)}function _(e,t){let n={...t},l=null;function d(){a({iframe:e,amount:n.amount})}function p(){o({iframe:e,merchantName:n.merchantName})}function m(){c({iframe:e,hasNativeApplePaySession:s()})}function _(t){if(t.source===e.contentWindow)switch(t.data.type){case`IFRAME_READY`:r(e),i({iframe:e,appearance:n.appearance}),m(),d(),p();break;case`UPDATE_HEIGHT`:l||n.onHeightChange?.(t.data.height);break;case`UPDATE_APPEARANCE`:i({iframe:e,appearance:t.data.appearance});break;case`UPDATED_APPEARANCE`:n.onAppearanceReady?.();break;case`EXPAND_IFRAME`:l||=h(e);break;case`COLLAPSE_IFRAME`:g(e,l),l=null;break;case`CREATE_PAYMENT_INTENT`:n.onInitiatePaymentIntentRequest({paymentIntentCreateAttributes:t.data.paymentIntentCreateAttributes,customerCreateAttributes:t.data.customerCreateAttributes}).then(t=>{u({iframe:e,token:t})}).catch(t=>{f({iframe:e,errorMessage:t instanceof Error?t.message:`Unknown error`})});break;case`PAYMENT_INTENT_CONFIRMATION_SUCCEEDED`:n.onPaymentIntentConfirmationSucceeded(t.data.paymentIntent);break;case`CONFIRMATION_FAILED`:n.onConfirmationFailed(t.data.errorMessage);break}}return window.addEventListener(`message`,_),{update(t){let r=`appearance`in t,a=`amount`in t,o=`merchantName`in t;n={...n,...t},r&&i({iframe:e,appearance:n.appearance}),a&&d(),o&&p()},destroy(){window.removeEventListener(`message`,_),g(e,l),l=null}}}function v(e){return`${t(e)}/iframe/google-pay?token=${e}`}function y(){return`40px`}function b(e,t){let n={...t};function s(){a({iframe:e,amount:n.amount})}function c(){o({iframe:e,merchantName:n.merchantName})}function l(t){switch(t.data.type){case`IFRAME_READY`:r(e),i({iframe:e,appearance:n.appearance}),s(),c();break;case`UPDATE_HEIGHT`:n.onHeightChange?.(t.data.height);break;case`UPDATE_APPEARANCE`:i({iframe:e,appearance:t.data.appearance});break;case`UPDATED_APPEARANCE`:n.onAppearanceReady?.();break;case`CREATE_PAYMENT_INTENT`:n.onInitiatePaymentIntentRequest({paymentIntentCreateAttributes:t.data.paymentIntentCreateAttributes,customerCreateAttributes:t.data.customerCreateAttributes}).then(t=>{u({iframe:e,token:t})}).catch(t=>{f({iframe:e,errorMessage:t instanceof Error?t.message:`Unknown error`})});break;case`PAYMENT_INTENT_CONFIRMATION_SUCCEEDED`:n.onPaymentIntentConfirmationSucceeded(t.data.paymentIntent);break;case`CONFIRMATION_FAILED`:n.onConfirmationFailed(t.data.errorMessage);break}}return window.addEventListener(`message`,l),{update(t){let r=`appearance`in t,a=`amount`in t,o=`merchantName`in t;n={...n,...t},r&&i({iframe:e,appearance:n.appearance}),a&&s(),o&&c()},destroy(){window.removeEventListener(`message`,l)}}}function x({paymentData:e}){return{paymentMethod:{type:`googlepay`,billing_address_attributes:{name:e.shippingAddress?.name,address_line1:e.shippingAddress?.address1,address_line2:e.shippingAddress?.address2,city:e.shippingAddress?.locality,state:e.shippingAddress?.administrativeArea,postal_code:e.shippingAddress?.postalCode,country:e.shippingAddress?.countryCode,email:e.email,phone:e.shippingAddress?.phoneNumber},card_profile_attributes:{wallet_provider:`googlepay`,wallet_payload:e.paymentMethodData.tokenizationData.token,wallet_last4:e.paymentMethodData.info?.cardDetails,wallet_brand:(()=>{switch(e.paymentMethodData.info?.cardNetwork){case`AMEX`:return`american_express`;case`VISA`:return`visa`;case`MASTERCARD`:return`master`;case`DISCOVER`:return`discover`;default:return}})()}}}}var S={country:212,full:452},C=80,w={country:400,full:640};function T(e,n={cardholderName:!1},r=`country`){let i=Object.entries(n).filter(([,e])=>e).map(([e])=>e).join(`,`),a=new URLSearchParams({token:e,additionalFields:i,billingAddressRequirement:r});return`${t(e)}/iframe/card?${a}`}function E(e,n=`country`){let r=new URLSearchParams({token:e,billingAddressRequirement:n});return`${t(e)}/iframe/bank?${r}`}function D(e={cardholderName:!1},t=`country`){return`${(S[t]??S.country)+(e.cardholderName?C:0)}px`}function O(e=`country`){return`${w[e]??w.country}px`}function k(e,t){let n={...t};function a(t){switch(t.data.type){case`IFRAME_READY`:r(e),i({iframe:e,appearance:n.appearance});break;case`UPDATE_HEIGHT`:n.onHeightChange?.(t.data.height);break;case`UPDATE_APPEARANCE`:i({iframe:e,appearance:t.data.appearance});break;case`UPDATED_APPEARANCE`:n.onAppearanceReady?.();break;case`PAYMENT_INTENT_CONFIRMATION_SUCCEEDED`:n.onPaymentIntentConfirmationSucceeded?.(t.data.paymentIntent);break;case`SETUP_INTENT_CONFIRMATION_SUCCEEDED`:n.onSetupIntentConfirmationSucceeded?.(t.data.setupIntent);break;case`CONFIRMATION_FAILED`:n.onConfirmationFailed(t.data.errorMessage);break}}return window.addEventListener(`message`,a),{update(t){let r=`appearance`in t;n={...n,...t},r&&i({iframe:e,appearance:n.appearance})},destroy(){window.removeEventListener(`message`,a)}}}function A(e){if(typeof e==`string`){let t=document.querySelector(e);if(!(t instanceof HTMLElement))throw Error(`[amos-js] Container "${e}" did not match any HTMLElement.`);return t}return e}var j={width:`calc(100% + 8px)`,transition:`opacity 150ms ease-in, height 200ms ease-in-out`,margin:`0 -4px`,opacity:`0`,border:`0`};function M({src:e,title:t,name:n,height:r,allow:i}){let a=document.createElement(`iframe`);return a.src=e,a.title=t,a.name=n,a.setAttribute(`role`,`presentation`),a.scrolling=`no`,i&&(a.allow=i),Object.assign(a.style,j,{height:r}),a}function N(e,t){let n=A(e),{renderToken:r,additionalFields:i={cardholderName:!1},billingAddressRequirement:a=`country`,...o}=t,s=M({src:T(r,i,a),title:`Secure credit card payment method form powered by Amos`,name:`amos-credit-card-payment-method-form`,height:D(i,a)});n.appendChild(s);let c=k(s,{...o,onHeightChange:e=>{s.style.height=e,o.onHeightChange?.(e)},onAppearanceReady:()=>{s.style.opacity=`1`,o.onAppearanceReady?.()}});return{iframe:s,update:c.update,destroy(){c.destroy(),s.remove()}}}function P(e,t){let n=A(e),{renderToken:r,billingAddressRequirement:i=`country`,...a}=t,o=M({src:E(r,i),title:`Secure bank account payment method form powered by Amos`,name:`amos-bank-account-payment-method-form`,height:O(i)});n.appendChild(o);let s=k(o,{...a,onHeightChange:e=>{o.style.height=e,a.onHeightChange?.(e)},onAppearanceReady:()=>{o.style.opacity=`1`,a.onAppearanceReady?.()}});return{iframe:o,update:s.update,destroy(){s.destroy(),o.remove()}}}function F(e,t){let n=A(e),{renderToken:r,...i}=t,a=M({src:v(r),title:`Secure Google Pay button powered by Amos`,name:`amos-google-pay-button`,height:y(),allow:`payment`});n.appendChild(a);let o=b(a,{...i,onHeightChange:e=>{a.style.height=e,i.onHeightChange?.(e)},onAppearanceReady:()=>{a.style.opacity=`1`,i.onAppearanceReady?.()}});return{iframe:a,update:o.update,destroy(){o.destroy(),a.remove()}}}function I(e,t){let n=A(e),{renderToken:r,...i}=t,a=M({src:p(r),title:`Secure Apple Pay button powered by Amos`,name:`amos-apple-pay-button`,height:m(),allow:`payment`});n.appendChild(a);let o=_(a,{...i,onHeightChange:e=>{a.style.height=e,i.onHeightChange?.(e)},onAppearanceReady:()=>{a.style.opacity=`1`,i.onAppearanceReady?.()}});return{iframe:a,update:o.update,destroy(){o.destroy(),a.remove()}}}exports.attachApplePayButtonListeners=_,exports.attachGooglePayButtonListeners=b,exports.attachPaymentMethodFormListeners=k,exports.confirmPaymentIntent=u,exports.confirmSetupIntent=d,exports.createMessage=n,exports.decodeJwt=e,exports.formatGooglePayPaymentData=x,exports.getApplePayButtonInitialHeight=m,exports.getApplePayButtonSrc=p,exports.getBankAccountFormInitialHeight=O,exports.getBankAccountFormSrc=E,exports.getCreditCardFormInitialHeight=D,exports.getCreditCardFormSrc=T,exports.getEmbedOrigin=t,exports.getGooglePayButtonInitialHeight=y,exports.getGooglePayButtonSrc=v,exports.hasNativeApplePaySession=s,exports.mountAmosApplePayButton=I,exports.mountAmosBankAccountPaymentMethodForm=P,exports.mountAmosCreditCardPaymentMethodForm=N,exports.mountAmosGooglePayButton=F,exports.sendConfirmationFailed=f,exports.sendParentReadyMessage=r,exports.updateAmount=a,exports.updateAppearance=i,exports.updateMerchantName=o,exports.updateNativeApplePaySession=c,exports.validateForm=l;
package/dist/index.mjs CHANGED
@@ -43,7 +43,16 @@ function o({ iframe: e, merchantName: t }) {
43
43
  merchantName: t
44
44
  }), "*");
45
45
  }
46
- function s({ iframe: e }) {
46
+ function s() {
47
+ return "ApplePaySession" in globalThis;
48
+ }
49
+ function c({ iframe: e, hasNativeApplePaySession: t }) {
50
+ e?.contentWindow?.postMessage(n({
51
+ type: "UPDATE_NATIVE_APPLE_PAY_SESSION",
52
+ hasNativeApplePaySession: t
53
+ }), "*");
54
+ }
55
+ function l({ iframe: e }) {
47
56
  let t = crypto.randomUUID();
48
57
  return new Promise((r) => {
49
58
  e?.contentWindow?.postMessage(n({
@@ -59,7 +68,7 @@ function s({ iframe: e }) {
59
68
  window.addEventListener("message", a);
60
69
  });
61
70
  }
62
- function c({ iframe: t, token: r }) {
71
+ function u({ iframe: t, token: r }) {
63
72
  let { payment_intent_id: i } = e(r).payload;
64
73
  t?.contentWindow?.postMessage(n({
65
74
  type: "CONFIRM_PAYMENT_INTENT",
@@ -67,7 +76,7 @@ function c({ iframe: t, token: r }) {
67
76
  id: i ?? void 0
68
77
  }), "*");
69
78
  }
70
- function l({ iframe: t, token: r }) {
79
+ function d({ iframe: t, token: r }) {
71
80
  let { setup_intent_id: i } = e(r).payload;
72
81
  t?.contentWindow?.postMessage(n({
73
82
  type: "CONFIRM_SETUP_INTENT",
@@ -75,7 +84,7 @@ function l({ iframe: t, token: r }) {
75
84
  id: i ?? void 0
76
85
  }), "*");
77
86
  }
78
- function u({ iframe: e, errorMessage: t }) {
87
+ function f({ iframe: e, errorMessage: t }) {
79
88
  e?.contentWindow?.postMessage(n({
80
89
  type: "CONFIRMATION_FAILED",
81
90
  errorMessage: t
@@ -83,36 +92,64 @@ function u({ iframe: e, errorMessage: t }) {
83
92
  }
84
93
  //#endregion
85
94
  //#region src/apple-pay.ts
86
- function d(e) {
95
+ function p(e) {
87
96
  return `${t(e)}/iframe/apple-pay?token=${e}`;
88
97
  }
89
- function f() {
98
+ function m() {
90
99
  return "40px";
91
100
  }
92
- function p(e, t) {
93
- let n = { ...t };
94
- function s() {
101
+ function h(e) {
102
+ let t = {
103
+ cssText: e.style.cssText,
104
+ bodyOverflow: document.body.style.overflow
105
+ };
106
+ return document.body.style.overflow = "hidden", Object.assign(e.style, {
107
+ position: "fixed",
108
+ inset: "0px",
109
+ width: "100vw",
110
+ height: "100vh",
111
+ maxWidth: "none",
112
+ maxHeight: "none",
113
+ margin: "0",
114
+ border: "0",
115
+ zIndex: "2147483647",
116
+ opacity: "1",
117
+ background: "#ffffff"
118
+ }), t;
119
+ }
120
+ function g(e, t) {
121
+ t && (document.body.style.overflow = t.bodyOverflow, e.style.cssText = t.cssText);
122
+ }
123
+ function _(e, t) {
124
+ let n = { ...t }, l = null;
125
+ function d() {
95
126
  a({
96
127
  iframe: e,
97
128
  amount: n.amount
98
129
  });
99
130
  }
100
- function l() {
131
+ function p() {
101
132
  o({
102
133
  iframe: e,
103
134
  merchantName: n.merchantName
104
135
  });
105
136
  }
106
- function d(t) {
107
- switch (t.data.type) {
137
+ function m() {
138
+ c({
139
+ iframe: e,
140
+ hasNativeApplePaySession: s()
141
+ });
142
+ }
143
+ function _(t) {
144
+ if (t.source === e.contentWindow) switch (t.data.type) {
108
145
  case "IFRAME_READY":
109
146
  r(e), i({
110
147
  iframe: e,
111
148
  appearance: n.appearance
112
- }), s(), l();
149
+ }), m(), d(), p();
113
150
  break;
114
151
  case "UPDATE_HEIGHT":
115
- n.onHeightChange?.(t.data.height);
152
+ l || n.onHeightChange?.(t.data.height);
116
153
  break;
117
154
  case "UPDATE_APPEARANCE":
118
155
  i({
@@ -123,17 +160,23 @@ function p(e, t) {
123
160
  case "UPDATED_APPEARANCE":
124
161
  n.onAppearanceReady?.();
125
162
  break;
163
+ case "EXPAND_IFRAME":
164
+ l ||= h(e);
165
+ break;
166
+ case "COLLAPSE_IFRAME":
167
+ g(e, l), l = null;
168
+ break;
126
169
  case "CREATE_PAYMENT_INTENT":
127
170
  n.onInitiatePaymentIntentRequest({
128
171
  paymentIntentCreateAttributes: t.data.paymentIntentCreateAttributes,
129
172
  customerCreateAttributes: t.data.customerCreateAttributes
130
173
  }).then((t) => {
131
- c({
174
+ u({
132
175
  iframe: e,
133
176
  token: t
134
177
  });
135
178
  }).catch((t) => {
136
- u({
179
+ f({
137
180
  iframe: e,
138
181
  errorMessage: t instanceof Error ? t.message : "Unknown error"
139
182
  });
@@ -147,7 +190,7 @@ function p(e, t) {
147
190
  break;
148
191
  }
149
192
  }
150
- return window.addEventListener("message", d), {
193
+ return window.addEventListener("message", _), {
151
194
  update(t) {
152
195
  let r = "appearance" in t, a = "amount" in t, o = "merchantName" in t;
153
196
  n = {
@@ -156,22 +199,22 @@ function p(e, t) {
156
199
  }, r && i({
157
200
  iframe: e,
158
201
  appearance: n.appearance
159
- }), a && s(), o && l();
202
+ }), a && d(), o && p();
160
203
  },
161
204
  destroy() {
162
- window.removeEventListener("message", d);
205
+ window.removeEventListener("message", _), g(e, l), l = null;
163
206
  }
164
207
  };
165
208
  }
166
209
  //#endregion
167
210
  //#region src/google-pay.ts
168
- function m(e) {
211
+ function v(e) {
169
212
  return `${t(e)}/iframe/google-pay?token=${e}`;
170
213
  }
171
- function h() {
214
+ function y() {
172
215
  return "40px";
173
216
  }
174
- function g(e, t) {
217
+ function b(e, t) {
175
218
  let n = { ...t };
176
219
  function s() {
177
220
  a({
@@ -179,19 +222,19 @@ function g(e, t) {
179
222
  amount: n.amount
180
223
  });
181
224
  }
182
- function l() {
225
+ function c() {
183
226
  o({
184
227
  iframe: e,
185
228
  merchantName: n.merchantName
186
229
  });
187
230
  }
188
- function d(t) {
231
+ function l(t) {
189
232
  switch (t.data.type) {
190
233
  case "IFRAME_READY":
191
234
  r(e), i({
192
235
  iframe: e,
193
236
  appearance: n.appearance
194
- }), s(), l();
237
+ }), s(), c();
195
238
  break;
196
239
  case "UPDATE_HEIGHT":
197
240
  n.onHeightChange?.(t.data.height);
@@ -210,12 +253,12 @@ function g(e, t) {
210
253
  paymentIntentCreateAttributes: t.data.paymentIntentCreateAttributes,
211
254
  customerCreateAttributes: t.data.customerCreateAttributes
212
255
  }).then((t) => {
213
- c({
256
+ u({
214
257
  iframe: e,
215
258
  token: t
216
259
  });
217
260
  }).catch((t) => {
218
- u({
261
+ f({
219
262
  iframe: e,
220
263
  errorMessage: t instanceof Error ? t.message : "Unknown error"
221
264
  });
@@ -229,7 +272,7 @@ function g(e, t) {
229
272
  break;
230
273
  }
231
274
  }
232
- return window.addEventListener("message", d), {
275
+ return window.addEventListener("message", l), {
233
276
  update(t) {
234
277
  let r = "appearance" in t, a = "amount" in t, o = "merchantName" in t;
235
278
  n = {
@@ -238,14 +281,14 @@ function g(e, t) {
238
281
  }, r && i({
239
282
  iframe: e,
240
283
  appearance: n.appearance
241
- }), a && s(), o && l();
284
+ }), a && s(), o && c();
242
285
  },
243
286
  destroy() {
244
- window.removeEventListener("message", d);
287
+ window.removeEventListener("message", l);
245
288
  }
246
289
  };
247
290
  }
248
- function _({ paymentData: e }) {
291
+ function x({ paymentData: e }) {
249
292
  return { paymentMethod: {
250
293
  type: "googlepay",
251
294
  billing_address_attributes: {
@@ -277,14 +320,14 @@ function _({ paymentData: e }) {
277
320
  }
278
321
  //#endregion
279
322
  //#region src/payment-method-form.ts
280
- var v = {
323
+ var S = {
281
324
  country: 212,
282
325
  full: 452
283
- }, y = 80, b = {
326
+ }, C = 80, w = {
284
327
  country: 400,
285
328
  full: 640
286
329
  };
287
- function x(e, n = { cardholderName: !1 }, r = "country") {
330
+ function T(e, n = { cardholderName: !1 }, r = "country") {
288
331
  let i = Object.entries(n).filter(([, e]) => e).map(([e]) => e).join(","), a = new URLSearchParams({
289
332
  token: e,
290
333
  additionalFields: i,
@@ -292,20 +335,20 @@ function x(e, n = { cardholderName: !1 }, r = "country") {
292
335
  });
293
336
  return `${t(e)}/iframe/card?${a}`;
294
337
  }
295
- function S(e, n = "country") {
338
+ function E(e, n = "country") {
296
339
  let r = new URLSearchParams({
297
340
  token: e,
298
341
  billingAddressRequirement: n
299
342
  });
300
343
  return `${t(e)}/iframe/bank?${r}`;
301
344
  }
302
- function C(e = { cardholderName: !1 }, t = "country") {
303
- return `${(v[t] ?? v.country) + (e.cardholderName ? y : 0)}px`;
345
+ function D(e = { cardholderName: !1 }, t = "country") {
346
+ return `${(S[t] ?? S.country) + (e.cardholderName ? C : 0)}px`;
304
347
  }
305
- function w(e = "country") {
306
- return `${b[e] ?? b.country}px`;
348
+ function O(e = "country") {
349
+ return `${w[e] ?? w.country}px`;
307
350
  }
308
- function T(e, t) {
351
+ function k(e, t) {
309
352
  let n = { ...t };
310
353
  function a(t) {
311
354
  switch (t.data.type) {
@@ -356,7 +399,7 @@ function T(e, t) {
356
399
  }
357
400
  //#endregion
358
401
  //#region src/mount.ts
359
- function E(e) {
402
+ function A(e) {
360
403
  if (typeof e == "string") {
361
404
  let t = document.querySelector(e);
362
405
  if (!(t instanceof HTMLElement)) throw Error(`[amos-js] Container "${e}" did not match any HTMLElement.`);
@@ -364,26 +407,26 @@ function E(e) {
364
407
  }
365
408
  return e;
366
409
  }
367
- var D = {
410
+ var j = {
368
411
  width: "calc(100% + 8px)",
369
412
  transition: "opacity 150ms ease-in, height 200ms ease-in-out",
370
413
  margin: "0 -4px",
371
414
  opacity: "0",
372
415
  border: "0"
373
416
  };
374
- function O({ src: e, title: t, name: n, height: r, allow: i }) {
417
+ function M({ src: e, title: t, name: n, height: r, allow: i }) {
375
418
  let a = document.createElement("iframe");
376
- return a.src = e, a.title = t, a.name = n, a.setAttribute("role", "presentation"), a.scrolling = "no", i && (a.allow = i), Object.assign(a.style, D, { height: r }), a;
419
+ return a.src = e, a.title = t, a.name = n, a.setAttribute("role", "presentation"), a.scrolling = "no", i && (a.allow = i), Object.assign(a.style, j, { height: r }), a;
377
420
  }
378
- function k(e, t) {
379
- let n = E(e), { renderToken: r, additionalFields: i = { cardholderName: !1 }, billingAddressRequirement: a = "country", ...o } = t, s = O({
380
- src: x(r, i, a),
421
+ function N(e, t) {
422
+ let n = A(e), { renderToken: r, additionalFields: i = { cardholderName: !1 }, billingAddressRequirement: a = "country", ...o } = t, s = M({
423
+ src: T(r, i, a),
381
424
  title: "Secure credit card payment method form powered by Amos",
382
425
  name: "amos-credit-card-payment-method-form",
383
- height: C(i, a)
426
+ height: D(i, a)
384
427
  });
385
428
  n.appendChild(s);
386
- let c = T(s, {
429
+ let c = k(s, {
387
430
  ...o,
388
431
  onHeightChange: (e) => {
389
432
  s.style.height = e, o.onHeightChange?.(e);
@@ -400,15 +443,15 @@ function k(e, t) {
400
443
  }
401
444
  };
402
445
  }
403
- function A(e, t) {
404
- let n = E(e), { renderToken: r, billingAddressRequirement: i = "country", ...a } = t, o = O({
405
- src: S(r, i),
446
+ function P(e, t) {
447
+ let n = A(e), { renderToken: r, billingAddressRequirement: i = "country", ...a } = t, o = M({
448
+ src: E(r, i),
406
449
  title: "Secure bank account payment method form powered by Amos",
407
450
  name: "amos-bank-account-payment-method-form",
408
- height: w(i)
451
+ height: O(i)
409
452
  });
410
453
  n.appendChild(o);
411
- let s = T(o, {
454
+ let s = k(o, {
412
455
  ...a,
413
456
  onHeightChange: (e) => {
414
457
  o.style.height = e, a.onHeightChange?.(e);
@@ -425,16 +468,16 @@ function A(e, t) {
425
468
  }
426
469
  };
427
470
  }
428
- function j(e, t) {
429
- let n = E(e), { renderToken: r, ...i } = t, a = O({
430
- src: m(r),
471
+ function F(e, t) {
472
+ let n = A(e), { renderToken: r, ...i } = t, a = M({
473
+ src: v(r),
431
474
  title: "Secure Google Pay button powered by Amos",
432
475
  name: "amos-google-pay-button",
433
- height: h(),
476
+ height: y(),
434
477
  allow: "payment"
435
478
  });
436
479
  n.appendChild(a);
437
- let o = g(a, {
480
+ let o = b(a, {
438
481
  ...i,
439
482
  onHeightChange: (e) => {
440
483
  a.style.height = e, i.onHeightChange?.(e);
@@ -451,16 +494,16 @@ function j(e, t) {
451
494
  }
452
495
  };
453
496
  }
454
- function M(e, t) {
455
- let n = E(e), { renderToken: r, ...i } = t, a = O({
456
- src: d(r),
497
+ function I(e, t) {
498
+ let n = A(e), { renderToken: r, ...i } = t, a = M({
499
+ src: p(r),
457
500
  title: "Secure Apple Pay button powered by Amos",
458
501
  name: "amos-apple-pay-button",
459
- height: f(),
502
+ height: m(),
460
503
  allow: "payment"
461
504
  });
462
505
  n.appendChild(a);
463
- let o = p(a, {
506
+ let o = _(a, {
464
507
  ...i,
465
508
  onHeightChange: (e) => {
466
509
  a.style.height = e, i.onHeightChange?.(e);
@@ -478,4 +521,4 @@ function M(e, t) {
478
521
  };
479
522
  }
480
523
  //#endregion
481
- export { p as attachApplePayButtonListeners, g as attachGooglePayButtonListeners, T as attachPaymentMethodFormListeners, c as confirmPaymentIntent, l as confirmSetupIntent, n as createMessage, e as decodeJwt, _ as formatGooglePayPaymentData, f as getApplePayButtonInitialHeight, d as getApplePayButtonSrc, w as getBankAccountFormInitialHeight, S as getBankAccountFormSrc, C as getCreditCardFormInitialHeight, x as getCreditCardFormSrc, t as getEmbedOrigin, h as getGooglePayButtonInitialHeight, m as getGooglePayButtonSrc, M as mountAmosApplePayButton, A as mountAmosBankAccountPaymentMethodForm, k as mountAmosCreditCardPaymentMethodForm, j as mountAmosGooglePayButton, u as sendConfirmationFailed, r as sendParentReadyMessage, a as updateAmount, i as updateAppearance, o as updateMerchantName, s as validateForm };
524
+ export { _ as attachApplePayButtonListeners, b as attachGooglePayButtonListeners, k as attachPaymentMethodFormListeners, u as confirmPaymentIntent, d as confirmSetupIntent, n as createMessage, e as decodeJwt, x as formatGooglePayPaymentData, m as getApplePayButtonInitialHeight, p as getApplePayButtonSrc, O as getBankAccountFormInitialHeight, E as getBankAccountFormSrc, D as getCreditCardFormInitialHeight, T as getCreditCardFormSrc, t as getEmbedOrigin, y as getGooglePayButtonInitialHeight, v as getGooglePayButtonSrc, s as hasNativeApplePaySession, I as mountAmosApplePayButton, P as mountAmosBankAccountPaymentMethodForm, N as mountAmosCreditCardPaymentMethodForm, F as mountAmosGooglePayButton, f as sendConfirmationFailed, r as sendParentReadyMessage, a as updateAmount, i as updateAppearance, o as updateMerchantName, c as updateNativeApplePaySession, l as validateForm };
@@ -34,6 +34,19 @@ export declare function updateMerchantName({ iframe, merchantName, }: {
34
34
  iframe: Iframe;
35
35
  merchantName: string;
36
36
  }): void;
37
+ /**
38
+ * Whether the host page exposes a native `ApplePaySession` (Safari).
39
+ * Non-Safari browsers load Apple's SDK inside the embed iframe instead.
40
+ */
41
+ export declare function hasNativeApplePaySession(): boolean;
42
+ /**
43
+ * Tell the Apple Pay iframe whether the parent has a native
44
+ * `ApplePaySession`. Sent during the `IFRAME_READY` handshake.
45
+ */
46
+ export declare function updateNativeApplePaySession({ iframe, hasNativeApplePaySession, }: {
47
+ iframe: Iframe;
48
+ hasNativeApplePaySession: boolean;
49
+ }): void;
37
50
  /**
38
51
  * Ask the embedded credit-card or bank-account iframe form to validate
39
52
  * its inputs.
package/dist/types.d.ts CHANGED
@@ -42,6 +42,14 @@ export type Message = {
42
42
  } | {
43
43
  type: "UPDATE_MERCHANT_NAME";
44
44
  merchantName: string;
45
+ } | {
46
+ /**
47
+ * Parent → iframe: whether the host page has a native
48
+ * `ApplePaySession` (Safari). When `false`, the iframe should
49
+ * expand for Chrome's in-iframe QR handoff UI.
50
+ */
51
+ type: "UPDATE_NATIVE_APPLE_PAY_SESSION";
52
+ hasNativeApplePaySession: boolean;
45
53
  } | {
46
54
  type: "UPDATE_APPEARANCE";
47
55
  appearance: Appearance;
@@ -68,6 +76,16 @@ export type Message = {
68
76
  errorMessage: string;
69
77
  } | {
70
78
  type: "UPDATED_APPEARANCE";
79
+ } | {
80
+ /**
81
+ * Iframe → parent: expand the Apple Pay iframe to a full-viewport
82
+ * overlay so Chrome's QR handoff UI is not clipped by the button
83
+ * height. The Apple Pay session stays on the Amos embed origin.
84
+ */
85
+ type: "EXPAND_IFRAME";
86
+ } | {
87
+ /** Iframe → parent: restore the Apple Pay iframe to its button size. */
88
+ type: "COLLAPSE_IFRAME";
71
89
  };
72
90
  /**
73
91
  * Identity helper that brands an object as a typed `Message`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amos.com/amos-js",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "main": "dist/index.js",
5
5
  "repository": {
6
6
  "type": "git",
@@ -44,7 +44,7 @@
44
44
  "vite-plugin-dts": "5.0.3"
45
45
  },
46
46
  "dependencies": {
47
- "@amos.com/node": "0.1.31",
47
+ "@amos.com/node": "0.1.32",
48
48
  "@types/googlepay": "0.7.11"
49
49
  }
50
50
  }
package/src/apple-pay.ts CHANGED
@@ -2,11 +2,13 @@ import type { components } from "@amos.com/node";
2
2
  import { getEmbedOrigin } from "./jwt";
3
3
  import {
4
4
  confirmPaymentIntent,
5
+ hasNativeApplePaySession,
5
6
  sendConfirmationFailed,
6
7
  sendParentReadyMessage,
7
8
  updateAmount as sendUpdateAmount,
8
9
  updateAppearance as sendUpdateAppearance,
9
10
  updateMerchantName as sendUpdateMerchantName,
11
+ updateNativeApplePaySession as sendUpdateNativeApplePaySession,
10
12
  } from "./messaging";
11
13
  import type { Appearance, Message } from "./types";
12
14
 
@@ -88,6 +90,49 @@ export type ApplePayButtonController = {
88
90
  destroy: () => void;
89
91
  };
90
92
 
93
+ type SavedIframeLayout = {
94
+ cssText: string;
95
+ bodyOverflow: string;
96
+ };
97
+
98
+ /**
99
+ * Expand the Apple Pay iframe to a full-viewport overlay so Chrome's QR
100
+ * handoff UI (rendered inside the iframe) is not clipped to the button
101
+ * height. Safari's native sheet is OS-level and unaffected.
102
+ */
103
+ function expandApplePayIframe(iframe: HTMLIFrameElement): SavedIframeLayout {
104
+ const saved: SavedIframeLayout = {
105
+ cssText: iframe.style.cssText,
106
+ bodyOverflow: document.body.style.overflow,
107
+ };
108
+ document.body.style.overflow = "hidden";
109
+ Object.assign(iframe.style, {
110
+ position: "fixed",
111
+ inset: "0px",
112
+ width: "100vw",
113
+ height: "100vh",
114
+ maxWidth: "none",
115
+ maxHeight: "none",
116
+ margin: "0",
117
+ border: "0",
118
+ zIndex: "2147483647",
119
+ opacity: "1",
120
+ background: "#ffffff",
121
+ });
122
+ return saved;
123
+ }
124
+
125
+ function collapseApplePayIframe(
126
+ iframe: HTMLIFrameElement,
127
+ saved: SavedIframeLayout | null,
128
+ ): void {
129
+ if (!saved) {
130
+ return;
131
+ }
132
+ document.body.style.overflow = saved.bodyOverflow;
133
+ iframe.style.cssText = saved.cssText;
134
+ }
135
+
91
136
  /**
92
137
  * Wire up the host-page side of the Apple Pay iframe message protocol
93
138
  * on an existing `<iframe>` element. Returns a controller for updating
@@ -96,13 +141,17 @@ export type ApplePayButtonController = {
96
141
  * The iframe is expected to have already been added to the DOM with the
97
142
  * correct `src` (see {@link getApplePayButtonSrc}).
98
143
  *
99
- * Uses the same postMessage protocol as Google Pay express checkout.
144
+ * The Apple Pay button and `ApplePaySession` run inside the Amos embed
145
+ * iframe (so only Amos domains need Apple merchant registration). On
146
+ * `EXPAND_IFRAME`, the parent temporarily overlays that iframe
147
+ * full-viewport for Chrome's in-iframe QR handoff UI.
100
148
  */
101
149
  export function attachApplePayButtonListeners(
102
150
  iframe: HTMLIFrameElement,
103
151
  options: ApplePayButtonListenerOptions,
104
152
  ): ApplePayButtonController {
105
153
  let current = { ...options };
154
+ let savedLayout: SavedIframeLayout | null = null;
106
155
 
107
156
  function pushAmount() {
108
157
  sendUpdateAmount({ iframe, amount: current.amount });
@@ -112,17 +161,33 @@ export function attachApplePayButtonListeners(
112
161
  sendUpdateMerchantName({ iframe, merchantName: current.merchantName });
113
162
  }
114
163
 
164
+ function pushNativeApplePaySession() {
165
+ sendUpdateNativeApplePaySession({
166
+ iframe,
167
+ hasNativeApplePaySession: hasNativeApplePaySession(),
168
+ });
169
+ }
170
+
115
171
  function handleMessage(event: MessageEvent<Message>) {
172
+ // Ignore messages from other frames / windows.
173
+ if (event.source !== iframe.contentWindow) {
174
+ return;
175
+ }
176
+
116
177
  switch (event.data.type) {
117
178
  case "IFRAME_READY":
118
179
  sendParentReadyMessage(iframe);
119
180
  sendUpdateAppearance({ iframe, appearance: current.appearance });
181
+ pushNativeApplePaySession();
120
182
  pushAmount();
121
183
  pushMerchantName();
122
184
  break;
123
185
 
124
186
  case "UPDATE_HEIGHT":
125
- current.onHeightChange?.(event.data.height);
187
+ // Ignore height updates while the session overlay is open.
188
+ if (!savedLayout) {
189
+ current.onHeightChange?.(event.data.height);
190
+ }
126
191
  break;
127
192
 
128
193
  case "UPDATE_APPEARANCE":
@@ -133,6 +198,17 @@ export function attachApplePayButtonListeners(
133
198
  current.onAppearanceReady?.();
134
199
  break;
135
200
 
201
+ case "EXPAND_IFRAME":
202
+ if (!savedLayout) {
203
+ savedLayout = expandApplePayIframe(iframe);
204
+ }
205
+ break;
206
+
207
+ case "COLLAPSE_IFRAME":
208
+ collapseApplePayIframe(iframe, savedLayout);
209
+ savedLayout = null;
210
+ break;
211
+
136
212
  case "CREATE_PAYMENT_INTENT":
137
213
  current
138
214
  .onInitiatePaymentIntentRequest({
@@ -182,6 +258,8 @@ export function attachApplePayButtonListeners(
182
258
  },
183
259
  destroy() {
184
260
  window.removeEventListener("message", handleMessage);
261
+ collapseApplePayIframe(iframe, savedLayout);
262
+ savedLayout = null;
185
263
  },
186
264
  };
187
265
  }
package/src/index.ts CHANGED
@@ -27,11 +27,13 @@ export { decodeJwt, getEmbedOrigin } from "./jwt";
27
27
  export {
28
28
  confirmPaymentIntent,
29
29
  confirmSetupIntent,
30
+ hasNativeApplePaySession,
30
31
  sendConfirmationFailed,
31
32
  sendParentReadyMessage,
32
33
  updateAmount,
33
34
  updateAppearance,
34
35
  updateMerchantName,
36
+ updateNativeApplePaySession,
35
37
  validateForm,
36
38
  } from "./messaging";
37
39
  export type {
package/src/messaging.ts CHANGED
@@ -70,6 +70,34 @@ export function updateMerchantName({
70
70
  );
71
71
  }
72
72
 
73
+ /**
74
+ * Whether the host page exposes a native `ApplePaySession` (Safari).
75
+ * Non-Safari browsers load Apple's SDK inside the embed iframe instead.
76
+ */
77
+ export function hasNativeApplePaySession(): boolean {
78
+ return "ApplePaySession" in globalThis;
79
+ }
80
+
81
+ /**
82
+ * Tell the Apple Pay iframe whether the parent has a native
83
+ * `ApplePaySession`. Sent during the `IFRAME_READY` handshake.
84
+ */
85
+ export function updateNativeApplePaySession({
86
+ iframe,
87
+ hasNativeApplePaySession,
88
+ }: {
89
+ iframe: Iframe;
90
+ hasNativeApplePaySession: boolean;
91
+ }): void {
92
+ iframe?.contentWindow?.postMessage(
93
+ createMessage({
94
+ type: "UPDATE_NATIVE_APPLE_PAY_SESSION",
95
+ hasNativeApplePaySession,
96
+ }),
97
+ "*",
98
+ );
99
+ }
100
+
73
101
  /**
74
102
  * Ask the embedded credit-card or bank-account iframe form to validate
75
103
  * its inputs.
package/src/types.ts CHANGED
@@ -155,6 +155,15 @@ export type Message =
155
155
  type: "UPDATE_MERCHANT_NAME";
156
156
  merchantName: string;
157
157
  }
158
+ | {
159
+ /**
160
+ * Parent → iframe: whether the host page has a native
161
+ * `ApplePaySession` (Safari). When `false`, the iframe should
162
+ * expand for Chrome's in-iframe QR handoff UI.
163
+ */
164
+ type: "UPDATE_NATIVE_APPLE_PAY_SESSION";
165
+ hasNativeApplePaySession: boolean;
166
+ }
158
167
  | {
159
168
  type: "UPDATE_APPEARANCE";
160
169
  appearance: Appearance;
@@ -191,6 +200,18 @@ export type Message =
191
200
  }
192
201
  | {
193
202
  type: "UPDATED_APPEARANCE";
203
+ }
204
+ | {
205
+ /**
206
+ * Iframe → parent: expand the Apple Pay iframe to a full-viewport
207
+ * overlay so Chrome's QR handoff UI is not clipped by the button
208
+ * height. The Apple Pay session stays on the Amos embed origin.
209
+ */
210
+ type: "EXPAND_IFRAME";
211
+ }
212
+ | {
213
+ /** Iframe → parent: restore the Apple Pay iframe to its button size. */
214
+ type: "COLLAPSE_IFRAME";
194
215
  };
195
216
 
196
217
  /**