@funnelsgrove/payments 0.1.39 → 0.1.41

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