@funnelsgrove/payments 0.7.8 → 0.7.10
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/dist/providers/solidgate/components/SolidgateCheckoutDialog.d.ts +9 -2
- package/dist/providers/solidgate/components/SolidgateCheckoutDialog.js +79 -12
- package/dist/providers/solidgate/services/solidgate.service.d.ts +1 -3
- package/dist/providers/solidgate/services/solidgate.service.js +11 -3
- package/package.json +2 -2
|
@@ -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,17 +121,31 @@ export function SolidgateCheckoutDialog({ open, request, checkoutAnalytics, cont
|
|
|
105
121
|
onStateChangeRef.current = onStateChange;
|
|
106
122
|
onSuccessRef.current = onSuccess;
|
|
107
123
|
}, [checkoutAnalytics, copy, onError, onStateChange, onSuccess]);
|
|
108
|
-
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
setEmailInput((customerEmail === null || customerEmail === void 0 ? void 0 : customerEmail.trim()) || '');
|
|
126
|
+
setCommittedEmail(normalizedCustomerEmail);
|
|
127
|
+
setEmailError(null);
|
|
128
|
+
}, [customerEmail, normalizedCustomerEmail]);
|
|
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;
|
|
111
|
-
const normalizedRequest = useMemo(() => (
|
|
112
|
-
?
|
|
113
|
-
:
|
|
132
|
+
const normalizedRequest = useMemo(() => ({
|
|
133
|
+
funnelId: (funnelId === null || funnelId === void 0 ? void 0 : funnelId.trim()) || null,
|
|
134
|
+
funnelEndUserId: funnelEndUserId.trim(),
|
|
135
|
+
offerSetId: offerSetId.trim(),
|
|
136
|
+
funnelPlanKey: funnelPlanKey.trim(),
|
|
137
|
+
idempotencyKey: idempotencyKey.trim(),
|
|
138
|
+
discountKeyOrProviderId: (discountKeyOrProviderId === null || discountKeyOrProviderId === void 0 ? void 0 : discountKeyOrProviderId.trim()) || null,
|
|
139
|
+
returnUrl: returnUrl.trim(),
|
|
140
|
+
successUrl: successUrl.trim(),
|
|
141
|
+
failUrl: failUrl.trim(),
|
|
142
|
+
runtimeConfig: hasRuntimeConfig ? {
|
|
114
143
|
apiBaseUrl: (apiBaseUrl === null || apiBaseUrl === void 0 ? void 0 : apiBaseUrl.trim()) || null,
|
|
115
144
|
funnelId: (runtimeFunnelId === null || runtimeFunnelId === void 0 ? void 0 : runtimeFunnelId.trim()) || null,
|
|
116
145
|
funnelVersionId: (funnelVersionId === null || funnelVersionId === void 0 ? void 0 : funnelVersionId.trim()) || null,
|
|
117
146
|
funnelSdkPublishableKey: (funnelSdkPublishableKey === null || funnelSdkPublishableKey === void 0 ? void 0 : funnelSdkPublishableKey.trim()) || null,
|
|
118
|
-
} : undefined
|
|
147
|
+
} : undefined,
|
|
148
|
+
}), [
|
|
119
149
|
apiBaseUrl,
|
|
120
150
|
discountKeyOrProviderId,
|
|
121
151
|
failUrl,
|
|
@@ -126,7 +156,6 @@ export function SolidgateCheckoutDialog({ open, request, checkoutAnalytics, cont
|
|
|
126
156
|
funnelVersionId,
|
|
127
157
|
idempotencyKey,
|
|
128
158
|
offerSetId,
|
|
129
|
-
projectBillingPlanId,
|
|
130
159
|
returnUrl,
|
|
131
160
|
hasRuntimeConfig,
|
|
132
161
|
runtimeFunnelId,
|
|
@@ -137,16 +166,27 @@ export function SolidgateCheckoutDialog({ open, request, checkoutAnalytics, cont
|
|
|
137
166
|
setView(nextView);
|
|
138
167
|
(_a = onStateChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onStateChangeRef, nextView.state);
|
|
139
168
|
}, []);
|
|
169
|
+
const requiresEmailCommit = Boolean(onCustomerEmailCommit);
|
|
140
170
|
useEffect(() => {
|
|
141
|
-
var _a, _b, _c, _d;
|
|
171
|
+
var _a, _b, _c, _d, _e, _f;
|
|
142
172
|
if (!open) {
|
|
143
173
|
(_a = prepareAbortRef.current) === null || _a === void 0 ? void 0 : _a.abort();
|
|
144
174
|
(_b = statusAbortRef.current) === null || _b === void 0 ? void 0 : _b.abort();
|
|
145
175
|
return;
|
|
146
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
|
+
}
|
|
147
187
|
const controller = new AbortController();
|
|
148
|
-
(
|
|
149
|
-
(
|
|
188
|
+
(_e = prepareAbortRef.current) === null || _e === void 0 ? void 0 : _e.abort();
|
|
189
|
+
(_f = statusAbortRef.current) === null || _f === void 0 ? void 0 : _f.abort();
|
|
150
190
|
prepareAbortRef.current = controller;
|
|
151
191
|
successAttemptRef.current = null;
|
|
152
192
|
paymentInfoAttemptRef.current = null;
|
|
@@ -170,7 +210,7 @@ export function SolidgateCheckoutDialog({ open, request, checkoutAnalytics, cont
|
|
|
170
210
|
}
|
|
171
211
|
});
|
|
172
212
|
return () => controller.abort();
|
|
173
|
-
}, [normalizedRequest, open, setState]);
|
|
213
|
+
}, [committedEmail, normalizedRequest, open, requiresEmailCommit, setState]);
|
|
174
214
|
useEffect(() => {
|
|
175
215
|
var _a;
|
|
176
216
|
if (!open) {
|
|
@@ -245,6 +285,30 @@ export function SolidgateCheckoutDialog({ open, request, checkoutAnalytics, cont
|
|
|
245
285
|
}
|
|
246
286
|
}
|
|
247
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]);
|
|
248
312
|
if (!open) {
|
|
249
313
|
return null;
|
|
250
314
|
}
|
|
@@ -264,7 +328,10 @@ export function SolidgateCheckoutDialog({ open, request, checkoutAnalytics, cont
|
|
|
264
328
|
: view.state === 'recoverable_error'
|
|
265
329
|
? copy.error
|
|
266
330
|
: null;
|
|
267
|
-
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: () => {
|
|
268
335
|
if (view.state === 'loading') {
|
|
269
336
|
setState(Object.assign(Object.assign({}, view), { state: 'ready' }));
|
|
270
337
|
}
|
|
@@ -25,9 +25,7 @@ export type PrepareSolidgateCheckoutInput = {
|
|
|
25
25
|
funnelId?: string | null;
|
|
26
26
|
funnelEndUserId: string;
|
|
27
27
|
offerSetId: string;
|
|
28
|
-
funnelPlanKey
|
|
29
|
-
/** @deprecated Published funnels may send this during the offer-set migration window. */
|
|
30
|
-
projectBillingPlanId?: string | null;
|
|
28
|
+
funnelPlanKey: string;
|
|
31
29
|
idempotencyKey: string;
|
|
32
30
|
discountKeyOrProviderId?: string | null;
|
|
33
31
|
returnUrl: string;
|
|
@@ -99,9 +99,17 @@ const parseStatus = (value) => {
|
|
|
99
99
|
};
|
|
100
100
|
export const prepareSolidgateCheckout = async (input) => {
|
|
101
101
|
var _a;
|
|
102
|
-
return parsePreparation(await postJson('/sdk/public/payments/solidgate/checkout',
|
|
103
|
-
|
|
104
|
-
:
|
|
102
|
+
return parsePreparation(await postJson('/sdk/public/payments/solidgate/checkout', {
|
|
103
|
+
funnelId: resolveFunnelId(input.funnelId, input.runtimeConfig),
|
|
104
|
+
funnelEndUserId: requiredString(input.funnelEndUserId, 'funnelEndUserId'),
|
|
105
|
+
offerSetId: requiredString(input.offerSetId, 'offerSetId'),
|
|
106
|
+
funnelPlanKey: requiredString(input.funnelPlanKey, 'funnelPlanKey'),
|
|
107
|
+
idempotencyKey: requiredString(input.idempotencyKey, 'idempotencyKey'),
|
|
108
|
+
discountKeyOrProviderId: ((_a = input.discountKeyOrProviderId) === null || _a === void 0 ? void 0 : _a.trim()) || null,
|
|
109
|
+
returnUrl: requiredString(input.returnUrl, 'returnUrl'),
|
|
110
|
+
successUrl: requiredString(input.successUrl, 'successUrl'),
|
|
111
|
+
failUrl: requiredString(input.failUrl, 'failUrl'),
|
|
112
|
+
}, input.runtimeConfig, input.signal));
|
|
105
113
|
};
|
|
106
114
|
export const retrieveSolidgateCheckoutStatus = async (input) => {
|
|
107
115
|
const attemptId = requiredString(input.attemptId, 'attemptId');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@funnelsgrove/payments",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.10",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/The-Solid-Grove/funnelsgrove.git",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@funnelsgrove/analytics": "^0.1.67",
|
|
36
|
-
"@funnelsgrove/runtime": "^0.7.
|
|
36
|
+
"@funnelsgrove/runtime": "^0.7.19",
|
|
37
37
|
"@solidgate/react-sdk": "1.34.0",
|
|
38
38
|
"@stripe/react-stripe-js": "^5.6.0",
|
|
39
39
|
"@stripe/stripe-js": "^8.7.0",
|