@funnelsgrove/payments 0.1.39 → 0.1.41
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/components/shared/SharedStripeCheckoutV2Dialog.d.ts +5 -1
- package/dist/components/shared/SharedStripeCheckoutV2Dialog.js +19 -5
- package/dist/components/shared/StripeCheckoutExpressCheckoutButton.d.ts +5 -1
- package/dist/components/shared/StripeCheckoutExpressCheckoutButton.js +13 -2
- package/dist/components/shared/StripeExpressCheckoutButton.d.ts +2 -1
- package/dist/components/shared/StripeExpressCheckoutButton.js +2 -1
- package/dist/providers/stripe/WalletSubscriptionCheckoutSlot.d.ts +4 -1
- package/dist/providers/stripe/WalletSubscriptionCheckoutSlot.js +2 -2
- package/dist/services/checkoutCompletionAnalytics.service.d.ts +10 -2
- package/dist/services/checkoutCompletionAnalytics.service.js +41 -9
- package/package.json +2 -2
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { Stripe } from '@stripe/stripe-js';
|
|
2
|
+
import { type StripeSubscriptionCheckoutAnalyticsContext } from '../../services/checkoutCompletionAnalytics.service.js';
|
|
2
3
|
export type SharedStripeCheckoutV2DialogProps = {
|
|
3
4
|
amountCents: number;
|
|
4
5
|
buttonColor: string;
|
|
5
6
|
cardSubmitLabel: string;
|
|
7
|
+
checkoutAnalytics?: StripeSubscriptionCheckoutAnalyticsContext | null;
|
|
8
|
+
checkoutSessionId?: string | null;
|
|
6
9
|
clientSecret: string;
|
|
7
10
|
closeAriaLabel: string;
|
|
8
11
|
countdownLabel: string;
|
|
@@ -22,6 +25,7 @@ export type SharedStripeCheckoutV2DialogProps = {
|
|
|
22
25
|
onCustomerEmailCommit?: (email: string) => Promise<string | null | void>;
|
|
23
26
|
onError?: (message: string | null) => void;
|
|
24
27
|
onInactiveCheckoutSession?: () => void;
|
|
28
|
+
onPaymentInfoSubmitted?: () => void | Promise<void>;
|
|
25
29
|
onSuccess?: () => void | Promise<void>;
|
|
26
30
|
paymentMethodLabels?: readonly string[];
|
|
27
31
|
paypalButtonLabel: string;
|
|
@@ -57,5 +61,5 @@ export type SharedCheckoutSpecialOfferDialogProps = {
|
|
|
57
61
|
themeColor: string;
|
|
58
62
|
title: string;
|
|
59
63
|
};
|
|
60
|
-
export declare function SharedStripeCheckoutV2Dialog({ clientSecret, onClose, stripePromise, ...props }: SharedStripeCheckoutV2DialogProps): import("react/jsx-runtime").JSX.Element;
|
|
64
|
+
export declare function SharedStripeCheckoutV2Dialog({ checkoutAnalytics, checkoutSessionId, clientSecret, onClose, stripePromise, ...props }: SharedStripeCheckoutV2DialogProps): import("react/jsx-runtime").JSX.Element;
|
|
61
65
|
export declare function SharedCheckoutSpecialOfferDialog({ buttonColor, buttonLabel, description, discountLabel, imageAlt, imageSrc, onAccept, themeColor, title, }: SharedCheckoutSpecialOfferDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -17,6 +17,7 @@ import { PaymentBrandMark, SecurityLockIcon } from './CheckoutBrandAssets.js';
|
|
|
17
17
|
import { StripeCheckoutExpressCheckoutButton } from './StripeCheckoutExpressCheckoutButton.js';
|
|
18
18
|
import { isInactiveCheckoutSessionError } from '../../providers/stripe/useStripeSubscriptionCheckoutSession.js';
|
|
19
19
|
import { getStripeWalletPaymentMethodOrder, usePlatformWalletPaymentMethods, } from './walletPlatform.js';
|
|
20
|
+
import { trackPaidStripeSubscriptionCheckoutCompleted, trackStripeSubscriptionCheckoutStarted, } from '../../services/checkoutCompletionAnalytics.service.js';
|
|
20
21
|
const defaultPaymentMethodLabels = ['Visa', 'Mastercard', 'Maestro', 'Discover'];
|
|
21
22
|
const stripeCheckoutV2Appearance = {
|
|
22
23
|
labels: 'above',
|
|
@@ -52,7 +53,7 @@ function getFriendlyCardErrorMessage(error) {
|
|
|
52
53
|
? error.message
|
|
53
54
|
: 'Payment failed. Please review your card details and try again.';
|
|
54
55
|
}
|
|
55
|
-
function SharedStripeCheckoutV2Form({ amountCents, buttonColor, cardSubmitLabel, closeAriaLabel, countdownLabel, customerEmail, customerEmailEditable = false, customerEmailLabel = 'Email', customerEmailPlaceholder = 'Enter your email', customerEmailInvalidMessage = 'Enter a valid email address.', customerName, discountAmountLabel, discountLabel, discountPercent, initialWalletAvailable, onClose, onCustomerEmailChange, onCustomerEmailCommit, onError, onInactiveCheckoutSession, onSuccess, paymentMethodLabels = defaultPaymentMethodLabels, promoActive, processingLabel, promoCode, promoCodeLabel, remainingSeconds, returnUrl, savedLabel, secureLabel, satisfactionLabel, satisfactionPercent, summaryLabel, themeColor, title, totalLabel, totalValue, walletButtonLabel, originalPriceLabel, originalPriceValue, }) {
|
|
56
|
+
function SharedStripeCheckoutV2Form({ amountCents, buttonColor, cardSubmitLabel, closeAriaLabel, countdownLabel, checkoutAnalytics, checkoutSessionId, customerEmail, customerEmailEditable = false, customerEmailLabel = 'Email', customerEmailPlaceholder = 'Enter your email', customerEmailInvalidMessage = 'Enter a valid email address.', customerName, discountAmountLabel, discountLabel, discountPercent, initialWalletAvailable, onClose, onCustomerEmailChange, onCustomerEmailCommit, onError, onInactiveCheckoutSession, onPaymentInfoSubmitted, onSuccess, paymentMethodLabels = defaultPaymentMethodLabels, promoActive, processingLabel, promoCode, promoCodeLabel, remainingSeconds, returnUrl, savedLabel, secureLabel, satisfactionLabel, satisfactionPercent, summaryLabel, themeColor, title, totalLabel, totalValue, walletButtonLabel, originalPriceLabel, originalPriceValue, }) {
|
|
56
57
|
const checkoutState = useCheckout();
|
|
57
58
|
const walletPaymentMethods = usePlatformWalletPaymentMethods();
|
|
58
59
|
const [selectedMethod, setSelectedMethod] = useState('wallet');
|
|
@@ -157,6 +158,12 @@ function SharedStripeCheckoutV2Form({ amountCents, buttonColor, cardSubmitLabel,
|
|
|
157
158
|
setSelectedMethod('card');
|
|
158
159
|
}
|
|
159
160
|
};
|
|
161
|
+
const trackCheckoutSuccess = async () => {
|
|
162
|
+
if (checkoutAnalytics) {
|
|
163
|
+
await trackPaidStripeSubscriptionCheckoutCompleted(Object.assign({ checkoutSessionId }, checkoutAnalytics));
|
|
164
|
+
}
|
|
165
|
+
await (onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess());
|
|
166
|
+
};
|
|
160
167
|
useEffect(() => {
|
|
161
168
|
if (effectiveSelectedMethod !== 'card' || typeof window === 'undefined') {
|
|
162
169
|
return;
|
|
@@ -217,6 +224,7 @@ function SharedStripeCheckoutV2Form({ amountCents, buttonColor, cardSubmitLabel,
|
|
|
217
224
|
setSubmitting(false);
|
|
218
225
|
return;
|
|
219
226
|
}
|
|
227
|
+
await (onPaymentInfoSubmitted === null || onPaymentInfoSubmitted === void 0 ? void 0 : onPaymentInfoSubmitted());
|
|
220
228
|
const result = await checkoutState.checkout.confirm({
|
|
221
229
|
redirect: 'if_required',
|
|
222
230
|
});
|
|
@@ -227,7 +235,7 @@ function SharedStripeCheckoutV2Form({ amountCents, buttonColor, cardSubmitLabel,
|
|
|
227
235
|
return;
|
|
228
236
|
}
|
|
229
237
|
if (typeof window !== 'undefined') {
|
|
230
|
-
await (
|
|
238
|
+
await trackCheckoutSuccess();
|
|
231
239
|
window.location.assign(returnUrl);
|
|
232
240
|
return;
|
|
233
241
|
}
|
|
@@ -259,7 +267,7 @@ function SharedStripeCheckoutV2Form({ amountCents, buttonColor, cardSubmitLabel,
|
|
|
259
267
|
effectiveSelectedMethod === 'wallet' ? 'is-visible' : '',
|
|
260
268
|
]
|
|
261
269
|
.filter(Boolean)
|
|
262
|
-
.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, className: 'shared-checkout-v2-express-buttons', confirmEmail: false, customerEmail: resolvedCustomerEmail, customerName: customerName, initialAvailable: initialWalletAvailable, keepInitialAvailableOnReady: initialWalletAvailable === true, onAvailabilityChange: handleWalletAvailabilityChange, onError: setSharedError, onSuccess:
|
|
270
|
+
.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, className: 'shared-checkout-v2-express-buttons', confirmEmail: false, customerEmail: resolvedCustomerEmail, customerName: customerName, initialAvailable: initialWalletAvailable, keepInitialAvailableOnReady: initialWalletAvailable === true, onAvailabilityChange: handleWalletAvailabilityChange, onError: setSharedError, onPaymentInfoSubmitted: onPaymentInfoSubmitted, onSuccess: trackCheckoutSuccess, options: expressCheckoutOptions, returnUrl: returnUrl, summaryLabel: expressCheckoutSummaryLabel }), _jsx("span", { className: 'shared-checkout-v2-sr-only', children: walletAriaLabel })] })] }), _jsxs("div", { hidden: effectiveSelectedMethod !== 'card', className: [
|
|
263
271
|
'shared-checkout-v2-card-panel',
|
|
264
272
|
effectiveSelectedMethod === 'card' ? 'is-visible' : '',
|
|
265
273
|
]
|
|
@@ -273,13 +281,19 @@ function SharedStripeCheckoutV2Form({ amountCents, buttonColor, cardSubmitLabel,
|
|
|
273
281
|
} }) })] }), _jsx(PaymentElement, { options: paymentElementOptions })] }), visibleError ? _jsx("p", { className: 'shared-checkout-v2-error', children: visibleError }) : null, _jsxs("button", { type: 'submit', className: 'shared-checkout-v2-card-submit', disabled: submitting, children: [_jsx(SecurityLockIcon, { className: 'shared-checkout-v2-card-submit-icon' }), submitting ? processingLabel : cardSubmitLabel] }), _jsx("div", { className: 'shared-checkout-v2-card-brands', "aria-hidden": 'true', children: cardBrandLabels.map((label) => (_jsx(PaymentBrandMark, { label: label, className: 'shared-checkout-v2-card-brand' }, label))) }), _jsxs("p", { className: 'shared-checkout-v2-secure-pill shared-checkout-v2-secure-pill--card', children: [_jsx(SecurityLockIcon, { className: 'shared-checkout-v2-secure-pill-icon' }), secureLabel] })] })] })] }));
|
|
274
282
|
}
|
|
275
283
|
export function SharedStripeCheckoutV2Dialog(_a) {
|
|
276
|
-
var { clientSecret, onClose, stripePromise } = _a, props = __rest(_a, ["clientSecret", "onClose", "stripePromise"]);
|
|
284
|
+
var { checkoutAnalytics, checkoutSessionId, clientSecret, onClose, stripePromise } = _a, props = __rest(_a, ["checkoutAnalytics", "checkoutSessionId", "clientSecret", "onClose", "stripePromise"]);
|
|
277
285
|
const checkoutProviderOptions = useMemo(() => ({
|
|
278
286
|
clientSecret,
|
|
279
287
|
elementsOptions: {
|
|
280
288
|
appearance: stripeCheckoutV2Appearance,
|
|
281
289
|
},
|
|
282
290
|
}), [clientSecret]);
|
|
291
|
+
useEffect(() => {
|
|
292
|
+
if (!checkoutAnalytics) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
void trackStripeSubscriptionCheckoutStarted(Object.assign({ checkoutSessionId }, checkoutAnalytics));
|
|
296
|
+
}, [checkoutAnalytics, checkoutSessionId, clientSecret]);
|
|
283
297
|
useEffect(() => {
|
|
284
298
|
const handleKeyDown = (event) => {
|
|
285
299
|
if (event.key === 'Escape') {
|
|
@@ -289,7 +303,7 @@ export function SharedStripeCheckoutV2Dialog(_a) {
|
|
|
289
303
|
window.addEventListener('keydown', handleKeyDown);
|
|
290
304
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
291
305
|
}, [onClose]);
|
|
292
|
-
return (_jsxs(_Fragment, { children: [_jsx("div", { className: 'shared-checkout-v2-overlay', children: _jsx("div", { className: 'shared-checkout-v2-dialog', role: 'dialog', "aria-modal": 'true', "aria-labelledby": 'shared-checkout-v2-title', children: _jsx(CheckoutProvider, { stripe: stripePromise, options: checkoutProviderOptions, children: _jsx(SharedStripeCheckoutV2Form, Object.assign({}, props, { clientSecret: clientSecret, onClose: onClose })) }, clientSecret) }) }), _jsx("style", { children: sharedStripeCheckoutV2Styles })] }));
|
|
306
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: 'shared-checkout-v2-overlay', children: _jsx("div", { className: 'shared-checkout-v2-dialog', role: 'dialog', "aria-modal": 'true', "aria-labelledby": 'shared-checkout-v2-title', children: _jsx(CheckoutProvider, { stripe: stripePromise, options: checkoutProviderOptions, children: _jsx(SharedStripeCheckoutV2Form, Object.assign({}, props, { checkoutAnalytics: checkoutAnalytics, checkoutSessionId: checkoutSessionId, clientSecret: clientSecret, onClose: onClose })) }, clientSecret) }) }), _jsx("style", { children: sharedStripeCheckoutV2Styles })] }));
|
|
293
307
|
}
|
|
294
308
|
function SharedCheckoutSpecialOfferGift({ discountLabel, imageAlt, imageSrc, }) {
|
|
295
309
|
if (imageSrc) {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { AvailablePaymentMethods, StripeCheckoutExpressCheckoutElementOptions } from '@stripe/stripe-js';
|
|
2
|
+
import { type StripeSubscriptionCheckoutAnalyticsContext } from '../../services/checkoutCompletionAnalytics.service.js';
|
|
2
3
|
export type StripeCheckoutExpressCheckoutButtonProps = {
|
|
3
4
|
amountCents: number;
|
|
4
5
|
beforeConfirm?: () => Promise<string | null>;
|
|
6
|
+
checkoutAnalytics?: StripeSubscriptionCheckoutAnalyticsContext | null;
|
|
7
|
+
checkoutSessionId?: string | null;
|
|
5
8
|
summaryLabel: string;
|
|
6
9
|
returnUrl: string;
|
|
7
10
|
confirmEmail?: boolean;
|
|
@@ -17,6 +20,7 @@ export type StripeCheckoutExpressCheckoutButtonProps = {
|
|
|
17
20
|
onAvailabilityChange?: (available: boolean) => void;
|
|
18
21
|
onCancel?: () => void;
|
|
19
22
|
onError?: (message: string | null) => void;
|
|
23
|
+
onPaymentInfoSubmitted?: () => void | Promise<void>;
|
|
20
24
|
onSuccess?: () => void | Promise<void>;
|
|
21
25
|
};
|
|
22
|
-
export declare function StripeCheckoutExpressCheckoutButton({ beforeConfirm, returnUrl, confirmEmail, customerEmail, className, options, availabilityPaymentMethods, initialAvailable, keepInitialAvailableOnReady, serverUpdateKey, onServerUpdate, onAvailabilityChange, onCancel, onError, onSuccess, }: StripeCheckoutExpressCheckoutButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
26
|
+
export declare function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAnalytics, checkoutSessionId, returnUrl, confirmEmail, customerEmail, className, options, availabilityPaymentMethods, initialAvailable, keepInitialAvailableOnReady, serverUpdateKey, onServerUpdate, onAvailabilityChange, onCancel, onError, onPaymentInfoSubmitted, onSuccess, }: StripeCheckoutExpressCheckoutButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { ExpressCheckoutElement, useCheckout, } from '@stripe/react-stripe-js/checkout';
|
|
5
|
+
import { trackPaidStripeSubscriptionCheckoutCompleted, trackStripeSubscriptionCheckoutStarted, } from '../../services/checkoutCompletionAnalytics.service.js';
|
|
5
6
|
const ExpressCheckoutElementWithCancel = ExpressCheckoutElement;
|
|
6
7
|
const buildDefaultOptions = () => ({
|
|
7
8
|
buttonHeight: 55,
|
|
@@ -26,7 +27,7 @@ const buildDefaultOptions = () => ({
|
|
|
26
27
|
paypal: 'never',
|
|
27
28
|
},
|
|
28
29
|
});
|
|
29
|
-
export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, returnUrl, confirmEmail = true, customerEmail, className, options, availabilityPaymentMethods = ['applePay'], initialAvailable, keepInitialAvailableOnReady = false, serverUpdateKey, onServerUpdate, onAvailabilityChange, onCancel, onError, onSuccess, }) {
|
|
30
|
+
export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAnalytics, checkoutSessionId, returnUrl, confirmEmail = true, customerEmail, className, options, availabilityPaymentMethods = ['applePay'], initialAvailable, keepInitialAvailableOnReady = false, serverUpdateKey, onServerUpdate, onAvailabilityChange, onCancel, onError, onPaymentInfoSubmitted, onSuccess, }) {
|
|
30
31
|
const checkoutState = useCheckout();
|
|
31
32
|
const appliedServerUpdateKeyRef = useRef('');
|
|
32
33
|
const serverUpdatePromiseRef = useRef(null);
|
|
@@ -86,6 +87,12 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, returnUrl,
|
|
|
86
87
|
}
|
|
87
88
|
void ensureServerUpdated();
|
|
88
89
|
}, [checkoutState.type, ensureServerUpdated, normalizedServerUpdateKey, onServerUpdate]);
|
|
90
|
+
const trackCheckoutSuccess = async () => {
|
|
91
|
+
if (checkoutAnalytics) {
|
|
92
|
+
await trackPaidStripeSubscriptionCheckoutCompleted(Object.assign({ checkoutSessionId }, checkoutAnalytics));
|
|
93
|
+
}
|
|
94
|
+
await (onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess());
|
|
95
|
+
};
|
|
89
96
|
const handleConfirm = async (event) => {
|
|
90
97
|
if (checkoutState.type !== 'success') {
|
|
91
98
|
const message = checkoutState.type === 'error'
|
|
@@ -103,6 +110,9 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, returnUrl,
|
|
|
103
110
|
event.paymentFailed({ reason: 'fail', message });
|
|
104
111
|
return;
|
|
105
112
|
}
|
|
113
|
+
if (checkoutAnalytics) {
|
|
114
|
+
await trackStripeSubscriptionCheckoutStarted(Object.assign({ checkoutSessionId }, checkoutAnalytics));
|
|
115
|
+
}
|
|
106
116
|
let preparedCustomerEmail = (customerEmail === null || customerEmail === void 0 ? void 0 : customerEmail.trim()) || null;
|
|
107
117
|
if (beforeConfirm) {
|
|
108
118
|
try {
|
|
@@ -123,6 +133,7 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, returnUrl,
|
|
|
123
133
|
event.paymentFailed({ reason: 'invalid_payment_data', message });
|
|
124
134
|
return;
|
|
125
135
|
}
|
|
136
|
+
await (onPaymentInfoSubmitted === null || onPaymentInfoSubmitted === void 0 ? void 0 : onPaymentInfoSubmitted());
|
|
126
137
|
const result = await checkoutState.checkout.confirm(Object.assign(Object.assign({}, (confirmEmail
|
|
127
138
|
? { email: preparedCustomerEmail || (customerEmail === null || customerEmail === void 0 ? void 0 : customerEmail.trim()) || undefined }
|
|
128
139
|
: {})), { expressCheckoutConfirmEvent: event, redirect: 'if_required' }));
|
|
@@ -134,7 +145,7 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, returnUrl,
|
|
|
134
145
|
}
|
|
135
146
|
onError === null || onError === void 0 ? void 0 : onError(null);
|
|
136
147
|
if (typeof window !== 'undefined') {
|
|
137
|
-
await (
|
|
148
|
+
await trackCheckoutSuccess();
|
|
138
149
|
window.location.assign(returnUrl);
|
|
139
150
|
}
|
|
140
151
|
};
|
|
@@ -14,6 +14,7 @@ export type StripeExpressCheckoutButtonProps = {
|
|
|
14
14
|
onAvailabilityChange?: (available: boolean) => void;
|
|
15
15
|
onCancel?: () => void;
|
|
16
16
|
onError?: (message: string | null) => void;
|
|
17
|
+
onPaymentInfoSubmitted?: () => void | Promise<void>;
|
|
17
18
|
onSuccess?: () => void | Promise<void>;
|
|
18
19
|
};
|
|
19
|
-
export declare function StripeExpressCheckoutButton({ amountCents, beforeConfirm, summaryLabel, returnUrl, customerEmail, customerName, className, options, availabilityPaymentMethods, initialAvailable, keepInitialAvailableOnReady, onAvailabilityChange, onCancel, onError, onSuccess, }: StripeExpressCheckoutButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
export declare function StripeExpressCheckoutButton({ amountCents, beforeConfirm, summaryLabel, returnUrl, customerEmail, customerName, className, options, availabilityPaymentMethods, initialAvailable, keepInitialAvailableOnReady, onAvailabilityChange, onCancel, onError, onPaymentInfoSubmitted, onSuccess, }: StripeExpressCheckoutButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -53,7 +53,7 @@ const buildDefaultOptions = (input) => {
|
|
|
53
53
|
const isSuccessfulIntentStatus = (status) => {
|
|
54
54
|
return status === 'succeeded' || status === 'processing' || status === 'requires_capture';
|
|
55
55
|
};
|
|
56
|
-
export function StripeExpressCheckoutButton({ amountCents, beforeConfirm, summaryLabel, returnUrl, customerEmail, customerName, className, options, availabilityPaymentMethods = ['applePay'], initialAvailable, keepInitialAvailableOnReady = false, onAvailabilityChange, onCancel, onError, onSuccess, }) {
|
|
56
|
+
export function StripeExpressCheckoutButton({ amountCents, beforeConfirm, summaryLabel, returnUrl, customerEmail, customerName, className, options, availabilityPaymentMethods = ['applePay'], initialAvailable, keepInitialAvailableOnReady = false, onAvailabilityChange, onCancel, onError, onPaymentInfoSubmitted, onSuccess, }) {
|
|
57
57
|
const stripe = useStripe();
|
|
58
58
|
const elements = useElements();
|
|
59
59
|
const [walletAvailable, setWalletAvailable] = useState(initialAvailable !== null && initialAvailable !== void 0 ? initialAvailable : null);
|
|
@@ -104,6 +104,7 @@ export function StripeExpressCheckoutButton({ amountCents, beforeConfirm, summar
|
|
|
104
104
|
event.paymentFailed({ reason: 'invalid_payment_data', message });
|
|
105
105
|
return;
|
|
106
106
|
}
|
|
107
|
+
await (onPaymentInfoSubmitted === null || onPaymentInfoSubmitted === void 0 ? void 0 : onPaymentInfoSubmitted());
|
|
107
108
|
const result = await stripe.confirmPayment({
|
|
108
109
|
elements,
|
|
109
110
|
confirmParams: {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import type { AvailablePaymentMethods, StripeCheckoutExpressCheckoutElementOptions } from '@stripe/stripe-js';
|
|
3
|
+
import type { StripeSubscriptionCheckoutAnalyticsContext } from '../../services/checkoutCompletionAnalytics.service.js';
|
|
3
4
|
import { type StripeSubscriptionCheckoutSession } from './useStripeSubscriptionCheckoutSession.js';
|
|
4
5
|
export type WalletSubscriptionCheckoutRenderStateInput = {
|
|
5
6
|
expressCheckoutAllowed: boolean;
|
|
@@ -25,6 +26,7 @@ export type WalletSubscriptionCheckoutSlotProps = {
|
|
|
25
26
|
amountCents: number;
|
|
26
27
|
availabilityPaymentMethod: keyof AvailablePaymentMethods;
|
|
27
28
|
beforeConfirm?: () => Promise<string | null>;
|
|
29
|
+
checkoutAnalytics?: StripeSubscriptionCheckoutAnalyticsContext | null;
|
|
28
30
|
className?: string;
|
|
29
31
|
customerEmail?: string | null;
|
|
30
32
|
customerName?: string | null;
|
|
@@ -33,6 +35,7 @@ export type WalletSubscriptionCheckoutSlotProps = {
|
|
|
33
35
|
onAvailabilityChange?: (available: boolean) => void;
|
|
34
36
|
onCancel?: () => void;
|
|
35
37
|
onError?: (message: string | null) => void;
|
|
38
|
+
onPaymentInfoSubmitted?: () => void | Promise<void>;
|
|
36
39
|
onSuccess?: () => void | Promise<void>;
|
|
37
40
|
onUnavailableClick?: () => Promise<void> | void;
|
|
38
41
|
options?: Partial<StripeCheckoutExpressCheckoutElementOptions>;
|
|
@@ -45,5 +48,5 @@ export type WalletSubscriptionCheckoutSlotProps = {
|
|
|
45
48
|
suspended?: boolean;
|
|
46
49
|
};
|
|
47
50
|
export declare function WalletSubscriptionCheckoutLoadingPlaceholder({ className, label, }: Pick<WalletPlaceholderButtonProps, 'className' | 'label'>): import("react/jsx-runtime").JSX.Element;
|
|
48
|
-
export declare function WalletSubscriptionCheckoutSlot({ amountCents, availabilityPaymentMethod, beforeConfirm, className, customerEmail, customerName, disabled, expressCheckoutAllowed, onAvailabilityChange, onCancel, onError, onSuccess, onUnavailableClick, options, placeholderLabel, renderPreparingPlaceholder, renderPlaceholder, returnUrl, session, summaryLabel, suspended, }: WalletSubscriptionCheckoutSlotProps): import("react/jsx-runtime").JSX.Element;
|
|
51
|
+
export declare function WalletSubscriptionCheckoutSlot({ amountCents, availabilityPaymentMethod, beforeConfirm, checkoutAnalytics, className, customerEmail, customerName, disabled, expressCheckoutAllowed, onAvailabilityChange, onCancel, onError, onPaymentInfoSubmitted, onSuccess, onUnavailableClick, options, placeholderLabel, renderPreparingPlaceholder, renderPlaceholder, returnUrl, session, summaryLabel, suspended, }: WalletSubscriptionCheckoutSlotProps): import("react/jsx-runtime").JSX.Element;
|
|
49
52
|
export {};
|
|
@@ -27,7 +27,7 @@ export function WalletSubscriptionCheckoutLoadingPlaceholder({ className, label,
|
|
|
27
27
|
.filter(Boolean)
|
|
28
28
|
.join(' '), "aria-label": label, role: 'status', children: [_jsx("span", { className: 'wallet-subscription-checkout-slot__loading-bar' }), _jsx("style", { children: walletSubscriptionCheckoutLoadingPlaceholderStyles })] }));
|
|
29
29
|
}
|
|
30
|
-
export function WalletSubscriptionCheckoutSlot({ amountCents, availabilityPaymentMethod, beforeConfirm, className, customerEmail, customerName, disabled = false, expressCheckoutAllowed = true, onAvailabilityChange, onCancel, onError, onSuccess, onUnavailableClick, options, placeholderLabel, renderPreparingPlaceholder, renderPlaceholder, returnUrl, session, summaryLabel, suspended = false, }) {
|
|
30
|
+
export function WalletSubscriptionCheckoutSlot({ amountCents, availabilityPaymentMethod, beforeConfirm, checkoutAnalytics, className, customerEmail, customerName, disabled = false, expressCheckoutAllowed = true, onAvailabilityChange, onCancel, onError, onPaymentInfoSubmitted, onSuccess, onUnavailableClick, options, placeholderLabel, renderPreparingPlaceholder, renderPlaceholder, returnUrl, session, summaryLabel, suspended = false, }) {
|
|
31
31
|
const [walletAvailability, setWalletAvailability] = useState({
|
|
32
32
|
available: null,
|
|
33
33
|
intentKey: session.intentKey,
|
|
@@ -131,7 +131,7 @@ export function WalletSubscriptionCheckoutSlot({ amountCents, availabilityPaymen
|
|
|
131
131
|
.filter(Boolean)
|
|
132
132
|
.join(' '), "aria-busy": placeholderIsPreparing || undefined, children: [shouldRenderExpressCheckout &&
|
|
133
133
|
session.activeClientSecret &&
|
|
134
|
-
session.stripePromise ? (_jsx(StripeExpressCheckoutElement, { amountCents: amountCents, availabilityPaymentMethods: [availabilityPaymentMethod], beforeConfirm: beforeConfirm, className: className, clientSecret: session.activeClientSecret, customerEmail: customerEmail, customerName: customerName, onAvailabilityChange: handleAvailabilityChange, onCancel: onCancel, onError: handleError, onSuccess: onSuccess, options: options, returnUrl: returnUrl, serverUpdateKey: serverUpdateKey, stripePromise: session.stripePromise, summaryLabel: summaryLabel, onServerUpdate: session.updateCheckoutSessionPlan })) : null, shouldRenderPlaceholder ? (_jsx("div", { className: 'wallet-subscription-checkout-slot__placeholder', children: placeholderRenderer({
|
|
134
|
+
session.stripePromise ? (_jsx(StripeExpressCheckoutElement, { amountCents: amountCents, availabilityPaymentMethods: [availabilityPaymentMethod], beforeConfirm: beforeConfirm, checkoutAnalytics: checkoutAnalytics, checkoutSessionId: session.checkoutSessionId, className: className, clientSecret: session.activeClientSecret, customerEmail: customerEmail, customerName: customerName, onAvailabilityChange: handleAvailabilityChange, onCancel: onCancel, onError: handleError, onPaymentInfoSubmitted: onPaymentInfoSubmitted, onSuccess: onSuccess, options: options, returnUrl: returnUrl, serverUpdateKey: serverUpdateKey, stripePromise: session.stripePromise, summaryLabel: summaryLabel, onServerUpdate: session.updateCheckoutSessionPlan })) : null, shouldRenderPlaceholder ? (_jsx("div", { className: 'wallet-subscription-checkout-slot__placeholder', children: placeholderRenderer({
|
|
135
135
|
className,
|
|
136
136
|
disabled: slotDisabled,
|
|
137
137
|
label: placeholderLabel,
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import type { RuntimeMode } from '@funnelsgrove/runtime';
|
|
2
2
|
import { type StripeRuntimeConfigOverrides } from './stripe.service.js';
|
|
3
|
-
export type
|
|
4
|
-
checkoutSessionId?: string | null;
|
|
3
|
+
export type StripeSubscriptionCheckoutAnalyticsContext = {
|
|
5
4
|
environment?: RuntimeMode;
|
|
5
|
+
featureFlags?: Record<string, string | null | undefined>;
|
|
6
6
|
metadata?: Record<string, unknown> | null;
|
|
7
7
|
runtimeConfig?: StripeRuntimeConfigOverrides;
|
|
8
8
|
stepId?: string;
|
|
9
9
|
stepName?: string;
|
|
10
10
|
};
|
|
11
|
+
export type TrackStripeSubscriptionCheckoutStartedInput = StripeSubscriptionCheckoutAnalyticsContext & {
|
|
12
|
+
checkoutSessionId?: string | null;
|
|
13
|
+
};
|
|
14
|
+
export type TrackPaidStripeSubscriptionCheckoutCompletedInput = StripeSubscriptionCheckoutAnalyticsContext & {
|
|
15
|
+
checkoutSessionId?: string | null;
|
|
16
|
+
};
|
|
17
|
+
export type TrackStripeSubscriptionCheckoutStartedResult = 'already_tracked' | 'missing_checkout_session' | 'tracked';
|
|
11
18
|
export type TrackPaidStripeSubscriptionCheckoutCompletedResult = 'already_tracked' | 'missing_checkout_session' | 'tracked' | 'unpaid';
|
|
19
|
+
export declare function trackStripeSubscriptionCheckoutStarted(input: TrackStripeSubscriptionCheckoutStartedInput): Promise<TrackStripeSubscriptionCheckoutStartedResult>;
|
|
12
20
|
export declare function trackPaidStripeSubscriptionCheckoutCompleted(input: TrackPaidStripeSubscriptionCheckoutCompletedInput): Promise<TrackPaidStripeSubscriptionCheckoutCompletedResult>;
|
|
@@ -6,44 +6,75 @@ const asRecord = (value) => {
|
|
|
6
6
|
: {};
|
|
7
7
|
};
|
|
8
8
|
const activeCheckoutCompletionKeys = new Set();
|
|
9
|
-
const
|
|
9
|
+
const activeCheckoutStartedKeys = new Set();
|
|
10
|
+
const buildCheckoutStorageKey = (eventName, checkoutSessionId, runtimeConfig) => {
|
|
10
11
|
var _a;
|
|
11
12
|
const funnelId = ((_a = runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelId) === null || _a === void 0 ? void 0 : _a.trim()) || 'unknown_funnel';
|
|
12
|
-
return `funnelsgrove
|
|
13
|
+
return `funnelsgrove:${eventName}:${funnelId}:${checkoutSessionId}`;
|
|
13
14
|
};
|
|
14
|
-
const hasTrackedCheckoutSession = (checkoutSessionId, runtimeConfig) => {
|
|
15
|
+
const hasTrackedCheckoutSession = (eventName, checkoutSessionId, runtimeConfig) => {
|
|
15
16
|
var _a;
|
|
16
17
|
if (typeof window === 'undefined') {
|
|
17
18
|
return false;
|
|
18
19
|
}
|
|
19
20
|
try {
|
|
20
|
-
return Boolean((_a = window.sessionStorage) === null || _a === void 0 ? void 0 : _a.getItem(
|
|
21
|
+
return Boolean((_a = window.sessionStorage) === null || _a === void 0 ? void 0 : _a.getItem(buildCheckoutStorageKey(eventName, checkoutSessionId, runtimeConfig)));
|
|
21
22
|
}
|
|
22
23
|
catch (_b) {
|
|
23
24
|
return false;
|
|
24
25
|
}
|
|
25
26
|
};
|
|
26
|
-
const markCheckoutSessionTracked = (checkoutSessionId, runtimeConfig) => {
|
|
27
|
+
const markCheckoutSessionTracked = (eventName, checkoutSessionId, runtimeConfig) => {
|
|
27
28
|
var _a;
|
|
28
29
|
if (typeof window === 'undefined') {
|
|
29
30
|
return;
|
|
30
31
|
}
|
|
31
32
|
try {
|
|
32
|
-
(_a = window.sessionStorage) === null || _a === void 0 ? void 0 : _a.setItem(
|
|
33
|
+
(_a = window.sessionStorage) === null || _a === void 0 ? void 0 : _a.setItem(buildCheckoutStorageKey(eventName, checkoutSessionId, runtimeConfig), '1');
|
|
33
34
|
}
|
|
34
35
|
catch (_b) {
|
|
35
36
|
// A stable checkout-session event id still protects provider-side dedupe.
|
|
36
37
|
}
|
|
37
38
|
};
|
|
39
|
+
export async function trackStripeSubscriptionCheckoutStarted(input) {
|
|
40
|
+
var _a;
|
|
41
|
+
const checkoutSessionId = ((_a = input.checkoutSessionId) === null || _a === void 0 ? void 0 : _a.trim()) || '';
|
|
42
|
+
if (!checkoutSessionId) {
|
|
43
|
+
return 'missing_checkout_session';
|
|
44
|
+
}
|
|
45
|
+
const startedKey = buildCheckoutStorageKey('checkout_started', checkoutSessionId, input.runtimeConfig);
|
|
46
|
+
if (activeCheckoutStartedKeys.has(startedKey) ||
|
|
47
|
+
hasTrackedCheckoutSession('checkout_started', checkoutSessionId, input.runtimeConfig)) {
|
|
48
|
+
return 'already_tracked';
|
|
49
|
+
}
|
|
50
|
+
activeCheckoutStartedKeys.add(startedKey);
|
|
51
|
+
try {
|
|
52
|
+
const metadata = asRecord(input.metadata);
|
|
53
|
+
const conversionInput = {
|
|
54
|
+
eventId: checkoutSessionId,
|
|
55
|
+
featureFlags: input.featureFlags,
|
|
56
|
+
stepId: input.stepId,
|
|
57
|
+
stepName: input.stepName,
|
|
58
|
+
metadata: Object.assign(Object.assign({}, metadata), { checkoutSessionId, checkout_session_id: checkoutSessionId, environment: input.environment || metadata.environment }),
|
|
59
|
+
};
|
|
60
|
+
publicAnalyticsSdk.trackCheckoutStarted(conversionInput);
|
|
61
|
+
markCheckoutSessionTracked('checkout_started', checkoutSessionId, input.runtimeConfig);
|
|
62
|
+
await publicAnalyticsSdk.flush().catch(() => 0);
|
|
63
|
+
return 'tracked';
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
activeCheckoutStartedKeys.delete(startedKey);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
38
69
|
export async function trackPaidStripeSubscriptionCheckoutCompleted(input) {
|
|
39
70
|
var _a;
|
|
40
71
|
const checkoutSessionId = ((_a = input.checkoutSessionId) === null || _a === void 0 ? void 0 : _a.trim()) || '';
|
|
41
72
|
if (!checkoutSessionId) {
|
|
42
73
|
return 'missing_checkout_session';
|
|
43
74
|
}
|
|
44
|
-
const completionKey =
|
|
75
|
+
const completionKey = buildCheckoutStorageKey('checkout_completed', checkoutSessionId, input.runtimeConfig);
|
|
45
76
|
if (activeCheckoutCompletionKeys.has(completionKey) ||
|
|
46
|
-
hasTrackedCheckoutSession(checkoutSessionId, input.runtimeConfig)) {
|
|
77
|
+
hasTrackedCheckoutSession('checkout_completed', checkoutSessionId, input.runtimeConfig)) {
|
|
47
78
|
return 'already_tracked';
|
|
48
79
|
}
|
|
49
80
|
activeCheckoutCompletionKeys.add(completionKey);
|
|
@@ -59,12 +90,13 @@ export async function trackPaidStripeSubscriptionCheckoutCompleted(input) {
|
|
|
59
90
|
const analyticsMetadata = asRecord(checkoutStatus.analyticsMetadata);
|
|
60
91
|
const conversionInput = {
|
|
61
92
|
eventId: checkoutSessionId,
|
|
93
|
+
featureFlags: input.featureFlags,
|
|
62
94
|
stepId: input.stepId,
|
|
63
95
|
stepName: input.stepName,
|
|
64
96
|
metadata: Object.assign(Object.assign(Object.assign({}, asRecord(input.metadata)), analyticsMetadata), { checkoutSessionId, checkout_session_id: checkoutSessionId, checkout_session_status: checkoutStatus.status || undefined, payment_status: checkoutStatus.paymentStatus || undefined, environment: checkoutStatus.environment || input.environment || analyticsMetadata.environment }),
|
|
65
97
|
};
|
|
66
98
|
publicAnalyticsSdk.trackCheckoutCompleted(conversionInput);
|
|
67
|
-
markCheckoutSessionTracked(checkoutSessionId, input.runtimeConfig);
|
|
99
|
+
markCheckoutSessionTracked('checkout_completed', checkoutSessionId, input.runtimeConfig);
|
|
68
100
|
await publicAnalyticsSdk.flush().catch(() => 0);
|
|
69
101
|
return 'tracked';
|
|
70
102
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@funnelsgrove/payments",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.41",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"test:run": "vitest run --passWithNoTests"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@funnelsgrove/analytics": "^0.1.
|
|
30
|
+
"@funnelsgrove/analytics": "^0.1.15",
|
|
31
31
|
"@funnelsgrove/runtime": "^0.1.18",
|
|
32
32
|
"@stripe/react-stripe-js": "^5.6.0",
|
|
33
33
|
"@stripe/stripe-js": "^8.7.0",
|