@funnelsgrove/payments 0.11.6 → 0.11.7

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/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export * from './services/runtimeBillingPlanCatalog.service.js';
7
7
  export * from './services/publishedBillingRuntime.service.js';
8
8
  export * from './services/paywallOffer.service.js';
9
9
  export * from './services/paymentMethodAnalytics.service.js';
10
+ export * from './services/checkoutObservability.service.js';
10
11
  export * from './providers/paymentProvider.types.js';
11
12
  export * from './providers/stripe/index.js';
12
13
  export * from './providers/solidgate/index.js';
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ export * from './services/runtimeBillingPlanCatalog.service.js';
8
8
  export * from './services/publishedBillingRuntime.service.js';
9
9
  export * from './services/paywallOffer.service.js';
10
10
  export * from './services/paymentMethodAnalytics.service.js';
11
+ export * from './services/checkoutObservability.service.js';
11
12
  export * from './providers/paymentProvider.types.js';
12
13
  // Provider-specific integrations are grouped behind provider barrels.
13
14
  export * from './providers/stripe/index.js';
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { publicAnalyticsSdk } from '@funnelsgrove/analytics';
4
4
  import { Suspense, lazy, useCallback, useEffect, useMemo, useRef, useState, } from 'react';
5
5
  import { pollSolidgateCheckoutStatus, prepareSolidgateCheckout, } from '../services/solidgate.service.js';
6
+ import { trackCheckoutCanceled, trackCheckoutFailureShown, } from '../../../services/checkoutObservability.service.js';
6
7
  const SolidgatePayment = lazy(async () => ({
7
8
  default: (await import('@solidgate/react-sdk')).default,
8
9
  }));
@@ -250,6 +251,20 @@ export function SolidgateCheckoutDialog({ open, request, customerEmail, checkout
250
251
  setView(nextView);
251
252
  (_a = onStateChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onStateChangeRef, nextView.state);
252
253
  }, []);
254
+ const trackFailure = useCallback((message, failureStage, attemptId) => {
255
+ const analytics = checkoutAnalyticsRef.current;
256
+ if (!analytics) {
257
+ return;
258
+ }
259
+ trackCheckoutFailureShown({
260
+ analytics,
261
+ checkoutSessionId: attemptId,
262
+ errorMessage: message,
263
+ failureStage,
264
+ paymentMethod: 'card',
265
+ provider: 'solidgate',
266
+ });
267
+ }, []);
253
268
  const requiresEmailCommit = Boolean(onCustomerEmailCommit);
254
269
  useEffect(() => {
255
270
  var _a, _b, _c, _d, _e, _f;
@@ -291,11 +306,12 @@ export function SolidgateCheckoutDialog({ open, request, customerEmail, checkout
291
306
  var _a;
292
307
  if (!controller.signal.aborted && !isAbortError(error)) {
293
308
  setState({ state: 'recoverable_error', checkout: null, canResumeVerification: false });
309
+ trackFailure(copyRef.current.error, 'checkout_session_creation');
294
310
  (_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef, copyRef.current.error);
295
311
  }
296
312
  });
297
313
  return () => controller.abort();
298
- }, [committedEmail, normalizedRequest, open, requiresEmailCommit, setState]);
314
+ }, [committedEmail, normalizedRequest, open, requiresEmailCommit, setState, trackFailure]);
299
315
  const paymentFormMounted = Boolean(view.checkout && mountedPaymentAttemptId === view.checkout.attemptId);
300
316
  const showPreparingLoader = view.state === 'loading'
301
317
  || (view.state === 'ready' && !paymentFormMounted);
