@funnelsgrove/payments 0.23.3 → 0.25.0

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.
@@ -0,0 +1 @@
1
+ export declare const PAYMENTS_PACKAGE_VERSION = "0.25.0";
@@ -0,0 +1,2 @@
1
+ // Generated by scripts/generate-package-version.mjs.
2
+ export const PAYMENTS_PACKAGE_VERSION = "0.25.0";
@@ -22,6 +22,8 @@ export type StripeCheckoutExpressCheckoutButtonProps = {
22
22
  supportedCountries?: readonly string[];
23
23
  onServerUpdate?: () => Promise<boolean>;
24
24
  onAvailabilityChange?: (available: boolean) => void;
25
+ onElementMount?: () => void;
26
+ onElementRetry?: () => void;
25
27
  onCancel?: () => void;
26
28
  onError?: (message: string | null) => void;
27
29
  onPaymentInfoSubmitted?: () => void | Promise<void>;
@@ -35,4 +37,4 @@ export type StripeCheckoutExpressCheckoutButtonProps = {
35
37
  onSuccess?: (result: StripeWalletCheckoutSuccess) => void | Promise<void>;
36
38
  unsupportedCountryMessage?: string;
37
39
  };
38
- export declare function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAnalytics, checkoutSessionId, returnUrl, confirmEmail, customerEmail, className, options, availabilityPaymentMethods, initialAvailable, keepInitialAvailableOnReady, serverUpdateKey, supportedCountries, onServerUpdate, onAvailabilityChange, onCancel, onError, onPaymentInfoSubmitted, onPaymentRejected, onSuccess, onEmbeddedComplete, onVerificationPending, unsupportedCountryMessage, }: StripeCheckoutExpressCheckoutButtonProps): import("react/jsx-runtime").JSX.Element;
40
+ export declare function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAnalytics, checkoutSessionId, returnUrl, confirmEmail, customerEmail, className, options, availabilityPaymentMethods, initialAvailable, keepInitialAvailableOnReady, serverUpdateKey, supportedCountries, onServerUpdate, onAvailabilityChange, onElementMount, onElementRetry, onCancel, onError, onPaymentInfoSubmitted, onPaymentRejected, onSuccess, onEmbeddedComplete, onVerificationPending, unsupportedCountryMessage, }: StripeCheckoutExpressCheckoutButtonProps): import("react/jsx-runtime").JSX.Element;
@@ -53,13 +53,19 @@ const normalizeStripeCheckoutProviderError = (error) => {
53
53
  type: typeof record.type === 'string' ? record.type : undefined,
54
54
  };
55
55
  };
