@funnelsgrove/payments 0.11.16 → 0.11.18

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.
@@ -5,6 +5,7 @@ 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
9
  export function getWalletSubscriptionCheckoutRenderState({ expressCheckoutAllowed, hasActiveClientSecret, hasStripePromise, suspended, walletAvailable, walletPreparing, }) {
9
10
  const shouldRenderExpressCheckout = !suspended &&
10
11
  expressCheckoutAllowed &&
@@ -42,6 +43,23 @@ export function WalletSubscriptionCheckoutSlot({ amountCents, appearance, availa
42
43
  const [walletCheckoutStartedAfterClickIntentKey, setWalletCheckoutStartedAfterClickIntentKey] = useState(null);
43
44
  const autoWalletPreparationIntentKeyRef = useRef(null);
44
45
  const walletCheckoutStartedAfterClickSessionIdRef = useRef(null);
46
+ const walletReadinessCycleRef = useRef(null);
47
+ if (!walletReadinessCycleRef.current
48
+ || walletReadinessCycleRef.current.intentKey !== session.intentKey) {
49
+ walletReadinessCycleRef.current = {
50
+ clientSecretReceivedAtMs: null,
51
+ intentKey: session.intentKey,
52
+ mountedAtMs: null,
53
+ reportedCheckoutSessionId: null,
54
+ requestStartedAtMs: null,
55
+ };
56
+ }
57
+ useEffect(() => {
58
+ const cycle = walletReadinessCycleRef.current;
59
+ if ((cycle === null || cycle === void 0 ? void 0 : cycle.intentKey) === session.intentKey && cycle.mountedAtMs === null) {
60
+ cycle.mountedAtMs = performance.now();
61
+ }
62
+ }, [session.intentKey]);
45
63
  const slotDisabled = disabled || !session.configured;
46
64
  const analyticsPaymentMethod = toAnalyticsPaymentMethod(availabilityPaymentMethod);
47
65
  const walletAvailable = walletAvailability.intentKey === session.intentKey
@@ -76,6 +94,25 @@ export function WalletSubscriptionCheckoutSlot({ amountCents, appearance, availa
76
94
  intentKey,
77
95
  });
78
96
  }, []);
97
+ const startWalletPreparationTiming = useCallback((intentKey) => {
98
+ var _a;
99
+ const cycle = walletReadinessCycleRef.current;
100
+ if (!cycle || cycle.intentKey !== intentKey) {
101
+ return;
102
+ }
103
+ const requestStartedAtMs = performance.now();
104
+ (_a = cycle.mountedAtMs) !== null && _a !== void 0 ? _a : (cycle.mountedAtMs = requestStartedAtMs);
105
+ cycle.requestStartedAtMs = requestStartedAtMs;
106
+ cycle.clientSecretReceivedAtMs = null;
107
+ cycle.reportedCheckoutSessionId = null;
108
+ }, []);
109
+ const finishWalletPreparationTiming = useCallback((intentKey, readyClientSecret) => {
110
+ const cycle = walletReadinessCycleRef.current;
111
+ if (!readyClientSecret || !cycle || cycle.intentKey !== intentKey) {
112
+ return;
113
+ }
114
+ cycle.clientSecretReceivedAtMs = performance.now();
115
+ }, []);
79
116
  const autoWalletClientSecret = session.activeClientSecret;
80
117
  const autoWalletIntentKey = session.intentKey;
81
118
  const autoWalletLoading = session.loading.wallet;
@@ -93,11 +130,13 @@ export function WalletSubscriptionCheckoutSlot({ amountCents, appearance, availa
93
130
  return;
94
131
  }
95
132
  autoWalletPreparationIntentKeyRef.current = autoWalletIntentKey;
133
+ startWalletPreparationTiming(autoWalletIntentKey);
96
134
  void prepareWalletCheckout({
97
135
  checkoutStartSource: 'wallet_render',
98
136
  paymentMethod: analyticsPaymentMethod,
99
137
  })
100
138
  .then((readyClientSecret) => {
139
+ finishWalletPreparationTiming(autoWalletIntentKey, readyClientSecret);
101
140
  if (!readyClientSecret) {
102
141
  reportWalletPreparationFailure(autoWalletIntentKey);
103
142
  }
@@ -108,9 +147,11 @@ export function WalletSubscriptionCheckoutSlot({ amountCents, appearance, availa
108
147
  autoWalletIntentKey,
109
148
  autoWalletLoading,
110
149
  expressCheckoutAllowed,
150
+ finishWalletPreparationTiming,
111
151
  prepareWalletCheckout,
112
152
  reportWalletPreparationFailure,
113
153
  slotDisabled,
154
+ startWalletPreparationTiming,
114
155
  suspended,
115
156
  walletAvailable,
116
157
  ]);
@@ -149,7 +190,33 @@ export function WalletSubscriptionCheckoutSlot({ amountCents, appearance, availa
149
190
  onError === null || onError === void 0 ? void 0 : onError(message);
150
191
  };
151
192
  const handleAvailabilityChange = (available) => {
193
+ var _a;
152
194
  reportWalletAvailability(session.intentKey, available);
195
+ const cycle = walletReadinessCycleRef.current;
196
+ const checkoutSessionId = ((_a = session.checkoutSessionId) === null || _a === void 0 ? void 0 : _a.trim()) || '';
197
+ if (!available
198
+ || !checkoutAnalytics
199
+ || !checkoutSessionId
200
+ || !cycle
201
+ || cycle.intentKey !== session.intentKey
202
+ || cycle.mountedAtMs === null
203
+ || cycle.requestStartedAtMs === null
204
+ || cycle.clientSecretReceivedAtMs === null
205
+ || cycle.reportedCheckoutSessionId === checkoutSessionId) {
206
+ return;
207
+ }
208
+ const readyAtMs = performance.now();
209
+ cycle.reportedCheckoutSessionId = checkoutSessionId;
210
+ trackCheckoutWalletReady({
211
+ analytics: checkoutAnalytics,
212
+ checkoutSessionId,
213
+ paymentMethod: availabilityPaymentMethod,
214
+ provider: 'stripe',
215
+ walletClientSecretToReadyMs: Math.max(0, readyAtMs - cycle.clientSecretReceivedAtMs),
216
+ 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),
219
+ });
153
220
  };
154
221
  const handlePlaceholderClick = async () => {
155
222
  if (slotDisabled || session.loading.wallet) {
@@ -160,10 +227,12 @@ export function WalletSubscriptionCheckoutSlot({ amountCents, appearance, availa
160
227
  return;
161
228
  }
162
229
  setWalletCheckoutStartedAfterClickIntentKey(session.intentKey);
230
+ startWalletPreparationTiming(session.intentKey);
163
231
  const readyClientSecret = await session.prepareWalletCheckout({
164
232
  checkoutStartSource: 'wallet_click',
165
233
  paymentMethod: analyticsPaymentMethod,
166
234
  });
235
+ finishWalletPreparationTiming(session.intentKey, readyClientSecret);
167
236
  if (!readyClientSecret) {
168
237
  setWalletCheckoutStartedAfterClickIntentKey(null);
169
238
  reportWalletPreparationFailure(session.intentKey);
@@ -1,11 +1,14 @@
1
1
  export type StripeCheckoutAttempt = {
2
2
  key: string;
3
3
  id: string;
4
+ createdAtMs: number;
4
5
  };
6
+ export declare const STRIPE_CHECKOUT_ATTEMPT_RETRY_WINDOW_MS: number;
5
7
  export declare const resolveStripeCheckoutAttempt: (input: {
6
8
  current: StripeCheckoutAttempt | null;
7
9
  forceNew: boolean;
8
10
  intentKey: string;
9
11
  paymentsApiVersion?: "v1" | "v2" | null;
10
12
  createId: () => string;
13
+ nowMs?: number;
11
14
  }) => StripeCheckoutAttempt | null;
@@ -1,10 +1,14 @@
1
+ export const STRIPE_CHECKOUT_ATTEMPT_RETRY_WINDOW_MS = 20 * 60 * 1000;
1
2
  export const resolveStripeCheckoutAttempt = (input) => {
2
- var _a;
3
+ var _a, _b;
3
4
  if (input.paymentsApiVersion !== 'v2') {
4
5
  return null;
5
6
  }
6
- if (!input.forceNew && ((_a = input.current) === null || _a === void 0 ? void 0 : _a.key) === input.intentKey) {
7
+ const nowMs = (_a = input.nowMs) !== null && _a !== void 0 ? _a : Date.now();
8
+ if (!input.forceNew
9
+ && ((_b = input.current) === null || _b === void 0 ? void 0 : _b.key) === input.intentKey
10
+ && nowMs - input.current.createdAtMs <= STRIPE_CHECKOUT_ATTEMPT_RETRY_WINDOW_MS) {
7
11
  return input.current;
8
12
  }
9
- return { key: input.intentKey, id: input.createId() };
13
+ return { key: input.intentKey, id: input.createId(), createdAtMs: nowMs };
10
14
  };
@@ -0,0 +1,7 @@
1
+ import { type CheckoutIdentityStatus } from '@funnelsgrove/runtime';
2
+ export declare const isStripeCheckoutIdentityReady: (input: {
3
+ runtimeConfig?: {
4
+ paymentsApiVersion?: "v1" | "v2" | null;
5
+ } | null;
6
+ userId?: string | null;
7
+ }, getIdentityStatus?: (userId: string | null | undefined) => CheckoutIdentityStatus) => boolean;
@@ -0,0 +1,6 @@
1
+ import { getCheckoutIdentityStatus, } from '@funnelsgrove/runtime';
2
+ export const isStripeCheckoutIdentityReady = (input, getIdentityStatus = getCheckoutIdentityStatus) => {
3
+ var _a;
4
+ return ((_a = input.runtimeConfig) === null || _a === void 0 ? void 0 : _a.paymentsApiVersion) !== 'v2'
5
+ || getIdentityStatus(input.userId) !== 'pending';
6
+ };
@@ -2,6 +2,7 @@ import type { Stripe } from '@stripe/stripe-js';
2
2
  import type { RuntimeMode } from '@funnelsgrove/runtime';
3
3
  import { type CheckoutPreparationLoading, type CheckoutPreparationSource } from '../../paymentProvider.types.js';
4
4
  import { type PaywallPlan, type StripeRuntimeConfigOverrides } from '../services/stripe.service.js';
5
+ export { isStripeCheckoutIdentityReady as isStripeOneTimeCheckoutIdentityReady, } from './checkoutIdentity.js';
5
6
  type StripeOneTimeCheckoutPlanInput = Pick<PaywallPlan, 'amountCents' | 'analyticsPurchaseValue' | 'description' | 'id' | 'providerPlanId' | 'title' | 'funnelPlanKey' | 'offerSetId' | 'offerSetKey'>;
6
7
  export type StripeOneTimeCheckoutSessionInput = {
7
8
  runtimeConfigRevisionId?: string | null;
@@ -49,4 +50,3 @@ export type StripeOneTimeCheckoutIntentKeyInput = Pick<StripeOneTimeCheckoutSess
49
50
  export declare function buildStripeOneTimeCheckoutIntentKey({ runtimeConfigRevisionId, paymentProfileId, provider, checkoutMode, couponId, customerEmail, plan, returnUrl, runtimeConfig, stripePublishableKey, userId, }: StripeOneTimeCheckoutIntentKeyInput): string;
50
51
  export declare function shouldResetStripeOneTimeCheckoutSessionForIntentKey(previousIntentKey: string | null, nextIntentKey: string): boolean;
51
52
  export declare function useStripeOneTimeCheckoutSession({ runtimeConfigRevisionId, paymentProfileId, provider, analyticsMetadata, checkoutMode, couponId, customerEmail, enabled, onError, plan, returnUrl, runtimeConfig, stripePublishableKey, userId, }: StripeOneTimeCheckoutSessionInput): StripeOneTimeCheckoutSession;
52
- export {};
@@ -16,6 +16,8 @@ import { createStripeOneTimeCheckout, getStripePromise, isStripeConfigured, } fr
16
16
  import { isInactiveCheckoutSessionError } from './useStripeSubscriptionCheckoutSession.js';
17
17
  import { toAnalyticsPaymentMethod } from '../../../services/paymentMethodAnalytics.service.js';
18
18
  import { resolveStripeCheckoutAttempt, } from './checkoutAttempt.js';
19
+ import { isStripeCheckoutIdentityReady } from './checkoutIdentity.js';
20
+ export { isStripeCheckoutIdentityReady as isStripeOneTimeCheckoutIdentityReady, } from './checkoutIdentity.js';
19
21
  export function resolveStripeOneTimeCheckoutPreparationOptions(source, options = {}) {
20
22
  var _a, _b;
21
23
  const checkoutStartSource = (_a = options.checkoutStartSource) !== null && _a !== void 0 ? _a : (source === 'wallet' ? 'wallet_render' : 'card_click');
@@ -85,7 +87,12 @@ export function useStripeOneTimeCheckoutSession({ runtimeConfigRevisionId, payme
85
87
  const requestIdRef = useRef(0);
86
88
  const attemptRef = useRef(null);
87
89
  const anonymousUserIdRef = useRef(null);
90
+ const checkoutIdentityReady = isStripeCheckoutIdentityReady({
91
+ runtimeConfig,
92
+ userId,
93
+ });
88
94
  const configured = enabled &&
95
+ checkoutIdentityReady &&
89
96
  (isStripeConfigured(checkoutMode) ||
90
97
  Boolean(normalizeRuntimeConfigValue(runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelSdkPublishableKey)) ||
91
98
  Boolean(normalizeRuntimeConfigValue(stripePublishableKey)));
@@ -4,6 +4,7 @@ import { type CheckoutPreparationLoading, type CheckoutPreparationSource } from
4
4
  import type { PaywallDisplayPlan } from '../../../services/paywallOffer.service.js';
5
5
  import { type PaywallPlan, type StripeRuntimeConfigOverrides } from '../services/stripe.service.js';
6
6
  import { type CheckoutObservabilityAnalyticsContext } from '../../../services/checkoutObservability.service.js';
7
+ export { isStripeCheckoutIdentityReady as isStripeSubscriptionCheckoutIdentityReady, } from './checkoutIdentity.js';
7
8
  export type StripeSubscriptionCheckoutSessionInput = {
8
9
  analyticsMetadata?: Record<string, unknown> | null;
9
10
  checkoutAnalytics?: CheckoutObservabilityAnalyticsContext | null;
@@ -16,6 +16,8 @@ import { createStripeSubscriptionCheckout, getStripePromise, isStripeConfigured,
16
16
  import { toAnalyticsPaymentMethod } from '../../../services/paymentMethodAnalytics.service.js';
17
17
  import { resolveCheckoutObservabilityAnalyticsContext, trackCheckoutFailureShown, } from '../../../services/checkoutObservability.service.js';
18
18
  import { resolveStripeCheckoutAttempt, } from './checkoutAttempt.js';
19
+ import { isStripeCheckoutIdentityReady } from './checkoutIdentity.js';
20
+ export { isStripeCheckoutIdentityReady as isStripeSubscriptionCheckoutIdentityReady, } from './checkoutIdentity.js';
19
21
  export function resolveStripeSubscriptionCheckoutPreparationOptions(source, options = {}) {
20
22
  var _a, _b;
21
23
  const checkoutStartSource = (_a = options.checkoutStartSource) !== null && _a !== void 0 ? _a : (source === 'wallet' ? 'wallet_render' : 'card_click');
@@ -90,7 +92,12 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
90
92
  const recoveredIntentKeyRef = useRef(null);
91
93
  const handledRecoveryRequestRef = useRef(0);
92
94
  const anonymousUserIdRef = useRef(null);
95
+ const checkoutIdentityReady = isStripeCheckoutIdentityReady({
96
+ runtimeConfig,
97
+ userId,
98
+ });
93
99
  const configured = enabled &&
100
+ checkoutIdentityReady &&
94
101
  (isStripeConfigured(checkoutMode) ||
95
102
  Boolean(normalizeRuntimeConfigValue(runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelSdkPublishableKey)) ||
96
103
  Boolean(normalizeRuntimeConfigValue(stripePublishableKey)));
@@ -1,5 +1,8 @@
1
1
  import type { CreateOneTimeCheckoutInput, CreateSubscriptionCheckoutInput, UpdateSubscriptionCheckoutPlanInput, VerifySubscriptionCheckoutPaymentInput } from './types.js';
2
2
  export declare const toOneTimeCheckoutRequest: (input: CreateOneTimeCheckoutInput) => {
3
+ environment: import("@funnelsgrove/runtime").RuntimeMode | undefined;
4
+ runtimeConfig: import("@funnelsgrove/runtime").FunnelSdkRuntimeConfigOverrides | undefined;
5
+ checkoutIdentityToken: string;
3
6
  planKey: string;
4
7
  runtimeConfigRevisionId: string | null | undefined;
5
8
  paymentProfileId: string | null | undefined;
@@ -18,10 +21,33 @@ export declare const toOneTimeCheckoutRequest: (input: CreateOneTimeCheckoutInpu
18
21
  customerEmail: string | undefined;
19
22
  returnUrl: string;
20
23
  userId: string | undefined;
24
+ } | {
21
25
  environment: import("@funnelsgrove/runtime").RuntimeMode | undefined;
22
26
  runtimeConfig: import("@funnelsgrove/runtime").FunnelSdkRuntimeConfigOverrides | undefined;
27
+ checkoutIdentityToken?: undefined;
28
+ planKey: string;
29
+ runtimeConfigRevisionId: string | null | undefined;
30
+ paymentProfileId: string | null | undefined;
31
+ provider: "stripe" | null | undefined;
32
+ anonymousUserId: string | null | undefined;
33
+ idempotencyKey: string | null | undefined;
34
+ offerSetId: string | undefined;
35
+ offerSetKey: string | undefined;
36
+ planId: string;
37
+ providerPlanId: string | undefined;
38
+ title: string;
39
+ description: string | undefined;
40
+ amountCents: number;
41
+ couponId: string | undefined;
42
+ analyticsMetadata: Record<string, unknown> | undefined;
43
+ customerEmail: string | undefined;
44
+ returnUrl: string;
45
+ userId: string | undefined;
23
46
  };
24
47
  export declare const toSubscriptionCheckoutRequest: (input: CreateSubscriptionCheckoutInput) => {
48
+ environment: import("@funnelsgrove/runtime").RuntimeMode | undefined;
49
+ runtimeConfig: import("@funnelsgrove/runtime").FunnelSdkRuntimeConfigOverrides | undefined;
50
+ checkoutIdentityToken: string;
25
51
  offerSetId: string | undefined;
26
52
  offerSetKey: string | undefined;
27
53
  planId: string;
@@ -47,8 +73,35 @@ export declare const toSubscriptionCheckoutRequest: (input: CreateSubscriptionCh
47
73
  customerEmail: string | undefined;
48
74
  returnUrl: string;
49
75
  userId: string | undefined;
76
+ } | {
50
77
  environment: import("@funnelsgrove/runtime").RuntimeMode | undefined;
51
78
  runtimeConfig: import("@funnelsgrove/runtime").FunnelSdkRuntimeConfigOverrides | undefined;
79
+ checkoutIdentityToken?: undefined;
80
+ offerSetId: string | undefined;
81
+ offerSetKey: string | undefined;
82
+ planId: string;
83
+ planKey: string;
84
+ providerPlanId: string | undefined;
85
+ provider: "stripe" | null | undefined;
86
+ paymentProfileId: string | undefined;
87
+ runtimeConfigRevisionId: string | undefined;
88
+ anonymousUserId: string | undefined;
89
+ idempotencyKey: string | undefined;
90
+ title: string;
91
+ description: string | undefined;
92
+ amountCents: number;
93
+ billingInterval: import("../../../../index.js").BillingPlanInterval;
94
+ billingIntervalCount: number;
95
+ followUpProviderPlanId: string | undefined;
96
+ followUpPlanTitle: string | undefined;
97
+ followUpBillingInterval: import("../../../../index.js").BillingPlanInterval | undefined;
98
+ followUpBillingIntervalCount: number | undefined;
99
+ introIterations: number | undefined;
100
+ couponId: string | undefined;
101
+ analyticsMetadata: Record<string, unknown> | undefined;
102
+ customerEmail: string | undefined;
103
+ returnUrl: string;
104
+ userId: string | undefined;
52
105
  };
53
106
  export declare const toSubscriptionCheckoutPlanRequest: (input: UpdateSubscriptionCheckoutPlanInput) => {
54
107
  checkoutSessionId: string;
@@ -1,63 +1,20 @@
1
1
  import { getPaywallPlanRequestId } from '../../../../services/paywallPlans/selection.js';
2
2
  import { normalizeCheckoutAnalyticsMetadata } from '../shared/checkoutMetadata.js';
3
3
  import { requireCheckoutRecurringConfig, requireCheckoutSessionId, } from '../shared/checkout.validator.js';
4
+ import { getCheckoutIdentityToken } from '@funnelsgrove/runtime';
5
+ const toCheckoutIdentityRequestFields = (userId) => {
6
+ const canonicalUserId = userId === null || userId === void 0 ? void 0 : userId.trim();
7
+ const checkoutIdentityToken = getCheckoutIdentityToken(canonicalUserId);
8
+ return checkoutIdentityToken ? { checkoutIdentityToken } : {};
9
+ };
4
10
  export const toOneTimeCheckoutRequest = (input) => {
5
11
  var _a, _b, _c, _d, _e, _f, _g, _h;
6
- return {
7
- planKey: ((_a = input.plan.funnelPlanKey) === null || _a === void 0 ? void 0 : _a.trim()) || input.plan.id,
8
- runtimeConfigRevisionId: input.runtimeConfigRevisionId,
9
- paymentProfileId: input.paymentProfileId,
10
- provider: input.provider,
11
- anonymousUserId: input.anonymousUserId,
12
- idempotencyKey: input.idempotencyKey,
13
- offerSetId: ((_b = input.plan.offerSetId) === null || _b === void 0 ? void 0 : _b.trim()) || undefined,
14
- offerSetKey: ((_c = input.plan.offerSetKey) === null || _c === void 0 ? void 0 : _c.trim()) || undefined,
15
- planId: getPaywallPlanRequestId(input.plan),
16
- providerPlanId: ((_d = input.plan.providerPlanId) === null || _d === void 0 ? void 0 : _d.trim()) || undefined,
17
- title: input.plan.title,
18
- description: ((_e = input.plan.description) === null || _e === void 0 ? void 0 : _e.trim()) || undefined,
19
- amountCents: input.plan.amountCents,
20
- couponId: ((_f = input.couponId) === null || _f === void 0 ? void 0 : _f.trim()) || undefined,
21
- analyticsMetadata: normalizeCheckoutAnalyticsMetadata(input.analyticsMetadata, input.plan),
22
- customerEmail: ((_g = input.customerEmail) === null || _g === void 0 ? void 0 : _g.trim()) || undefined,
23
- returnUrl: input.returnUrl,
24
- userId: ((_h = input.user_id) === null || _h === void 0 ? void 0 : _h.trim()) || undefined,
25
- environment: input.environment,
26
- runtimeConfig: input.runtimeConfig,
27
- };
12
+ return Object.assign(Object.assign({ planKey: ((_a = input.plan.funnelPlanKey) === null || _a === void 0 ? void 0 : _a.trim()) || input.plan.id, runtimeConfigRevisionId: input.runtimeConfigRevisionId, paymentProfileId: input.paymentProfileId, provider: input.provider, anonymousUserId: input.anonymousUserId, idempotencyKey: input.idempotencyKey, offerSetId: ((_b = input.plan.offerSetId) === null || _b === void 0 ? void 0 : _b.trim()) || undefined, offerSetKey: ((_c = input.plan.offerSetKey) === null || _c === void 0 ? void 0 : _c.trim()) || undefined, planId: getPaywallPlanRequestId(input.plan), providerPlanId: ((_d = input.plan.providerPlanId) === null || _d === void 0 ? void 0 : _d.trim()) || undefined, title: input.plan.title, description: ((_e = input.plan.description) === null || _e === void 0 ? void 0 : _e.trim()) || undefined, amountCents: input.plan.amountCents, couponId: ((_f = input.couponId) === null || _f === void 0 ? void 0 : _f.trim()) || undefined, analyticsMetadata: normalizeCheckoutAnalyticsMetadata(input.analyticsMetadata, input.plan), customerEmail: ((_g = input.customerEmail) === null || _g === void 0 ? void 0 : _g.trim()) || undefined, returnUrl: input.returnUrl, userId: ((_h = input.user_id) === null || _h === void 0 ? void 0 : _h.trim()) || undefined }, toCheckoutIdentityRequestFields(input.user_id)), { environment: input.environment, runtimeConfig: input.runtimeConfig });
28
13
  };
29
14
  export const toSubscriptionCheckoutRequest = (input) => {
30
15
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
31
16
  const recurring = requireCheckoutRecurringConfig(input.plan);
32
- return {
33
- offerSetId: ((_a = input.plan.offerSetId) === null || _a === void 0 ? void 0 : _a.trim()) || undefined,
34
- offerSetKey: ((_b = input.plan.offerSetKey) === null || _b === void 0 ? void 0 : _b.trim()) || undefined,
35
- planId: getPaywallPlanRequestId(input.plan),
36
- planKey: ((_c = input.plan.funnelPlanKey) === null || _c === void 0 ? void 0 : _c.trim()) || input.plan.id,
37
- providerPlanId: ((_d = input.plan.providerPlanId) === null || _d === void 0 ? void 0 : _d.trim()) || undefined,
38
- provider: input.provider,
39
- paymentProfileId: ((_e = input.paymentProfileId) === null || _e === void 0 ? void 0 : _e.trim()) || undefined,
40
- runtimeConfigRevisionId: ((_f = input.runtimeConfigRevisionId) === null || _f === void 0 ? void 0 : _f.trim()) || undefined,
41
- anonymousUserId: ((_g = input.anonymousUserId) === null || _g === void 0 ? void 0 : _g.trim()) || undefined,
42
- idempotencyKey: ((_h = input.idempotencyKey) === null || _h === void 0 ? void 0 : _h.trim()) || undefined,
43
- title: input.plan.title,
44
- description: ((_j = input.plan.description) === null || _j === void 0 ? void 0 : _j.trim()) || undefined,
45
- amountCents: input.plan.amountCents,
46
- billingInterval: recurring.interval,
47
- billingIntervalCount: recurring.intervalCount,
48
- followUpProviderPlanId: ((_k = input.plan.followUpProviderPlanId) === null || _k === void 0 ? void 0 : _k.trim()) || undefined,
49
- followUpPlanTitle: ((_l = input.plan.followUpPlanTitle) === null || _l === void 0 ? void 0 : _l.trim()) || undefined,
50
- followUpBillingInterval: input.plan.followUpBillingInterval,
51
- followUpBillingIntervalCount: input.plan.followUpBillingIntervalCount,
52
- introIterations: input.plan.introIterations,
53
- couponId: ((_m = input.couponId) === null || _m === void 0 ? void 0 : _m.trim()) || undefined,
54
- analyticsMetadata: normalizeCheckoutAnalyticsMetadata(input.analyticsMetadata, input.plan),
55
- customerEmail: ((_o = input.customerEmail) === null || _o === void 0 ? void 0 : _o.trim()) || undefined,
56
- returnUrl: input.returnUrl,
57
- userId: ((_p = input.user_id) === null || _p === void 0 ? void 0 : _p.trim()) || undefined,
58
- environment: input.environment,
59
- runtimeConfig: input.runtimeConfig,
60
- };
17
+ return Object.assign(Object.assign({ offerSetId: ((_a = input.plan.offerSetId) === null || _a === void 0 ? void 0 : _a.trim()) || undefined, offerSetKey: ((_b = input.plan.offerSetKey) === null || _b === void 0 ? void 0 : _b.trim()) || undefined, planId: getPaywallPlanRequestId(input.plan), planKey: ((_c = input.plan.funnelPlanKey) === null || _c === void 0 ? void 0 : _c.trim()) || input.plan.id, providerPlanId: ((_d = input.plan.providerPlanId) === null || _d === void 0 ? void 0 : _d.trim()) || undefined, provider: input.provider, paymentProfileId: ((_e = input.paymentProfileId) === null || _e === void 0 ? void 0 : _e.trim()) || undefined, runtimeConfigRevisionId: ((_f = input.runtimeConfigRevisionId) === null || _f === void 0 ? void 0 : _f.trim()) || undefined, anonymousUserId: ((_g = input.anonymousUserId) === null || _g === void 0 ? void 0 : _g.trim()) || undefined, idempotencyKey: ((_h = input.idempotencyKey) === null || _h === void 0 ? void 0 : _h.trim()) || undefined, title: input.plan.title, description: ((_j = input.plan.description) === null || _j === void 0 ? void 0 : _j.trim()) || undefined, amountCents: input.plan.amountCents, billingInterval: recurring.interval, billingIntervalCount: recurring.intervalCount, followUpProviderPlanId: ((_k = input.plan.followUpProviderPlanId) === null || _k === void 0 ? void 0 : _k.trim()) || undefined, followUpPlanTitle: ((_l = input.plan.followUpPlanTitle) === null || _l === void 0 ? void 0 : _l.trim()) || undefined, followUpBillingInterval: input.plan.followUpBillingInterval, followUpBillingIntervalCount: input.plan.followUpBillingIntervalCount, introIterations: input.plan.introIterations, couponId: ((_m = input.couponId) === null || _m === void 0 ? void 0 : _m.trim()) || undefined, analyticsMetadata: normalizeCheckoutAnalyticsMetadata(input.analyticsMetadata, input.plan), customerEmail: ((_o = input.customerEmail) === null || _o === void 0 ? void 0 : _o.trim()) || undefined, returnUrl: input.returnUrl, userId: ((_p = input.user_id) === null || _p === void 0 ? void 0 : _p.trim()) || undefined }, toCheckoutIdentityRequestFields(input.user_id)), { environment: input.environment, runtimeConfig: input.runtimeConfig });
61
18
  };
62
19
  export const toSubscriptionCheckoutPlanRequest = (input) => {
63
20
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
@@ -34,4 +34,15 @@ export type CheckoutFailureClassificationInput = Pick<CheckoutFailureShownInput,
34
34
  export declare const classifyCheckoutFailure: (input: CheckoutFailureClassificationInput) => CheckoutFailureCategory;
35
35
  export declare const trackCheckoutFailureShown: (input: CheckoutFailureShownInput) => string | null;
36
36
  export declare const trackCheckoutCanceled: (input: CheckoutObservabilityInput) => string | null;
37
+ export type CheckoutWalletReadyInput = {
38
+ analytics: CheckoutObservabilityAnalyticsContext;
39
+ checkoutSessionId: string;
40
+ paymentMethod: unknown;
41
+ provider: 'stripe';
42
+ walletClientSecretToReadyMs: number;
43
+ walletMountToReadyMs: number;
44
+ walletMountToRequestMs: number;
45
+ walletRequestMs: number;
46
+ };
47
+ export declare const trackCheckoutWalletReady: (input: CheckoutWalletReadyInput) => string | null;
37
48
  export {};
@@ -103,6 +103,10 @@ const reportCheckoutObservability = (input) => {
103
103
  providerDeclineCode: input.providerDeclineCode,
104
104
  providerErrorCode: input.providerErrorCode,
105
105
  providerErrorType: input.providerErrorType,
106
+ walletClientSecretToReadyMs: input.walletClientSecretToReadyMs,
107
+ walletMountToReadyMs: input.walletMountToReadyMs,
108
+ walletMountToRequestMs: input.walletMountToRequestMs,
109
+ walletRequestMs: input.walletRequestMs,
106
110
  }),
107
111
  keepalive: true,
108
112
  }).then(() => undefined);
@@ -184,3 +188,49 @@ export const trackCheckoutFailureShown = (input) => {
184
188
  });
185
189
  };
186
190
  export const trackCheckoutCanceled = (input) => emitCheckoutObservability('checkout_canceled', input);
191
+ const normalizeWalletTiming = (value) => (Number.isFinite(value) && value >= 0 && value <= 300000 ? value : null);
192
+ export const trackCheckoutWalletReady = (input) => {
193
+ var _a;
194
+ const checkoutSessionId = input.checkoutSessionId.trim();
195
+ const paymentMethod = toAnalyticsPaymentMethod(input.paymentMethod);
196
+ const walletClientSecretToReadyMs = normalizeWalletTiming(input.walletClientSecretToReadyMs);
197
+ const walletMountToReadyMs = normalizeWalletTiming(input.walletMountToReadyMs);
198
+ const walletMountToRequestMs = normalizeWalletTiming(input.walletMountToRequestMs);
199
+ const walletRequestMs = normalizeWalletTiming(input.walletRequestMs);
200
+ if (!checkoutSessionId
201
+ || !paymentMethod
202
+ || walletClientSecretToReadyMs === null
203
+ || walletMountToReadyMs === null
204
+ || walletMountToRequestMs === null
205
+ || walletRequestMs === null) {
206
+ return null;
207
+ }
208
+ const eventId = publicAnalyticsSdk.track({
209
+ environment: input.analytics.environment,
210
+ eventId: `wallet-ready:${checkoutSessionId}:${paymentMethod}`,
211
+ eventType: 'checkout_wallet_ready',
212
+ featureFlags: input.analytics.featureFlags,
213
+ 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 }),
214
+ stepContractVersion: input.analytics.stepContractVersion,
215
+ stepId: input.analytics.stepId,
216
+ stepName: input.analytics.stepName,
217
+ stepType: input.analytics.stepType,
218
+ });
219
+ if (!eventId) {
220
+ return null;
221
+ }
222
+ void reportCheckoutObservability({
223
+ analytics: input.analytics,
224
+ checkoutSessionId,
225
+ eventId,
226
+ eventType: 'checkout_wallet_ready',
227
+ paymentMethod,
228
+ provider: input.provider,
229
+ walletClientSecretToReadyMs,
230
+ walletMountToReadyMs,
231
+ walletMountToRequestMs,
232
+ walletRequestMs,
233
+ }).catch(() => undefined);
234
+ void publicAnalyticsSdk.flush().catch(() => 0);
235
+ return eventId;
236
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnelsgrove/payments",
3
- "version": "0.11.16",
3
+ "version": "0.11.18",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/The-Solid-Grove/funnelsgrove.git",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@funnelsgrove/analytics": "^0.1.86",
36
- "@funnelsgrove/runtime": "^0.11.2",
36
+ "@funnelsgrove/runtime": "^0.11.5",
37
37
  "@solidgate/react-sdk": "1.34.0",
38
38
  "@stripe/react-stripe-js": "^5.6.0",
39
39
  "@stripe/stripe-js": "^8.7.0",