@flopay/react 1.4.22 → 1.4.24

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
@@ -123,12 +123,29 @@ count as success while pending. Completion fires only after verification says
123
123
  the card is usable; decline, validation, retryable technical failure, and
124
124
  unmount cancellation remain separate outcomes.
125
125
 
126
+ Submission feedback matches checkout: the same processing overlay covers the
127
+ form from the buyer's submit ("SAVING CARD...") and flips to "CARD SAVED"
128
+ (held briefly before `onComplete`) or "CARD NOT SAVED" with the decline
129
+ message (cleared automatically so the buyer can retry in place).
130
+
126
131
  Saved-card listing, setup-session creation, and deletion remain authenticated
127
132
  merchant REST calls. Replacement is composition: complete setup for the new
128
133
  card, then ask your server to delete the old card. `FloPayCardSetup` deliberately
129
134
  has no customer-id, merchant-credential, list, delete, purchase, or Stripe
130
135
  Elements prop.
131
136
 
137
+ Prefer importing it from the dedicated subpath on pages that never run a
138
+ checkout:
139
+
140
+ ```tsx
141
+ import { FloPayCardSetup } from '@flopay/react/card-setup';
142
+ ```
143
+
144
+ The main entry re-exports the component for back-compat, but its module graph
145
+ eagerly loads Stripe.js (a deliberate checkout preload); the `card-setup`
146
+ entry's graph never reaches Stripe, so saved-card pages stay free of Stripe
147
+ network activity by construction.
148
+
132
149
  #### Expired Sessions
133
150
 
134
151
  When `FloPayCheckout` or `FloPayAutomaticPaymentButton` loads a checkout session whose `status` is `expired`, the component calls `onError` with a `FloPayError` so your app can route the customer into a recovery flow.
