@funnelsgrove/payments 0.15.11 → 0.20.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/dist/providers/solidgate/components/SolidgateCheckoutDialog.js +14 -7
- package/dist/providers/solidgate/services/solidgate.service.js +7 -5
- package/dist/providers/stripe/components/SharedStripeCheckoutV2Form.internal.js +56 -35
- package/dist/providers/stripe/components/StripeCheckoutExpressCheckoutButton.d.ts +3 -1
- package/dist/providers/stripe/components/StripeCheckoutExpressCheckoutButton.js +44 -4
- package/package.json +3 -3
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { completePaywallCheckout } from '@funnelsgrove/runtime';
|
|
3
4
|
import { publicAnalyticsSdk } from '@funnelsgrove/analytics';
|
|
4
5
|
import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
5
6
|
import { pollSolidgateCheckoutStatus, prepareSolidgateCheckout, } from '../services/solidgate.service.js';
|
|
@@ -41,7 +42,7 @@ const trackSolidgateCheckoutEvent = async (eventName, attemptId, context) => {
|
|
|
41
42
|
return;
|
|
42
43
|
}
|
|
43
44
|
const input = {
|
|
44
|
-
eventId: eventName === '
|
|
45
|
+
eventId: eventName === 'add_payment_info' ? `add_payment_info:${attemptId}` : attemptId,
|
|
45
46
|
featureFlags: context.featureFlags,
|
|
46
47
|
stepId: context.stepId,
|
|
47
48
|
stepName: context.stepName,
|
|
@@ -52,7 +53,9 @@ const trackSolidgateCheckoutEvent = async (eventName, attemptId, context) => {
|
|
|
52
53
|
try {
|
|
53
54
|
const trackedEventId = eventName === 'checkout_started'
|
|
54
55
|
? publicAnalyticsSdk.trackCheckoutStarted(input)
|
|
55
|
-
:
|
|
56
|
+
: eventName === 'checkout_completed'
|
|
57
|
+
? publicAnalyticsSdk.trackCheckoutCompleted(input)
|
|
58
|
+
: publicAnalyticsSdk.trackPaymentInfoSubmitted(input);
|
|
56
59
|
if (trackedEventId) {
|
|
57
60
|
await publicAnalyticsSdk.flush().catch(() => 0);
|
|
58
61
|
}
|
|
@@ -269,11 +272,15 @@ export function SolidgateCheckoutDialog({ open, request, customerEmail, checkout
|
|
|
269
272
|
return;
|
|
270
273
|
}
|
|
271
274
|
if (result.status === 'succeeded') {
|
|
272
|
-
setState({ state: 'success', checkout, canResumeVerification: false });
|
|
273
275
|
if (successAttemptRef.current !== checkout.attemptId) {
|
|
276
|
+
const embedded = await completePaywallCheckout('solidgate', checkout.attemptId, {
|
|
277
|
+
beforeNotify: () => trackSolidgateCheckoutEvent('checkout_completed', checkout.attemptId, checkoutAnalyticsRef.current),
|
|
278
|
+
});
|
|
279
|
+
if (!embedded)
|
|
280
|
+
await ((_b = onSuccessRef.current) === null || _b === void 0 ? void 0 : _b.call(onSuccessRef, checkout));
|
|
274
281
|
successAttemptRef.current = checkout.attemptId;
|
|
275
|
-
await ((_b = onSuccessRef.current) === null || _b === void 0 ? void 0 : _b.call(onSuccessRef, checkout));
|
|
276
282
|
}
|
|
283
|
+
setState({ state: 'success', checkout, canResumeVerification: false });
|
|
277
284
|
}
|
|
278
285
|
else if (result.status === 'expired') {
|
|
279
286
|
setState({ state: 'expired', checkout, canResumeVerification: false });
|
|
@@ -291,8 +298,8 @@ export function SolidgateCheckoutDialog({ open, request, customerEmail, checkout
|
|
|
291
298
|
setState({ state: 'verifying', checkout, canResumeVerification: result.timedOut });
|
|
292
299
|
}
|
|
293
300
|
}
|
|
294
|
-
catch (
|
|
295
|
-
if (!controller.signal.aborted
|
|
301
|
+
catch (_e) {
|
|
302
|
+
if (!controller.signal.aborted) {
|
|
296
303
|
setState({ state: 'recoverable_error', checkout, canResumeVerification: true });
|
|
297
304
|
trackFailure(copyRef.current.error, 'payment_verification', checkout.attemptId);
|
|
298
305
|
(_d = onErrorRef.current) === null || _d === void 0 ? void 0 : _d.call(onErrorRef, copyRef.current.error);
|
|
@@ -385,5 +392,5 @@ export function SolidgateCheckoutDialog({ open, request, customerEmail, checkout
|
|
|
385
392
|
setState(Object.assign(Object.assign({}, view), { state: 'recoverable_error', canResumeVerification: false }));
|
|
386
393
|
trackFailure(copyRef.current.error, 'payment_form', (_a = view.checkout) === null || _a === void 0 ? void 0 : _a.attemptId);
|
|
387
394
|
(_b = onErrorRef.current) === null || _b === void 0 ? void 0 : _b.call(onErrorRef, copyRef.current.error);
|
|
388
|
-
} }, view.checkout.attemptId)) : null, presentation && view.checkout && showForm ? (_jsx("p", { className: 'solidgate-checkout-secure', children: presentation.secureLabel })) : null, statusMessage && view.state !== 'ready' ? (_jsx("div", { className: 'solidgate-checkout-status', children: _jsx("p", { role: 'status', "aria-live": 'polite', children: statusMessage }) })) : null, view.
|
|
395
|
+
} }, view.checkout.attemptId)) : null, presentation && view.checkout && showForm ? (_jsx("p", { className: 'solidgate-checkout-secure', children: presentation.secureLabel })) : null, statusMessage && view.state !== 'ready' ? (_jsx("div", { className: 'solidgate-checkout-status', children: _jsx("p", { role: 'status', "aria-live": 'polite', children: statusMessage }) })) : null, view.canResumeVerification ? (_jsx("button", { type: 'button', onClick: () => void verifyCanonicalStatus(), className: 'solidgate-checkout-action solidgate-checkout-action--spaced', children: copy.resumeVerification })) : null, (view.state === 'recoverable_error' || view.state === 'expired') && !view.canResumeVerification && onRetry ? (_jsx("button", { type: 'button', onClick: onRetry, className: 'solidgate-checkout-action solidgate-checkout-action--spaced', children: copy.retry })) : null] })) : null] }), _jsx("style", { children: solidgateCheckoutDialogStyles })] }));
|
|
389
396
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FUNNEL_ID, FUNNEL_SDK_PUBLISHABLE_KEY, buildMainApiUrl, resolvePreviewPaymentPublishableKey, } from '@funnelsgrove/runtime';
|
|
1
|
+
import { paywallCheckoutReturnUrl, retainPaywallCheckout, FUNNEL_ID, FUNNEL_SDK_PUBLISHABLE_KEY, buildMainApiUrl, resolvePreviewPaymentPublishableKey, } from '@funnelsgrove/runtime';
|
|
2
2
|
const trimTrailingSlash = (value) => value.replace(/\/+$/, '');
|
|
3
3
|
const requiredString = (value, name) => {
|
|
4
4
|
const normalized = typeof value === 'string' ? value.trim() : '';
|
|
@@ -118,7 +118,7 @@ export const prepareSolidgateCheckout = async (input) => {
|
|
|
118
118
|
if (!paymentProfileId || !providerPlanId || !checkoutType || !environment) {
|
|
119
119
|
throw new Error('Solidgate V2 checkout selection is incomplete');
|
|
120
120
|
}
|
|
121
|
-
|
|
121
|
+
const result = parsePreparation(await postJson('/sdk/public/v2/payments/checkout-sessions', {
|
|
122
122
|
checkoutType,
|
|
123
123
|
uiMode: 'custom',
|
|
124
124
|
environment,
|
|
@@ -131,10 +131,12 @@ export const prepareSolidgateCheckout = async (input) => {
|
|
|
131
131
|
offerSetId: requiredString(input.offerSetId, 'offerSetId'),
|
|
132
132
|
idempotencyKey: requiredString(input.idempotencyKey, 'idempotencyKey'),
|
|
133
133
|
couponId: ((_c = input.discountKeyOrProviderId) === null || _c === void 0 ? void 0 : _c.trim()) || null,
|
|
134
|
-
returnUrl: requiredString(input.returnUrl, 'returnUrl'),
|
|
135
|
-
successUrl: requiredString(input.successUrl, 'successUrl'),
|
|
136
|
-
failUrl: requiredString(input.failUrl, 'failUrl'),
|
|
134
|
+
returnUrl: paywallCheckoutReturnUrl(requiredString(input.returnUrl, 'returnUrl'), 'solidgate'),
|
|
135
|
+
successUrl: paywallCheckoutReturnUrl(requiredString(input.successUrl, 'successUrl'), 'solidgate'),
|
|
136
|
+
failUrl: paywallCheckoutReturnUrl(requiredString(input.failUrl, 'failUrl'), 'solidgate', true),
|
|
137
137
|
}, input.runtimeConfig, input.signal));
|
|
138
|
+
retainPaywallCheckout('solidgate', result.attemptId);
|
|
139
|
+
return result;
|
|
138
140
|
};
|
|
139
141
|
export const retrieveSolidgateCheckoutStatus = async (input) => {
|
|
140
142
|
const attemptId = requiredString(input.attemptId, 'attemptId');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { completePaywallCheckout } from '@funnelsgrove/runtime';
|
|
3
4
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
5
|
import { PaymentElement, useCheckout } from '@stripe/react-stripe-js/checkout';
|
|
5
6
|
import { isValidCheckoutEmail as isValidEmail, isCheckoutEmailError } from '../../../services/checkoutEmail.js';
|
|
@@ -32,6 +33,8 @@ export function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonC
|
|
|
32
33
|
? true
|
|
33
34
|
: walletAvailable;
|
|
34
35
|
const [submitting, setSubmitting] = useState(false);
|
|
36
|
+
const [completed, setCompleted] = useState(false);
|
|
37
|
+
const [providerConfirmed, setProviderConfirmed] = useState(false);
|
|
35
38
|
const submitInFlightRef = useRef(false);
|
|
36
39
|
const [paymentElementReady, setPaymentElementReady] = useState(false);
|
|
37
40
|
const [paymentElementComplete, setPaymentElementComplete] = useState(false);
|
|
@@ -164,11 +167,12 @@ export function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonC
|
|
|
164
167
|
setPaymentElementComplete(event.complete);
|
|
165
168
|
setBillingCountry((_b = (_a = event.value.billingDetails) === null || _a === void 0 ? void 0 : _a.address.country) !== null && _b !== void 0 ? _b : fixedBillingCountry);
|
|
166
169
|
};
|
|
167
|
-
const trackCheckoutSuccess = async () => {
|
|
170
|
+
const trackCheckoutSuccess = async (embedded = false) => {
|
|
168
171
|
if (checkoutAnalytics) {
|
|
169
172
|
await trackPaidStripeSubscriptionCheckoutCompleted(Object.assign({ checkoutSessionId }, checkoutAnalytics));
|
|
170
173
|
}
|
|
171
|
-
|
|
174
|
+
if (!embedded)
|
|
175
|
+
await (onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess());
|
|
172
176
|
};
|
|
173
177
|
const trackPaymentInfoSubmitted = async () => {
|
|
174
178
|
if (checkoutAnalytics) {
|
|
@@ -187,23 +191,25 @@ export function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonC
|
|
|
187
191
|
}, [effectiveSelectedMethod]);
|
|
188
192
|
const handleSubmit = async (event) => {
|
|
189
193
|
event.preventDefault();
|
|
190
|
-
if (submitInFlightRef.current || emailSessionChanged) {
|
|
194
|
+
if (submitInFlightRef.current || (emailSessionChanged && !providerConfirmed)) {
|
|
191
195
|
return;
|
|
192
196
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
197
|
+
if (!providerConfirmed) {
|
|
198
|
+
const billingCountryError = getBillingCountryError(billingCountry !== null && billingCountry !== void 0 ? billingCountry : fixedBillingCountry);
|
|
199
|
+
if (billingCountryError) {
|
|
200
|
+
setSharedError(billingCountryError);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (checkoutState.type !== 'success') {
|
|
204
|
+
setSharedError(checkoutState.type === 'error'
|
|
205
|
+
? checkoutState.error.message
|
|
206
|
+
: 'Payment form is not ready yet.');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (!paymentElementReady) {
|
|
210
|
+
setSharedError('Payment form is not ready yet.', true);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
207
213
|
}
|
|
208
214
|
submitInFlightRef.current = true;
|
|
209
215
|
setSubmitting(true);
|
|
@@ -212,25 +218,36 @@ export function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonC
|
|
|
212
218
|
submitInFlightRef.current = false;
|
|
213
219
|
setSubmitting(false);
|
|
214
220
|
};
|
|
221
|
+
let confirmed = providerConfirmed;
|
|
215
222
|
try {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
223
|
+
if (!providerConfirmed && checkoutState.type === 'success') {
|
|
224
|
+
const syncedEmail = await ensureCustomerEmail();
|
|
225
|
+
if (!syncedEmail) {
|
|
226
|
+
finishSubmitting();
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (paymentElementComplete) {
|
|
230
|
+
await trackPaymentInfoSubmitted();
|
|
231
|
+
}
|
|
232
|
+
const result = await checkoutState.checkout.confirm({
|
|
233
|
+
redirect: 'if_required',
|
|
234
|
+
});
|
|
235
|
+
if (result.type === 'error') {
|
|
236
|
+
const message = result.error.message || 'Payment failed.';
|
|
237
|
+
setSharedError(message, false, result.error);
|
|
238
|
+
finishSubmitting();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
confirmed = true;
|
|
242
|
+
setProviderConfirmed(true);
|
|
232
243
|
}
|
|
233
244
|
if (typeof window !== 'undefined') {
|
|
245
|
+
if (await completePaywallCheckout('stripe', checkoutSessionId || '', { beforeNotify: async () => {
|
|
246
|
+
setCompleted(true);
|
|
247
|
+
await trackCheckoutSuccess(true);
|
|
248
|
+
} })) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
234
251
|
await trackCheckoutSuccess();
|
|
235
252
|
window.location.assign(returnUrl);
|
|
236
253
|
return;
|
|
@@ -238,11 +255,15 @@ export function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonC
|
|
|
238
255
|
finishSubmitting();
|
|
239
256
|
}
|
|
240
257
|
catch (submitError) {
|
|
241
|
-
setSharedError(getFriendlyCardErrorMessage(submitError));
|
|
258
|
+
setSharedError(getFriendlyCardErrorMessage(submitError), confirmed);
|
|
242
259
|
finishSubmitting();
|
|
243
260
|
}
|
|
244
261
|
};
|
|
245
262
|
const visibleError = error;
|
|
263
|
+
if (providerConfirmed && !completed)
|
|
264
|
+
return _jsxs("form", { onSubmit: handleSubmit, className: 'bg-background text-foreground p-6', children: [_jsx("p", { role: error ? 'alert' : 'status', children: error || 'Verifying payment…' }), _jsx("button", { type: 'submit', disabled: submitting, className: 'bg-accent text-foreground border-border rounded border p-3', children: "Check payment status" })] });
|
|
265
|
+
if (completed)
|
|
266
|
+
return _jsx("div", { role: 'status', className: 'bg-background text-foreground p-6', children: "Payment confirmed. You can return to the app." });
|
|
246
267
|
return (_jsxs("div", { className: 'shared-checkout-v2-shell', "data-variant": variant, style: checkoutStyle, children: [_jsxs("header", { className: 'shared-checkout-v2-header', children: [_jsx("button", { type: 'button', className: 'shared-checkout-v2-close', onClick: onClose, "aria-label": closeAriaLabel, children: _jsx("span", { "aria-hidden": 'true' }) }), _jsx("h2", { id: 'shared-checkout-v2-title', children: title })] }), _jsx(SharedStripeCheckoutV2Summary, { discountPercent, countdownLabel, remainingSeconds, variant, satisfactionPercent, satisfactionLabel, originalPriceLabel, originalPriceValue, discountLabel, discountAmountLabel, promoCodeLabel, promoCode, totalLabel, totalValue, savedLabel, showPromo }), _jsxs("form", { className: 'shared-checkout-v2-payment', onSubmit: (event) => void handleSubmit(event), children: [showWalletMethod ? (_jsxs("div", { className: 'shared-checkout-v2-methods', role: 'tablist', "aria-label": 'Payment method', children: [_jsxs("button", { type: 'button', className: [
|
|
247
268
|
'shared-checkout-v2-method',
|
|
248
269
|
effectiveSelectedMethod === 'wallet' ? 'is-selected' : '',
|
|
@@ -258,7 +279,7 @@ export function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonC
|
|
|
258
279
|
effectiveSelectedMethod === 'wallet' ? 'is-visible' : '',
|
|
259
280
|
]
|
|
260
281
|
.filter(Boolean)
|
|
261
|
-
.join(' '), children: [_jsxs("p", { className: 'shared-checkout-v2-secure-pill', children: [_jsx(SecurityLockIcon, { className: 'shared-checkout-v2-secure-pill-icon' }), secureLabel] }), _jsxs("div", { className: 'shared-checkout-v2-wallet-shell', "aria-label": walletAriaLabel, children: [_jsx(StripeCheckoutExpressCheckoutButton, { amountCents: amountCents, availabilityPaymentMethods: walletPaymentMethods, beforeConfirm: ensureCustomerEmail, checkoutAnalytics: checkoutAnalytics, checkoutSessionId: checkoutSessionId, className: 'shared-checkout-v2-express-buttons', confirmEmail: false, customerEmail: resolvedCustomerEmail, customerName: customerName, initialAvailable: initialWalletAvailable, keepInitialAvailableOnReady: initialWalletAvailable === true, onAvailabilityChange: handleWalletAvailabilityChange, onError: (message) => setSharedError(message, true), onPaymentInfoSubmitted: onPaymentInfoSubmitted, onPaymentRejected: () => setSelectedMethod('card'), onSuccess: trackCheckoutSuccess, options: expressCheckoutOptions, returnUrl: returnUrl, summaryLabel: expressCheckoutSummaryLabel, supportedCountries: normalizedSupportedCountries, unsupportedCountryMessage: unsupportedCountryMessage }), _jsx("span", { className: 'shared-checkout-v2-sr-only', children: walletAriaLabel })] })] }), _jsxs("div", { hidden: effectiveSelectedMethod !== 'card', className: [
|
|
282
|
+
.join(' '), children: [_jsxs("p", { className: 'shared-checkout-v2-secure-pill', children: [_jsx(SecurityLockIcon, { className: 'shared-checkout-v2-secure-pill-icon' }), secureLabel] }), _jsxs("div", { className: 'shared-checkout-v2-wallet-shell', "aria-label": walletAriaLabel, children: [_jsx(StripeCheckoutExpressCheckoutButton, { amountCents: amountCents, availabilityPaymentMethods: walletPaymentMethods, beforeConfirm: ensureCustomerEmail, checkoutAnalytics: checkoutAnalytics, checkoutSessionId: checkoutSessionId, className: 'shared-checkout-v2-express-buttons', confirmEmail: false, customerEmail: resolvedCustomerEmail, customerName: customerName, initialAvailable: initialWalletAvailable, keepInitialAvailableOnReady: initialWalletAvailable === true, onAvailabilityChange: handleWalletAvailabilityChange, onError: (message) => setSharedError(message, true), onPaymentInfoSubmitted: onPaymentInfoSubmitted, onPaymentRejected: () => setSelectedMethod('card'), onSuccess: () => trackCheckoutSuccess(), onEmbeddedComplete: () => setCompleted(true), onVerificationPending: () => setProviderConfirmed(true), options: expressCheckoutOptions, returnUrl: returnUrl, summaryLabel: expressCheckoutSummaryLabel, supportedCountries: normalizedSupportedCountries, unsupportedCountryMessage: unsupportedCountryMessage }), _jsx("span", { className: 'shared-checkout-v2-sr-only', children: walletAriaLabel })] })] }), _jsxs("div", { hidden: effectiveSelectedMethod !== 'card', className: [
|
|
262
283
|
'shared-checkout-v2-card-panel',
|
|
263
284
|
effectiveSelectedMethod === 'card' ? 'is-visible' : '',
|
|
264
285
|
]
|
|
@@ -30,7 +30,9 @@ export type StripeCheckoutExpressCheckoutButtonProps = {
|
|
|
30
30
|
message: string;
|
|
31
31
|
paymentMethod?: string;
|
|
32
32
|
}) => void;
|
|
33
|
+
onEmbeddedComplete?: () => void;
|
|
34
|
+
onVerificationPending?: () => void;
|
|
33
35
|
onSuccess?: (result: StripeWalletCheckoutSuccess) => void | Promise<void>;
|
|
34
36
|
unsupportedCountryMessage?: string;
|
|
35
37
|
};
|
|
36
|
-
export declare function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAnalytics, checkoutSessionId, returnUrl, confirmEmail, customerEmail, className, options, availabilityPaymentMethods, initialAvailable, keepInitialAvailableOnReady, serverUpdateKey, supportedCountries, onServerUpdate, onAvailabilityChange, onCancel, onError, onPaymentInfoSubmitted, onPaymentRejected, onSuccess, unsupportedCountryMessage, }: StripeCheckoutExpressCheckoutButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
38
|
+
export declare function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAnalytics, checkoutSessionId, returnUrl, confirmEmail, customerEmail, className, options, availabilityPaymentMethods, initialAvailable, keepInitialAvailableOnReady, serverUpdateKey, supportedCountries, onServerUpdate, onAvailabilityChange, onCancel, onError, onPaymentInfoSubmitted, onPaymentRejected, onSuccess, onEmbeddedComplete, onVerificationPending, unsupportedCountryMessage, }: StripeCheckoutExpressCheckoutButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx,
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { completePaywallCheckout } from '@funnelsgrove/runtime';
|
|
3
4
|
import { isValidCheckoutEmail, isCheckoutEmailError } from '../../../services/checkoutEmail.js';
|
|
4
5
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
5
6
|
import { ExpressCheckoutElement, useCheckout, } from '@stripe/react-stripe-js/checkout';
|
|
@@ -52,8 +53,13 @@ const normalizeStripeCheckoutProviderError = (error) => {
|
|
|
52
53
|
type: typeof record.type === 'string' ? record.type : undefined,
|
|
53
54
|
};
|
|
54
55
|
};
|
|
55
|
-
export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAnalytics, checkoutSessionId, returnUrl, confirmEmail = true, customerEmail, className, options, availabilityPaymentMethods = ['applePay'], initialAvailable, keepInitialAvailableOnReady = false, serverUpdateKey, supportedCountries, onServerUpdate, onAvailabilityChange, onCancel, onError, onPaymentInfoSubmitted, onPaymentRejected, onSuccess, unsupportedCountryMessage, }) {
|
|
56
|
+
export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAnalytics, checkoutSessionId, returnUrl, confirmEmail = true, customerEmail, className, options, availabilityPaymentMethods = ['applePay'], initialAvailable, keepInitialAvailableOnReady = false, serverUpdateKey, supportedCountries, onServerUpdate, onAvailabilityChange, onCancel, onError, onPaymentInfoSubmitted, onPaymentRejected, onSuccess, onEmbeddedComplete, onVerificationPending, unsupportedCountryMessage, }) {
|
|
56
57
|
const checkoutState = useCheckout();
|
|
58
|
+
const [completed, setCompleted] = useState(false);
|
|
59
|
+
const completedRef = useRef(false);
|
|
60
|
+
const verificationInFlightRef = useRef(false);
|
|
61
|
+
const [verificationInFlight, setVerificationInFlight] = useState(false);
|
|
62
|
+
const [verificationError, setVerificationError] = useState(null);
|
|
57
63
|
const [loadRetry, setLoadRetry] = useState(0);
|
|
58
64
|
const loadRetryUsedRef = useRef(false);
|
|
59
65
|
const walletReadyRef = useRef(false);
|
|
@@ -160,11 +166,12 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAna
|
|
|
160
166
|
}
|
|
161
167
|
void ensureServerUpdated();
|
|
162
168
|
}, [checkoutState.type, ensureServerUpdated, normalizedServerUpdateKey, onServerUpdate]);
|
|
163
|
-
const trackCheckoutSuccess = async () => {
|
|
169
|
+
const trackCheckoutSuccess = async (embedded = false) => {
|
|
164
170
|
if (checkoutAnalytics) {
|
|
165
171
|
await trackPaidStripeSubscriptionCheckoutCompleted(Object.assign({ checkoutSessionId }, checkoutAnalytics));
|
|
166
172
|
}
|
|
167
|
-
|
|
173
|
+
if (!embedded)
|
|
174
|
+
await (onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess({ checkoutSessionId: checkoutSessionId !== null && checkoutSessionId !== void 0 ? checkoutSessionId : null }));
|
|
168
175
|
};
|
|
169
176
|
const queueCheckoutStarted = (paymentMethod, checkoutStartSource = 'wallet_click') => {
|
|
170
177
|
if (!checkoutAnalytics) {
|
|
@@ -191,8 +198,35 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAna
|
|
|
191
198
|
}
|
|
192
199
|
onError === null || onError === void 0 ? void 0 : onError(isCheckoutEmailError(providerError) ? 'Enter a valid email address to continue.' : message);
|
|
193
200
|
};
|
|
201
|
+
const verifyPaywall = async () => {
|
|
202
|
+
if (verificationInFlightRef.current || completedRef.current)
|
|
203
|
+
return true;
|
|
204
|
+
verificationInFlightRef.current = true;
|
|
205
|
+
setVerificationInFlight(true);
|
|
206
|
+
try {
|
|
207
|
+
return await completePaywallCheckout('stripe', checkoutSessionId || '', { beforeNotify: async () => {
|
|
208
|
+
completedRef.current = true;
|
|
209
|
+
setCompleted(true);
|
|
210
|
+
onEmbeddedComplete === null || onEmbeddedComplete === void 0 ? void 0 : onEmbeddedComplete();
|
|
211
|
+
await trackCheckoutSuccess(true);
|
|
212
|
+
} });
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
const message = error instanceof Error ? error.message : 'Unable to confirm payment';
|
|
216
|
+
setVerificationError(message);
|
|
217
|
+
onError === null || onError === void 0 ? void 0 : onError(message);
|
|
218
|
+
onVerificationPending === null || onVerificationPending === void 0 ? void 0 : onVerificationPending();
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
verificationInFlightRef.current = false;
|
|
223
|
+
setVerificationInFlight(false);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
194
226
|
const handleConfirm = async (event) => {
|
|
195
227
|
var _a, _b, _c, _d, _e, _f;
|
|
228
|
+
if (completedRef.current)
|
|
229
|
+
return;
|
|
196
230
|
// Checkout Sessions omits the Express Checkout onClick callback. Confirmation
|
|
197
231
|
// and cancellation are the reliable wallet-intent boundaries for this provider.
|
|
198
232
|
// Session-based analytics deduplication keeps this idempotent if onClick fires.
|
|
@@ -287,6 +321,8 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAna
|
|
|
287
321
|
}
|
|
288
322
|
onError === null || onError === void 0 ? void 0 : onError(null);
|
|
289
323
|
if (typeof window !== 'undefined') {
|
|
324
|
+
if (await verifyPaywall())
|
|
325
|
+
return;
|
|
290
326
|
await trackCheckoutSuccess();
|
|
291
327
|
window.location.assign(returnUrl);
|
|
292
328
|
}
|
|
@@ -320,6 +356,10 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAna
|
|
|
320
356
|
onCancel === null || onCancel === void 0 ? void 0 : onCancel();
|
|
321
357
|
};
|
|
322
358
|
const inertWhileUpdating = (serverUpdatePending ? { inert: true } : {});
|
|
359
|
+
if (verificationError && !completed)
|
|
360
|
+
return _jsxs("div", { className: 'bg-background text-foreground p-6', children: [_jsx("p", { role: 'alert', children: verificationError }), _jsx("button", { type: 'button', disabled: verificationInFlight, className: 'bg-accent text-foreground border-border rounded border p-3', onClick: () => void verifyPaywall(), children: "Check payment status" })] });
|
|
361
|
+
if (completed)
|
|
362
|
+
return _jsx("div", { role: 'status', className: 'bg-background text-foreground p-6', children: "Payment confirmed. You can return to the app." });
|
|
323
363
|
return (_jsxs(_Fragment, { children: [_jsx("div", Object.assign({}, inertWhileUpdating, { "aria-busy": serverUpdatePending || undefined, "aria-disabled": serverUpdatePending || undefined, className: [
|
|
324
364
|
'stripe-express-checkout-button',
|
|
325
365
|
className !== null && className !== void 0 ? className : '',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@funnelsgrove/payments",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/The-Solid-Grove/funnelsgrove.git",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"test:run": "vitest run --passWithNoTests"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@funnelsgrove/analytics": "0.1.
|
|
36
|
-
"@funnelsgrove/runtime": "0.
|
|
35
|
+
"@funnelsgrove/analytics": "0.1.110",
|
|
36
|
+
"@funnelsgrove/runtime": "0.19.0",
|
|
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",
|