@funnelsgrove/payments 0.11.14 → 0.11.16
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/stripe/components/SharedStripeCheckoutV2Dialog.js +1 -1
- package/dist/providers/stripe/components/StripeCheckoutExpressCheckoutButton.d.ts +6 -1
- package/dist/providers/stripe/components/StripeCheckoutExpressCheckoutButton.js +43 -5
- package/dist/providers/stripe/hooks/useStripeSubscriptionCheckoutSession.d.ts +3 -1
- package/dist/providers/stripe/hooks/useStripeSubscriptionCheckoutSession.js +42 -13
- package/dist/services/checkoutObservability.service.d.ts +3 -0
- package/dist/services/checkoutObservability.service.js +37 -5
- package/package.json +1 -1
|
@@ -319,7 +319,7 @@ function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonColor, c
|
|
|
319
319
|
effectiveSelectedMethod === 'wallet' ? 'is-visible' : '',
|
|
320
320
|
]
|
|
321
321
|
.filter(Boolean)
|
|
322
|
-
.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, 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: [
|
|
322
|
+
.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: [
|
|
323
323
|
'shared-checkout-v2-card-panel',
|
|
324
324
|
effectiveSelectedMethod === 'card' ? 'is-visible' : '',
|
|
325
325
|
]
|
|
@@ -22,7 +22,12 @@ export type StripeCheckoutExpressCheckoutButtonProps = {
|
|
|
22
22
|
onCancel?: () => void;
|
|
23
23
|
onError?: (message: string | null) => void;
|
|
24
24
|
onPaymentInfoSubmitted?: () => void | Promise<void>;
|
|
25
|
+
onPaymentRejected?: (input: {
|
|
26
|
+
declineCode?: string;
|
|
27
|
+
message: string;
|
|
28
|
+
paymentMethod?: string;
|
|
29
|
+
}) => void;
|
|
25
30
|
onSuccess?: () => void | Promise<void>;
|
|
26
31
|
unsupportedCountryMessage?: string;
|
|
27
32
|
};
|
|
28
|
-
export declare function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAnalytics, checkoutSessionId, returnUrl, confirmEmail, customerEmail, className, options, availabilityPaymentMethods, initialAvailable, keepInitialAvailableOnReady, serverUpdateKey, supportedCountries, onServerUpdate, onAvailabilityChange, onCancel, onError, onPaymentInfoSubmitted, onSuccess, unsupportedCountryMessage, }: StripeCheckoutExpressCheckoutButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
33
|
+
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;
|
|
@@ -4,7 +4,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
4
4
|
import { ExpressCheckoutElement, useCheckout, } from '@stripe/react-stripe-js/checkout';
|
|
5
5
|
import { queueStripeSubscriptionCheckoutStarted, trackPaidStripeSubscriptionCheckoutCompleted, trackStripeSubscriptionPaymentInfoSubmitted, } from '../services/checkoutCompletionAnalytics.service.js';
|
|
6
6
|
import { getUnsupportedCheckoutCountryMessage, normalizeSupportedCheckoutCountries, } from './checkoutCountries.js';
|
|
7
|
-
import { trackCheckoutCanceled, trackCheckoutFailureShown, } from '../../../services/checkoutObservability.service.js';
|
|
7
|
+
import { classifyCheckoutFailure, trackCheckoutCanceled, trackCheckoutFailureShown, } from '../../../services/checkoutObservability.service.js';
|
|
8
|
+
import { toAnalyticsPaymentMethod } from '../../../services/paymentMethodAnalytics.service.js';
|
|
8
9
|
const ExpressCheckoutElementWithCancel = ExpressCheckoutElement;
|
|
9
10
|
const buildDefaultOptions = () => ({
|
|
10
11
|
buttonHeight: 55,
|
|
@@ -29,7 +30,28 @@ const buildDefaultOptions = () => ({
|
|
|
29
30
|
paypal: 'never',
|
|
30
31
|
},
|
|
31
32
|
});
|
|
32
|
-
|
|
33
|
+
const normalizeStripeCheckoutProviderError = (error) => {
|
|
34
|
+
if (!error || typeof error !== 'object') {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
const record = error;
|
|
38
|
+
const paymentFailed = record.paymentFailed && typeof record.paymentFailed === 'object'
|
|
39
|
+
? record.paymentFailed
|
|
40
|
+
: null;
|
|
41
|
+
return {
|
|
42
|
+
code: typeof record.code === 'string' ? record.code : undefined,
|
|
43
|
+
decline_code: typeof record.decline_code === 'string' ? record.decline_code : undefined,
|
|
44
|
+
paymentFailed: paymentFailed
|
|
45
|
+
? {
|
|
46
|
+
declineCode: typeof paymentFailed.declineCode === 'string'
|
|
47
|
+
? paymentFailed.declineCode
|
|
48
|
+
: undefined,
|
|
49
|
+
}
|
|
50
|
+
: undefined,
|
|
51
|
+
type: typeof record.type === 'string' ? record.type : undefined,
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
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, }) {
|
|
33
55
|
const checkoutState = useCheckout();
|
|
34
56
|
const appliedServerUpdateKeyRef = useRef('');
|
|
35
57
|
const [appliedServerUpdateKey, setAppliedServerUpdateKey] = useState('');
|
|
@@ -143,6 +165,7 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAna
|
|
|
143
165
|
paymentMethod }));
|
|
144
166
|
};
|
|
145
167
|
const reportFailure = (message, failureStage, paymentMethod, providerError) => {
|
|
168
|
+
var _a, _b;
|
|
146
169
|
if (checkoutAnalytics) {
|
|
147
170
|
trackCheckoutFailureShown({
|
|
148
171
|
analytics: checkoutAnalytics,
|
|
@@ -151,7 +174,7 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAna
|
|
|
151
174
|
failureStage,
|
|
152
175
|
paymentMethod,
|
|
153
176
|
provider: 'stripe',
|
|
154
|
-
providerDeclineCode: providerError === null || providerError === void 0 ? void 0 : providerError.decline_code,
|
|
177
|
+
providerDeclineCode: (_a = providerError === null || providerError === void 0 ? void 0 : providerError.decline_code) !== null && _a !== void 0 ? _a : (_b = providerError === null || providerError === void 0 ? void 0 : providerError.paymentFailed) === null || _b === void 0 ? void 0 : _b.declineCode,
|
|
155
178
|
providerErrorCode: providerError === null || providerError === void 0 ? void 0 : providerError.code,
|
|
156
179
|
providerErrorType: providerError === null || providerError === void 0 ? void 0 : providerError.type,
|
|
157
180
|
});
|
|
@@ -159,7 +182,7 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAna
|
|
|
159
182
|
onError === null || onError === void 0 ? void 0 : onError(message);
|
|
160
183
|
};
|
|
161
184
|
const handleConfirm = async (event) => {
|
|
162
|
-
var _a, _b, _c;
|
|
185
|
+
var _a, _b, _c, _d, _e, _f;
|
|
163
186
|
// Checkout Sessions omits the Express Checkout onClick callback. Confirmation
|
|
164
187
|
// and cancellation are the reliable wallet-intent boundaries for this provider.
|
|
165
188
|
// Session-based analytics deduplication keeps this idempotent if onClick fires.
|
|
@@ -227,7 +250,22 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAna
|
|
|
227
250
|
: {})), { expressCheckoutConfirmEvent: event, redirect: 'if_required' }));
|
|
228
251
|
if (result.type === 'error') {
|
|
229
252
|
const message = result.error.message || 'Wallet checkout failed.';
|
|
230
|
-
|
|
253
|
+
const providerError = normalizeStripeCheckoutProviderError(result.error);
|
|
254
|
+
reportFailure(message, 'wallet_confirmation', event.expressPaymentType, providerError);
|
|
255
|
+
const declineCode = (_f = (_d = providerError.decline_code) !== null && _d !== void 0 ? _d : (_e = providerError.paymentFailed) === null || _e === void 0 ? void 0 : _e.declineCode) !== null && _f !== void 0 ? _f : undefined;
|
|
256
|
+
if (classifyCheckoutFailure({
|
|
257
|
+
errorMessage: message,
|
|
258
|
+
provider: 'stripe',
|
|
259
|
+
providerDeclineCode: declineCode,
|
|
260
|
+
providerErrorCode: providerError.code,
|
|
261
|
+
providerErrorType: providerError.type,
|
|
262
|
+
}) === 'payment_rejection') {
|
|
263
|
+
onPaymentRejected === null || onPaymentRejected === void 0 ? void 0 : onPaymentRejected({
|
|
264
|
+
declineCode,
|
|
265
|
+
message,
|
|
266
|
+
paymentMethod: toAnalyticsPaymentMethod(event.expressPaymentType) || undefined,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
231
269
|
event.paymentFailed({ reason: 'fail', message });
|
|
232
270
|
return;
|
|
233
271
|
}
|
|
@@ -50,9 +50,11 @@ export type StripeSubscriptionCheckoutSession = {
|
|
|
50
50
|
export declare const buildStripeSubscriptionCheckoutPreparationAnalyticsMetadata: (analyticsMetadata: Record<string, unknown> | null | undefined, options?: StripeSubscriptionCheckoutPreparationOptions) => Record<string, unknown> | null | undefined;
|
|
51
51
|
export type StripeSubscriptionCheckoutIntentKeyInput = Pick<StripeSubscriptionCheckoutSessionInput, 'checkoutMode' | 'couponId' | 'customerEmail' | 'paymentProfileId' | 'provider' | 'returnUrl' | 'runtimeConfig' | 'runtimeConfigRevisionId' | 'stripePublishableKey' | 'userId'> & {
|
|
52
52
|
analyticsMetadata?: Record<string, unknown> | null;
|
|
53
|
+
planKey?: string | null;
|
|
53
54
|
};
|
|
54
|
-
export declare function buildStripeSubscriptionCheckoutIntentKey({ checkoutMode, couponId, customerEmail, returnUrl, runtimeConfig, runtimeConfigRevisionId, paymentProfileId, provider, stripePublishableKey, userId, }: StripeSubscriptionCheckoutIntentKeyInput): string;
|
|
55
|
+
export declare function buildStripeSubscriptionCheckoutIntentKey({ checkoutMode, couponId, customerEmail, returnUrl, runtimeConfig, runtimeConfigRevisionId, paymentProfileId, planKey, provider, stripePublishableKey, userId, }: StripeSubscriptionCheckoutIntentKeyInput): string;
|
|
55
56
|
export declare function shouldResetStripeSubscriptionCheckoutSessionForIntentKey(previousIntentKey: string | null, nextIntentKey: string): boolean;
|
|
56
57
|
export declare function isInactiveCheckoutSessionError(message?: string | null): boolean;
|
|
58
|
+
export declare function shouldAutoRecoverStripeSubscriptionCheckoutSession(message: string | null | undefined, alreadyRecovered: boolean): boolean;
|
|
57
59
|
export declare const shouldReportStripeCheckoutFailureShown: (message: string) => boolean;
|
|
58
60
|
export declare function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMetadata, checkoutAnalytics, couponId, customerEmail, displayPlan, enabled, onError, plan, returnUrl, runtimeConfig, runtimeConfigRevisionId, paymentProfileId, provider, stripePublishableKey, userId, }: StripeSubscriptionCheckoutSessionInput): StripeSubscriptionCheckoutSession;
|
|
@@ -37,7 +37,7 @@ export const buildStripeSubscriptionCheckoutPreparationAnalyticsMetadata = (anal
|
|
|
37
37
|
const _b = analyticsMetadata !== null && analyticsMetadata !== void 0 ? analyticsMetadata : {}, { payment_method: _paymentMethod, paymentMethod: _camelCasePaymentMethod } = _b, metadata = __rest(_b, ["payment_method", "paymentMethod"]);
|
|
38
38
|
return Object.assign(Object.assign(Object.assign({}, metadata), (checkoutStartSource ? { checkoutStartSource } : {})), (paymentMethod ? { payment_method: paymentMethod } : {}));
|
|
39
39
|
};
|
|
40
|
-
export function buildStripeSubscriptionCheckoutIntentKey({ checkoutMode, couponId, customerEmail, returnUrl, runtimeConfig, runtimeConfigRevisionId, paymentProfileId, provider, stripePublishableKey, userId, }) {
|
|
40
|
+
export function buildStripeSubscriptionCheckoutIntentKey({ checkoutMode, couponId, customerEmail, returnUrl, runtimeConfig, runtimeConfigRevisionId, paymentProfileId, planKey, provider, stripePublishableKey, userId, }) {
|
|
41
41
|
var _a;
|
|
42
42
|
const runtimeConfigSignature = [
|
|
43
43
|
normalizeRuntimeConfigValue(runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.apiBaseUrl),
|
|
@@ -59,6 +59,7 @@ export function buildStripeSubscriptionCheckoutIntentKey({ checkoutMode, couponI
|
|
|
59
59
|
customerEmailSignature,
|
|
60
60
|
userIdSignature,
|
|
61
61
|
(couponId === null || couponId === void 0 ? void 0 : couponId.trim()) || '',
|
|
62
|
+
(planKey === null || planKey === void 0 ? void 0 : planKey.trim()) || '',
|
|
62
63
|
returnUrl.trim(),
|
|
63
64
|
].join('|');
|
|
64
65
|
}
|
|
@@ -69,6 +70,9 @@ export function isInactiveCheckoutSessionError(message) {
|
|
|
69
70
|
var _a;
|
|
70
71
|
return /\bcheckout session\b.*\bno longer active\b/i.test((_a = message === null || message === void 0 ? void 0 : message.trim()) !== null && _a !== void 0 ? _a : '');
|
|
71
72
|
}
|
|
73
|
+
export function shouldAutoRecoverStripeSubscriptionCheckoutSession(message, alreadyRecovered) {
|
|
74
|
+
return !alreadyRecovered && isInactiveCheckoutSessionError(message);
|
|
75
|
+
}
|
|
72
76
|
export const shouldReportStripeCheckoutFailureShown = (message) => !isInactiveCheckoutSessionError(message);
|
|
73
77
|
export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMetadata, checkoutAnalytics, couponId, customerEmail, displayPlan, enabled = true, onError, plan, returnUrl, runtimeConfig, runtimeConfigRevisionId, paymentProfileId, provider, stripePublishableKey, userId, }) {
|
|
74
78
|
var _a, _b, _c, _d, _e;
|
|
@@ -77,20 +81,31 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
|
|
|
77
81
|
const [checkoutSessionId, setCheckoutSessionId] = useState(null);
|
|
78
82
|
const [clientSecretPlanKey, setClientSecretPlanKey] = useState(null);
|
|
79
83
|
const [error, setErrorState] = useState(null);
|
|
84
|
+
const [recoveryRequest, setRecoveryRequest] = useState(0);
|
|
80
85
|
const [loading, setLoading] = useState(createEmptyCheckoutPreparationLoading);
|
|
81
86
|
const [runtimeStripePublishableKey, setRuntimeStripePublishableKey] = useState(null);
|
|
82
87
|
const creatingIntentRef = useRef(null);
|
|
83
88
|
const requestIdRef = useRef(0);
|
|
84
89
|
const attemptRef = useRef(null);
|
|
90
|
+
const recoveredIntentKeyRef = useRef(null);
|
|
91
|
+
const handledRecoveryRequestRef = useRef(0);
|
|
85
92
|
const anonymousUserIdRef = useRef(null);
|
|
86
93
|
const configured = enabled &&
|
|
87
94
|
(isStripeConfigured(checkoutMode) ||
|
|
88
95
|
Boolean(normalizeRuntimeConfigValue(runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelSdkPublishableKey)) ||
|
|
89
96
|
Boolean(normalizeRuntimeConfigValue(stripePublishableKey)));
|
|
97
|
+
const planKey = [
|
|
98
|
+
(_a = plan === null || plan === void 0 ? void 0 : plan.id) !== null && _a !== void 0 ? _a : '',
|
|
99
|
+
(_b = plan === null || plan === void 0 ? void 0 : plan.providerPlanId) !== null && _b !== void 0 ? _b : '',
|
|
100
|
+
(_c = plan === null || plan === void 0 ? void 0 : plan.amountCents) !== null && _c !== void 0 ? _c : '',
|
|
101
|
+
(_d = displayPlan === null || displayPlan === void 0 ? void 0 : displayPlan.id) !== null && _d !== void 0 ? _d : '',
|
|
102
|
+
(_e = displayPlan === null || displayPlan === void 0 ? void 0 : displayPlan.amountCents) !== null && _e !== void 0 ? _e : '',
|
|
103
|
+
].join('|');
|
|
90
104
|
const intentKey = buildStripeSubscriptionCheckoutIntentKey({
|
|
91
105
|
checkoutMode,
|
|
92
106
|
couponId,
|
|
93
107
|
customerEmail,
|
|
108
|
+
planKey,
|
|
94
109
|
returnUrl,
|
|
95
110
|
runtimeConfig,
|
|
96
111
|
runtimeConfigRevisionId,
|
|
@@ -99,13 +114,6 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
|
|
|
99
114
|
stripePublishableKey,
|
|
100
115
|
userId,
|
|
101
116
|
});
|
|
102
|
-
const planKey = [
|
|
103
|
-
(_a = plan === null || plan === void 0 ? void 0 : plan.id) !== null && _a !== void 0 ? _a : '',
|
|
104
|
-
(_b = plan === null || plan === void 0 ? void 0 : plan.providerPlanId) !== null && _b !== void 0 ? _b : '',
|
|
105
|
-
(_c = plan === null || plan === void 0 ? void 0 : plan.amountCents) !== null && _c !== void 0 ? _c : '',
|
|
106
|
-
(_d = displayPlan === null || displayPlan === void 0 ? void 0 : displayPlan.id) !== null && _d !== void 0 ? _d : '',
|
|
107
|
-
(_e = displayPlan === null || displayPlan === void 0 ? void 0 : displayPlan.amountCents) !== null && _e !== void 0 ? _e : '',
|
|
108
|
-
].join('|');
|
|
109
117
|
const activeClientSecret = clientSecretIntentKey === intentKey ? clientSecret : null;
|
|
110
118
|
const activeCheckoutSessionId = clientSecretIntentKey === intentKey ? checkoutSessionId : null;
|
|
111
119
|
const activePlanKey = clientSecretIntentKey === intentKey ? clientSecretPlanKey : null;
|
|
@@ -125,21 +133,32 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
|
|
|
125
133
|
}, []);
|
|
126
134
|
const setError = useCallback((message) => {
|
|
127
135
|
if (isInactiveCheckoutSessionError(message)) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
136
|
+
const alreadyRecovered = recoveredIntentKeyRef.current === intentKey;
|
|
137
|
+
if (shouldAutoRecoverStripeSubscriptionCheckoutSession(message, alreadyRecovered)) {
|
|
138
|
+
clearActiveCheckoutSession();
|
|
139
|
+
setErrorState(null);
|
|
140
|
+
onError === null || onError === void 0 ? void 0 : onError(null);
|
|
141
|
+
recoveredIntentKeyRef.current = intentKey;
|
|
142
|
+
setRecoveryRequest((current) => current + 1);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
setErrorState(message);
|
|
146
|
+
onError === null || onError === void 0 ? void 0 : onError(message);
|
|
147
|
+
}
|
|
131
148
|
return;
|
|
132
149
|
}
|
|
133
150
|
setErrorState(message);
|
|
134
151
|
onError === null || onError === void 0 ? void 0 : onError(message);
|
|
135
|
-
}, [clearActiveCheckoutSession, onError]);
|
|
152
|
+
}, [clearActiveCheckoutSession, intentKey, onError]);
|
|
136
153
|
const reset = useCallback(() => {
|
|
137
154
|
attemptRef.current = null;
|
|
155
|
+
recoveredIntentKeyRef.current = null;
|
|
156
|
+
handledRecoveryRequestRef.current = recoveryRequest;
|
|
138
157
|
clearActiveCheckoutSession();
|
|
139
158
|
setLoading(createEmptyCheckoutPreparationLoading());
|
|
140
159
|
setRuntimeStripePublishableKey(null);
|
|
141
160
|
setError(null);
|
|
142
|
-
}, [clearActiveCheckoutSession, setError]);
|
|
161
|
+
}, [clearActiveCheckoutSession, recoveryRequest, setError]);
|
|
143
162
|
useIsomorphicLayoutEffect(() => {
|
|
144
163
|
const previousIntentKey = previousIntentKeyRef.current;
|
|
145
164
|
previousIntentKeyRef.current = intentKey;
|
|
@@ -302,6 +321,16 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
|
|
|
302
321
|
const prepareWalletCheckout = useCallback((options = {}) => prepareCheckout('wallet', options), [prepareCheckout]);
|
|
303
322
|
const restartCardCheckout = useCallback((options = {}) => prepareCheckout('card', Object.assign(Object.assign({}, options), { forceNew: true })), [prepareCheckout]);
|
|
304
323
|
const restartWalletCheckout = useCallback((options = {}) => prepareCheckout('wallet', Object.assign(Object.assign({}, options), { forceNew: true })), [prepareCheckout]);
|
|
324
|
+
useEffect(() => {
|
|
325
|
+
if (recoveryRequest === 0
|
|
326
|
+
|| handledRecoveryRequestRef.current === recoveryRequest) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
handledRecoveryRequestRef.current = recoveryRequest;
|
|
330
|
+
void restartCardCheckout({
|
|
331
|
+
checkoutStartSource: 'checkout_render',
|
|
332
|
+
});
|
|
333
|
+
}, [recoveryRequest, restartCardCheckout]);
|
|
305
334
|
const updateCheckoutSessionPlan = useCallback(async () => {
|
|
306
335
|
if (!configured || !plan || !displayPlan || !activeCheckoutSessionId) {
|
|
307
336
|
return false;
|
|
@@ -29,6 +29,9 @@ type CheckoutFailureShownInput = CheckoutObservabilityInput & {
|
|
|
29
29
|
providerErrorCode?: string | null;
|
|
30
30
|
providerErrorType?: string | null;
|
|
31
31
|
};
|
|
32
|
+
export type CheckoutFailureCategory = 'network_failure' | 'payment_rejection' | 'platform_failure' | 'session_failure' | 'validation_failure';
|
|
33
|
+
export type CheckoutFailureClassificationInput = Pick<CheckoutFailureShownInput, 'errorMessage' | 'provider' | 'providerDeclineCode' | 'providerErrorCode' | 'providerErrorType'>;
|
|
34
|
+
export declare const classifyCheckoutFailure: (input: CheckoutFailureClassificationInput) => CheckoutFailureCategory;
|
|
32
35
|
export declare const trackCheckoutFailureShown: (input: CheckoutFailureShownInput) => string | null;
|
|
33
36
|
export declare const trackCheckoutCanceled: (input: CheckoutObservabilityInput) => string | null;
|
|
34
37
|
export {};
|
|
@@ -37,7 +37,31 @@ const STRIPE_PAYMENT_REJECTION_CODES = new Set([
|
|
|
37
37
|
'invalid_expiry_month',
|
|
38
38
|
'invalid_expiry_year',
|
|
39
39
|
'payment_intent_authentication_failure',
|
|
40
|
+
'paymentFailed',
|
|
40
41
|
]);
|
|
42
|
+
export const classifyCheckoutFailure = (input) => {
|
|
43
|
+
const providerDeclineCode = asNonEmptyString(input.providerDeclineCode);
|
|
44
|
+
const providerErrorCode = asNonEmptyString(input.providerErrorCode);
|
|
45
|
+
const providerErrorType = asNonEmptyString(input.providerErrorType);
|
|
46
|
+
if (input.provider === 'stripe' && (Boolean(providerDeclineCode)
|
|
47
|
+
|| providerErrorType === 'card_error'
|
|
48
|
+
|| (providerErrorCode ? STRIPE_PAYMENT_REJECTION_CODES.has(providerErrorCode) : false))) {
|
|
49
|
+
return 'payment_rejection';
|
|
50
|
+
}
|
|
51
|
+
const message = input.errorMessage.trim();
|
|
52
|
+
if (/checkout session.*no longer active/i.test(message)
|
|
53
|
+
|| /selected offer is no longer available/i.test(message)) {
|
|
54
|
+
return 'session_failure';
|
|
55
|
+
}
|
|
56
|
+
if (/failed to fetch|load failed|network|connection to (?:the )?provider|too many requests/i.test(message)) {
|
|
57
|
+
return 'network_failure';
|
|
58
|
+
}
|
|
59
|
+
if (/(?:email|card number|expiration|expiry|cvc|zip|postal code|billing address|country).*(?:required|incomplete|incorrect|invalid|unsupported)/i.test(message)
|
|
60
|
+
|| /(?:required|incomplete|incorrect|invalid|unsupported).*(?:email|card number|expiration|expiry|cvc|zip|postal code|billing address|country)/i.test(message)) {
|
|
61
|
+
return 'validation_failure';
|
|
62
|
+
}
|
|
63
|
+
return 'platform_failure';
|
|
64
|
+
};
|
|
41
65
|
const sanitizeErrorMessage = (value) => value
|
|
42
66
|
.replace(/[\r\n\t]+/g, ' ')
|
|
43
67
|
.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, '[redacted-email]')
|
|
@@ -68,6 +92,7 @@ const reportCheckoutObservability = (input) => {
|
|
|
68
92
|
environment,
|
|
69
93
|
errorCode: input.errorCode,
|
|
70
94
|
errorMessage: input.errorMessage,
|
|
95
|
+
failureCategory: input.failureCategory,
|
|
71
96
|
eventId: input.eventId,
|
|
72
97
|
eventType: input.eventType,
|
|
73
98
|
failureStage: input.failureStage,
|
|
@@ -96,9 +121,9 @@ const resolveErrorCode = (message, requestedCode) => {
|
|
|
96
121
|
return 'checkout_failure';
|
|
97
122
|
};
|
|
98
123
|
const emitCheckoutObservability = (eventType, input, failure) => {
|
|
99
|
-
var _a, _b;
|
|
124
|
+
var _a, _b, _c, _d;
|
|
100
125
|
const paymentMethod = toAnalyticsPaymentMethod(input.paymentMethod);
|
|
101
|
-
const metadata = Object.assign(Object.assign({}, ((_a = input.analytics.metadata) !== null && _a !== void 0 ? _a : {})), { checkout_session_id: ((_b = input.checkoutSessionId) === null || _b === void 0 ? void 0 : _b.trim()) || undefined, checkout_failure_stage: input.failureStage, payment_provider: input.provider, payment_method: paymentMethod || undefined, error_code: failure === null || failure === void 0 ? void 0 : failure.errorCode, error_message: failure === null || failure === void 0 ? void 0 : failure.errorMessage, provider_decline_code: failure === null || failure === void 0 ? void 0 : failure.providerDeclineCode, provider_error_code: failure === null || failure === void 0 ? void 0 : failure.providerErrorCode, provider_error_type: failure === null || failure === void 0 ? void 0 : failure.providerErrorType });
|
|
126
|
+
const metadata = Object.assign(Object.assign({}, ((_a = input.analytics.metadata) !== null && _a !== void 0 ? _a : {})), { checkout_session_id: ((_b = input.checkoutSessionId) === null || _b === void 0 ? void 0 : _b.trim()) || undefined, checkout_failure_stage: input.failureStage, payment_provider: input.provider, payment_method: paymentMethod || undefined, error_code: failure === null || failure === void 0 ? void 0 : failure.errorCode, error_message: failure === null || failure === void 0 ? void 0 : failure.errorMessage, checkout_failure_category: (_c = failure === null || failure === void 0 ? void 0 : failure.failureCategory) !== null && _c !== void 0 ? _c : (eventType === 'checkout_canceled' ? 'user_canceled' : undefined), provider_decline_code: failure === null || failure === void 0 ? void 0 : failure.providerDeclineCode, provider_error_code: failure === null || failure === void 0 ? void 0 : failure.providerErrorCode, provider_error_type: failure === null || failure === void 0 ? void 0 : failure.providerErrorType });
|
|
102
127
|
const eventId = publicAnalyticsSdk.track({
|
|
103
128
|
eventType,
|
|
104
129
|
environment: input.analytics.environment,
|
|
@@ -117,6 +142,7 @@ const emitCheckoutObservability = (eventType, input, failure) => {
|
|
|
117
142
|
checkoutSessionId: input.checkoutSessionId,
|
|
118
143
|
errorCode: failure === null || failure === void 0 ? void 0 : failure.errorCode,
|
|
119
144
|
errorMessage: failure === null || failure === void 0 ? void 0 : failure.errorMessage,
|
|
145
|
+
failureCategory: (_d = failure === null || failure === void 0 ? void 0 : failure.failureCategory) !== null && _d !== void 0 ? _d : (eventType === 'checkout_canceled' ? 'user_canceled' : undefined),
|
|
120
146
|
eventId,
|
|
121
147
|
eventType,
|
|
122
148
|
failureStage: input.failureStage,
|
|
@@ -138,14 +164,20 @@ export const trackCheckoutFailureShown = (input) => {
|
|
|
138
164
|
const providerDeclineCode = (_a = asNonEmptyString(input.providerDeclineCode)) === null || _a === void 0 ? void 0 : _a.slice(0, 120);
|
|
139
165
|
const providerErrorCode = (_b = asNonEmptyString(input.providerErrorCode)) === null || _b === void 0 ? void 0 : _b.slice(0, 120);
|
|
140
166
|
const providerErrorType = (_c = asNonEmptyString(input.providerErrorType)) === null || _c === void 0 ? void 0 : _c.slice(0, 120);
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
167
|
+
const failureCategory = classifyCheckoutFailure({
|
|
168
|
+
errorMessage,
|
|
169
|
+
provider: input.provider,
|
|
170
|
+
providerDeclineCode,
|
|
171
|
+
providerErrorCode,
|
|
172
|
+
providerErrorType,
|
|
173
|
+
});
|
|
174
|
+
const paymentRejected = failureCategory === 'payment_rejection';
|
|
144
175
|
return emitCheckoutObservability(paymentRejected ? 'checkout_payment_rejected' : 'checkout_failure_shown', input, {
|
|
145
176
|
errorCode: paymentRejected
|
|
146
177
|
? providerDeclineCode || providerErrorCode || 'payment_rejected'
|
|
147
178
|
: resolveErrorCode(errorMessage, input.errorCode),
|
|
148
179
|
errorMessage,
|
|
180
|
+
failureCategory,
|
|
149
181
|
providerDeclineCode,
|
|
150
182
|
providerErrorCode,
|
|
151
183
|
providerErrorType,
|