@@ -0,0 +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:`
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 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});
@@ -0,0 +1,75 @@
1
+ import React from 'react';
2
+ import { ThemeId, VaultCardThemeColors, SavedCardDisplay, FloPayError, FloPayErrorType } from '@flopay/shared';
3
+
4
+ /** Verified terminal result from a no-charge card setup. */
5
+ interface FloPayCardSetupCompleteEvent {
6
+ status: 'succeeded';
7
+ sessionId: string;
8
+ /** Present when the backend terminal message includes the saved-card display DTO. */
9
+ paymentMethod?: SavedCardDisplay;
10
+ }
11
+ /** Provider-neutral terminal decline from card verification. */
12
+ interface FloPayCardSetupDeclineEvent {
13
+ status: 'declined';
14
+ sessionId: string;
15
+ reason?: string;
16
+ message?: string;
17
+ }
18
+ /** Abandoned setup result emitted when the component unmounts pre-terminally. */
19
+ interface FloPayCardSetupCancelEvent {
20
+ status: 'cancelled';
21
+ sessionId: string;
22
+ }
23
+ /** Structured technical/validation failure from the card-setup surface. */
24
+ declare class FloPayCardSetupError extends FloPayError {
25
+ readonly retryable: boolean;
26
+ constructor(message: string, type: FloPayErrorType, options: {
27
+ code: string;
28
+ retryable: boolean;
29
+ param?: string;
30
+ statusCode?: number;
31
+ });
32
+ }
33
+ /** Props for the browser-only secure card-setup surface. */
34
+ interface FloPayCardSetupProps {
35
+ /** Opaque id returned by the merchant-authenticated setup-session endpoint. */
36
+ sessionId: string;
37
+ /** Session-bound token returned alongside {@link FloPayCardSetupProps.sessionId}. */
38
+ nonce: string;
39
+ /** Billing API base URL. Defaults to the configured FloPay environment URL. */
40
+ billingApiUrl?: string;
41
+ /** Flo-owned privacy-safe telemetry is enabled by default; set false to opt out. */
42
+ telemetry?: boolean;
43
+ /**
44
+ * Theme for FloPay's hosted, PCI-compliant card widget. Accepts the same
45
+ * high-level {@link ThemeId} as `FloPayCheckout` (resolved through the same
46
+ * bundle, so one value styles both surfaces identically) or raw
47
+ * {@link VaultCardThemeColors} for full manual control. Defaults to
48
+ * `'modern-light'`; pass `'classic'` for the historic FloPay look.
49
+ */
50
+ theme?: ThemeId | VaultCardThemeColors;
51
+ /** Inline styles for the hosted-widget container. */
52
+ containerStyle?: React.CSSProperties;
53
+ /** Optional content shown while the session and hosted widget are loading. */
54
+ loading?: React.ReactNode;
55
+ /** Fired once the hosted widget has mounted and can accept card input. */
56
+ onReady?: () => void;
57
+ /** Fired only after the backend reports that the card is verified and usable. */
58
+ onComplete?: (event: FloPayCardSetupCompleteEvent) => void;
59
+ /** Fired when verification terminally declines; never paired with `onComplete`. */
60
+ onDecline?: (event: FloPayCardSetupDeclineEvent) => void;
61
+ /** Fired exactly once if the setup surface unmounts before a terminal outcome. */
62
+ onCancel?: (event: FloPayCardSetupCancelEvent) => void;
63
+ /** Live hosted-field validation message, or `null` when validation clears. */
64
+ onValidation?: (message: string | null) => void;
65
+ /** Fired for setup-session validation, transport, or hosted-widget failures. */
66
+ onError?: (error: FloPayCardSetupError) => void;
67
+ }
68
+ /**
69
+ * Mounts FloPay's hosted card capture for a setup session created by the
70
+ * merchant's server. It has no customer-id, list, delete, purchase, or Stripe
71
+ * Elements surface.
72
+ */
73
+ declare function FloPayCardSetup({ sessionId, nonce, billingApiUrl, telemetry, theme, containerStyle, loading, onReady, onComplete, onDecline, onCancel, onValidation, onError, }: FloPayCardSetupProps): React.ReactElement;
74
+
75
+ export { FloPayCardSetup, type FloPayCardSetupCancelEvent, type FloPayCardSetupCompleteEvent, type FloPayCardSetupDeclineEvent, FloPayCardSetupError, type FloPayCardSetupProps };
@@ -0,0 +1,75 @@
1
+ import React from 'react';
2
+ import { ThemeId, VaultCardThemeColors, SavedCardDisplay, FloPayError, FloPayErrorType } from '@flopay/shared';
3
+
4
+ /** Verified terminal result from a no-charge card setup. */
5
+ interface FloPayCardSetupCompleteEvent {
6
+ status: 'succeeded';
7
+ sessionId: string;
8
+ /** Present when the backend terminal message includes the saved-card display DTO. */
9
+ paymentMethod?: SavedCardDisplay;
10
+ }
11
+ /** Provider-neutral terminal decline from card verification. */
12
+ interface FloPayCardSetupDeclineEvent {
13
+ status: 'declined';
14
+ sessionId: string;
15
+ reason?: string;
16
+ message?: string;
17
+ }
18
+ /** Abandoned setup result emitted when the component unmounts pre-terminally. */
19
+ interface FloPayCardSetupCancelEvent {
20
+ status: 'cancelled';
21
+ sessionId: string;
22
+ }
23
+ /** Structured technical/validation failure from the card-setup surface. */
24
+ declare class FloPayCardSetupError extends FloPayError {
25
+ readonly retryable: boolean;
26
+ constructor(message: string, type: FloPayErrorType, options: {
27
+ code: string;
28
+ retryable: boolean;
29
+ param?: string;
30
+ statusCode?: number;
31
+ });
32
+ }
33
+ /** Props for the browser-only secure card-setup surface. */
34
+ interface FloPayCardSetupProps {
35
+ /** Opaque id returned by the merchant-authenticated setup-session endpoint. */
36
+ sessionId: string;
37
+ /** Session-bound token returned alongside {@link FloPayCardSetupProps.sessionId}. */
38
+ nonce: string;
39
+ /** Billing API base URL. Defaults to the configured FloPay environment URL. */
40
+ billingApiUrl?: string;
41
+ /** Flo-owned privacy-safe telemetry is enabled by default; set false to opt out. */
42
+ telemetry?: boolean;
43
+ /**
44
+ * Theme for FloPay's hosted, PCI-compliant card widget. Accepts the same
45
+ * high-level {@link ThemeId} as `FloPayCheckout` (resolved through the same
46
+ * bundle, so one value styles both surfaces identically) or raw
47
+ * {@link VaultCardThemeColors} for full manual control. Defaults to
48
+ * `'modern-light'`; pass `'classic'` for the historic FloPay look.
49
+ */
50
+ theme?: ThemeId | VaultCardThemeColors;
51
+ /** Inline styles for the hosted-widget container. */
52
+ containerStyle?: React.CSSProperties;
53
+ /** Optional content shown while the session and hosted widget are loading. */
54
+ loading?: React.ReactNode;
55
+ /** Fired once the hosted widget has mounted and can accept card input. */
56
+ onReady?: () => void;
57
+ /** Fired only after the backend reports that the card is verified and usable. */
58
+ onComplete?: (event: FloPayCardSetupCompleteEvent) => void;
59
+ /** Fired when verification terminally declines; never paired with `onComplete`. */
60
+ onDecline?: (event: FloPayCardSetupDeclineEvent) => void;
61
+ /** Fired exactly once if the setup surface unmounts before a terminal outcome. */
62
+ onCancel?: (event: FloPayCardSetupCancelEvent) => void;
63
+ /** Live hosted-field validation message, or `null` when validation clears. */
64
+ onValidation?: (message: string | null) => void;
65
+ /** Fired for setup-session validation, transport, or hosted-widget failures. */
66
+ onError?: (error: FloPayCardSetupError) => void;
67
+ }
68
+ /**
69
+ * Mounts FloPay's hosted card capture for a setup session created by the
70
+ * merchant's server. It has no customer-id, list, delete, purchase, or Stripe
71
+ * Elements surface.
72
+ */
73
+ declare function FloPayCardSetup({ sessionId, nonce, billingApiUrl, telemetry, theme, containerStyle, loading, onReady, onComplete, onDecline, onCancel, onValidation, onError, }: FloPayCardSetupProps): React.ReactElement;
74
+
75
+ export { FloPayCardSetup, type FloPayCardSetupCancelEvent, type FloPayCardSetupCompleteEvent, type FloPayCardSetupDeclineEvent, FloPayCardSetupError, type FloPayCardSetupProps };
@@ -0,0 +1 @@
1
+ import{i as e,j as t}from"./chunk-P5QXEIBB.mjs";export{t as FloPayCardSetup,e as FloPayCardSetupError};
@@ -0,0 +1,6 @@
1
+ import{useEffect as Z,useRef as M}from"react";import{jsx as ie}from"react/jsx-runtime";function ee({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,Z(()=>{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]),Z(()=>{r&&e.applyTheme?.(r)},[e,r]),ie("div",{ref:y,"data-testid":"flopay-vault-card-fields",style:f})}import{useEffect as q,useMemo as ue,useRef as u,useState as T}from"react";import{PaymentAPI as de,PciVaultCardCapture as fe}from"@flopay/js/card-setup";import{FloPayError as E,isCardSetupCheckoutSession as pe,resolveBillingApiUrl as me,resolveTheme as ye}from"@flopay/shared";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"react";import{jsx as l,jsxs as w}from"react/jsx-runtime";var re=1200,G=1500;function te({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 le(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 ce(e){if(typeof e=="number"||typeof e=="string")return e}function oe({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)??le(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(ce(m?.fontWeight)??400),borderRadius:r(o?.borderRadius)??"8px"}}import{Fragment as ae,jsx as B,jsxs as ge}from"react/jsx-runtime";var X=class extends E{constructor(n,t,o){super(n,t,o),this.name="FloPayCardSetupError",this.retryable=o.retryable}};function ne(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 X(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 Ne({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),se=ue(()=>{if(typeof r=="object")return r;let a=ye(r);if(a)return oe({appearance:a.appearance,buttonsStyles:a.buttonsLayout,layout:"default"})},[r]),$=u(p),Y=u(x),j=u(m),J=u(y),K=u(k),Q=u(R),V=u(!1),z=u(!1),U=u(0);return $.current=p,Y.current=x,j.current=m,J.current=y,K.current=k,Q.current=R,q(()=>{let a=U.current+1;return U.current=a,()=>{queueMicrotask(()=>{U.current===a&&(V.current||z.current||(z.current=!0,S(()=>{K.current?.({status:"cancelled",sessionId:e})})))})}},[e]),q(()=>{let a=!0,v=new de(me(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||!pe(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 fe({sessionId:e,operation:"card_setup",telemetry:o}))}catch(_){if(!a)return;let C=ne(_);V.current=!0,L(C),S(()=>Y.current?.(C))}})(),()=>{a=!1,v.destroy()}},[t,n,e,o]),q(()=>{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,re)),!W.current&&(W.current=!0,S(()=>{j.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,G)),a||g(null)))()});return()=>{a=!0,v(),_(),C()}},[c,e]),A?B("div",{role:"alert",children:A.message}):!c||!h?.html?B(ae,{children:i}):ge(ae,{children:[D&&B(te,{status:D,errorMessage:N,processingLabel:"SAVING CARD...",successLabel:"CARD SAVED",errorLabel:"CARD NOT SAVED",successNote:null}),B(ee,{capture:c,html:h.html,messageToken:h.messageToken,expectedOrigin:h.expectedOrigin,theme:se,containerStyle:f,onReady:()=>{S(()=>$.current?.())},onValidation:a=>{S(()=>Q.current?.(a))},onError:a=>{if(!a)return;b.current&&(b.current=!1,d(a),g("error"),setTimeout(()=>g(null),G));let v=ne(new Error(a),{code:"card_setup_widget_failed",retryable:!0});S(()=>Y.current?.(v))}})]})}export{ee as a,S as b,re as c,G as d,te as e,le as f,ce as g,oe as h,X as i,Ne as j};