@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 +49 -2
- package/dist/card-setup-entry.cjs +2 -2
- package/dist/card-setup-entry.d.cts +1 -1
- package/dist/card-setup-entry.d.ts +1 -1
- package/dist/card-setup-entry.mjs +1 -1
- package/dist/chunk-VWT2TDA6.mjs +6 -0
- package/dist/index.cjs +10 -10
- package/dist/index.d.cts +650 -636
- package/dist/index.d.ts +650 -636
- package/dist/index.mjs +8 -8
- package/package.json +3 -3
- package/dist/chunk-P5QXEIBB.mjs +0 -6
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
|
|
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
|
|
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
|
|
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{
|
|
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};
|