@@ -308,7 +324,18 @@ export function SolidgateCheckoutDialog({ open, request, customerEmail, checkout
308
324
  ? document.activeElement
309
325
  : null;
310
326
  const handleKeyDown = (event) => {
327
+ var _a;
311
328
  if (event.key === 'Escape') {
329
+ const analytics = checkoutAnalyticsRef.current;
330
+ if (analytics) {
331
+ trackCheckoutCanceled({
332
+ analytics,
333
+ checkoutSessionId: (_a = view.checkout) === null || _a === void 0 ? void 0 : _a.attemptId,
334
+ failureStage: 'checkout_dialog',
335
+ paymentMethod: 'card',
336
+ provider: 'solidgate',
337
+ });
338
+ }
312
339
  onCloseRef.current();
313
340
  }
314
341
  };
@@ -320,7 +347,7 @@ export function SolidgateCheckoutDialog({ open, request, customerEmail, checkout
320
347
  window.removeEventListener('keydown', handleKeyDown);
321
348
  previouslyFocused === null || previouslyFocused === void 0 ? void 0 : previouslyFocused.focus();
322
349
  };
323
- }, [open, showPreparingLoader]);
350
+ }, [open, showPreparingLoader, view.checkout]);
324
351
  useEffect(() => () => {
325
352
  var _a, _b;
326
353
  (_a = prepareAbortRef.current) === null || _a === void 0 ? void 0 : _a.abort();
@@ -356,9 +383,11 @@ export function SolidgateCheckoutDialog({ open, request, customerEmail, checkout
356
383
  }
357
384
  else if (result.status === 'expired') {
358
385
  setState({ state: 'expired', checkout, canResumeVerification: false });
386
+ trackFailure(copyRef.current.expired, 'payment_expired', checkout.attemptId);
359
387
  }
360
388
  else if (result.status === 'failed') {
361
389
  setState({ state: 'recoverable_error', checkout, canResumeVerification: false });
390
+ trackFailure(copyRef.current.error, 'payment_failed', checkout.attemptId);
362
391
  (_c = onErrorRef.current) === null || _c === void 0 ? void 0 : _c.call(onErrorRef, copyRef.current.error);
363
392
  }
364
393
  else if (result.status === 'requires_payment') {
@@ -371,15 +400,18 @@ export function SolidgateCheckoutDialog({ open, request, customerEmail, checkout
371
400
  catch (error) {
372
401
  if (!controller.signal.aborted && !isAbortError(error)) {
373
402
  setState({ state: 'recoverable_error', checkout, canResumeVerification: true });
403
+ trackFailure(copyRef.current.error, 'payment_verification', checkout.attemptId);
374
404
  (_d = onErrorRef.current) === null || _d === void 0 ? void 0 : _d.call(onErrorRef, copyRef.current.error);
375
405
  }
376
406
  }
377
- }, [normalizedRequest, setState, view]);
407
+ }, [normalizedRequest, setState, trackFailure, view]);
378
408
  const commitCustomerEmail = useCallback(async (event) => {
409
+ var _a, _b;
379
410
  event.preventDefault();
380
411
  const normalized = normalizeValidEmail(emailInput);
381
412
  if (!normalized || !onCustomerEmailCommit) {
382
413
  setEmailError(copy.customerEmailInvalid);
414
+ trackFailure(copy.customerEmailInvalid, 'customer_email', (_a = view.checkout) === null || _a === void 0 ? void 0 : _a.attemptId);
383
415
  return;
384
416
  }
385
417
  setEmailSubmitting(true);
@@ -392,13 +424,28 @@ export function SolidgateCheckoutDialog({ open, request, customerEmail, checkout
392
424
  setEmailInput(committed);
393
425
  setCommittedEmail(committed);
394
426
  }
395
- catch (_a) {
427
+ catch (_c) {
396
428
  setEmailError(copy.customerEmailUnavailable);
429
+ trackFailure(copy.customerEmailUnavailable, 'customer_email', (_b = view.checkout) === null || _b === void 0 ? void 0 : _b.attemptId);
397
430
  }
398
431
  finally {
399
432
  setEmailSubmitting(false);
400
433
  }
401
- }, [copy.customerEmailInvalid, copy.customerEmailUnavailable, emailInput, onCustomerEmailCommit]);
434
+ }, [copy.customerEmailInvalid, copy.customerEmailUnavailable, emailInput, onCustomerEmailCommit, trackFailure, view.checkout]);
435
+ const handleClose = () => {
436
+ var _a;
437
+ const analytics = checkoutAnalyticsRef.current;
438
+ if (analytics) {
439
+ trackCheckoutCanceled({
440
+ analytics,
441
+ checkoutSessionId: (_a = view.checkout) === null || _a === void 0 ? void 0 : _a.attemptId,
442
+ failureStage: 'checkout_dialog',
443
+ paymentMethod: 'card',
444
+ provider: 'solidgate',
445
+ });
446
+ }
447
+ onCloseRef.current();
448
+ };
402
449
  if (!open) {
403
450
  return null;
404
451
  }
@@ -429,7 +476,7 @@ export function SolidgateCheckoutDialog({ open, request, customerEmail, checkout
429
476
  'solidgate-checkout-dialog',
430
477
  showPreparingLoader ? 'is-preparing' : '',
431
478
  presentation ? 'is-branded' : '',
432
- ].filter(Boolean).join(' '), children: [_jsxs("div", { className: 'solidgate-checkout-header', children: [_jsx("button", { ref: closeButtonRef, type: 'button', onClick: onClose, "aria-label": copy.closeAriaLabel, className: 'solidgate-checkout-close', children: _jsx("span", { "aria-hidden": 'true' }) }), _jsx("h2", { id: 'solidgate-checkout-title', className: 'solidgate-checkout-title', children: (presentation === null || presentation === void 0 ? void 0 : presentation.title) || copy.title })] }), presentation ? (_jsxs("section", { className: 'solidgate-checkout-summary', children: [showPromo ? (_jsxs("p", { className: 'solidgate-checkout-countdown', children: [presentation.discountPercent, "% ", presentation.countdownLabel, ' ', formatCountdown(presentation.remainingSeconds), " min"] })) : null, _jsxs("p", { className: 'solidgate-checkout-satisfaction', children: [_jsx("strong", { children: presentation.satisfactionPercent }), ' ', presentation.satisfactionLabel] }), showPromo ? (_jsxs("div", { className: 'solidgate-checkout-price-stack', children: [_jsxs("div", { className: 'solidgate-checkout-price-row is-muted', children: [_jsx("span", { children: presentation.originalPriceLabel }), _jsx("span", { className: 'solidgate-checkout-strike', children: presentation.originalPriceValue })] }), _jsxs("div", { className: 'solidgate-checkout-price-row is-discount', children: [_jsx("strong", { children: presentation.discountLabel }), _jsx("strong", { children: presentation.discountAmountLabel })] }), _jsxs("div", { className: 'solidgate-checkout-promo', children: [_jsx("span", { children: presentation.promoCodeLabel }), _jsx("strong", { children: presentation.promoCode.trim() })] })] })) : null, _jsxs("div", { className: `solidgate-checkout-total${showPromo ? '' : ' is-plain'}`, children: [_jsx("span", { children: presentation.totalLabel }), _jsx("strong", { children: presentation.totalValue })] }), showPromo ? (_jsxs("p", { className: 'solidgate-checkout-saved', children: [_jsx("span", { "aria-hidden": 'true', children: "\uD83D\uDD25" }), presentation.savedLabel] })) : null] })) : null, view.state === 'collecting_email' ? (_jsxs("form", { className: 'solidgate-checkout-email-form', onSubmit: (event) => void commitCustomerEmail(event), children: [_jsxs("label", { className: 'solidgate-checkout-email-label', children: [copy.customerEmailLabel, _jsx("input", { type: 'email', autoComplete: 'email', inputMode: 'email', maxLength: 320, required: true, value: emailInput, onChange: (event) => {
479
+ ].filter(Boolean).join(' '), children: [_jsxs("div", { className: 'solidgate-checkout-header', children: [_jsx("button", { ref: closeButtonRef, type: 'button', onClick: handleClose, "aria-label": copy.closeAriaLabel, className: 'solidgate-checkout-close', children: _jsx("span", { "aria-hidden": 'true' }) }), _jsx("h2", { id: 'solidgate-checkout-title', className: 'solidgate-checkout-title', children: (presentation === null || presentation === void 0 ? void 0 : presentation.title) || copy.title })] }), presentation ? (_jsxs("section", { className: 'solidgate-checkout-summary', children: [showPromo ? (_jsxs("p", { className: 'solidgate-checkout-countdown', children: [presentation.discountPercent, "% ", presentation.countdownLabel, ' ', formatCountdown(presentation.remainingSeconds), " min"] })) : null, _jsxs("p", { className: 'solidgate-checkout-satisfaction', children: [_jsx("strong", { children: presentation.satisfactionPercent }), ' ', presentation.satisfactionLabel] }), showPromo ? (_jsxs("div", { className: 'solidgate-checkout-price-stack', children: [_jsxs("div", { className: 'solidgate-checkout-price-row is-muted', children: [_jsx("span", { children: presentation.originalPriceLabel }), _jsx("span", { className: 'solidgate-checkout-strike', children: presentation.originalPriceValue })] }), _jsxs("div", { className: 'solidgate-checkout-price-row is-discount', children: [_jsx("strong", { children: presentation.discountLabel }), _jsx("strong", { children: presentation.discountAmountLabel })] }), _jsxs("div", { className: 'solidgate-checkout-promo', children: [_jsx("span", { children: presentation.promoCodeLabel }), _jsx("strong", { children: presentation.promoCode.trim() })] })] })) : null, _jsxs("div", { className: `solidgate-checkout-total${showPromo ? '' : ' is-plain'}`, children: [_jsx("span", { children: presentation.totalLabel }), _jsx("strong", { children: presentation.totalValue })] }), showPromo ? (_jsxs("p", { className: 'solidgate-checkout-saved', children: [_jsx("span", { "aria-hidden": 'true', children: "\uD83D\uDD25" }), presentation.savedLabel] })) : null] })) : null, view.state === 'collecting_email' ? (_jsxs("form", { className: 'solidgate-checkout-email-form', onSubmit: (event) => void commitCustomerEmail(event), children: [_jsxs("label", { className: 'solidgate-checkout-email-label', children: [copy.customerEmailLabel, _jsx("input", { type: 'email', autoComplete: 'email', inputMode: 'email', maxLength: 320, required: true, value: emailInput, onChange: (event) => {
433
480
  setEmailInput(event.target.value);
434
481
  setEmailError(null);
435
482
  }, placeholder: copy.customerEmailPlaceholder, "aria-invalid": Boolean(emailError), className: 'solidgate-checkout-email-input' })] }), emailError ? (_jsx("p", { role: 'alert', className: 'solidgate-checkout-alert', children: emailError })) : null, _jsx("button", { type: 'submit', disabled: emailSubmitting, className: 'solidgate-checkout-action', children: copy.customerEmailContinue })] })) : null, view.checkout ? (_jsx(SolidgatePaymentForm, { buttonColor: (presentation === null || presentation === void 0 ? void 0 : presentation.buttonColor) || '#3f51b5', cardSubmitLabel: (presentation === null || presentation === void 0 ? void 0 : presentation.cardSubmitLabel) || 'Pay securely', checkout: view.checkout, hidden: !showForm, loadingLabel: copy.loading, onMounted: () => {
@@ -443,9 +490,10 @@ export function SolidgateCheckoutDialog({ open, request, customerEmail, checkout
443
490
  }
444
491
  setState(Object.assign(Object.assign({}, view), { state: 'submitting' }));
445
492
  }, onVerify: () => void verifyCanonicalStatus(), onError: () => {
446
- var _a;
493
+ var _a, _b;
447
494
  setState(Object.assign(Object.assign({}, view), { state: 'recoverable_error', canResumeVerification: false }));
448
- (_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef, copyRef.current.error);
495
+ trackFailure(copyRef.current.error, 'payment_form', (_a = view.checkout) === null || _a === void 0 ? void 0 : _a.attemptId);
496
+ (_b = onErrorRef.current) === null || _b === void 0 ? void 0 : _b.call(onErrorRef, copyRef.current.error);
449
497
  } }, 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.state === 'verifying' && 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') && onRetry ? (_jsx("button", { type: 'button', onClick: onRetry, className: 'solidgate-checkout-action solidgate-checkout-action--spaced', children: copy.retry })) : null] })) : null] }), _jsx("style", { children: solidgateCheckoutDialogStyles })] }));
450
498
  }
451
499
  const solidgateCheckoutDialogStyles = `
@@ -11,12 +11,13 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  return t;
12
12
  };
13
13
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
14
- import { useEffect, useMemo, useState, useSyncExternalStore, } from 'react';
14
+ import { useCallback, useEffect, useMemo, useState, useSyncExternalStore, } from 'react';
15
15
  import { CardCvcElement, CardExpiryElement, CardNumberElement, Elements, useElements, useStripe, } from '@stripe/react-stripe-js';
16
16
  import { ApplePaySubscribeButton } from './ApplePaySubscribeButton.js';
17
17
  import { PaymentBrandMark, SecurityLockIcon, StripeWordmark, } from './CheckoutBrandAssets.js';
18
18
  import { StripeExpressCheckoutButton } from './StripeExpressCheckoutButton.js';
19
19
  import { queueStripeSubscriptionCheckoutStarted, } from '../services/checkoutCompletionAnalytics.service.js';
20
+ import { trackCheckoutCanceled, trackCheckoutFailureShown, } from '../../../services/checkoutObservability.service.js';
20
21
  const isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
21
22
  const defaultPaymentMethodLabels = ['Visa', 'Mastercard', 'PayPal', 'G Pay', 'Apple Pay'];
22
23
  const normalizeBillingName = (input) => {
@@ -141,7 +142,17 @@ function SharedStripeCheckoutForm({ amountCents, checkoutAnalytics, checkoutSess
141
142
  !submitting;
142
143
  const walletCheckoutAllowed = allowApplePayExpressCheckout || allowApplePayQrFallback;
143
144
  const walletFallbackDisabled = !walletCheckoutAllowed || walletAvailable === false;
144
- const setSharedError = (message) => {
145
+ const setSharedError = (message, alreadyTracked = false) => {
146
+ if (message && checkoutAnalytics && !alreadyTracked) {
147
+ trackCheckoutFailureShown({
148
+ analytics: checkoutAnalytics,
149
+ checkoutSessionId,
150
+ errorMessage: message,
151
+ failureStage: 'card_form',
152
+ paymentMethod: 'card',
153
+ provider: 'stripe',
154
+ });
155
+ }
145
156
  setError(message);
146
157
  onError === null || onError === void 0 ? void 0 : onError(message);
147
158
  };
@@ -227,7 +238,7 @@ function SharedStripeCheckoutForm({ amountCents, checkoutAnalytics, checkoutSess
227
238
  ? 'is-positive'
228
239
  : row.tone === 'negative'
229
240
  ? 'is-negative'
230
- : undefined, children: row.value })] }, row.id))), _jsxs("div", { className: 'shared-stripe-checkout-summary-total', children: [_jsx("span", { children: totalLabel }), _jsx("strong", { children: totalValue })] }), summaryNote ? (_jsx("p", { className: 'shared-stripe-checkout-summary-note', children: summaryNote })) : null] }), _jsxs("form", { className: 'shared-stripe-checkout-form', onSubmit: (event) => void handleSubmit(event), children: [_jsxs("div", { className: 'shared-stripe-checkout-wallet-shell', children: [walletAvailable !== true ? (_jsx(ApplePaySubscribeButton, { className: 'shared-stripe-checkout-wallet-placeholder', disabled: walletFallbackDisabled, label: walletPlaceholderLabel })) : null, walletCheckoutAllowed ? (_jsx(StripeExpressCheckoutButton, { amountCents: amountCents, beforeConfirm: ensureCustomerEmail, checkoutAnalytics: checkoutAnalytics, checkoutSessionId: checkoutSessionId, summaryLabel: summaryLabel, returnUrl: returnUrl, customerEmail: resolvedCustomerEmail, customerName: customerName, className: 'shared-stripe-checkout-wallet-button', onAvailabilityChange: setWalletAvailable, onError: setSharedError })) : null] }), _jsxs("div", { className: 'shared-stripe-checkout-card-surface', children: [_jsxs("div", { className: 'shared-stripe-checkout-field-group shared-stripe-checkout-field-group--wide', children: [_jsx("p", { className: 'shared-stripe-checkout-field-label', children: customerEmailLabel }), _jsx("div", { className: 'shared-stripe-checkout-field shared-stripe-checkout-field--wide', children: _jsx("input", { type: 'email', inputMode: 'email', autoComplete: 'email', className: [
241
+ : undefined, children: row.value })] }, row.id))), _jsxs("div", { className: 'shared-stripe-checkout-summary-total', children: [_jsx("span", { children: totalLabel }), _jsx("strong", { children: totalValue })] }), summaryNote ? (_jsx("p", { className: 'shared-stripe-checkout-summary-note', children: summaryNote })) : null] }), _jsxs("form", { className: 'shared-stripe-checkout-form', onSubmit: (event) => void handleSubmit(event), children: [_jsxs("div", { className: 'shared-stripe-checkout-wallet-shell', children: [walletAvailable !== true ? (_jsx(ApplePaySubscribeButton, { className: 'shared-stripe-checkout-wallet-placeholder', disabled: walletFallbackDisabled, label: walletPlaceholderLabel })) : null, walletCheckoutAllowed ? (_jsx(StripeExpressCheckoutButton, { amountCents: amountCents, beforeConfirm: ensureCustomerEmail, checkoutAnalytics: checkoutAnalytics, checkoutSessionId: checkoutSessionId, summaryLabel: summaryLabel, returnUrl: returnUrl, customerEmail: resolvedCustomerEmail, customerName: customerName, className: 'shared-stripe-checkout-wallet-button', onAvailabilityChange: setWalletAvailable, onError: (message) => setSharedError(message, true) })) : null] }), _jsxs("div", { className: 'shared-stripe-checkout-card-surface', children: [_jsxs("div", { className: 'shared-stripe-checkout-field-group shared-stripe-checkout-field-group--wide', children: [_jsx("p", { className: 'shared-stripe-checkout-field-label', children: customerEmailLabel }), _jsx("div", { className: 'shared-stripe-checkout-field shared-stripe-checkout-field--wide', children: _jsx("input", { type: 'email', inputMode: 'email', autoComplete: 'email', className: [
231
242
  'shared-stripe-checkout-text-input',
232
243
  customerEmailEditable ? '' : 'is-readonly',
233
244
  ]
@@ -246,6 +257,17 @@ function SharedStripeCheckoutForm({ amountCents, checkoutAnalytics, checkoutSess
246
257
  }
247
258
  export function SharedStripeCheckoutDialog(_a) {
248
259
  var { checkoutAnalytics, checkoutSessionId, clientSecret, onClose, stripePromise } = _a, props = __rest(_a, ["checkoutAnalytics", "checkoutSessionId", "clientSecret", "onClose", "stripePromise"]);
260
+ const handleClose = useCallback(() => {
261
+ if (checkoutAnalytics) {
262
+ trackCheckoutCanceled({
263
+ analytics: checkoutAnalytics,
264
+ checkoutSessionId,
265
+ failureStage: 'checkout_dialog',
266
+ provider: 'stripe',
267
+ });
268
+ }
269
+ onClose();
270
+ }, [checkoutAnalytics, checkoutSessionId, onClose]);
249
271
  useEffect(() => {
250
272
  if (!checkoutAnalytics) {
251
273
  return;
@@ -255,15 +277,15 @@ export function SharedStripeCheckoutDialog(_a) {
255
277
  useEffect(() => {
256
278
  const handleKeyDown = (event) => {
257
279
  if (event.key === 'Escape') {
258
- onClose();
280
+ handleClose();
259
281
  }
260
282
  };
261
283
  window.addEventListener('keydown', handleKeyDown);
262
284
  return () => window.removeEventListener('keydown', handleKeyDown);
263
- }, [onClose]);
264
- return (_jsxs(_Fragment, { children: [_jsx("div", { className: 'shared-stripe-checkout-overlay', role: 'presentation', onClick: onClose, children: _jsx("div", { className: 'shared-stripe-checkout-shell', role: 'dialog', "aria-modal": 'true', "aria-labelledby": 'shared-stripe-checkout-title', onClick: (event) => event.stopPropagation(), children: _jsx(Elements, { stripe: stripePromise, options: {
285
+ }, [handleClose]);
286
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: 'shared-stripe-checkout-overlay', role: 'presentation', onClick: handleClose, children: _jsx("div", { className: 'shared-stripe-checkout-shell', role: 'dialog', "aria-modal": 'true', "aria-labelledby": 'shared-stripe-checkout-title', onClick: (event) => event.stopPropagation(), children: _jsx(Elements, { stripe: stripePromise, options: {
265
287
  clientSecret,
266
- }, children: _jsx(SharedStripeCheckoutForm, Object.assign({}, props, { checkoutAnalytics: checkoutAnalytics, checkoutSessionId: checkoutSessionId, clientSecret: clientSecret, onClose: onClose })) }) }) }), _jsx("style", { children: sharedStripeCheckoutDialogStyles })] }));
288
+ }, children: _jsx(SharedStripeCheckoutForm, Object.assign({}, props, { checkoutAnalytics: checkoutAnalytics, checkoutSessionId: checkoutSessionId, clientSecret: clientSecret, onClose: handleClose })) }) }) }), _jsx("style", { children: sharedStripeCheckoutDialogStyles })] }));
267
289
  }
268
290
  const sharedStripeCheckoutDialogStyles = `
269
291
  .shared-stripe-checkout-overlay {
@@ -11,7 +11,7 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  return t;
12
12
  };
13
13
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
14
- import { useEffect, useMemo, useState, } from 'react';
14
+ import { useCallback, useEffect, useMemo, useState, } from 'react';
15
15
  import { CheckoutProvider, PaymentElement, useCheckout, } from '@stripe/react-stripe-js/checkout';
16
16
  import { PaymentBrandMark, SecurityLockIcon } from './CheckoutBrandAssets.js';
17
17
  import { StripeCheckoutExpressCheckoutButton } from './StripeCheckoutExpressCheckoutButton.js';
@@ -19,6 +19,7 @@ import { getUnsupportedCheckoutCountryMessage, normalizeSupportedCheckoutCountri
19
19
  import { isInactiveCheckoutSessionError } from '../hooks/useStripeSubscriptionCheckoutSession.js';
20
20
  import { getStripeWalletPaymentMethodOrder, usePlatformWalletPaymentMethods, } from './walletPlatform.js';
21
21
  import { queueStripeSubscriptionCheckoutStarted, trackPaidStripeSubscriptionCheckoutCompleted, trackStripeSubscriptionPaymentInfoSubmitted, } from '../services/checkoutCompletionAnalytics.service.js';
22
+ import { trackCheckoutCanceled, trackCheckoutFailureShown, } from '../../../services/checkoutObservability.service.js';
22
23
  const defaultPaymentMethodLabels = ['Visa', 'Mastercard', 'Maestro', 'Discover'];
23
24
  const stripeCheckoutV2Appearance = {
24
25
  labels: 'above',
@@ -151,11 +152,21 @@ function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonColor, c
151
152
  setError(null);
152
153
  onInactiveCheckoutSession === null || onInactiveCheckoutSession === void 0 ? void 0 : onInactiveCheckoutSession();
153
154
  };
154
- const setSharedError = (message) => {
155
+ const setSharedError = (message, alreadyTracked = false) => {
155
156
  if (isInactiveCheckoutSessionError(message)) {
156
157
  handleInactiveCheckoutSession();
157
158
  return;
158
159
  }
160
+ if (message && checkoutAnalytics && !alreadyTracked) {
161
+ trackCheckoutFailureShown({
162
+ analytics: checkoutAnalytics,
163
+ checkoutSessionId,
164
+ errorMessage: message,
165
+ failureStage: 'card_form',
166
+ paymentMethod: 'card',
167
+ provider: 'stripe',
168
+ });
169
+ }
159
170
  setError(message);
160
171
  onError === null || onError === void 0 ? void 0 : onError(message);
161
172
  };
@@ -300,7 +311,7 @@ function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonColor, c
300
311
  effectiveSelectedMethod === 'wallet' ? 'is-visible' : '',
301
312
  ]
302
313
  .filter(Boolean)
303
- .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: setSharedError, 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: [
314
+ .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: [
304
315
  'shared-checkout-v2-card-panel',
305
316
  effectiveSelectedMethod === 'card' ? 'is-visible' : '',
306
317
  ]
@@ -336,6 +347,17 @@ export function SharedStripeCheckoutV2Dialog(_a) {
336
347
  var { checkoutAnalytics, checkoutSessionId, clientSecret, onClose, stripePromise, supportedCountries, variant = 'standard' } = _a, props = __rest(_a, ["checkoutAnalytics", "checkoutSessionId", "clientSecret", "onClose", "stripePromise", "supportedCountries", "variant"]);
337
348
  const normalizedSupportedCountries = useMemo(() => normalizeSupportedCheckoutCountries(supportedCountries), [supportedCountries]);
338
349
  const fixedBillingCountry = getFixedSupportedBillingCountry(normalizedSupportedCountries);
350
+ const handleClose = useCallback(() => {
351
+ if (checkoutAnalytics) {
352
+ trackCheckoutCanceled({
353
+ analytics: checkoutAnalytics,
354
+ checkoutSessionId,
355
+ failureStage: 'checkout_dialog',
356
+ provider: 'stripe',
357
+ });
358
+ }
359
+ onClose();
360
+ }, [checkoutAnalytics, checkoutSessionId, onClose]);
339
361
  useEffect(() => {
340
362
  if (!checkoutAnalytics) {
341
363
  return;
@@ -345,13 +367,13 @@ export function SharedStripeCheckoutV2Dialog(_a) {
345
367
  useEffect(() => {
346
368
  const handleKeyDown = (event) => {
347
369
  if (event.key === 'Escape') {
348
- onClose();
370
+ handleClose();
349
371
  }
350
372
  };
351
373
  window.addEventListener('keydown', handleKeyDown);
352
374
  return () => window.removeEventListener('keydown', handleKeyDown);
353
- }, [onClose]);
354
- return (_jsxs(_Fragment, { children: [_jsx("div", { className: 'shared-checkout-v2-overlay', children: _jsx("div", { className: 'shared-checkout-v2-dialog', "data-variant": variant, role: 'dialog', "aria-modal": 'true', "aria-labelledby": 'shared-checkout-v2-title', children: _jsx(CheckoutSessionProvider, { clientSecret: clientSecret, fixedBillingCountry: fixedBillingCountry, stripePromise: stripePromise, variant: variant, children: _jsx(SharedStripeCheckoutV2CheckoutSessionForm, Object.assign({}, props, { checkoutAnalytics: checkoutAnalytics, checkoutSessionId: checkoutSessionId, clientSecret: clientSecret, onClose: onClose, supportedCountries: normalizedSupportedCountries, variant: variant })) }) }) }), _jsx("style", { children: sharedStripeCheckoutV2Styles })] }));
375
+ }, [handleClose]);
376
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: 'shared-checkout-v2-overlay', children: _jsx("div", { className: 'shared-checkout-v2-dialog', "data-variant": variant, role: 'dialog', "aria-modal": 'true', "aria-labelledby": 'shared-checkout-v2-title', children: _jsx(CheckoutSessionProvider, { clientSecret: clientSecret, fixedBillingCountry: fixedBillingCountry, stripePromise: stripePromise, variant: variant, children: _jsx(SharedStripeCheckoutV2CheckoutSessionForm, Object.assign({}, props, { checkoutAnalytics: checkoutAnalytics, checkoutSessionId: checkoutSessionId, clientSecret: clientSecret, onClose: handleClose, supportedCountries: normalizedSupportedCountries, variant: variant })) }) }) }), _jsx("style", { children: sharedStripeCheckoutV2Styles })] }));
355
377
  }
356
378
  function SharedCheckoutSpecialOfferGift({ discountLabel, imageAlt, imageSrc, }) {
357
379
  if (imageSrc) {
@@ -4,6 +4,7 @@ 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
8
  const ExpressCheckoutElementWithCancel = ExpressCheckoutElement;
8
9
  const buildDefaultOptions = () => ({
9
10
  buttonHeight: 55,
@@ -43,7 +44,7 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAna
43
44
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
44
45
  return Object.assign(Object.assign(Object.assign({}, baseOptions), (options !== null && options !== void 0 ? options : {})), { buttonTheme: Object.assign(Object.assign({}, ((_a = baseOptions.buttonTheme) !== null && _a !== void 0 ? _a : {})), ((_b = options === null || options === void 0 ? void 0 : options.buttonTheme) !== null && _b !== void 0 ? _b : {})), buttonType: Object.assign(Object.assign({}, ((_c = baseOptions.buttonType) !== null && _c !== void 0 ? _c : {})), ((_d = options === null || options === void 0 ? void 0 : options.buttonType) !== null && _d !== void 0 ? _d : {})), layout: Object.assign(Object.assign({}, ((_e = baseOptions.layout) !== null && _e !== void 0 ? _e : {})), ((_f = options === null || options === void 0 ? void 0 : options.layout) !== null && _f !== void 0 ? _f : {})), paymentMethods: Object.assign(Object.assign({}, ((_g = baseOptions.paymentMethods) !== null && _g !== void 0 ? _g : {})), ((_h = options === null || options === void 0 ? void 0 : options.paymentMethods) !== null && _h !== void 0 ? _h : {})), paymentMethodOrder: (_j = options === null || options === void 0 ? void 0 : options.paymentMethodOrder) !== null && _j !== void 0 ? _j : baseOptions.paymentMethodOrder });
45
46
  }, [baseOptions, options]);
46
- const ensureServerUpdated = useCallback(async () => {
47
+ const ensureServerUpdated = useCallback(async (paymentMethod) => {
47
48
  if (!normalizedServerUpdateKey) {
48
49
  return true;
49
50
  }
@@ -57,14 +58,30 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAna
57
58
  return serverUpdatePromiseRef.current;
58
59
  }
59
60
  const updatePromise = (async () => {
61
+ let updateCallbackFailed = false;
60
62
  const updateResult = await checkoutState.checkout.runServerUpdate(async () => {
61
63
  const updated = await onServerUpdate();
62
64
  if (!updated) {
65
+ updateCallbackFailed = true;
63
66
  throw new Error('Unable to update checkout session.');
64
67
  }
65
68
  });
66
69
  if (updateResult.type === 'error') {
67
- onError === null || onError === void 0 ? void 0 : onError(updateResult.error.message || 'Unable to update checkout session.');
70
+ if (updateCallbackFailed) {
71
+ return false;
72
+ }
73
+ const message = updateResult.error.message || 'Unable to update checkout session.';
74
+ if (checkoutAnalytics) {
75
+ trackCheckoutFailureShown({
76
+ analytics: checkoutAnalytics,
77
+ checkoutSessionId,
78
+ errorMessage: message,
79
+ failureStage: 'wallet_session_update',
80
+ paymentMethod,
81
+ provider: 'stripe',
82
+ });
83
+ }
84
+ onError === null || onError === void 0 ? void 0 : onError(message);
68
85
  return false;
69
86
  }
70
87
  appliedServerUpdateKeyRef.current = normalizedServerUpdateKey;
@@ -78,7 +95,7 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAna
78
95
  finally {
79
96
  serverUpdatePromiseRef.current = null;
80
97
  }
81
- }, [checkoutState, normalizedServerUpdateKey, onError, onServerUpdate]);
98
+ }, [checkoutAnalytics, checkoutSessionId, checkoutState, normalizedServerUpdateKey, onError, onServerUpdate]);
82
99
  useEffect(() => {
83
100
  if (!normalizedServerUpdateKey) {
84
101
  appliedServerUpdateKeyRef.current = '';
@@ -103,6 +120,19 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAna
103
120
  checkoutStartSource,
104
121
  paymentMethod }));
105
122
  };
123
+ const reportFailure = (message, failureStage, paymentMethod) => {
124
+ if (checkoutAnalytics) {
125
+ trackCheckoutFailureShown({
126
+ analytics: checkoutAnalytics,
127
+ checkoutSessionId,
128
+ errorMessage: message,
129
+ failureStage,
130
+ paymentMethod,
131
+ provider: 'stripe',
132
+ });
133
+ }
134
+ onError === null || onError === void 0 ? void 0 : onError(message);
135
+ };
106
136
  const handleConfirm = async (event) => {
107
137
  var _a, _b, _c;
108
138
  // Checkout Sessions omits the Express Checkout onClick callback. Confirmation
@@ -113,7 +143,7 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAna
113
143
  const message = checkoutState.type === 'error'
114
144
  ? checkoutState.error.message
115
145
  : 'Wallet checkout is not ready yet.';
116
- onError === null || onError === void 0 ? void 0 : onError(message);
146
+ reportFailure(message, 'wallet_checkout_not_ready', event.expressPaymentType);
117
147
  event.paymentFailed({ reason: 'fail', message });
118
148
  return;
119
149
  }
@@ -124,14 +154,13 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAna
124
154
  supportedCountries: normalizedSupportedCountries,
125
155
  });
126
156
  if (unsupportedCountryError) {
127
- onError === null || onError === void 0 ? void 0 : onError(unsupportedCountryError);
157
+ reportFailure(unsupportedCountryError, 'wallet_billing_country', event.expressPaymentType);
128
158
  event.paymentFailed({ reason: 'invalid_billing_address', message: unsupportedCountryError });
129
159
  return;
130
160
  }
131
- const updated = await ensureServerUpdated();
161
+ const updated = await ensureServerUpdated(event.expressPaymentType);
132
162
  if (!updated) {
133
163
  const message = 'Unable to update checkout session.';
134
- onError === null || onError === void 0 ? void 0 : onError(message);
135
164
  event.paymentFailed({ reason: 'fail', message });
136
165
  return;
137
166
  }
@@ -144,14 +173,23 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAna
144
173
  const message = confirmError instanceof Error && confirmError.message
145
174
  ? confirmError.message
146
175
  : 'Unable to prepare wallet checkout.';
147
- onError === null || onError === void 0 ? void 0 : onError(message);
176
+ reportFailure(message, 'wallet_before_confirm', event.expressPaymentType);
148
177
  event.paymentFailed({ reason: 'invalid_payment_data', message });
149
178
  return;
150
179
  }
151
180
  }
152
181
  if (beforeConfirm && !preparedCustomerEmail) {
153
182
  const message = 'Email is required to continue.';
154
- onError === null || onError === void 0 ? void 0 : onError(message);
183
+ if (checkoutAnalytics) {
184
+ trackCheckoutCanceled({
185
+ analytics: checkoutAnalytics,
186
+ checkoutSessionId,
187
+ failureStage: 'wallet_preconfirm',
188
+ paymentMethod: event.expressPaymentType,
189
+ provider: 'stripe',
190
+ });
191
+ }
192
+ reportFailure(message, 'wallet_email_required', event.expressPaymentType);
155
193
  event.paymentFailed({ reason: 'invalid_payment_data', message });
156
194
  return;
157
195
  }
@@ -164,7 +202,7 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAna
164
202
  : {})), { expressCheckoutConfirmEvent: event, redirect: 'if_required' }));
165
203
  if (result.type === 'error') {
166
204
  const message = result.error.message || 'Wallet checkout failed.';
167
- onError === null || onError === void 0 ? void 0 : onError(message);
205
+ reportFailure(message, 'wallet_confirmation', event.expressPaymentType);
168
206
  event.paymentFailed({ reason: 'fail', message });
169
207
  return;
170
208
  }
@@ -184,7 +222,17 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAna
184
222
  // Cancel has no method field; only single-method placements can identify it.
185
223
  const enabledMethods = Object.entries((_a = resolvedOptions.paymentMethods) !== null && _a !== void 0 ? _a : {})
186
224
  .filter(([, availability]) => availability !== 'never');
187
- queueCheckoutStarted(enabledMethods.length === 1 ? enabledMethods[0][0] : undefined, 'wallet_cancel');
225
+ const paymentMethod = enabledMethods.length === 1 ? enabledMethods[0][0] : undefined;
226
+ queueCheckoutStarted(paymentMethod, 'wallet_cancel');
227
+ if (checkoutAnalytics) {
228
+ trackCheckoutCanceled({
229
+ analytics: checkoutAnalytics,
230
+ checkoutSessionId,
231
+ failureStage: 'wallet_sheet',
232
+ paymentMethod,
233
+ provider: 'stripe',
234
+ });
235
+ }
188
236
  onCancel === null || onCancel === void 0 ? void 0 : onCancel();
189
237
  };
190
238
  return (_jsxs(_Fragment, { children: [_jsx("div", { className: [
@@ -196,7 +244,7 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAna
196
244
  .join(' '), children: _jsx(ExpressCheckoutElementWithCancel, { options: resolvedOptions, onCancel: handleCancel, onClick: handleClick, onConfirm: (event) => void handleConfirm(event), onLoadError: ({ error }) => {
197
245
  setWalletAvailable(false);
198
246
  onAvailabilityChange === null || onAvailabilityChange === void 0 ? void 0 : onAvailabilityChange(false);
199
- onError === null || onError === void 0 ? void 0 : onError(error.message || 'Unable to load wallet checkout.');
247
+ reportFailure(error.message || 'Unable to load wallet checkout.', 'wallet_load');
200
248
  }, onReady: ({ availablePaymentMethods: availablePaymentMethodsMap }) => {
201
249
  const available = Boolean(availabilityPaymentMethods.some((paymentMethod) => availablePaymentMethodsMap === null || availablePaymentMethodsMap === void 0 ? void 0 : availablePaymentMethodsMap[paymentMethod]));
202
250
  const resolvedAvailable = available || (keepInitialAvailableOnReady && initialAvailable === true);
@@ -3,6 +3,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
3
3
  import { useMemo, useState } from 'react';
4
4
  import { ExpressCheckoutElement, useElements, useStripe } from '@stripe/react-stripe-js';
5
5
  import { queueStripeSubscriptionCheckoutStarted, trackStripeSubscriptionPaymentInfoSubmitted, } from '../services/checkoutCompletionAnalytics.service.js';
6
+ import { trackCheckoutCanceled, trackCheckoutFailureShown, } from '../../../services/checkoutObservability.service.js';
6
7
  const normalizeBillingName = (input) => {
7
8
  var _a, _b, _c;
8
9
  const normalizedName = (_a = input.name) === null || _a === void 0 ? void 0 : _a.trim();
@@ -69,11 +70,24 @@ export function StripeExpressCheckoutButton({ amountCents, beforeConfirm, checko
69
70
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
70
71
  return Object.assign(Object.assign(Object.assign({}, baseOptions), (options !== null && options !== void 0 ? options : {})), { buttonTheme: Object.assign(Object.assign({}, ((_a = baseOptions.buttonTheme) !== null && _a !== void 0 ? _a : {})), ((_b = options === null || options === void 0 ? void 0 : options.buttonTheme) !== null && _b !== void 0 ? _b : {})), buttonType: Object.assign(Object.assign({}, ((_c = baseOptions.buttonType) !== null && _c !== void 0 ? _c : {})), ((_d = options === null || options === void 0 ? void 0 : options.buttonType) !== null && _d !== void 0 ? _d : {})), layout: Object.assign(Object.assign({}, ((_e = baseOptions.layout) !== null && _e !== void 0 ? _e : {})), ((_f = options === null || options === void 0 ? void 0 : options.layout) !== null && _f !== void 0 ? _f : {})), paymentMethods: Object.assign(Object.assign({}, ((_g = baseOptions.paymentMethods) !== null && _g !== void 0 ? _g : {})), ((_h = options === null || options === void 0 ? void 0 : options.paymentMethods) !== null && _h !== void 0 ? _h : {})), lineItems: (_j = options === null || options === void 0 ? void 0 : options.lineItems) !== null && _j !== void 0 ? _j : baseOptions.lineItems, paymentMethodOrder: (_k = options === null || options === void 0 ? void 0 : options.paymentMethodOrder) !== null && _k !== void 0 ? _k : baseOptions.paymentMethodOrder });
71
72
  }, [baseOptions, options]);
73
+ const reportFailure = (message, failureStage, paymentMethod) => {
74
+ if (checkoutAnalytics) {
75
+ trackCheckoutFailureShown({
76
+ analytics: checkoutAnalytics,
77
+ checkoutSessionId,
78
+ errorMessage: message,
79
+ failureStage,
80
+ paymentMethod,
81
+ provider: 'stripe',
82
+ });
83
+ }
84
+ onError === null || onError === void 0 ? void 0 : onError(message);
85
+ };
72
86
  const handleConfirm = async (event) => {
73
87
  var _a;
74
88
  if (!stripe || !elements) {
75
89
  const message = 'Wallet checkout is not ready yet.';
76
- onError === null || onError === void 0 ? void 0 : onError(message);
90
+ reportFailure(message, 'wallet_checkout_not_ready', event.expressPaymentType);
77
91
  event.paymentFailed({ reason: 'fail', message });
78
92
  return;
79
93
  }
@@ -87,21 +101,30 @@ export function StripeExpressCheckoutButton({ amountCents, beforeConfirm, checko
87
101
  const message = confirmError instanceof Error && confirmError.message
88
102
  ? confirmError.message
89
103
  : 'Unable to prepare wallet checkout.';
90
- onError === null || onError === void 0 ? void 0 : onError(message);
104
+ reportFailure(message, 'wallet_before_confirm', event.expressPaymentType);
91
105
  event.paymentFailed({ reason: 'invalid_payment_data', message });
92
106
  return;
93
107
  }
94
108
  }
95
109
  if (beforeConfirm && !preparedCustomerEmail) {
96
110
  const message = 'Email is required to continue.';
97
- onError === null || onError === void 0 ? void 0 : onError(message);
111
+ if (checkoutAnalytics) {
112
+ trackCheckoutCanceled({
113
+ analytics: checkoutAnalytics,
114
+ checkoutSessionId,
115
+ failureStage: 'wallet_preconfirm',
116
+ paymentMethod: event.expressPaymentType,
117
+ provider: 'stripe',
118
+ });
119
+ }
120
+ reportFailure(message, 'wallet_email_required', event.expressPaymentType);
98
121
  event.paymentFailed({ reason: 'invalid_payment_data', message });
99
122
  return;
100
123
  }
101
124
  const submitResult = await elements.submit();
102
125
  if (submitResult.error) {
103
126
  const message = submitResult.error.message || 'Unable to start wallet checkout.';
104
- onError === null || onError === void 0 ? void 0 : onError(message);
127
+ reportFailure(message, 'wallet_payment_details', event.expressPaymentType);
105
128
  event.paymentFailed({ reason: 'invalid_payment_data', message });
106
129
  return;
107
130
  }
@@ -127,7 +150,7 @@ export function StripeExpressCheckoutButton({ amountCents, beforeConfirm, checko
127
150
  });
128
151
  if (result.error) {
129
152
  const message = result.error.message || 'Wallet checkout failed.';
130
- onError === null || onError === void 0 ? void 0 : onError(message);
153
+ reportFailure(message, 'wallet_confirmation', event.expressPaymentType);
131
154
  event.paymentFailed({ reason: 'fail', message });
132
155
  return;
133
156
  }
@@ -144,16 +167,32 @@ export function StripeExpressCheckoutButton({ amountCents, beforeConfirm, checko
144
167
  }
145
168
  event.resolve();
146
169
  };
170
+ const handleCancel = () => {
171
+ var _a;
172
+ const enabledMethods = Object.entries((_a = resolvedOptions.paymentMethods) !== null && _a !== void 0 ? _a : {})
173
+ .filter(([, availability]) => availability !== 'never');
174
+ const paymentMethod = enabledMethods.length === 1 ? enabledMethods[0][0] : undefined;
175
+ if (checkoutAnalytics) {
176
+ trackCheckoutCanceled({
177
+ analytics: checkoutAnalytics,
178
+ checkoutSessionId,
179
+ failureStage: 'wallet_sheet',
180
+ paymentMethod,
181
+ provider: 'stripe',
182
+ });
183
+ }
184
+ onCancel === null || onCancel === void 0 ? void 0 : onCancel();
185
+ };
147
186
  return (_jsxs(_Fragment, { children: [_jsx("div", { className: [
148
187
  'stripe-express-checkout-button',
149
188
  className !== null && className !== void 0 ? className : '',
150
189
  effectiveWalletAvailable === false ? 'is-hidden' : '',
151
190
  ]
152
191
  .filter(Boolean)
153
- .join(' '), children: _jsx(ExpressCheckoutElement, { options: resolvedOptions, onCancel: onCancel, onClick: handleClick, onConfirm: (event) => void handleConfirm(event), onLoadError: ({ error }) => {
192
+ .join(' '), children: _jsx(ExpressCheckoutElement, { options: resolvedOptions, onCancel: handleCancel, onClick: handleClick, onConfirm: (event) => void handleConfirm(event), onLoadError: ({ error }) => {
154
193
  setWalletAvailable(false);
155
194
  onAvailabilityChange === null || onAvailabilityChange === void 0 ? void 0 : onAvailabilityChange(false);
156
- onError === null || onError === void 0 ? void 0 : onError(error.message || 'Unable to load wallet checkout.');
195
+ reportFailure(error.message || 'Unable to load wallet checkout.', 'wallet_load');
157
196
  }, onReady: ({ availablePaymentMethods: availablePaymentMethodsMap }) => {
158
197
  const available = Boolean(availabilityPaymentMethods.some((paymentMethod) => availablePaymentMethodsMap === null || availablePaymentMethodsMap === void 0 ? void 0 : availablePaymentMethodsMap[paymentMethod]));
159
198
  const resolvedAvailable = available || (keepInitialAvailableOnReady && initialAvailable === true);
@@ -20,6 +20,6 @@ export function StripeSubscriptionWalletSurface(_a) {
20
20
  const normalizedSurfaceId = surfaceId.trim();
21
21
  const { customerName } = checkout, checkoutSessionInput = __rest(checkout, ["customerName"]);
22
22
  const surfaceAnalyticsMetadata = useMemo(() => buildStripeSubscriptionWalletSurfaceAnalyticsMetadata(checkout.analyticsMetadata, normalizedSurfaceId), [checkout.analyticsMetadata, normalizedSurfaceId]);
23
- const session = useStripeSubscriptionCheckoutSession(Object.assign(Object.assign({}, checkoutSessionInput), { analyticsMetadata: surfaceAnalyticsMetadata }));
23
+ const session = useStripeSubscriptionCheckoutSession(Object.assign(Object.assign({}, checkoutSessionInput), { analyticsMetadata: surfaceAnalyticsMetadata, checkoutAnalytics: surfaceProps.checkoutAnalytics }));
24
24
  return (_jsx(StripeWalletSurface, Object.assign({}, surfaceProps, { customerEmail: checkout.customerEmail, customerName: customerName, returnUrl: checkout.returnUrl, session: session, surfaceId: normalizedSurfaceId })));
25
25
  }
@@ -3,8 +3,10 @@ import type { RuntimeMode } from '@funnelsgrove/runtime';
3
3
  import { type CheckoutPreparationLoading, type CheckoutPreparationSource } from '../../paymentProvider.types.js';
4
4
  import type { PaywallDisplayPlan } from '../../../services/paywallOffer.service.js';
5
5
  import { type PaywallPlan, type StripeRuntimeConfigOverrides } from '../services/stripe.service.js';
6
+ import { type CheckoutObservabilityAnalyticsContext } from '../../../services/checkoutObservability.service.js';
6
7
  export type StripeSubscriptionCheckoutSessionInput = {
7
8
  analyticsMetadata?: Record<string, unknown> | null;
9
+ checkoutAnalytics?: CheckoutObservabilityAnalyticsContext | null;
8
10
  checkoutMode: RuntimeMode;
9
11
  couponId?: string | null;
10
12
  customerEmail?: string | null;
@@ -52,4 +54,5 @@ export type StripeSubscriptionCheckoutIntentKeyInput = Pick<StripeSubscriptionCh
52
54
  export declare function buildStripeSubscriptionCheckoutIntentKey({ checkoutMode, couponId, customerEmail, returnUrl, runtimeConfig, runtimeConfigRevisionId, paymentProfileId, provider, stripePublishableKey, userId, }: StripeSubscriptionCheckoutIntentKeyInput): string;
53
55
  export declare function shouldResetStripeSubscriptionCheckoutSessionForIntentKey(previousIntentKey: string | null, nextIntentKey: string): boolean;
54
56
  export declare function isInactiveCheckoutSessionError(message?: string | null): boolean;
55
- export declare function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMetadata, couponId, customerEmail, displayPlan, enabled, onError, plan, returnUrl, runtimeConfig, runtimeConfigRevisionId, paymentProfileId, provider, stripePublishableKey, userId, }: StripeSubscriptionCheckoutSessionInput): StripeSubscriptionCheckoutSession;
57
+ export declare const shouldReportStripeCheckoutFailureShown: (message: string) => boolean;
58
+ export declare function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMetadata, checkoutAnalytics, couponId, customerEmail, displayPlan, enabled, onError, plan, returnUrl, runtimeConfig, runtimeConfigRevisionId, paymentProfileId, provider, stripePublishableKey, userId, }: StripeSubscriptionCheckoutSessionInput): StripeSubscriptionCheckoutSession;
@@ -14,6 +14,7 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } f
14
14
  import { createEmptyCheckoutPreparationLoading, } from '../../paymentProvider.types.js';
15
15
  import { createStripeSubscriptionCheckout, getStripePromise, isStripeConfigured, updateStripeSubscriptionCheckoutPlan, } from '../services/stripe.service.js';
16
16
  import { toAnalyticsPaymentMethod } from '../../../services/paymentMethodAnalytics.service.js';
17
+ import { resolveCheckoutObservabilityAnalyticsContext, trackCheckoutFailureShown, } from '../../../services/checkoutObservability.service.js';
17
18
  import { resolveStripeCheckoutAttempt, } from './checkoutAttempt.js';
18
19
  export function resolveStripeSubscriptionCheckoutPreparationOptions(source, options = {}) {
19
20
  var _a, _b;
@@ -68,7 +69,8 @@ export function isInactiveCheckoutSessionError(message) {
68
69
  var _a;
69
70
  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 : '');
70
71
  }
71
- export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMetadata, couponId, customerEmail, displayPlan, enabled = true, onError, plan, returnUrl, runtimeConfig, runtimeConfigRevisionId, paymentProfileId, provider, stripePublishableKey, userId, }) {
72
+ export const shouldReportStripeCheckoutFailureShown = (message) => !isInactiveCheckoutSessionError(message);
73
+ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMetadata, checkoutAnalytics, couponId, customerEmail, displayPlan, enabled = true, onError, plan, returnUrl, runtimeConfig, runtimeConfigRevisionId, paymentProfileId, provider, stripePublishableKey, userId, }) {
72
74
  var _a, _b, _c, _d, _e;
73
75
  const [clientSecret, setClientSecret] = useState(null);
74
76
  const [clientSecretIntentKey, setClientSecretIntentKey] = useState(null);
@@ -218,7 +220,24 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
218
220
  catch (subscriptionError) {
219
221
  if (((_f = creatingIntentRef.current) === null || _f === void 0 ? void 0 : _f.key) === requestKey &&
220
222
  ((_g = creatingIntentRef.current) === null || _g === void 0 ? void 0 : _g.id) === requestId) {
221
- setError(getFriendlyStripeCheckoutError(subscriptionError));
223
+ const message = getFriendlyStripeCheckoutError(subscriptionError);
224
+ const observabilityAnalytics = resolveCheckoutObservabilityAnalyticsContext({
225
+ analytics: checkoutAnalytics,
226
+ environment: checkoutMode,
227
+ metadata: analyticsMetadataRef.current,
228
+ runtimeConfig,
229
+ });
230
+ if (observabilityAnalytics
231
+ && shouldReportStripeCheckoutFailureShown(message)) {
232
+ trackCheckoutFailureShown({
233
+ analytics: observabilityAnalytics,
234
+ errorMessage: message,
235
+ failureStage: 'checkout_session_creation',
236
+ paymentMethod: options.paymentMethod,
237
+ provider: 'stripe',
238
+ });
239
+ }
240
+ setError(message);
222
241
  }
223
242
  return null;
224
243
  }
@@ -238,6 +257,7 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
238
257
  }, [
239
258
  activeClientSecret,
240
259
  checkoutMode,
260
+ checkoutAnalytics,
241
261
  clearActiveCheckoutSession,
242
262
  configured,
243
263
  couponId,
@@ -308,13 +328,31 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
308
328
  return true;
309
329
  }
310
330
  catch (subscriptionError) {
311
- setError(getFriendlyStripeCheckoutError(subscriptionError));
331
+ const message = getFriendlyStripeCheckoutError(subscriptionError);
332
+ const observabilityAnalytics = resolveCheckoutObservabilityAnalyticsContext({
333
+ analytics: checkoutAnalytics,
334
+ environment: checkoutMode,
335
+ metadata: analyticsMetadataRef.current,
336
+ runtimeConfig,
337
+ });
338
+ if (observabilityAnalytics
339
+ && shouldReportStripeCheckoutFailureShown(message)) {
340
+ trackCheckoutFailureShown({
341
+ analytics: observabilityAnalytics,
342
+ checkoutSessionId: activeCheckoutSessionId,
343
+ errorMessage: message,
344
+ failureStage: 'checkout_session_update',
345
+ provider: 'stripe',
346
+ });
347
+ }
348
+ setError(message);
312
349
  return false;
313
350
  }
314
351
  }, [
315
352
  activeCheckoutSessionId,
316
353
  activePlanKey,
317
354
  checkoutMode,
355
+ checkoutAnalytics,
318
356
  configured,
319
357
  couponId,
320
358
  customerEmail,
@@ -0,0 +1,31 @@
1
+ import { type FunnelSdkRuntimeConfigOverrides, type FunnelStepType, type RuntimeMode } from '@funnelsgrove/runtime';
2
+ export type CheckoutObservabilityAnalyticsContext = {
3
+ environment?: RuntimeMode;
4
+ featureFlags?: Record<string, string | null | undefined>;
5
+ metadata?: Record<string, unknown> | null;
6
+ runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
7
+ stepId?: string;
8
+ stepName?: string;
9
+ stepType?: Extract<FunnelStepType, 'checkout' | 'paywall_offer' | 'upsell_offer'>;
10
+ stepContractVersion?: number;
11
+ };
12
+ export declare const resolveCheckoutObservabilityAnalyticsContext: (input: {
13
+ analytics?: CheckoutObservabilityAnalyticsContext | null;
14
+ environment?: RuntimeMode;
15
+ metadata?: Record<string, unknown> | null;
16
+ runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
17
+ }) => CheckoutObservabilityAnalyticsContext;
18
+ type CheckoutObservabilityInput = {
19
+ analytics: CheckoutObservabilityAnalyticsContext;
20
+ checkoutSessionId?: string | null;
21
+ failureStage: string;
22
+ paymentMethod?: unknown;
23
+ provider: 'solidgate' | 'stripe';
24
+ };
25
+ type CheckoutFailureShownInput = CheckoutObservabilityInput & {
26
+ errorCode?: string | null;
27
+ errorMessage: string;
28
+ };
29
+ export declare const trackCheckoutFailureShown: (input: CheckoutFailureShownInput) => string | null;
30
+ export declare const trackCheckoutCanceled: (input: CheckoutObservabilityInput) => string | null;
31
+ export {};
@@ -0,0 +1,124 @@
1
+ import { publicAnalyticsSdk } from '@funnelsgrove/analytics';
2
+ import { buildMainApiUrl, buildSdkHeaders, FUNNEL_ID, FUNNEL_VERSION_ID, } from '@funnelsgrove/runtime';
3
+ import { toAnalyticsPaymentMethod } from './paymentMethodAnalytics.service.js';
4
+ const asNonEmptyString = (value) => typeof value === 'string' && value.trim() ? value.trim() : null;
5
+ export const resolveCheckoutObservabilityAnalyticsContext = (input) => {
6
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
7
+ const metadata = Object.assign(Object.assign({}, ((_a = input.metadata) !== null && _a !== void 0 ? _a : {})), ((_c = (_b = input.analytics) === null || _b === void 0 ? void 0 : _b.metadata) !== null && _c !== void 0 ? _c : {}));
8
+ const stepId = (_d = asNonEmptyString(metadata.step_id)) !== null && _d !== void 0 ? _d : asNonEmptyString(metadata.stepId);
9
+ const stepName = (_e = asNonEmptyString(metadata.step_name)) !== null && _e !== void 0 ? _e : asNonEmptyString(metadata.stepName);
10
+ const stepType = (_f = asNonEmptyString(metadata.step_type)) !== null && _f !== void 0 ? _f : asNonEmptyString(metadata.stepType);
11
+ const stepContractVersion = Number((_g = metadata.step_contract_version) !== null && _g !== void 0 ? _g : metadata.stepContractVersion);
12
+ const validStepType = ['checkout', 'paywall_offer', 'upsell_offer'].includes(stepType !== null && stepType !== void 0 ? stepType : '')
13
+ ? stepType
14
+ : undefined;
15
+ const validStepContractVersion = Number.isInteger(stepContractVersion) && stepContractVersion >= 1
16
+ ? stepContractVersion
17
+ : undefined;
18
+ return {
19
+ environment: (_j = (_h = input.analytics) === null || _h === void 0 ? void 0 : _h.environment) !== null && _j !== void 0 ? _j : input.environment,
20
+ featureFlags: (_k = input.analytics) === null || _k === void 0 ? void 0 : _k.featureFlags,
21
+ metadata,
22
+ runtimeConfig: (_m = (_l = input.analytics) === null || _l === void 0 ? void 0 : _l.runtimeConfig) !== null && _m !== void 0 ? _m : input.runtimeConfig,
23
+ stepId: (_q = (_p = (_o = input.analytics) === null || _o === void 0 ? void 0 : _o.stepId) !== null && _p !== void 0 ? _p : stepId) !== null && _q !== void 0 ? _q : undefined,
24
+ stepName: (_t = (_s = (_r = input.analytics) === null || _r === void 0 ? void 0 : _r.stepName) !== null && _s !== void 0 ? _s : stepName) !== null && _t !== void 0 ? _t : undefined,
25
+ stepType: (_v = (_u = input.analytics) === null || _u === void 0 ? void 0 : _u.stepType) !== null && _v !== void 0 ? _v : validStepType,
26
+ stepContractVersion: (_x = (_w = input.analytics) === null || _w === void 0 ? void 0 : _w.stepContractVersion) !== null && _x !== void 0 ? _x : validStepContractVersion,
27
+ };
28
+ };
29
+ const sanitizeErrorMessage = (value) => value
30
+ .replace(/[\r\n\t]+/g, ' ')
31
+ .replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, '[redacted-email]')
32
+ .replace(/\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]+\b/g, '[redacted-key]')
33
+ .trim()
34
+ .slice(0, 500);
35
+ const reportCheckoutObservability = (input) => {
36
+ var _a, _b, _c, _d, _e, _f, _g;
37
+ const runtimeConfig = input.analytics.runtimeConfig;
38
+ const funnelId = ((_a = runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelId) === null || _a === void 0 ? void 0 : _a.trim()) || FUNNEL_ID.trim();
39
+ const environment = (_b = input.analytics.environment) !== null && _b !== void 0 ? _b : (((_c = input.analytics.metadata) === null || _c === void 0 ? void 0 : _c.environment) === 'test' || ((_d = input.analytics.metadata) === null || _d === void 0 ? void 0 : _d.environment) === 'live'
40
+ ? input.analytics.metadata.environment
41
+ : null);
42
+ if (!funnelId || !environment) {
43
+ return Promise.resolve();
44
+ }
45
+ const path = '/sdk/public/payments/checkout-observability';
46
+ const baseUrl = (_e = runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.apiBaseUrl) === null || _e === void 0 ? void 0 : _e.trim().replace(/\/+$/, '');
47
+ const url = baseUrl ? `${baseUrl}${path}` : buildMainApiUrl(path);
48
+ const publishableKey = (_f = runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelSdkPublishableKey) === null || _f === void 0 ? void 0 : _f.trim();
49
+ return fetch(url, {
50
+ method: 'POST',
51
+ headers: publishableKey
52
+ ? { 'Content-Type': 'application/json', 'x-sdk-publishable-key': publishableKey }
53
+ : buildSdkHeaders({ 'Content-Type': 'application/json' }),
54
+ body: JSON.stringify({
55
+ checkoutSessionId: input.checkoutSessionId || undefined,
56
+ environment,
57
+ errorCode: input.errorCode,
58
+ errorMessage: input.errorMessage,
59
+ eventId: input.eventId,
60
+ eventType: input.eventType,
61
+ failureStage: input.failureStage,
62
+ funnelId,
63
+ funnelVersionId: ((_g = runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelVersionId) === null || _g === void 0 ? void 0 : _g.trim()) || FUNNEL_VERSION_ID.trim() || undefined,
64
+ paymentMethod: input.paymentMethod || undefined,
65
+ provider: input.provider,
66
+ }),
67
+ keepalive: true,
68
+ }).then(() => undefined);
69
+ };
70
+ const resolveErrorCode = (message, requestedCode) => {
71
+ const normalizedCode = requestedCode === null || requestedCode === void 0 ? void 0 : requestedCode.trim().slice(0, 120);
72
+ if (normalizedCode) {
73
+ return normalizedCode;
74
+ }
75
+ if (message === 'Published Stripe checkout selection is incomplete') {
76
+ return 'published_checkout_selection_incomplete';
77
+ }
78
+ if (/checkout session.*no longer active/i.test(message)) {
79
+ return 'checkout_session_inactive';
80
+ }
81
+ return 'checkout_failure';
82
+ };
83
+ const emitCheckoutObservability = (eventType, input, failure) => {
84
+ var _a, _b;
85
+ const paymentMethod = toAnalyticsPaymentMethod(input.paymentMethod);
86
+ 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 });
87
+ const eventId = publicAnalyticsSdk.track({
88
+ eventType,
89
+ environment: input.analytics.environment,
90
+ featureFlags: input.analytics.featureFlags,
91
+ metadata,
92
+ stepContractVersion: input.analytics.stepContractVersion,
93
+ stepId: input.analytics.stepId,
94
+ stepName: input.analytics.stepName,
95
+ stepType: input.analytics.stepType,
96
+ });
97
+ if (!eventId) {
98
+ return null;
99
+ }
100
+ void reportCheckoutObservability({
101
+ analytics: input.analytics,
102
+ checkoutSessionId: input.checkoutSessionId,
103
+ errorCode: failure === null || failure === void 0 ? void 0 : failure.errorCode,
104
+ errorMessage: failure === null || failure === void 0 ? void 0 : failure.errorMessage,
105
+ eventId,
106
+ eventType,
107
+ failureStage: input.failureStage,
108
+ paymentMethod,
109
+ provider: input.provider,
110
+ }).catch(() => undefined);
111
+ void publicAnalyticsSdk.flush().catch(() => 0);
112
+ return eventId;
113
+ };
114
+ export const trackCheckoutFailureShown = (input) => {
115
+ const errorMessage = sanitizeErrorMessage(input.errorMessage);
116
+ if (!errorMessage) {
117
+ return null;
118
+ }
119
+ return emitCheckoutObservability('checkout_failure_shown', input, {
120
+ errorCode: resolveErrorCode(errorMessage, input.errorCode),
121
+ errorMessage,
122
+ });
123
+ };
124
+ export const trackCheckoutCanceled = (input) => emitCheckoutObservability('checkout_canceled', input);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnelsgrove/payments",
3
- "version": "0.11.6",
3
+ "version": "0.11.7",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/The-Solid-Grove/funnelsgrove.git",