56
- export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAnalytics, checkoutSessionId, returnUrl, confirmEmail = true, customerEmail, className, options, availabilityPaymentMethods = ['applePay'], initialAvailable, keepInitialAvailableOnReady = false, serverUpdateKey, supportedCountries, onServerUpdate, onAvailabilityChange, onCancel, onError, onPaymentInfoSubmitted, onPaymentRejected, onSuccess, onEmbeddedComplete, onVerificationPending, unsupportedCountryMessage, }) {
56
+ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAnalytics, checkoutSessionId, returnUrl, confirmEmail = true, customerEmail, className, options, availabilityPaymentMethods = ['applePay'], initialAvailable, keepInitialAvailableOnReady = false, serverUpdateKey, supportedCountries, onServerUpdate, onAvailabilityChange, onElementMount, onElementRetry, onCancel, onError, onPaymentInfoSubmitted, onPaymentRejected, onSuccess, onEmbeddedComplete, onVerificationPending, unsupportedCountryMessage, }) {
57
57
  const checkoutState = useCheckout();
58
58
  const [completed, setCompleted] = useState(false);
59
59
  const completedRef = useRef(false);
60
60
  const verificationInFlightRef = useRef(false);
61
61
  const [verificationInFlight, setVerificationInFlight] = useState(false);
62
62
  const [verificationError, setVerificationError] = useState(null);
63
+ const onElementMountRef = useRef(onElementMount);
64
+ onElementMountRef.current = onElementMount;
65
+ useEffect(() => {
66
+ var _a;
67
+ (_a = onElementMountRef.current) === null || _a === void 0 ? void 0 : _a.call(onElementMountRef);
68
+ }, []);
63
69
  const [loadRetry, setLoadRetry] = useState(0);
64
70
  const loadRetryUsedRef = useRef(false);
65
71
  const walletReadyRef = useRef(false);
@@ -373,6 +379,7 @@ export function StripeCheckoutExpressCheckoutButton({ beforeConfirm, checkoutAna
373
379
  }
374
380
  if (!walletReadyRef.current && !loadRetryUsedRef.current) {
375
381
  loadRetryUsedRef.current = true;
382
+ onElementRetry === null || onElementRetry === void 0 ? void 0 : onElementRetry();
376
383
  // Replace only the failed Element, retaining the Checkout Session.
377
384
  loadRetryTimerRef.current = setTimeout(() => {
378
385
  loadRetryTimerRef.current = null;
@@ -5,7 +5,8 @@ import { StripeExpressCheckoutElement } from './StripeExpressCheckoutElement.js'
5
5
  import { trackStripeSubscriptionCheckoutStarted, } from '../services/checkoutCompletionAnalytics.service.js';
6
6
  import { toAnalyticsPaymentMethod } from '../../../services/paymentMethodAnalytics.service.js';
7
7
  import { isInactiveCheckoutSessionError, } from '../hooks/useStripeSubscriptionCheckoutSession.js';
8
- import { trackCheckoutWalletReady } from '../../../services/checkoutObservability.service.js';
8
+ import { trackCheckoutWalletReady, trackCheckoutWalletSlow } from '../../../services/checkoutObservability.service.js';
9
+ const WALLET_SLOW_THRESHOLD_MS = 15000;
9
10
  export function getWalletSubscriptionCheckoutRenderState({ expressCheckoutAllowed, hasActiveClientSecret, hasStripePromise, suspended, walletAvailable, walletPreparing, }) {
10
11
  const shouldRenderExpressCheckout = !suspended &&
11
12
  expressCheckoutAllowed &&
@@ -44,14 +45,21 @@ export function WalletSubscriptionCheckoutSlot({ amountCents, appearance, availa
44
45
  const autoWalletPreparationIntentKeyRef = useRef(null);
45
46
  const walletCheckoutStartedAfterClickSessionIdRef = useRef(null);
46
47
  const walletReadinessCycleRef = useRef(null);
48
+ const checkoutAnalyticsRef = useRef(checkoutAnalytics);
49
+ checkoutAnalyticsRef.current = checkoutAnalytics;
47
50
  if (!walletReadinessCycleRef.current
48
51
  || walletReadinessCycleRef.current.intentKey !== session.intentKey) {
49
52
  walletReadinessCycleRef.current = {
53
+ activeSecretObservedAtMs: null,
50
54
  clientSecretReceivedAtMs: null,
55
+ elementMountedAtMs: null,
56
+ elementRetryCount: 0,
51
57
  intentKey: session.intentKey,
52
58
  mountedAtMs: null,
53
59
  reportedCheckoutSessionId: null,
54
60
  requestStartedAtMs: null,
61
+ slowReportedCheckoutSessionId: null,
62
+ stripePromiseWaitMs: null,
55
63
  };
56
64
  }
57
65
  useEffect(() => {
@@ -60,11 +68,71 @@ export function WalletSubscriptionCheckoutSlot({ amountCents, appearance, availa
60
68
  cycle.mountedAtMs = performance.now();
61
69
  }
62
70
  }, [session.intentKey]);
71
+ useEffect(() => {
72
+ const cycle = walletReadinessCycleRef.current;
73
+ if (session.activeClientSecret && (cycle === null || cycle === void 0 ? void 0 : cycle.intentKey) === session.intentKey) {
74
+ cycle.activeSecretObservedAtMs = performance.now();
75
+ }
76
+ }, [session.activeClientSecret, session.intentKey]);
77
+ useEffect(() => {
78
+ const stripePromise = session.stripePromise;
79
+ if (!stripePromise) {
80
+ return;
81
+ }
82
+ const intentKey = session.intentKey;
83
+ const startedAtMs = performance.now();
84
+ let active = true;
85
+ void stripePromise.then((stripe) => {
86
+ const cycle = walletReadinessCycleRef.current;
87
+ if (active && stripe && (cycle === null || cycle === void 0 ? void 0 : cycle.intentKey) === intentKey) {
88
+ cycle.stripePromiseWaitMs = Math.max(0, performance.now() - startedAtMs);
89
+ }
90
+ }).catch(() => undefined);
91
+ return () => {
92
+ active = false;
93
+ };
94
+ }, [session.intentKey, session.stripePromise]);
63
95
  const slotDisabled = disabled || !session.configured;
64
96
  const analyticsPaymentMethod = toAnalyticsPaymentMethod(availabilityPaymentMethod);
65
97
  const walletAvailable = walletAvailability.intentKey === session.intentKey
66
98
  ? walletAvailability.available
67
99
  : null;
100
+ useEffect(() => {
101
+ var _a;
102
+ const checkoutSessionId = (_a = session.checkoutSessionId) === null || _a === void 0 ? void 0 : _a.trim();
103
+ if (slotDisabled
104
+ || suspended
105
+ || !expressCheckoutAllowed
106
+ || !session.activeClientSecret
107
+ || !checkoutSessionId
108
+ || walletAvailable !== null) {
109
+ return;
110
+ }
111
+ const intentKey = session.intentKey;
112
+ const observedAtMs = performance.now();
113
+ const timer = setTimeout(() => {
114
+ var _a;
115
+ const cycle = walletReadinessCycleRef.current;
116
+ const analytics = checkoutAnalyticsRef.current;
117
+ if (!analytics
118
+ || !cycle
119
+ || cycle.intentKey !== intentKey
120
+ || cycle.reportedCheckoutSessionId === checkoutSessionId
121
+ || cycle.slowReportedCheckoutSessionId === checkoutSessionId) {
122
+ return;
123
+ }
124
+ cycle.slowReportedCheckoutSessionId = checkoutSessionId;
125
+ trackCheckoutWalletSlow({
126
+ analytics,
127
+ checkoutSessionId,
128
+ paymentMethod: availabilityPaymentMethod,
129
+ provider: 'stripe',
130
+ walletTimingOrigin: cycle.clientSecretReceivedAtMs === null ? 'already_active' : 'prepared_here',
131
+ walletClientSecretToSlowMs: Math.max(0, performance.now() - ((_a = cycle.clientSecretReceivedAtMs) !== null && _a !== void 0 ? _a : observedAtMs)),
132
+ });
133
+ }, WALLET_SLOW_THRESHOLD_MS);
134
+ return () => clearTimeout(timer);
135
+ }, [availabilityPaymentMethod, expressCheckoutAllowed, session.activeClientSecret, session.checkoutSessionId, session.intentKey, slotDisabled, suspended, walletAvailable]);
68
136
  const { placeholderIsPreparing, shouldRenderExpressCheckout, shouldRenderPlaceholder, } = getWalletSubscriptionCheckoutRenderState({
69
137
  expressCheckoutAllowed,
70
138
  hasActiveClientSecret: Boolean(session.activeClientSecret),
@@ -103,7 +171,10 @@ export function WalletSubscriptionCheckoutSlot({ amountCents, appearance, availa
103
171
  const requestStartedAtMs = performance.now();
104
172
  (_a = cycle.mountedAtMs) !== null && _a !== void 0 ? _a : (cycle.mountedAtMs = requestStartedAtMs);
105
173
  cycle.requestStartedAtMs = requestStartedAtMs;
174
+ cycle.activeSecretObservedAtMs = null;
106
175
  cycle.clientSecretReceivedAtMs = null;
176
+ cycle.elementMountedAtMs = null;
177
+ cycle.elementRetryCount = 0;
107
178
  cycle.reportedCheckoutSessionId = null;
108
179
  }, []);
109
180
  const finishWalletPreparationTiming = useCallback((intentKey, readyClientSecret) => {
@@ -180,9 +251,12 @@ export function WalletSubscriptionCheckoutSlot({ amountCents, appearance, availa
180
251
  available: null,
181
252
  intentKey: session.intentKey,
182
253
  });
254
+ startWalletPreparationTiming(session.intentKey);
183
255
  void session.restartWalletCheckout({
184
256
  checkoutStartSource: 'wallet_click',
185
257
  paymentMethod: analyticsPaymentMethod,
258
+ }).then((readyClientSecret) => {
259
+ finishWalletPreparationTiming(session.intentKey, readyClientSecret);
186
260
  });
187
261
  return;
188
262
  }
@@ -190,7 +264,7 @@ export function WalletSubscriptionCheckoutSlot({ amountCents, appearance, availa
190
264
  onError === null || onError === void 0 ? void 0 : onError(message);
191
265
  };
192
266
  const handleAvailabilityChange = (available) => {
193
- var _a;
267
+ var _a, _b, _c;
194
268
  reportWalletAvailability(session.intentKey, available);
195
269
  const cycle = walletReadinessCycleRef.current;
196
270
  const checkoutSessionId = ((_a = session.checkoutSessionId) === null || _a === void 0 ? void 0 : _a.trim()) || '';
@@ -200,22 +274,40 @@ export function WalletSubscriptionCheckoutSlot({ amountCents, appearance, availa
200
274
  || !cycle
201
275
  || cycle.intentKey !== session.intentKey
202
276
  || cycle.mountedAtMs === null
203
- || cycle.requestStartedAtMs === null
204
- || cycle.clientSecretReceivedAtMs === null
205
277
  || cycle.reportedCheckoutSessionId === checkoutSessionId) {
206
278
  return;
207
279
  }
208
280
  const readyAtMs = performance.now();
281
+ const { requestStartedAtMs, clientSecretReceivedAtMs } = cycle;
282
+ const preparedHere = requestStartedAtMs !== null && clientSecretReceivedAtMs !== null;
283
+ const observedAtMs = (_b = cycle.activeSecretObservedAtMs) !== null && _b !== void 0 ? _b : cycle.mountedAtMs;
209
284
  cycle.reportedCheckoutSessionId = checkoutSessionId;
210
285
  trackCheckoutWalletReady({
211
286
  analytics: checkoutAnalytics,
212
287
  checkoutSessionId,
213
288
  paymentMethod: availabilityPaymentMethod,
214
289
  provider: 'stripe',
215
- walletClientSecretToReadyMs: Math.max(0, readyAtMs - cycle.clientSecretReceivedAtMs),
290
+ walletTimingOrigin: preparedHere ? 'prepared_here' : 'already_active',
291
+ walletClientSecretToReadyMs: preparedHere
292
+ ? Math.max(0, readyAtMs - clientSecretReceivedAtMs) : undefined,
293
+ walletObservedActiveToReadyMs: preparedHere
294
+ ? undefined : Math.max(0, readyAtMs - observedAtMs),
216
295
  walletMountToReadyMs: Math.max(0, readyAtMs - cycle.mountedAtMs),
217
- walletMountToRequestMs: Math.max(0, cycle.requestStartedAtMs - cycle.mountedAtMs),
218
- walletRequestMs: Math.max(0, cycle.clientSecretReceivedAtMs - cycle.requestStartedAtMs),
296
+ walletMountToRequestMs: preparedHere
297
+ ? Math.max(0, requestStartedAtMs - cycle.mountedAtMs) : undefined,
298
+ walletRequestMs: preparedHere
299
+ ? Math.max(0, clientSecretReceivedAtMs - requestStartedAtMs) : undefined,
300
+ walletClientSecretToElementMountMs: !preparedHere || cycle.elementMountedAtMs === null
301
+ ? undefined
302
+ : Math.max(0, cycle.elementMountedAtMs - clientSecretReceivedAtMs),
303
+ walletObservedActiveToElementMountMs: preparedHere || cycle.elementMountedAtMs === null
304
+ ? undefined
305
+ : Math.max(0, cycle.elementMountedAtMs - observedAtMs),
306
+ walletElementMountToReadyMs: cycle.elementMountedAtMs === null
307
+ ? undefined
308
+ : Math.max(0, readyAtMs - cycle.elementMountedAtMs),
309
+ walletElementRetryCount: cycle.elementRetryCount,
310
+ walletStripePromiseWaitMs: (_c = cycle.stripePromiseWaitMs) !== null && _c !== void 0 ? _c : undefined,
219
311
  });
220
312
  };
221
313
  const handlePlaceholderClick = async () => {
@@ -250,7 +342,17 @@ export function WalletSubscriptionCheckoutSlot({ amountCents, appearance, availa
250
342
  .filter(Boolean)
251
343
  .join(' '), "aria-busy": placeholderIsPreparing || undefined, children: [shouldRenderExpressCheckout &&
252
344
  session.activeClientSecret &&
253
- session.stripePromise ? (_jsx(StripeExpressCheckoutElement, { amountCents: amountCents, appearance: appearance, 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({
345
+ session.stripePromise ? (_jsx(StripeExpressCheckoutElement, { amountCents: amountCents, appearance: appearance, availabilityPaymentMethods: [availabilityPaymentMethod], beforeConfirm: beforeConfirm, checkoutAnalytics: checkoutAnalytics, checkoutSessionId: session.checkoutSessionId, className: className, clientSecret: session.activeClientSecret, customerEmail: customerEmail, customerName: customerName, onAvailabilityChange: handleAvailabilityChange, onElementMount: () => {
346
+ const cycle = walletReadinessCycleRef.current;
347
+ if ((cycle === null || cycle === void 0 ? void 0 : cycle.intentKey) === session.intentKey) {
348
+ cycle.elementMountedAtMs = performance.now();
349
+ }
350
+ }, onElementRetry: () => {
351
+ const cycle = walletReadinessCycleRef.current;
352
+ if ((cycle === null || cycle === void 0 ? void 0 : cycle.intentKey) === session.intentKey) {
353
+ cycle.elementRetryCount += 1;
354
+ }
355
+ }, 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({
254
356
  className,
255
357
  disabled: slotDisabled,
256
358
  label: placeholderLabel,
@@ -41,10 +41,25 @@ export type CheckoutWalletReadyInput = {
41
41
  checkoutSessionId: string;
42
42
  paymentMethod: unknown;
43
43
  provider: 'stripe';
44
- walletClientSecretToReadyMs: number;
44
+ walletTimingOrigin?: 'prepared_here' | 'already_active';
45
+ walletClientSecretToReadyMs?: number;
46
+ walletClientSecretToElementMountMs?: number;
47
+ walletObservedActiveToReadyMs?: number;
48
+ walletObservedActiveToElementMountMs?: number;
49
+ walletElementMountToReadyMs?: number;
50
+ walletElementRetryCount?: number;
51
+ walletStripePromiseWaitMs?: number;
45
52
  walletMountToReadyMs: number;
46
- walletMountToRequestMs: number;
47
- walletRequestMs: number;
53
+ walletMountToRequestMs?: number;
54
+ walletRequestMs?: number;
48
55
  };
49
56
  export declare const trackCheckoutWalletReady: (input: CheckoutWalletReadyInput) => string | null;
57
+ export declare const trackCheckoutWalletSlow: (input: {
58
+ analytics: CheckoutObservabilityAnalyticsContext;
59
+ checkoutSessionId: string;
60
+ paymentMethod: unknown;
61
+ provider: "stripe";
62
+ walletTimingOrigin: "prepared_here" | "already_active";
63
+ walletClientSecretToSlowMs: number;
64
+ }) => string | null;
50
65
  export {};
@@ -1,6 +1,7 @@
1
1
  import { publicAnalyticsSdk, } from '@funnelsgrove/analytics';
2
2
  import { buildMainApiUrl, buildSdkHeaders, FUNNEL_ID, FUNNEL_VERSION_ID, } from '@funnelsgrove/runtime';
3
3
  import { toAnalyticsPaymentMethod } from './paymentMethodAnalytics.service.js';
4
+ import { PAYMENTS_PACKAGE_VERSION } from '../generated/package-version.js';
4
5
  const asNonEmptyString = (value) => typeof value === 'string' && value.trim() ? value.trim() : null;
5
6
  export const resolveCheckoutObservabilityAnalyticsContext = (input) => {
6
7
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
@@ -112,6 +113,19 @@ const reportCheckoutObservability = (input) => {
112
113
  providerErrorCode: input.providerErrorCode,
113
114
  providerErrorType: input.providerErrorType,
114
115
  walletClientSecretToReadyMs: input.walletClientSecretToReadyMs,
116
+ walletClientSecretToElementMountMs: input.walletClientSecretToElementMountMs,
117
+ walletClientSecretToSlowMs: input.walletClientSecretToSlowMs,
118
+ walletObservedActiveToReadyMs: input.walletObservedActiveToReadyMs,
119
+ walletObservedActiveToElementMountMs: input.walletObservedActiveToElementMountMs,
120
+ walletTimingOrigin: input.walletTimingOrigin,
121
+ walletElementMountToReadyMs: input.walletElementMountToReadyMs,
122
+ walletElementRetryCount: input.walletElementRetryCount,
123
+ walletStripePromiseWaitMs: input.walletStripePromiseWaitMs,
124
+ walletVisibilityAtReady: input.walletVisibilityAtReady,
125
+ walletVisibilityAtSlow: input.walletVisibilityAtSlow,
126
+ walletBrowserFamily: input.walletBrowserFamily,
127
+ walletOsFamily: input.walletOsFamily,
128
+ paymentsSdkVersion: input.paymentsSdkVersion,
115
129
  walletMountToReadyMs: input.walletMountToReadyMs,
116
130
  walletMountToRequestMs: input.walletMountToRequestMs,
117
131
  walletRequestMs: input.walletRequestMs,
@@ -201,36 +215,90 @@ export const trackCheckoutFailureShown = (input) => {
201
215
  };
202
216
  export const trackCheckoutCanceled = (input) => emitCheckoutObservability('checkout_canceled', input);
203
217
  const normalizeWalletTiming = (value) => (Number.isFinite(value) && value >= 0 && value <= 300000 ? value : null);
218
+ const browserFamily = (userAgent) => {
219
+ if (/Edg(?:e|iOS|A)?\//i.test(userAgent))
220
+ return 'edge';
221
+ if (/FxiOS\/|Firefox\//i.test(userAgent))
222
+ return 'firefox';
223
+ if (/CriOS\/|Chrome\//i.test(userAgent))
224
+ return 'chrome';
225
+ if (/Safari\//i.test(userAgent))
226
+ return 'safari';
227
+ return 'other';
228
+ };
229
+ const osFamily = (userAgent) => {
230
+ if (/iPhone|iPad|iPod/i.test(userAgent))
231
+ return 'ios';
232
+ if (/Android/i.test(userAgent))
233
+ return 'android';
234
+ if (/Windows/i.test(userAgent))
235
+ return 'windows';
236
+ if (/Macintosh|Mac OS X/i.test(userAgent))
237
+ return 'macos';
238
+ return 'other';
239
+ };
240
+ const getWalletClientContext = () => {
241
+ const userAgent = typeof navigator === 'undefined' ? '' : navigator.userAgent;
242
+ const visibilityState = typeof document === 'undefined' ? undefined : document.visibilityState;
243
+ return {
244
+ walletBrowserFamily: browserFamily(userAgent),
245
+ walletOsFamily: osFamily(userAgent),
246
+ visibilityState: visibilityState === 'visible'
247
+ || visibilityState === 'hidden'
248
+ || visibilityState === 'prerender'
249
+ ? visibilityState : 'unknown',
250
+ };
251
+ };
204
252
  export const trackCheckoutWalletReady = (input) => {
205
- var _a;
253
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
206
254
  const checkoutSessionId = input.checkoutSessionId.trim();
207
255
  const paymentMethod = toAnalyticsPaymentMethod(input.paymentMethod);
208
- const walletClientSecretToReadyMs = normalizeWalletTiming(input.walletClientSecretToReadyMs);
256
+ const walletClientSecretToReadyMs = input.walletClientSecretToReadyMs === undefined
257
+ ? undefined : (_a = normalizeWalletTiming(input.walletClientSecretToReadyMs)) !== null && _a !== void 0 ? _a : undefined;
258
+ const walletClientSecretToElementMountMs = input.walletClientSecretToElementMountMs === undefined
259
+ ? undefined : (_b = normalizeWalletTiming(input.walletClientSecretToElementMountMs)) !== null && _b !== void 0 ? _b : undefined;
260
+ const walletElementMountToReadyMs = input.walletElementMountToReadyMs === undefined
261
+ ? undefined : (_c = normalizeWalletTiming(input.walletElementMountToReadyMs)) !== null && _c !== void 0 ? _c : undefined;
262
+ const walletObservedActiveToReadyMs = input.walletObservedActiveToReadyMs === undefined
263
+ ? undefined : (_d = normalizeWalletTiming(input.walletObservedActiveToReadyMs)) !== null && _d !== void 0 ? _d : undefined;
264
+ const walletObservedActiveToElementMountMs = input.walletObservedActiveToElementMountMs === undefined
265
+ ? undefined : (_e = normalizeWalletTiming(input.walletObservedActiveToElementMountMs)) !== null && _e !== void 0 ? _e : undefined;
266
+ const walletStripePromiseWaitMs = input.walletStripePromiseWaitMs === undefined
267
+ ? undefined : (_f = normalizeWalletTiming(input.walletStripePromiseWaitMs)) !== null && _f !== void 0 ? _f : undefined;
268
+ const walletElementRetryCount = input.walletElementRetryCount !== undefined
269
+ && Number.isInteger(input.walletElementRetryCount)
270
+ && input.walletElementRetryCount >= 0
271
+ && input.walletElementRetryCount <= 5
272
+ ? input.walletElementRetryCount : undefined;
209
273
  const walletMountToReadyMs = normalizeWalletTiming(input.walletMountToReadyMs);
210
- const walletMountToRequestMs = normalizeWalletTiming(input.walletMountToRequestMs);
211
- const walletRequestMs = normalizeWalletTiming(input.walletRequestMs);
274
+ const walletMountToRequestMs = input.walletMountToRequestMs === undefined
275
+ ? undefined : (_g = normalizeWalletTiming(input.walletMountToRequestMs)) !== null && _g !== void 0 ? _g : undefined;
276
+ const walletRequestMs = input.walletRequestMs === undefined
277
+ ? undefined : (_h = normalizeWalletTiming(input.walletRequestMs)) !== null && _h !== void 0 ? _h : undefined;
278
+ const alreadyActive = input.walletTimingOrigin === 'already_active';
212
279
  if (!checkoutSessionId
213
280
  || !paymentMethod
214
- || walletClientSecretToReadyMs === null
215
281
  || walletMountToReadyMs === null
216
- || walletMountToRequestMs === null
217
- || walletRequestMs === null) {
282
+ || (alreadyActive
283
+ ? walletObservedActiveToReadyMs === undefined
284
+ : walletClientSecretToReadyMs === undefined
285
+ || walletMountToRequestMs === undefined
286
+ || walletRequestMs === undefined)) {
218
287
  return null;
219
288
  }
220
- const eventId = publicAnalyticsSdk.track({
289
+ const { walletBrowserFamily, walletOsFamily, visibilityState: walletVisibilityAtReady } = getWalletClientContext();
290
+ const eventId = `wallet-ready:${checkoutSessionId}:${paymentMethod}`;
291
+ const analyticsEventId = publicAnalyticsSdk.track({
221
292
  environment: input.analytics.environment,
222
- eventId: `wallet-ready:${checkoutSessionId}:${paymentMethod}`,
293
+ eventId,
223
294
  eventType: 'checkout_wallet_ready',
224
295
  featureFlags: input.analytics.featureFlags,
225
- metadata: Object.assign(Object.assign({}, ((_a = input.analytics.metadata) !== null && _a !== void 0 ? _a : {})), { checkout_session_id: checkoutSessionId, payment_method: paymentMethod, payment_provider: input.provider, wallet_client_secret_to_ready_ms: walletClientSecretToReadyMs, wallet_mount_to_ready_ms: walletMountToReadyMs, wallet_mount_to_request_ms: walletMountToRequestMs, wallet_checkout_request_ms: walletRequestMs }),
296
+ metadata: Object.assign(Object.assign({}, ((_j = input.analytics.metadata) !== null && _j !== void 0 ? _j : {})), { checkout_session_id: checkoutSessionId, payment_method: paymentMethod, payment_provider: input.provider, wallet_client_secret_to_ready_ms: walletClientSecretToReadyMs, wallet_observed_active_to_ready_ms: walletObservedActiveToReadyMs, wallet_observed_active_to_element_mount_ms: walletObservedActiveToElementMountMs, wallet_timing_origin: (_k = input.walletTimingOrigin) !== null && _k !== void 0 ? _k : 'prepared_here', wallet_client_secret_to_element_mount_ms: walletClientSecretToElementMountMs, wallet_element_mount_to_ready_ms: walletElementMountToReadyMs, wallet_element_retry_count: walletElementRetryCount, wallet_stripe_promise_wait_ms: walletStripePromiseWaitMs, wallet_visibility_at_ready: walletVisibilityAtReady, wallet_browser_family: walletBrowserFamily, wallet_os_family: walletOsFamily, payments_sdk_version: PAYMENTS_PACKAGE_VERSION, wallet_mount_to_ready_ms: walletMountToReadyMs, wallet_mount_to_request_ms: walletMountToRequestMs, wallet_checkout_request_ms: walletRequestMs }),
226
297
  stepContractVersion: input.analytics.stepContractVersion,
227
298
  stepId: input.analytics.stepId,
228
299
  stepName: input.analytics.stepName,
229
300
  stepType: input.analytics.stepType,
230
301
  });
231
- if (!eventId) {
232
- return null;
233
- }
234
302
  void reportCheckoutObservability({
235
303
  analytics: input.analytics,
236
304
  checkoutSessionId,
@@ -239,10 +307,61 @@ export const trackCheckoutWalletReady = (input) => {
239
307
  paymentMethod,
240
308
  provider: input.provider,
241
309
  walletClientSecretToReadyMs,
310
+ walletObservedActiveToReadyMs,
311
+ walletObservedActiveToElementMountMs,
312
+ walletTimingOrigin: (_l = input.walletTimingOrigin) !== null && _l !== void 0 ? _l : 'prepared_here',
313
+ walletClientSecretToElementMountMs,
314
+ walletElementMountToReadyMs,
315
+ walletElementRetryCount,
316
+ walletStripePromiseWaitMs,
317
+ walletVisibilityAtReady,
318
+ walletBrowserFamily,
319
+ walletOsFamily,
320
+ paymentsSdkVersion: PAYMENTS_PACKAGE_VERSION,
242
321
  walletMountToReadyMs,
243
322
  walletMountToRequestMs,
244
323
  walletRequestMs,
245
324
  }).catch(() => undefined);
246
- void publicAnalyticsSdk.flush().catch(() => 0);
325
+ if (analyticsEventId)
326
+ void publicAnalyticsSdk.flush().catch(() => 0);
327
+ return eventId;
328
+ };
329
+ export const trackCheckoutWalletSlow = (input) => {
330
+ var _a;
331
+ const checkoutSessionId = input.checkoutSessionId.trim();
332
+ const paymentMethod = toAnalyticsPaymentMethod(input.paymentMethod);
333
+ const walletClientSecretToSlowMs = normalizeWalletTiming(input.walletClientSecretToSlowMs);
334
+ if (!checkoutSessionId || !paymentMethod || walletClientSecretToSlowMs === null) {
335
+ return null;
336
+ }
337
+ const { walletBrowserFamily, walletOsFamily, visibilityState: walletVisibilityAtSlow } = getWalletClientContext();
338
+ const eventId = `wallet-slow:${checkoutSessionId}:${paymentMethod}`;
339
+ const analyticsEventId = publicAnalyticsSdk.track({
340
+ environment: input.analytics.environment,
341
+ eventId,
342
+ eventType: 'checkout_wallet_slow',
343
+ featureFlags: input.analytics.featureFlags,
344
+ metadata: Object.assign(Object.assign({}, ((_a = input.analytics.metadata) !== null && _a !== void 0 ? _a : {})), { checkout_session_id: checkoutSessionId, payment_method: paymentMethod, payment_provider: input.provider, wallet_timing_origin: input.walletTimingOrigin, wallet_client_secret_to_slow_ms: walletClientSecretToSlowMs, wallet_visibility_at_slow: walletVisibilityAtSlow, wallet_browser_family: walletBrowserFamily, wallet_os_family: walletOsFamily, payments_sdk_version: PAYMENTS_PACKAGE_VERSION }),
345
+ stepContractVersion: input.analytics.stepContractVersion,
346
+ stepId: input.analytics.stepId,
347
+ stepName: input.analytics.stepName,
348
+ stepType: input.analytics.stepType,
349
+ });
350
+ void reportCheckoutObservability({
351
+ analytics: input.analytics,
352
+ checkoutSessionId,
353
+ eventId,
354
+ eventType: 'checkout_wallet_slow',
355
+ paymentMethod,
356
+ provider: input.provider,
357
+ walletTimingOrigin: input.walletTimingOrigin,
358
+ walletClientSecretToSlowMs,
359
+ walletVisibilityAtSlow,
360
+ walletBrowserFamily,
361
+ walletOsFamily,
362
+ paymentsSdkVersion: PAYMENTS_PACKAGE_VERSION,
363
+ }).catch(() => undefined);
364
+ if (analyticsEventId)
365
+ void publicAnalyticsSdk.flush().catch(() => 0);
247
366
  return eventId;
248
367
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnelsgrove/payments",
3
- "version": "0.23.3",
3
+ "version": "0.25.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/The-Solid-Grove/funnelsgrove.git",
@@ -25,7 +25,7 @@
25
25
  "access": "public"
26
26
  },
27
27
  "scripts": {
28
- "build": "rm -rf dist && tsc -p tsconfig.build.json && node ../../scripts/fix-esm-relative-imports.mjs dist",
28
+ "build": "node scripts/generate-package-version.mjs && rm -rf dist && tsc -p tsconfig.build.json && node ../../scripts/fix-esm-relative-imports.mjs dist",
29
29
  "lint": "eslint .",
30
30
  "prepublishOnly": "npm run build",
31
31
  "smoke:wallet": "npm run build && node scripts/wallet-checkout-smoke.mjs",
@@ -46,11 +46,11 @@
46
46
  "jsdom": "20.0.3",
47
47
  "typescript": "^5",
48
48
  "vitest": "^3.2.4",
49
- "@funnelsgrove/runtime": "0.22.0",
50
- "@funnelsgrove/analytics": "0.4.2"
49
+ "@funnelsgrove/runtime": "0.24.0",
50
+ "@funnelsgrove/analytics": "0.6.0"
51
51
  },
52
52
  "peerDependencies": {
53
- "@funnelsgrove/runtime": "^0.22.0",
54
- "@funnelsgrove/analytics": "^0.4.2"
53
+ "@funnelsgrove/runtime": "^0.24.0",
54
+ "@funnelsgrove/analytics": "^0.6.0"
55
55
  }
56
56
  }