@flopay/react 1.5.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -55,6 +55,37 @@ function CheckoutPage() {
55
55
 
56
56
  `FloPayCheckout` automatically fetches the session, initializes the correct payment providers, and renders a `SplitCardForm` with the hosted vault card widget plus the session's supported wallets, APMs, and PayPal. Customer data (email, userId, name) is injected from the session.
57
57
 
58
+ #### Merchant instrument feed
59
+
60
+ Use `onInstrument` on `FloPayCheckout` or `FloPayProvider` to forward a stable,
61
+ privacy-safe checkout funnel to your own analytics. The callback receives the
62
+ exported `FloInstrumentEvent` discriminated union; `schemaVersion` is always `1`,
63
+ and `gateway` is included as `stripe` or `paypal` only when the event is
64
+ attributable to that payment provider. Hosted-vault events may omit `gateway`.
65
+
66
+ ```tsx
67
+ import type { FloInstrumentEvent } from '@flopay/react';
68
+
69
+ function forwardCheckoutInstrument(event: FloInstrumentEvent) {
70
+ window.analytics?.track(event.name, event);
71
+ }
72
+
73
+ <FloPayCheckout sessionId={sessionId} onInstrument={forwardCheckoutInstrument} />;
74
+ ```
75
+
76
+ Lifecycle names are `checkout_mount`, `sdk_loaded`, `form_rendered`,
77
+ `card_expanded`, `tokenize`, `process_attempt`, and `3ds_challenge`.
78
+ `checkout_mount`, `sdk_loaded`, `form_rendered`, and `card_expanded` arrive at
79
+ most once per logical checkout. `tokenize`, `process_attempt`, and
80
+ `3ds_challenge` may repeat for each attempt.
81
+ `checkout_error` adds one of four phases: `session_create`, `sdk_load`,
82
+ `process`, or `wallets`.
83
+
84
+ The callback is an allowlisted projection with no card data, tokens, provider
85
+ object IDs, secrets, or raw PII. A throwing `onInstrument` consumer never
86
+ breaks checkout. This merchant-owned feed is independent of the Flo-owned
87
+ backend telemetry setting, so `telemetry={false}` does not disable it.
88
+
58
89
  #### Checkout Modes
59
90
 
60
91
  `FloPayCheckout` supports three checkout modes matching the billing API's `checkoutMode` field:
@@ -715,6 +746,20 @@ Renderer selection is mutually exclusive per session — direct PayPal takes pri
715
746
 
716
747
  **PayPal-only sessions:** When the backend advertises only `gateways.paypal` (no `gateways.stripe`), `FloPayCheckout` skips Stripe Elements entirely and renders `<DirectPayPalButton>` as the sole payment surface. Sessions that advertise no supported gateway at all throw a `validation_error` explaining the expected shape.
717
748
 
749
+ **Flo-owned subscription continuations:** `gateways.paypal.providerObjectType`
750
+ selects the direct PayPal operation. `'order'` loads the PayPal SDK with
751
+ `intent=capture` and invokes `createOrder`, even when the checkout session mode
752
+ is subscription. `'subscription'` preserves the provider-managed
753
+ `intent=subscription` / `createSubscription` flow. `'setup_token'` invokes
754
+ `createVaultSetupToken`; approval submits the returned `vaultSetupToken` to the
755
+ nonce-bound session `/process` route for backend exchange and activation. An
756
+ advertised `'order'`/`'setup_token'` opts the intent request and tokenized
757
+ process body into the `paypal_vaulted` payment-method channel; the intent
758
+ response must match the advertised object type. When the field is absent,
759
+ legacy sessions continue to derive Order versus Subscription from
760
+ `isSubscription` and keep the released `paypal` requests byte-identical. See
761
+ the full [channel-selection and cutover contract](../../docs/PAYPAL_FLO_SUBSCRIPTION_CONTINUATIONS.md).
762
+
718
763
  **Direct PayPal initialization recovery:** PayPal's own cross-window bridge owns
719
764
  its 10-second `postMessage init()` acknowledgement deadline; FloPay does not
720
765
  change that upstream timeout. If the exact acknowledgement timeout is reported,
@@ -744,7 +789,7 @@ The SDK exposes the relevant pieces in three ways:
744
789
 
745
790
  - **`FloPayProvider`** (manual composition): accepts an optional `paypalFlopay` prop used to drive Stripe's PayPal redirect leg. Load a second `FloPay` instance yourself only if you need to render PayPal manually. `usePayPalFloPay()` exposes it to descendants.
746
791
 
747
- - **`DirectPayPalButton`** (standalone): can be rendered outside `SplitCardForm` when you only need the PayPal button. Pass `clientId`, `currency`, `environment`, and `isSubscription`.
792
+ - **`DirectPayPalButton`** (standalone): can be rendered outside `SplitCardForm` when you only need the PayPal button. Pass `clientId`, `currency`, `environment`, `isSubscription`, and the backend-advertised `providerObjectType` when present. `isSubscription` remains the compatibility fallback when the field is absent.
748
793
 
749
794
  - **`PayPalButton`** (standalone): Must be rendered inside its own `FloPayProvider`:
750
795
 
@@ -816,7 +861,7 @@ function PaymentStatus() {
816
861
  | `SplitCardForm` | Advanced checkout surface combining hosted-vault cards with wallets, APMs, and PayPal. Supports `ref` for imperative next-action handling. |
817
862
  | `VaultCardFields` | Hosted vault PCI card fields. Used internally by `SplitCardForm` on the vault path; consumes a `CardCaptureAdapter` from `useFloPay().cardCapture()`. |
818
863
  | `PayPalButton` | Standalone PayPal button. Requires its own `FloPayProvider` with `paymentMethodCreation` set to something other than `'manual'`. |
819
- | `DirectPayPalButton` | Standalone direct PayPal order/subscription button using the session-scoped intent contract. |
864
+ | `DirectPayPalButton` | Standalone direct PayPal Order, provider-managed Subscription, or setup-token button using the session-scoped intent contract. |
820
865
  | `PaymentElement` | Provider element for an explicitly declared non-card `paymentMethodTypes` allowlist. |
821
866
  | `AddressElement` | Address input element |
822
867
 
@@ -839,6 +884,7 @@ function PaymentStatus() {
839
884
  | `options.amount` | `number?` | Amount in cents for deferred non-card Elements without a `clientSecret` |
840
885
  | `options.currency` | `string?` | ISO 4217 currency code for deferred non-card Elements without a `clientSecret` |
841
886
  | `options.paymentMethodCreation` | `'manual' \| 'auto'` | How payment methods are created |
887
+ | `onInstrument` | `(event: FloInstrumentEvent) => void` | Versioned, privacy-safe checkout funnel and phased error feed for merchant analytics. |
842
888
 
843
889
  ### FloPayCardSetupProps
844
890
 
@@ -980,3 +1026,4 @@ endpoint when a buyer enters the card path.
980
1026
  | `PayPalButtonProps` | Props for `PayPalButton` |
981
1027
  | `ElementComponentProps` | Shared props for all element components |
982
1028
  | `CheckoutState` | `{ session, loading, error, claimPending }` |
1029
+ | `FloInstrumentEvent` | Versioned merchant instrument union with seven lifecycle names, four `checkout_error` phases, and an optional gateway. |
@@ -1,6 +1,6 @@
1
- "use strict";var H=Object.defineProperty;var ae=Object.getOwnPropertyDescriptor;var se=Object.getOwnPropertyNames;var ie=Object.prototype.hasOwnProperty;var le=(e,t)=>{for(var o in t)H(e,o,{get:t[o],enumerable:!0})},ce=(e,t,o,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of se(t))!ie.call(e,r)&&r!==o&&H(e,r,{get:()=>t[r],enumerable:!(n=ae(t,r))||n.enumerable});return e};var ue=e=>ce(H({},"__esModule",{value:!0}),e);var fe={};le(fe,{FloPayCardSetup:()=>oe,FloPayCardSetupError:()=>D});module.exports=ue(fe);var a=require("react"),I=require("@flopay/js/card-setup"),c=require("@flopay/shared");var S=require("react"),Q=require("react/jsx-runtime");function K({capture:e,html:t,messageToken:o,expectedOrigin:n,theme:r,containerStyle:m,onReady:u,onError:y,onValidation:g}){let C=(0,S.useRef)(null),x=(0,S.useRef)(u),w=(0,S.useRef)(y),k=(0,S.useRef)(g);x.current=u,w.current=y,k.current=g;let d=(0,S.useRef)(r);return d.current=r,(0,S.useEffect)(()=>{let F=C.current;if(!F)return;let v=!0,A=!1,V=()=>{!v||A||(A=!0,x.current?.())},L=e.on("ready",()=>{V()}),O=e.on("error",p=>{w.current?.(p.message??"There was a problem loading the secure card form.")}),h=e.on("validation",p=>{k.current?.(p.message??null)}),B={html:t,...o?{messageToken:o}:{},...n?{expectedOrigin:n}:{},...d.current?{theme:d.current}:{}};return e.mount(F,B).then(()=>{V()}).catch(p=>{v&&w.current?.(p instanceof Error?p.message:"Failed to load the secure card form.")}),()=>{v=!1,L(),O(),h(),e.unmount()}},[e,t,o,n]),(0,S.useEffect)(()=>{r&&e.applyTheme?.(r)},[e,r]),(0,Q.jsx)("div",{ref:C,"data-testid":"flopay-vault-card-fields",style:m})}function R(e){if(!e)return;let t=o=>{console.error("[FloPay] Merchant callback failed; checkout continued.",o)};try{Promise.resolve(e()).catch(t)}catch(o){t(o)}}var ve=require("react"),i=require("react/jsx-runtime"),Z=1200,G=1500;function ee({status:e,errorMessage:t,authorized:o=!1,processingLabel:n="PROCESSING...",successLabel:r,errorLabel:m="PAYMENT FAILED",successNote:u="You will be automatically redirected, do not close or navigate away from this window."}){return(0,i.jsx)("div",{"data-testid":"flopay-processing-overlay","data-status":e,role:"dialog","aria-modal":"true",style:{position:"fixed",inset:0,background:"rgba(0,0,0,0.35)",display:"flex",alignItems:"center",justifyContent:"center",zIndex:1e3,backdropFilter:"blur(2px)"},children:(0,i.jsxs)("div",{style:{background:"white",borderRadius:12,padding:"2rem 2.5rem",textAlign:"center",boxShadow:"0 8px 32px rgba(0,0,0,0.18)",minWidth:240,display:"flex",flexDirection:"column",alignItems:"center",gap:16},children:[(0,i.jsxs)("div",{style:{width:48,height:48,position:"relative"},children:[e==="processing"&&(0,i.jsxs)("svg",{width:"48",height:"48",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",style:{animation:"flopay-spin 0.8s linear infinite"},children:[(0,i.jsx)("circle",{cx:"12",cy:"12",r:"10",stroke:"#e5e7eb",strokeWidth:"3"}),(0,i.jsx)("path",{d:"M4 12a8 8 0 018-8v3a5 5 0 00-5 5H4z",fill:"#4A49FF"})]}),e==="success"&&(0,i.jsx)("div",{style:{animation:"flopay-pop 0.4s cubic-bezier(0.34,1.56,0.64,1)"},children:(0,i.jsxs)("svg",{width:"48",height:"48",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:[(0,i.jsx)("circle",{cx:"12",cy:"12",r:"11",fill:"#22c55e"}),(0,i.jsx)("path",{d:"M7 12.5l3 3 7-7",stroke:"white",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round",style:{strokeDasharray:20,strokeDashoffset:20,animation:"flopay-draw 0.4s 0.15s ease forwards"}})]})}),e==="error"&&(0,i.jsx)("div",{style:{animation:"flopay-shake 0.4s ease"},children:(0,i.jsxs)("svg",{width:"48",height:"48",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:[(0,i.jsx)("circle",{cx:"12",cy:"12",r:"11",fill:"#ef4444"}),(0,i.jsx)("path",{d:"M8 8l8 8M16 8l-8 8",stroke:"white",strokeWidth:"2.5",strokeLinecap:"round",style:{strokeDasharray:12,strokeDashoffset:12,animation:"flopay-draw 0.3s 0.1s ease forwards"}})]})})]}),(0,i.jsxs)("span",{style:{fontSize:14,fontWeight:600,letterSpacing:"0.05em",color:e==="success"?"#16a34a":e==="error"?"#dc2626":"#374151"},children:[e==="processing"&&n,e==="success"&&(r??(o?"PAYMENT AUTHORISED":"PAYMENT SUCCESSFUL")),e==="error"&&m]}),e==="success"&&u!==null&&(0,i.jsx)("p",{style:{fontSize:13,color:"#6b7280",fontWeight:400,maxWidth:260,lineHeight:1.4,margin:0},children:u}),e==="error"&&t&&(0,i.jsx)("p",{style:{fontSize:13,color:"#6b7280",fontWeight:400,maxWidth:260,lineHeight:1.4,margin:0},children:t}),(0,i.jsx)("style",{children:`
1
+ "use strict";var H=Object.defineProperty;var ne=Object.getOwnPropertyDescriptor;var ae=Object.getOwnPropertyNames;var se=Object.prototype.hasOwnProperty;var ie=(e,t)=>{for(var o in t)H(e,o,{get:t[o],enumerable:!0})},le=(e,t,o,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of ae(t))!se.call(e,r)&&r!==o&&H(e,r,{get:()=>t[r],enumerable:!(n=ne(t,r))||n.enumerable});return e};var ce=e=>le(H({},"__esModule",{value:!0}),e);var fe={};ie(fe,{FloPayCardSetup:()=>te,FloPayCardSetupError:()=>D});module.exports=ce(fe);var I=require("@flopay/js/card-setup"),c=require("@flopay/shared"),a=require("react");function R(e){if(!e)return;let t=o=>{console.error("[FloPay] Merchant callback failed; checkout continued.",o)};try{Promise.resolve(e()).catch(t)}catch(o){t(o)}}var i=require("react/jsx-runtime");function J({status:e,errorMessage:t,authorized:o=!1,processingLabel:n="PROCESSING...",successLabel:r,errorLabel:m="PAYMENT FAILED",successNote:u="You will be automatically redirected, do not close or navigate away from this window."}){return(0,i.jsx)("div",{"data-testid":"flopay-processing-overlay","data-status":e,role:"dialog","aria-modal":"true",style:{position:"fixed",inset:0,background:"rgba(0,0,0,0.35)",display:"flex",alignItems:"center",justifyContent:"center",zIndex:1e3,backdropFilter:"blur(2px)"},children:(0,i.jsxs)("div",{style:{background:"white",borderRadius:12,padding:"2rem 2.5rem",textAlign:"center",boxShadow:"0 8px 32px rgba(0,0,0,0.18)",minWidth:240,display:"flex",flexDirection:"column",alignItems:"center",gap:16},children:[(0,i.jsxs)("div",{style:{width:48,height:48,position:"relative"},children:[e==="processing"&&(0,i.jsxs)("svg",{width:"48",height:"48",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",style:{animation:"flopay-spin 0.8s linear infinite"},children:[(0,i.jsx)("circle",{cx:"12",cy:"12",r:"10",stroke:"#e5e7eb",strokeWidth:"3"}),(0,i.jsx)("path",{d:"M4 12a8 8 0 018-8v3a5 5 0 00-5 5H4z",fill:"#4A49FF"})]}),e==="success"&&(0,i.jsx)("div",{style:{animation:"flopay-pop 0.4s cubic-bezier(0.34,1.56,0.64,1)"},children:(0,i.jsxs)("svg",{width:"48",height:"48",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:[(0,i.jsx)("circle",{cx:"12",cy:"12",r:"11",fill:"#22c55e"}),(0,i.jsx)("path",{d:"M7 12.5l3 3 7-7",stroke:"white",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round",style:{strokeDasharray:20,strokeDashoffset:20,animation:"flopay-draw 0.4s 0.15s ease forwards"}})]})}),e==="error"&&(0,i.jsx)("div",{style:{animation:"flopay-shake 0.4s ease"},children:(0,i.jsxs)("svg",{width:"48",height:"48",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:[(0,i.jsx)("circle",{cx:"12",cy:"12",r:"11",fill:"#ef4444"}),(0,i.jsx)("path",{d:"M8 8l8 8M16 8l-8 8",stroke:"white",strokeWidth:"2.5",strokeLinecap:"round",style:{strokeDasharray:12,strokeDashoffset:12,animation:"flopay-draw 0.3s 0.1s ease forwards"}})]})})]}),(0,i.jsxs)("span",{style:{fontSize:14,fontWeight:600,letterSpacing:"0.05em",color:e==="success"?"#16a34a":e==="error"?"#dc2626":"#374151"},children:[e==="processing"&&n,e==="success"&&(r??(o?"PAYMENT AUTHORISED":"PAYMENT SUCCESSFUL")),e==="error"&&m]}),e==="success"&&u!==null&&(0,i.jsx)("p",{style:{fontSize:13,color:"#6b7280",fontWeight:400,maxWidth:260,lineHeight:1.4,margin:0},children:u}),e==="error"&&t&&(0,i.jsx)("p",{style:{fontSize:13,color:"#6b7280",fontWeight:400,maxWidth:260,lineHeight:1.4,margin:0},children:t}),(0,i.jsx)("style",{children:`
2
2
  @keyframes flopay-spin { to { transform: rotate(360deg); } }
3
3
  @keyframes flopay-pop { 0% { transform: scale(0); } 100% { transform: scale(1); } }
4
4
  @keyframes flopay-draw { to { stroke-dashoffset: 0; } }
5
5
  @keyframes flopay-shake { 0%,100% { transform: translateX(0); } 20%,60% { transform: translateX(-4px); } 40%,80% { transform: translateX(4px); } }
6
- `})]})})}function de(e,t=.12){let o=/^#?([0-9a-fA-F]{6})$/.exec(e.trim());if(!o)return e;let n=parseInt(o[1],16),r=Math.max(0,Math.min(1,1-t)),m=Math.round((n>>16&255)*r),u=Math.round((n>>8&255)*r),y=Math.round((n&255)*r);return`#${(1<<24|m<<16|u<<8|y).toString(16).slice(1)}`}function pe(e){if(typeof e=="number"||typeof e=="string")return e}function re({appearance:e,buttonsStyles:t,layout:o="default"}){let n=e?.variables,r=C=>typeof C=="string"&&C?C:void 0,m=t.submitButton,u=r(n?.colorPrimary)??r(m?.backgroundColor)??"#4A49FF",y=o==="buttons",g=t.nameInput;return{primaryColor:u,primaryHoverColor:r(n?.colorPrimaryHover)??de(u,.12),inputBackgroundColor:r(t.cardInputBackground)??r(n?.colorBackground)??"#ffffff",textColor:r(t.cardInputColor)??r(g?.color)??r(n?.colorText)??"#262833",borderColor:r(t.cardInputBorder)??(y?"#e5e7eb":"#A4A4FF"),placeholderColor:r(t.cardInputPlaceholderColor)??"#9ca3af",errorColor:r(n?.colorDanger)??"#dc2626",successColor:"#16a34a",fontFamily:r(g?.fontFamily)??r(n?.fontFamily)??"Poppins, sans-serif",fontSize:r(t.cardInputFontSize)??r(n?.fontSizeBase)??"16px",fontWeight:String(pe(g?.fontWeight)??400),borderRadius:r(n?.borderRadius)??"8px"}}var f=require("react/jsx-runtime"),D=class extends c.FloPayError{constructor(t,o,n){super(t,o,n),this.name="FloPayCardSetupError",this.retryable=n.retryable}};function te(e,t={}){let o=e instanceof c.FloPayError?e:void 0,n=o?.statusCode,r=t.retryable??(o?.type==="network_error"||o?.type==="rate_limit_error"||typeof n=="number"&&n>=500||o?.code==="vault_capture_timeout"||!o);return new D(o?.message??(e instanceof Error?e.message:"Failed to load the secure card form."),o?.type??"api_error",{code:t.code??o?.code??"card_setup_load_failed",retryable:r,...o?.param?{param:o.param}:{},...n!==void 0?{statusCode:n}:{}})}function oe({sessionId:e,nonce:t,billingApiUrl:o,telemetry:n,theme:r="modern-light",containerStyle:m,loading:u=null,onReady:y,onComplete:g,onDecline:C,onCancel:x,onValidation:w,onError:k}){let[d,F]=(0,a.useState)(null),[v,A]=(0,a.useState)(null),[V,L]=(0,a.useState)(null),[O,h]=(0,a.useState)(null),[B,p]=(0,a.useState)(null),P=(0,a.useRef)(!1),N=(0,a.useRef)(!1),ne=(0,a.useMemo)(()=>{if(typeof r=="object")return r;let s=(0,c.resolveTheme)(r);if(s)return re({appearance:s.appearance,buttonsStyles:s.buttonsLayout,layout:"default"})},[r]),q=(0,a.useRef)(y),W=(0,a.useRef)(k),X=(0,a.useRef)(g),$=(0,a.useRef)(C),j=(0,a.useRef)(x),J=(0,a.useRef)(w),_=(0,a.useRef)(!1),Y=(0,a.useRef)(!1),z=(0,a.useRef)(0);return q.current=y,W.current=k,X.current=g,$.current=C,j.current=x,J.current=w,(0,a.useEffect)(()=>{let s=z.current+1;return z.current=s,()=>{queueMicrotask(()=>{z.current===s&&(_.current||Y.current||(Y.current=!0,R(()=>{j.current?.({status:"cancelled",sessionId:e})})))})}},[e]),(0,a.useEffect)(()=>{let s=!0,b=new I.PaymentAPI((0,c.resolveBillingApiUrl)(o),{telemetry:n});return F(null),A(null),L(null),_.current=!1,Y.current=!1,(async()=>{try{if(!e.trim())throw new c.FloPayError("FloPayCardSetup requires a sessionId.","validation_error",{code:"MissingCheckoutSessionId",param:"sessionId"});if(!t.trim())throw new c.FloPayError("FloPayCardSetup requires a session nonce.","validation_error",{code:"MissingCheckoutSessionToken",param:"nonce"});let M=await b.getUnifiedCheckoutSession(e,t);if(!s)return;let E=M.data.session;if(!E||!(0,c.isCardSetupCheckoutSession)(E))throw new c.FloPayError("FloPayCardSetup only accepts a zero-amount, productless setup session.","validation_error",{code:"InvalidCardSetupSession",param:"sessionId"});let l=E.vault;if(l?.html?.trim()||(l=await b.getVaultCapture(e,t)),!l.html?.trim())throw new c.FloPayError("FloPay: this setup session has no vault card capability. Upgrade the billing backend integration.","api_error",{code:"UnsupportedBackendVaultCapability"});if(!s)return;A(l),F(new I.PciVaultCardCapture({sessionId:e,operation:"card_setup",telemetry:n}))}catch(M){if(!s)return;let E=te(M);_.current=!0,L(E),R(()=>W.current?.(E))}})(),()=>{s=!1,b.destroy()}},[o,t,e,n]),(0,a.useEffect)(()=>{if(!d)return;let s=!1;N.current=!1,P.current=!1;let b=d.on("submitting",()=>{P.current=!0,p(null),h("processing")}),M=d.on("complete",l=>{_.current=!0,P.current=!1,h("success"),(async()=>(await new Promise(U=>setTimeout(U,Z)),!N.current&&(N.current=!0,R(()=>{X.current?.({status:"succeeded",sessionId:e,...l.paymentMethod?{paymentMethod:l.paymentMethod}:{}})}),s||h(null))))()}),E=d.on("decline",l=>{_.current=!0,P.current=!1,p(l.message??null),h("error"),R(()=>{$.current?.({status:"declined",sessionId:e,...l.declineReason?{reason:l.declineReason}:{},...l.message?{message:l.message}:{}})}),(async()=>(await new Promise(U=>setTimeout(U,G)),s||h(null)))()});return()=>{s=!0,b(),M(),E()}},[d,e]),V?(0,f.jsx)("div",{role:"alert",children:V.message}):!d||!v?.html?(0,f.jsx)(f.Fragment,{children:u}):(0,f.jsxs)(f.Fragment,{children:[O&&(0,f.jsx)(ee,{status:O,errorMessage:B,processingLabel:"SAVING CARD...",successLabel:"CARD SAVED",errorLabel:"CARD NOT SAVED",successNote:null}),(0,f.jsx)(K,{capture:d,html:v.html,messageToken:v.messageToken,expectedOrigin:v.expectedOrigin,theme:ne,containerStyle:m,onReady:()=>{R(()=>q.current?.())},onValidation:s=>{R(()=>J.current?.(s))},onError:s=>{if(!s)return;P.current&&(P.current=!1,p(s),h("error"),setTimeout(()=>h(null),G));let b=te(new Error(s),{code:"card_setup_widget_failed",retryable:!0});R(()=>W.current?.(b))}})]})}0&&(module.exports={FloPayCardSetup,FloPayCardSetupError});
6
+ `})]})})}var S=require("react"),Q=require("react/jsx-runtime");function K({capture:e,html:t,messageToken:o,expectedOrigin:n,theme:r,containerStyle:m,onReady:u,onError:y,onValidation:g}){let C=(0,S.useRef)(null),x=(0,S.useRef)(u),w=(0,S.useRef)(y),k=(0,S.useRef)(g);x.current=u,w.current=y,k.current=g;let d=(0,S.useRef)(r);return d.current=r,(0,S.useEffect)(()=>{let F=C.current;if(!F)return;let v=!0,A=!1,V=()=>{!v||A||(A=!0,x.current?.())},L=e.on("ready",()=>{V()}),O=e.on("error",p=>{w.current?.(p.message??"There was a problem loading the secure card form.")}),h=e.on("validation",p=>{k.current?.(p.message??null)}),B={html:t,...o?{messageToken:o}:{},...n?{expectedOrigin:n}:{},...d.current?{theme:d.current}:{}};return e.mount(F,B).then(()=>{V()}).catch(p=>{v&&w.current?.(p instanceof Error?p.message:"Failed to load the secure card form.")}),()=>{v=!1,L(),O(),h(),e.unmount()}},[e,t,o,n]),(0,S.useEffect)(()=>{r&&e.applyTheme?.(r)},[e,r]),(0,Q.jsx)("div",{ref:C,"data-testid":"flopay-vault-card-fields",style:m})}function ue(e,t=.12){let o=/^#?([0-9a-fA-F]{6})$/.exec(e.trim());if(!o)return e;let n=parseInt(o[1],16),r=Math.max(0,Math.min(1,1-t)),m=Math.round((n>>16&255)*r),u=Math.round((n>>8&255)*r),y=Math.round((n&255)*r);return`#${(1<<24|m<<16|u<<8|y).toString(16).slice(1)}`}function de(e){if(typeof e=="number"||typeof e=="string")return e}function Z({appearance:e,buttonsStyles:t,layout:o="default"}){let n=e?.variables,r=C=>typeof C=="string"&&C?C:void 0,m=t.submitButton,u=r(n?.colorPrimary)??r(m?.backgroundColor)??"#4A49FF",y=o==="buttons",g=t.nameInput;return{primaryColor:u,primaryHoverColor:r(n?.colorPrimaryHover)??ue(u,.12),inputBackgroundColor:r(t.cardInputBackground)??r(n?.colorBackground)??"#ffffff",textColor:r(t.cardInputColor)??r(g?.color)??r(n?.colorText)??"#262833",borderColor:r(t.cardInputBorder)??(y?"#e5e7eb":"#A4A4FF"),placeholderColor:r(t.cardInputPlaceholderColor)??"#9ca3af",errorColor:r(n?.colorDanger)??"#dc2626",successColor:"#16a34a",fontFamily:r(g?.fontFamily)??r(n?.fontFamily)??"Poppins, sans-serif",fontSize:r(t.cardInputFontSize)??r(n?.fontSizeBase)??"16px",fontWeight:String(de(g?.fontWeight)??400),borderRadius:r(n?.borderRadius)??"8px"}}var f=require("react/jsx-runtime"),D=class extends c.FloPayError{constructor(t,o,n){super(t,o,n),this.name="FloPayCardSetupError",this.retryable=n.retryable}};function re(e,t={}){let o=e instanceof c.FloPayError?e:void 0,n=o?.statusCode,r=t.retryable??(o?.type==="network_error"||o?.type==="rate_limit_error"||typeof n=="number"&&n>=500||o?.code==="vault_capture_timeout"||!o);return new D(o?.message??(e instanceof Error?e.message:"Failed to load the secure card form."),o?.type??"api_error",{code:t.code??o?.code??"card_setup_load_failed",retryable:r,...o?.param?{param:o.param}:{},...n!==void 0?{statusCode:n}:{}})}function te({sessionId:e,nonce:t,billingApiUrl:o,telemetry:n,theme:r="modern-light",containerStyle:m,loading:u=null,onReady:y,onComplete:g,onDecline:C,onCancel:x,onValidation:w,onError:k}){let[d,F]=(0,a.useState)(null),[v,A]=(0,a.useState)(null),[V,L]=(0,a.useState)(null),[O,h]=(0,a.useState)(null),[B,p]=(0,a.useState)(null),P=(0,a.useRef)(!1),N=(0,a.useRef)(!1),oe=(0,a.useMemo)(()=>{if(typeof r=="object")return r;let s=(0,c.resolveTheme)(r);if(s)return Z({appearance:s.appearance,buttonsStyles:s.buttonsLayout,layout:"default"})},[r]),G=(0,a.useRef)(y),W=(0,a.useRef)(k),q=(0,a.useRef)(g),X=(0,a.useRef)(C),$=(0,a.useRef)(x),j=(0,a.useRef)(w),_=(0,a.useRef)(!1),Y=(0,a.useRef)(!1),z=(0,a.useRef)(0);return G.current=y,W.current=k,q.current=g,X.current=C,$.current=x,j.current=w,(0,a.useEffect)(()=>{let s=z.current+1;return z.current=s,()=>{queueMicrotask(()=>{z.current===s&&(_.current||Y.current||(Y.current=!0,R(()=>{$.current?.({status:"cancelled",sessionId:e})})))})}},[e]),(0,a.useEffect)(()=>{let s=!0,b=new I.PaymentAPI((0,c.resolveBillingApiUrl)(o),{telemetry:n});return F(null),A(null),L(null),_.current=!1,Y.current=!1,(async()=>{try{if(!e.trim())throw new c.FloPayError("FloPayCardSetup requires a sessionId.","validation_error",{code:"MissingCheckoutSessionId",param:"sessionId"});if(!t.trim())throw new c.FloPayError("FloPayCardSetup requires a session nonce.","validation_error",{code:"MissingCheckoutSessionToken",param:"nonce"});let M=await b.getUnifiedCheckoutSession(e,t);if(!s)return;let E=M.data.session;if(!E||!(0,c.isCardSetupCheckoutSession)(E))throw new c.FloPayError("FloPayCardSetup only accepts a zero-amount, productless setup session.","validation_error",{code:"InvalidCardSetupSession",param:"sessionId"});let l=E.vault;if(l?.html?.trim()||(l=await b.getVaultCapture(e,t)),!l.html?.trim())throw new c.FloPayError("FloPay: this setup session has no vault card capability. Upgrade the billing backend integration.","api_error",{code:"UnsupportedBackendVaultCapability"});if(!s)return;A(l),F(new I.PciVaultCardCapture({sessionId:e,operation:"card_setup",telemetry:n}))}catch(M){if(!s)return;let E=re(M);_.current=!0,L(E),R(()=>W.current?.(E))}})(),()=>{s=!1,b.destroy()}},[o,t,e,n]),(0,a.useEffect)(()=>{if(!d)return;let s=!1;N.current=!1,P.current=!1;let b=d.on("submitting",()=>{P.current=!0,p(null),h("processing")}),M=d.on("complete",l=>{_.current=!0,P.current=!1,h("success"),(async()=>(await new Promise(U=>setTimeout(U,1200)),!N.current&&(N.current=!0,R(()=>{q.current?.({status:"succeeded",sessionId:e,...l.paymentMethod?{paymentMethod:l.paymentMethod}:{}})}),s||h(null))))()}),E=d.on("decline",l=>{_.current=!0,P.current=!1,p(l.message??null),h("error"),R(()=>{X.current?.({status:"declined",sessionId:e,...l.declineReason?{reason:l.declineReason}:{},...l.message?{message:l.message}:{}})}),(async()=>(await new Promise(U=>setTimeout(U,1500)),s||h(null)))()});return()=>{s=!0,b(),M(),E()}},[d,e]),V?(0,f.jsx)("div",{role:"alert",children:V.message}):!d||!v?.html?(0,f.jsx)(f.Fragment,{children:u}):(0,f.jsxs)(f.Fragment,{children:[O&&(0,f.jsx)(J,{status:O,errorMessage:B,processingLabel:"SAVING CARD...",successLabel:"CARD SAVED",errorLabel:"CARD NOT SAVED",successNote:null}),(0,f.jsx)(K,{capture:d,html:v.html,messageToken:v.messageToken,expectedOrigin:v.expectedOrigin,theme:oe,containerStyle:m,onReady:()=>{R(()=>G.current?.())},onValidation:s=>{R(()=>j.current?.(s))},onError:s=>{if(!s)return;P.current&&(P.current=!1,p(s),h("error"),setTimeout(()=>h(null),1500));let b=re(new Error(s),{code:"card_setup_widget_failed",retryable:!0});R(()=>W.current?.(b))}})]})}0&&(module.exports={FloPayCardSetup,FloPayCardSetupError});
@@ -1,5 +1,5 @@
1
- import React from 'react';
2
1
  import { ThemeId, VaultCardThemeColors, SavedCardDisplay, FloPayError, FloPayErrorType } from '@flopay/shared';
2
+ import React from 'react';
3
3
 
4
4
  /** Verified terminal result from a no-charge card setup. */
5
5
  interface FloPayCardSetupCompleteEvent {
@@ -1,5 +1,5 @@
1
- import React from 'react';
2
1
  import { ThemeId, VaultCardThemeColors, SavedCardDisplay, FloPayError, FloPayErrorType } from '@flopay/shared';
2
+ import React from 'react';
3
3
 
4
4
  /** Verified terminal result from a no-charge card setup. */
5
5
  interface FloPayCardSetupCompleteEvent {
@@ -1 +1 @@
1
- import{i as e,j as t}from"./chunk-P5QXEIBB.mjs";export{t as FloPayCardSetup,e as FloPayCardSetupError};
1
+ import{g as e,h as t}from"./chunk-VWT2TDA6.mjs";export{t as FloPayCardSetup,e as FloPayCardSetupError};
@@ -0,0 +1,6 @@
1
+ import{useEffect as Q,useRef as M}from"react";import{jsx as se}from"react/jsx-runtime";function Z({capture:e,html:n,messageToken:t,expectedOrigin:o,theme:r,containerStyle:f,onReady:i,onError:p,onValidation:m}){let y=M(null),k=M(i),R=M(p),x=M(m);k.current=i,R.current=p,x.current=m;let c=M(r);return c.current=r,Q(()=>{let P=y.current;if(!P)return;let h=!0,F=!1,A=()=>{!h||F||(F=!0,k.current?.())},L=e.on("ready",()=>{A()}),D=e.on("error",d=>{R.current?.(d.message??"There was a problem loading the secure card form.")}),g=e.on("validation",d=>{x.current?.(d.message??null)}),N={html:n,...t?{messageToken:t}:{},...o?{expectedOrigin:o}:{},...c.current?{theme:c.current}:{}};return e.mount(P,N).then(()=>{A()}).catch(d=>{h&&R.current?.(d instanceof Error?d.message:"Failed to load the secure card form.")}),()=>{h=!1,L(),D(),g(),e.unmount()}},[e,n,t,o]),Q(()=>{r&&e.applyTheme?.(r)},[e,r]),se("div",{ref:y,"data-testid":"flopay-vault-card-fields",style:f})}import{PaymentAPI as ue,PciVaultCardCapture as de}from"@flopay/js/card-setup";import{FloPayError as E,isCardSetupCheckoutSession as fe,resolveBillingApiUrl as pe,resolveTheme as me}from"@flopay/shared";import{useEffect as G,useMemo as ye,useRef as u,useState as T}from"react";function S(e){if(!e)return;let n=t=>{console.error("[FloPay] Merchant callback failed; checkout continued.",t)};try{Promise.resolve(e()).catch(n)}catch(t){n(t)}}import{jsx as l,jsxs as w}from"react/jsx-runtime";function ee({status:e,errorMessage:n,authorized:t=!1,processingLabel:o="PROCESSING...",successLabel:r,errorLabel:f="PAYMENT FAILED",successNote:i="You will be automatically redirected, do not close or navigate away from this window."}){return l("div",{"data-testid":"flopay-processing-overlay","data-status":e,role:"dialog","aria-modal":"true",style:{position:"fixed",inset:0,background:"rgba(0,0,0,0.35)",display:"flex",alignItems:"center",justifyContent:"center",zIndex:1e3,backdropFilter:"blur(2px)"},children:w("div",{style:{background:"white",borderRadius:12,padding:"2rem 2.5rem",textAlign:"center",boxShadow:"0 8px 32px rgba(0,0,0,0.18)",minWidth:240,display:"flex",flexDirection:"column",alignItems:"center",gap:16},children:[w("div",{style:{width:48,height:48,position:"relative"},children:[e==="processing"&&w("svg",{width:"48",height:"48",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",style:{animation:"flopay-spin 0.8s linear infinite"},children:[l("circle",{cx:"12",cy:"12",r:"10",stroke:"#e5e7eb",strokeWidth:"3"}),l("path",{d:"M4 12a8 8 0 018-8v3a5 5 0 00-5 5H4z",fill:"#4A49FF"})]}),e==="success"&&l("div",{style:{animation:"flopay-pop 0.4s cubic-bezier(0.34,1.56,0.64,1)"},children:w("svg",{width:"48",height:"48",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:[l("circle",{cx:"12",cy:"12",r:"11",fill:"#22c55e"}),l("path",{d:"M7 12.5l3 3 7-7",stroke:"white",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round",style:{strokeDasharray:20,strokeDashoffset:20,animation:"flopay-draw 0.4s 0.15s ease forwards"}})]})}),e==="error"&&l("div",{style:{animation:"flopay-shake 0.4s ease"},children:w("svg",{width:"48",height:"48",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",children:[l("circle",{cx:"12",cy:"12",r:"11",fill:"#ef4444"}),l("path",{d:"M8 8l8 8M16 8l-8 8",stroke:"white",strokeWidth:"2.5",strokeLinecap:"round",style:{strokeDasharray:12,strokeDashoffset:12,animation:"flopay-draw 0.3s 0.1s ease forwards"}})]})})]}),w("span",{style:{fontSize:14,fontWeight:600,letterSpacing:"0.05em",color:e==="success"?"#16a34a":e==="error"?"#dc2626":"#374151"},children:[e==="processing"&&o,e==="success"&&(r??(t?"PAYMENT AUTHORISED":"PAYMENT SUCCESSFUL")),e==="error"&&f]}),e==="success"&&i!==null&&l("p",{style:{fontSize:13,color:"#6b7280",fontWeight:400,maxWidth:260,lineHeight:1.4,margin:0},children:i}),e==="error"&&n&&l("p",{style:{fontSize:13,color:"#6b7280",fontWeight:400,maxWidth:260,lineHeight:1.4,margin:0},children:n}),l("style",{children:`
2
+ @keyframes flopay-spin { to { transform: rotate(360deg); } }
3
+ @keyframes flopay-pop { 0% { transform: scale(0); } 100% { transform: scale(1); } }
4
+ @keyframes flopay-draw { to { stroke-dashoffset: 0; } }
5
+ @keyframes flopay-shake { 0%,100% { transform: translateX(0); } 20%,60% { transform: translateX(-4px); } 40%,80% { transform: translateX(4px); } }
6
+ `})]})})}function ie(e,n=.12){let t=/^#?([0-9a-fA-F]{6})$/.exec(e.trim());if(!t)return e;let o=parseInt(t[1],16),r=Math.max(0,Math.min(1,1-n)),f=Math.round((o>>16&255)*r),i=Math.round((o>>8&255)*r),p=Math.round((o&255)*r);return`#${(1<<24|f<<16|i<<8|p).toString(16).slice(1)}`}function le(e){if(typeof e=="number"||typeof e=="string")return e}function re({appearance:e,buttonsStyles:n,layout:t="default"}){let o=e?.variables,r=y=>typeof y=="string"&&y?y:void 0,f=n.submitButton,i=r(o?.colorPrimary)??r(f?.backgroundColor)??"#4A49FF",p=t==="buttons",m=n.nameInput;return{primaryColor:i,primaryHoverColor:r(o?.colorPrimaryHover)??ie(i,.12),inputBackgroundColor:r(n.cardInputBackground)??r(o?.colorBackground)??"#ffffff",textColor:r(n.cardInputColor)??r(m?.color)??r(o?.colorText)??"#262833",borderColor:r(n.cardInputBorder)??(p?"#e5e7eb":"#A4A4FF"),placeholderColor:r(n.cardInputPlaceholderColor)??"#9ca3af",errorColor:r(o?.colorDanger)??"#dc2626",successColor:"#16a34a",fontFamily:r(m?.fontFamily)??r(o?.fontFamily)??"Poppins, sans-serif",fontSize:r(n.cardInputFontSize)??r(o?.fontSizeBase)??"16px",fontWeight:String(le(m?.fontWeight)??400),borderRadius:r(o?.borderRadius)??"8px"}}import{Fragment as ne,jsx as B,jsxs as ge}from"react/jsx-runtime";var q=class extends E{constructor(n,t,o){super(n,t,o),this.name="FloPayCardSetupError",this.retryable=o.retryable}};function oe(e,n={}){let t=e instanceof E?e:void 0,o=t?.statusCode,r=n.retryable??(t?.type==="network_error"||t?.type==="rate_limit_error"||typeof o=="number"&&o>=500||t?.code==="vault_capture_timeout"||!t);return new q(t?.message??(e instanceof Error?e.message:"Failed to load the secure card form."),t?.type??"api_error",{code:n.code??t?.code??"card_setup_load_failed",retryable:r,...t?.param?{param:t.param}:{},...o!==void 0?{statusCode:o}:{}})}function De({sessionId:e,nonce:n,billingApiUrl:t,telemetry:o,theme:r="modern-light",containerStyle:f,loading:i=null,onReady:p,onComplete:m,onDecline:y,onCancel:k,onValidation:R,onError:x}){let[c,P]=T(null),[h,F]=T(null),[A,L]=T(null),[D,g]=T(null),[N,d]=T(null),b=u(!1),W=u(!1),ae=ye(()=>{if(typeof r=="object")return r;let a=me(r);if(a)return re({appearance:a.appearance,buttonsStyles:a.buttonsLayout,layout:"default"})},[r]),X=u(p),Y=u(x),$=u(m),j=u(y),J=u(k),K=u(R),V=u(!1),z=u(!1),U=u(0);return X.current=p,Y.current=x,$.current=m,j.current=y,J.current=k,K.current=R,G(()=>{let a=U.current+1;return U.current=a,()=>{queueMicrotask(()=>{U.current===a&&(V.current||z.current||(z.current=!0,S(()=>{J.current?.({status:"cancelled",sessionId:e})})))})}},[e]),G(()=>{let a=!0,v=new ue(pe(t),{telemetry:o});return P(null),F(null),L(null),V.current=!1,z.current=!1,(async()=>{try{if(!e.trim())throw new E("FloPayCardSetup requires a sessionId.","validation_error",{code:"MissingCheckoutSessionId",param:"sessionId"});if(!n.trim())throw new E("FloPayCardSetup requires a session nonce.","validation_error",{code:"MissingCheckoutSessionToken",param:"nonce"});let _=await v.getUnifiedCheckoutSession(e,n);if(!a)return;let C=_.data.session;if(!C||!fe(C))throw new E("FloPayCardSetup only accepts a zero-amount, productless setup session.","validation_error",{code:"InvalidCardSetupSession",param:"sessionId"});let s=C.vault;if(s?.html?.trim()||(s=await v.getVaultCapture(e,n)),!s.html?.trim())throw new E("FloPay: this setup session has no vault card capability. Upgrade the billing backend integration.","api_error",{code:"UnsupportedBackendVaultCapability"});if(!a)return;F(s),P(new de({sessionId:e,operation:"card_setup",telemetry:o}))}catch(_){if(!a)return;let C=oe(_);V.current=!0,L(C),S(()=>Y.current?.(C))}})(),()=>{a=!1,v.destroy()}},[t,n,e,o]),G(()=>{if(!c)return;let a=!1;W.current=!1,b.current=!1;let v=c.on("submitting",()=>{b.current=!0,d(null),g("processing")}),_=c.on("complete",s=>{V.current=!0,b.current=!1,g("success"),(async()=>(await new Promise(H=>setTimeout(H,1200)),!W.current&&(W.current=!0,S(()=>{$.current?.({status:"succeeded",sessionId:e,...s.paymentMethod?{paymentMethod:s.paymentMethod}:{}})}),a||g(null))))()}),C=c.on("decline",s=>{V.current=!0,b.current=!1,d(s.message??null),g("error"),S(()=>{j.current?.({status:"declined",sessionId:e,...s.declineReason?{reason:s.declineReason}:{},...s.message?{message:s.message}:{}})}),(async()=>(await new Promise(H=>setTimeout(H,1500)),a||g(null)))()});return()=>{a=!0,v(),_(),C()}},[c,e]),A?B("div",{role:"alert",children:A.message}):!c||!h?.html?B(ne,{children:i}):ge(ne,{children:[D&&B(ee,{status:D,errorMessage:N,processingLabel:"SAVING CARD...",successLabel:"CARD SAVED",errorLabel:"CARD NOT SAVED",successNote:null}),B(Z,{capture:c,html:h.html,messageToken:h.messageToken,expectedOrigin:h.expectedOrigin,theme:ae,containerStyle:f,onReady:()=>{S(()=>X.current?.())},onValidation:a=>{S(()=>K.current?.(a))},onError:a=>{if(!a)return;b.current&&(b.current=!1,d(a),g("error"),setTimeout(()=>g(null),1500));let v=oe(new Error(a),{code:"card_setup_widget_failed",retryable:!0});S(()=>Y.current?.(v))}})]})}export{S as a,ee as b,Z as c,ie as d,le as e,re as f,q as g,De as h};