@funnelsgrove/payments 0.7.9 → 0.7.11
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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FunnelSdkRuntimeConfigOverrides, FunnelStepType, RuntimeMode } from '@funnelsgrove/runtime';
|
|
2
2
|
import { type PrepareSolidgateCheckoutInput, type SolidgateCheckoutPreparation } from '../services/solidgate.service.js';
|
|
3
|
-
export type SolidgateCheckoutDialogState = 'loading' | 'ready' | 'submitting' | 'verifying' | 'success' | 'recoverable_error' | 'expired';
|
|
3
|
+
export type SolidgateCheckoutDialogState = 'collecting_email' | 'loading' | 'ready' | 'submitting' | 'verifying' | 'success' | 'recoverable_error' | 'expired';
|
|
4
4
|
export type SolidgateCheckoutDialogContent = {
|
|
5
5
|
title: string;
|
|
6
6
|
closeAriaLabel: string;
|
|
@@ -12,13 +12,20 @@ export type SolidgateCheckoutDialogContent = {
|
|
|
12
12
|
expired: string;
|
|
13
13
|
retry: string;
|
|
14
14
|
resumeVerification: string;
|
|
15
|
+
customerEmailLabel: string;
|
|
16
|
+
customerEmailPlaceholder: string;
|
|
17
|
+
customerEmailInvalid: string;
|
|
18
|
+
customerEmailUnavailable: string;
|
|
19
|
+
customerEmailContinue: string;
|
|
15
20
|
};
|
|
16
21
|
export type SolidgateCheckoutDialogProps = {
|
|
17
22
|
open: boolean;
|
|
18
23
|
request: Omit<PrepareSolidgateCheckoutInput, 'signal'>;
|
|
24
|
+
customerEmail?: string | null;
|
|
19
25
|
checkoutAnalytics?: SolidgateCheckoutAnalyticsContext | null;
|
|
20
26
|
content?: Partial<SolidgateCheckoutDialogContent>;
|
|
21
27
|
onClose: () => void;
|
|
28
|
+
onCustomerEmailCommit?: (email: string) => Promise<string>;
|
|
22
29
|
onSuccess?: (checkout: SolidgateCheckoutPreparation) => void | Promise<void>;
|
|
23
30
|
onRetry?: () => void;
|
|
24
31
|
onError?: (message: string) => void;
|
|
@@ -34,4 +41,4 @@ export type SolidgateCheckoutAnalyticsContext = {
|
|
|
34
41
|
stepType: Extract<FunnelStepType, 'checkout' | 'paywall_offer' | 'upsell_offer'>;
|
|
35
42
|
stepContractVersion: number;
|
|
36
43
|
};
|
|
37
|
-
export declare function SolidgateCheckoutDialog({ open, request, checkoutAnalytics, content, onClose, onSuccess, onRetry, onError, onStateChange, }: SolidgateCheckoutDialogProps): import("react/jsx-runtime").JSX.Element | null;
|
|
44
|
+
export declare function SolidgateCheckoutDialog({ open, request, customerEmail, checkoutAnalytics, content, onClose, onCustomerEmailCommit, onSuccess, onRetry, onError, onStateChange, }: SolidgateCheckoutDialogProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -17,6 +17,17 @@ const defaultContent = {
|
|
|
17
17
|
expired: 'This checkout has expired. Start a new payment to continue.',
|
|
18
18
|
retry: 'Try again',
|
|
19
19
|
resumeVerification: 'Check payment status',
|
|
20
|
+
customerEmailLabel: 'Email address',
|
|
21
|
+
customerEmailPlaceholder: 'you@example.com',
|
|
22
|
+
customerEmailInvalid: 'Enter a valid email address to continue.',
|
|
23
|
+
customerEmailUnavailable: 'Your email could not be saved. Please try again.',
|
|
24
|
+
customerEmailContinue: 'Continue to payment',
|
|
25
|
+
};
|
|
26
|
+
const normalizeValidEmail = (value) => {
|
|
27
|
+
const normalized = (value === null || value === void 0 ? void 0 : value.trim()) || '';
|
|
28
|
+
return normalized.length <= 320 && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalized)
|
|
29
|
+
? normalized
|
|
30
|
+
: null;
|
|
20
31
|
};
|
|
21
32
|
const initialView = {
|
|
22
33
|
state: 'loading',
|
|
@@ -85,9 +96,14 @@ function SolidgatePaymentForm({ checkout, hidden, loadingLabel, onError, onMount
|
|
|
85
96
|
instanceRef.current = instance;
|
|
86
97
|
}, onMounted: onMounted, onSubmit: onSubmit, onVerify: onVerify, onSuccess: onVerify, onFail: onVerify, onError: onError }, checkout.attemptId) })] }));
|
|
87
98
|
}
|
|
88
|
-
export function SolidgateCheckoutDialog({ open, request, checkoutAnalytics, content, onClose, onSuccess, onRetry, onError, onStateChange, }) {
|
|
99
|
+
export function SolidgateCheckoutDialog({ open, request, customerEmail, checkoutAnalytics, content, onClose, onCustomerEmailCommit, onSuccess, onRetry, onError, onStateChange, }) {
|
|
89
100
|
const copy = useMemo(() => (Object.assign(Object.assign({}, defaultContent), content)), [content]);
|
|
90
101
|
const [view, setView] = useState(initialView);
|
|
102
|
+
const normalizedCustomerEmail = normalizeValidEmail(customerEmail);
|
|
103
|
+
const [emailInput, setEmailInput] = useState((customerEmail === null || customerEmail === void 0 ? void 0 : customerEmail.trim()) || '');
|
|
104
|
+
const [committedEmail, setCommittedEmail] = useState(normalizedCustomerEmail);
|
|
105
|
+
const [emailError, setEmailError] = useState(null);
|
|
106
|
+
const [emailSubmitting, setEmailSubmitting] = useState(false);
|
|
91
107
|
const prepareAbortRef = useRef(null);
|
|
92
108
|
const statusAbortRef = useRef(null);
|
|
93
109
|
const successAttemptRef = useRef(null);
|
|
@@ -105,6 +121,11 @@ export function SolidgateCheckoutDialog({ open, request, checkoutAnalytics, cont
|
|
|
105
121
|
onStateChangeRef.current = onStateChange;
|
|
106
122
|
onSuccessRef.current = onSuccess;
|
|
107
123
|
}, [checkoutAnalytics, copy, onError, onStateChange, onSuccess]);
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
setEmailInput((customerEmail === null || customerEmail === void 0 ? void 0 : customerEmail.trim()) || '');
|
|
126
|
+
setCommittedEmail(normalizedCustomerEmail);
|
|
127
|
+
setEmailError(null);
|
|
128
|
+
}, [customerEmail, normalizedCustomerEmail]);
|
|
108
129
|
const { discountKeyOrProviderId, failUrl, funnelPlanKey, funnelEndUserId, funnelId, idempotencyKey, offerSetId, returnUrl, runtimeConfig, successUrl, } = request;
|
|
109
130
|
const { apiBaseUrl, funnelId: runtimeFunnelId, funnelSdkPublishableKey, funnelVersionId, } = runtimeConfig || {};
|
|
110
131
|
const hasRuntimeConfig = runtimeConfig !== undefined;
|
|
@@ -145,16 +166,27 @@ export function SolidgateCheckoutDialog({ open, request, checkoutAnalytics, cont
|
|
|
145
166
|
setView(nextView);
|
|
146
167
|
(_a = onStateChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onStateChangeRef, nextView.state);
|
|
147
168
|
}, []);
|
|
169
|
+
const requiresEmailCommit = Boolean(onCustomerEmailCommit);
|
|
148
170
|
useEffect(() => {
|
|
149
|
-
var _a, _b, _c, _d;
|
|
171
|
+
var _a, _b, _c, _d, _e, _f;
|
|
150
172
|
if (!open) {
|
|
151
173
|
(_a = prepareAbortRef.current) === null || _a === void 0 ? void 0 : _a.abort();
|
|
152
174
|
(_b = statusAbortRef.current) === null || _b === void 0 ? void 0 : _b.abort();
|
|
153
175
|
return;
|
|
154
176
|
}
|
|
177
|
+
if (requiresEmailCommit && !committedEmail) {
|
|
178
|
+
(_c = prepareAbortRef.current) === null || _c === void 0 ? void 0 : _c.abort();
|
|
179
|
+
(_d = statusAbortRef.current) === null || _d === void 0 ? void 0 : _d.abort();
|
|
180
|
+
queueMicrotask(() => setState({
|
|
181
|
+
state: 'collecting_email',
|
|
182
|
+
checkout: null,
|
|
183
|
+
canResumeVerification: false,
|
|
184
|
+
}));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
155
187
|
const controller = new AbortController();
|
|
156
|
-
(
|
|
157
|
-
(
|
|
188
|
+
(_e = prepareAbortRef.current) === null || _e === void 0 ? void 0 : _e.abort();
|
|
189
|
+
(_f = statusAbortRef.current) === null || _f === void 0 ? void 0 : _f.abort();
|
|
158
190
|
prepareAbortRef.current = controller;
|
|
159
191
|
successAttemptRef.current = null;
|
|
160
192
|
paymentInfoAttemptRef.current = null;
|
|
@@ -178,7 +210,7 @@ export function SolidgateCheckoutDialog({ open, request, checkoutAnalytics, cont
|
|
|
178
210
|
}
|
|
179
211
|
});
|
|
180
212
|
return () => controller.abort();
|
|
181
|
-
}, [normalizedRequest, open, setState]);
|
|
213
|
+
}, [committedEmail, normalizedRequest, open, requiresEmailCommit, setState]);
|
|
182
214
|
useEffect(() => {
|
|
183
215
|
var _a;
|
|
184
216
|
if (!open) {
|
|
@@ -253,6 +285,30 @@ export function SolidgateCheckoutDialog({ open, request, checkoutAnalytics, cont
|
|
|
253
285
|
}
|
|
254
286
|
}
|
|
255
287
|
}, [normalizedRequest, setState, view]);
|
|
288
|
+
const commitCustomerEmail = useCallback(async (event) => {
|
|
289
|
+
event.preventDefault();
|
|
290
|
+
const normalized = normalizeValidEmail(emailInput);
|
|
291
|
+
if (!normalized || !onCustomerEmailCommit) {
|
|
292
|
+
setEmailError(copy.customerEmailInvalid);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
setEmailSubmitting(true);
|
|
296
|
+
setEmailError(null);
|
|
297
|
+
try {
|
|
298
|
+
const committed = normalizeValidEmail(await onCustomerEmailCommit(normalized));
|
|
299
|
+
if (!committed) {
|
|
300
|
+
throw new Error('customer-email-not-committed');
|
|
301
|
+
}
|
|
302
|
+
setEmailInput(committed);
|
|
303
|
+
setCommittedEmail(committed);
|
|
304
|
+
}
|
|
305
|
+
catch (_a) {
|
|
306
|
+
setEmailError(copy.customerEmailUnavailable);
|
|
307
|
+
}
|
|
308
|
+
finally {
|
|
309
|
+
setEmailSubmitting(false);
|
|
310
|
+
}
|
|
311
|
+
}, [copy.customerEmailInvalid, copy.customerEmailUnavailable, emailInput, onCustomerEmailCommit]);
|
|
256
312
|
if (!open) {
|
|
257
313
|
return null;
|
|
258
314
|
}
|
|
@@ -272,7 +328,10 @@ export function SolidgateCheckoutDialog({ open, request, checkoutAnalytics, cont
|
|
|
272
328
|
: view.state === 'recoverable_error'
|
|
273
329
|
? copy.error
|
|
274
330
|
: null;
|
|
275
|
-
return (_jsx("div", { className: 'fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4', children: _jsxs("div", { role: 'dialog', "aria-modal": 'true', "aria-labelledby": 'solidgate-checkout-title', "data-checkout-state": view.state, className: 'w-full max-w-lg rounded-2xl border border-border bg-card p-5 text-foreground shadow-xl', children: [_jsxs("div", { className: 'mb-4 flex items-center justify-between gap-4', children: [_jsx("h2", { id: 'solidgate-checkout-title', className: 'text-lg font-semibold text-foreground', children: copy.title }), _jsx("button", { ref: closeButtonRef, type: 'button', onClick: onClose, "aria-label": copy.closeAriaLabel, className: 'rounded-md border border-border bg-background px-3 py-1.5 text-foreground hover:bg-accent', children: "\u00D7" })] }), view.
|
|
331
|
+
return (_jsx("div", { className: 'fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4', children: _jsxs("div", { role: 'dialog', "aria-modal": 'true', "aria-labelledby": 'solidgate-checkout-title', "data-checkout-state": view.state, className: 'w-full max-w-lg rounded-2xl border border-border bg-card p-5 text-foreground shadow-xl', children: [_jsxs("div", { className: 'mb-4 flex items-center justify-between gap-4', children: [_jsx("h2", { id: 'solidgate-checkout-title', className: 'text-lg font-semibold text-foreground', children: copy.title }), _jsx("button", { ref: closeButtonRef, type: 'button', onClick: onClose, "aria-label": copy.closeAriaLabel, className: 'rounded-md border border-border bg-background px-3 py-1.5 text-foreground hover:bg-accent', children: "\u00D7" })] }), view.state === 'collecting_email' ? (_jsxs("form", { className: 'grid gap-4', onSubmit: (event) => void commitCustomerEmail(event), children: [_jsxs("label", { className: 'grid gap-2 text-sm font-medium text-foreground', children: [copy.customerEmailLabel, _jsx("input", { type: 'email', autoComplete: 'email', inputMode: 'email', maxLength: 320, required: true, value: emailInput, onChange: (event) => {
|
|
332
|
+
setEmailInput(event.target.value);
|
|
333
|
+
setEmailError(null);
|
|
334
|
+
}, placeholder: copy.customerEmailPlaceholder, "aria-invalid": Boolean(emailError), className: 'w-full rounded-lg border border-border bg-background px-3 py-2 text-foreground placeholder:text-muted-foreground' })] }), emailError ? (_jsx("p", { role: 'alert', className: 'text-sm text-destructive', children: emailError })) : null, _jsx("button", { type: 'submit', disabled: emailSubmitting, className: 'w-full rounded-lg border border-border bg-accent px-4 py-2 font-medium text-foreground disabled:opacity-60', children: copy.customerEmailContinue })] })) : null, view.checkout ? (_jsx(SolidgatePaymentForm, { checkout: view.checkout, hidden: !showForm, loadingLabel: copy.loading, onMounted: () => {
|
|
276
335
|
if (view.state === 'loading') {
|
|
277
336
|
setState(Object.assign(Object.assign({}, view), { state: 'ready' }));
|
|
278
337
|
}
|