@funnelsgrove/payments 0.11.15 → 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.
@@ -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
- export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAnalytics, checkoutSessionId, returnUrl, confirmEmail = true, customerEmail, className, options, availabilityPaymentMethods = ['applePay'], initialAvailable, keepInitialAvailableOnReady = false, serverUpdateKey, supportedCountries, onServerUpdate, onAvailabilityChange, onCancel, onError, onPaymentInfoSubmitted, onSuccess, unsupportedCountryMessage, }) {
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
- reportFailure(message, 'wallet_confirmation', event.expressPaymentType, result.error);
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
  }
@@ -55,5 +55,6 @@ export type StripeSubscriptionCheckoutIntentKeyInput = Pick<StripeSubscriptionCh
55
55
  export declare function buildStripeSubscriptionCheckoutIntentKey({ checkoutMode, couponId, customerEmail, returnUrl, runtimeConfig, runtimeConfigRevisionId, paymentProfileId, planKey, provider, stripePublishableKey, userId, }: StripeSubscriptionCheckoutIntentKeyInput): string;
56
56
  export declare function shouldResetStripeSubscriptionCheckoutSessionForIntentKey(previousIntentKey: string | null, nextIntentKey: string): boolean;
57
57
  export declare function isInactiveCheckoutSessionError(message?: string | null): boolean;
58
+ export declare function shouldAutoRecoverStripeSubscriptionCheckoutSession(message: string | null | undefined, alreadyRecovered: boolean): boolean;
58
59
  export declare const shouldReportStripeCheckoutFailureShown: (message: string) => boolean;
59
60
  export declare function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMetadata, checkoutAnalytics, couponId, customerEmail, displayPlan, enabled, onError, plan, returnUrl, runtimeConfig, runtimeConfigRevisionId, paymentProfileId, provider, stripePublishableKey, userId, }: StripeSubscriptionCheckoutSessionInput): StripeSubscriptionCheckoutSession;
@@ -70,6 +70,9 @@ export function isInactiveCheckoutSessionError(message) {
70
70
  var _a;
71
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 : '');
72
72
  }
73
+ export function shouldAutoRecoverStripeSubscriptionCheckoutSession(message, alreadyRecovered) {
74
+ return !alreadyRecovered && isInactiveCheckoutSessionError(message);
75
+ }
73
76
  export const shouldReportStripeCheckoutFailureShown = (message) => !isInactiveCheckoutSessionError(message);
74
77
  export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMetadata, checkoutAnalytics, couponId, customerEmail, displayPlan, enabled = true, onError, plan, returnUrl, runtimeConfig, runtimeConfigRevisionId, paymentProfileId, provider, stripePublishableKey, userId, }) {
75
78
  var _a, _b, _c, _d, _e;
@@ -78,11 +81,14 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
78
81
  const [checkoutSessionId, setCheckoutSessionId] = useState(null);
79
82
  const [clientSecretPlanKey, setClientSecretPlanKey] = useState(null);
80
83
  const [error, setErrorState] = useState(null);
84
+ const [recoveryRequest, setRecoveryRequest] = useState(0);
81
85
  const [loading, setLoading] = useState(createEmptyCheckoutPreparationLoading);
82
86
  const [runtimeStripePublishableKey, setRuntimeStripePublishableKey] = useState(null);
83
87
  const creatingIntentRef = useRef(null);
84
88
  const requestIdRef = useRef(0);
85
89
  const attemptRef = useRef(null);
90
+ const recoveredIntentKeyRef = useRef(null);
91
+ const handledRecoveryRequestRef = useRef(0);
86
92
  const anonymousUserIdRef = useRef(null);
87
93
  const configured = enabled &&
88
94
  (isStripeConfigured(checkoutMode) ||
@@ -127,21 +133,32 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
127
133
  }, []);
128
134
  const setError = useCallback((message) => {
129
135
  if (isInactiveCheckoutSessionError(message)) {
130
- clearActiveCheckoutSession();
131
- setErrorState(null);
132
- onError === null || onError === void 0 ? void 0 : onError(null);
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
+ }
133
148
  return;
134
149
  }
135
150
  setErrorState(message);
136
151
  onError === null || onError === void 0 ? void 0 : onError(message);
137
- }, [clearActiveCheckoutSession, onError]);
152
+ }, [clearActiveCheckoutSession, intentKey, onError]);
138
153
  const reset = useCallback(() => {
139
154
  attemptRef.current = null;
155
+ recoveredIntentKeyRef.current = null;
156
+ handledRecoveryRequestRef.current = recoveryRequest;
140
157
  clearActiveCheckoutSession();
141
158
  setLoading(createEmptyCheckoutPreparationLoading());
142
159
  setRuntimeStripePublishableKey(null);
143
160
  setError(null);
144
- }, [clearActiveCheckoutSession, setError]);
161
+ }, [clearActiveCheckoutSession, recoveryRequest, setError]);
145
162
  useIsomorphicLayoutEffect(() => {
146
163
  const previousIntentKey = previousIntentKeyRef.current;
147
164
  previousIntentKeyRef.current = intentKey;
@@ -304,6 +321,16 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
304
321
  const prepareWalletCheckout = useCallback((options = {}) => prepareCheckout('wallet', options), [prepareCheckout]);
305
322
  const restartCardCheckout = useCallback((options = {}) => prepareCheckout('card', Object.assign(Object.assign({}, options), { forceNew: true })), [prepareCheckout]);
306
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]);
307
334
  const updateCheckoutSessionPlan = useCallback(async () => {
308
335
  if (!configured || !plan || !displayPlan || !activeCheckoutSessionId) {
309
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 paymentRejected = input.provider === 'stripe' && (Boolean(providerDeclineCode)
142
- || providerErrorType === 'card_error'
143
- || (providerErrorCode ? STRIPE_PAYMENT_REJECTION_CODES.has(providerErrorCode) : false));
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnelsgrove/payments",
3
- "version": "0.11.15",
3
+ "version": "0.11.16",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/The-Solid-Grove/funnelsgrove.git",