@funnelsgrove/payments 0.22.6 → 0.23.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.
Files changed (29) hide show
  1. package/README.md +10 -0
  2. package/dist/config/billing.config.d.ts +3 -0
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +1 -0
  5. package/dist/providers/solidgate/components/SolidgateCheckoutDialog.d.ts +1 -1
  6. package/dist/providers/solidgate/components/SolidgateCheckoutDialog.js +19 -10
  7. package/dist/providers/solidgate/components/SolidgateCheckoutDialog.types.d.ts +3 -1
  8. package/dist/providers/stripe/hooks/useStripeOneTimeCheckoutSession.d.ts +3 -1
  9. package/dist/providers/stripe/hooks/useStripeOneTimeCheckoutSession.js +29 -7
  10. package/dist/providers/stripe/hooks/useStripeSubscriptionCheckoutSession.d.ts +3 -1
  11. package/dist/providers/stripe/hooks/useStripeSubscriptionCheckoutSession.js +31 -9
  12. package/dist/providers/stripe/services/checkout/requests.d.ts +39 -29
  13. package/dist/providers/stripe/services/checkout/requests.js +12 -32
  14. package/dist/providers/stripe/services/checkoutCompletionAnalytics.service.d.ts +2 -1
  15. package/dist/providers/stripe/services/checkoutCompletionAnalytics.service.js +2 -2
  16. package/dist/providers/stripe/services/hostedCheckout/requests.d.ts +12 -10
  17. package/dist/providers/stripe/services/hostedCheckout/requests.js +4 -27
  18. package/dist/providers/stripe/services/oneClick/requests.d.ts +9 -8
  19. package/dist/providers/stripe/services/oneClick/requests.js +2 -20
  20. package/dist/providers/stripe/services/oneClick/types.d.ts +3 -1
  21. package/dist/services/checkoutObservability.service.d.ts +2 -1
  22. package/dist/services/checkoutObservability.service.js +1 -1
  23. package/dist/services/paywallOffer.service.js +19 -8
  24. package/dist/services/paywallPlans/types.d.ts +4 -2
  25. package/dist/services/publishedBillingRuntime.service.d.ts +23 -2
  26. package/dist/services/publishedBillingRuntime.service.js +38 -4
  27. package/dist/services/usePinnedPublishedBillingRuntime.d.ts +10 -0
  28. package/dist/services/usePinnedPublishedBillingRuntime.js +23 -0
  29. package/package.json +5 -5
package/README.md CHANGED
@@ -194,3 +194,13 @@ including legacy wallet launchers. Mount it before wallet launch, persist throug
194
194
  `isValidCheckoutEmail` and `isCheckoutEmailError` share validation and recovery
195
195
  classification. `onCustomerEmailChange` in the card dialog reports a committed
196
196
  email, not each draft keystroke, so typing cannot prepare sessions for unsaved input.
197
+
198
+ ## Offer price money
199
+
200
+ Published plans expose optional `money: ProviderMoney`; new mappings supply it. Discount presentation and purchase analytics use its amount, currency and exponent. `amountCents` remains a legacy wire alias and does not define the scale of structured money. Independent plans can use different currencies. Currency is selected by the provider price, not by an Offer Set setting.
201
+
202
+ ### Published offer selection and checkout attempts
203
+
204
+ Use `resolvePublishedBillingRuntimeState` with configuration, browser locale and experiment assignments that are ready. Its result is `pending`, `resolved` or `error`. Pending/error states must not prepare Stripe card/wallet or Solidgate checkout; the maintained templates render an inert zero-plan state rather than a starter catalog.
205
+
206
+ `usePinnedPublishedBillingRuntime` holds the complete resolved catalog at the first payment preparation. Wire its `pin` callback to preparation and release both the payment session and this snapshot on explicit reset. Displayed prices, provider, revision, source version, locale and experiment assignment then remain consistent through background updates. The visitor can still choose another plan within the pinned offer set. A new attempt after reset can adopt the latest configuration.
@@ -1,3 +1,4 @@
1
+ import type { ProviderMoney } from '@funnelsgrove/runtime';
1
2
  export type BillingPlanComparison = {
2
3
  perDay: string;
3
4
  perWeek: string;
@@ -16,6 +17,8 @@ export type BillingPlanConfig = {
16
17
  featuredTag?: string;
17
18
  perDayAmount: string;
18
19
  amountCents: number;
20
+ money?: ProviderMoney;
21
+ currency?: string;
19
22
  analyticsPurchaseValue?: number;
20
23
  checkoutSummaryLabel?: string;
21
24
  followUpProviderPlanId?: string;
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export * from './services/planCatalog.service.js';
5
5
  export * from './services/paywallPlans.service.js';
6
6
  export * from './services/runtimeBillingPlanCatalog.service.js';
7
7
  export * from './services/publishedBillingRuntime.service.js';
8
+ export * from './services/usePinnedPublishedBillingRuntime.js';
8
9
  export * from './services/paywallOffer.service.js';
9
10
  export * from './services/paymentMethodAnalytics.service.js';
10
11
  export * from './services/checkoutObservability.service.js';
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ export * from './services/planCatalog.service.js';
6
6
  export * from './services/paywallPlans.service.js';
7
7
  export * from './services/runtimeBillingPlanCatalog.service.js';
8
8
  export * from './services/publishedBillingRuntime.service.js';
9
+ export * from './services/usePinnedPublishedBillingRuntime.js';
9
10
  export * from './services/paywallOffer.service.js';
10
11
  export * from './services/paymentMethodAnalytics.service.js';
11
12
  export * from './services/checkoutObservability.service.js';
@@ -1,3 +1,3 @@
1
1
  import type { SolidgateCheckoutDialogProps } from './SolidgateCheckoutDialog.types.js';
2
2
  export type { SolidgateCheckoutDialogState, SolidgateCheckoutDialogContent, SolidgateCheckoutDialogPresentation, SolidgateCheckoutDialogProps, SolidgateCheckoutAnalyticsContext, } from './SolidgateCheckoutDialog.types.js';
3
- export declare function SolidgateCheckoutDialog({ open, request, customerEmail, checkoutAnalytics, content, presentation, onClose, onCustomerEmailCommit, onSuccess, onRetry, onError, onStateChange, }: SolidgateCheckoutDialogProps): import("react/jsx-runtime").JSX.Element | null;
3
+ export declare function SolidgateCheckoutDialog({ open, request, customerEmail, checkoutAnalytics, content, presentation, onClose, onCustomerEmailCommit, onSuccess, onRetry, onError, onPreparationStart, onStateChange, }: SolidgateCheckoutDialogProps): import("react/jsx-runtime").JSX.Element | null;
@@ -65,7 +65,7 @@ const trackSolidgateCheckoutEvent = async (eventName, attemptId, context) => {
65
65
  }
66
66
  };
67
67
  const isAbortError = (error) => (error instanceof DOMException && error.name === 'AbortError');
68
- export function SolidgateCheckoutDialog({ open, request, customerEmail, checkoutAnalytics, content, presentation, onClose, onCustomerEmailCommit, onSuccess, onRetry, onError, onStateChange, }) {
68
+ export function SolidgateCheckoutDialog({ open, request, customerEmail, checkoutAnalytics, content, presentation, onClose, onCustomerEmailCommit, onSuccess, onRetry, onError, onPreparationStart, onStateChange, }) {
69
69
  const copy = useMemo(() => (Object.assign(Object.assign({}, defaultContent), content)), [content]);
70
70
  const [view, setView] = useState(initialView);
71
71
  const [retryRequest, setRetryRequest] = useState(null);
@@ -76,6 +76,7 @@ export function SolidgateCheckoutDialog({ open, request, customerEmail, checkout
76
76
  const [emailSubmitting, setEmailSubmitting] = useState(false);
77
77
  const [mountedPaymentAttemptId, setMountedPaymentAttemptId] = useState(null);
78
78
  const prepareAbortRef = useRef(null);
79
+ const preparedRequestRef = useRef(null);
79
80
  const statusAbortRef = useRef(null);
80
81
  const successAttemptRef = useRef(null);
81
82
  const paymentInfoAttemptRef = useRef(null);
@@ -146,6 +147,8 @@ export function SolidgateCheckoutDialog({ open, request, customerEmail, checkout
146
147
  runtimeFunnelId,
147
148
  successUrl,
148
149
  ]);
150
+ const latestRequestRef = useRef(normalizedRequest);
151
+ latestRequestRef.current = normalizedRequest;
149
152
  const setState = useCallback((nextView) => {
150
153
  var _a;
151
154
  setView(nextView);
@@ -167,8 +170,9 @@ export function SolidgateCheckoutDialog({ open, request, customerEmail, checkout
167
170
  }, []);
168
171
  const requiresEmailCommit = Boolean(onCustomerEmailCommit);
169
172
  useEffect(() => {
170
- var _a, _b, _c, _d, _e, _f;
173
+ var _a, _b, _c, _d, _e, _f, _g;
171
174
  if (!open) {
175
+ preparedRequestRef.current = null;
172
176
  (_a = prepareAbortRef.current) === null || _a === void 0 ? void 0 : _a.abort();
173
177
  (_b = statusAbortRef.current) === null || _b === void 0 ? void 0 : _b.abort();
174
178
  return;
@@ -184,8 +188,10 @@ export function SolidgateCheckoutDialog({ open, request, customerEmail, checkout
184
188
  return;
185
189
  }
186
190
  const controller = new AbortController();
187
- (_e = prepareAbortRef.current) === null || _e === void 0 ? void 0 : _e.abort();
188
- (_f = statusAbortRef.current) === null || _f === void 0 ? void 0 : _f.abort();
191
+ onPreparationStart === null || onPreparationStart === void 0 ? void 0 : onPreparationStart();
192
+ const preparedRequest = (_e = preparedRequestRef.current) !== null && _e !== void 0 ? _e : (preparedRequestRef.current = latestRequestRef.current);
193
+ (_f = prepareAbortRef.current) === null || _f === void 0 ? void 0 : _f.abort();
194
+ (_g = statusAbortRef.current) === null || _g === void 0 ? void 0 : _g.abort();
189
195
  prepareAbortRef.current = controller;
190
196
  successAttemptRef.current = null;
191
197
  paymentInfoAttemptRef.current = null;
@@ -195,7 +201,7 @@ export function SolidgateCheckoutDialog({ open, request, customerEmail, checkout
195
201
  setState(initialView);
196
202
  }
197
203
  });
198
- void prepareSolidgateCheckout(Object.assign(Object.assign({}, normalizedRequest), { signal: controller.signal }))
204
+ void prepareSolidgateCheckout(Object.assign(Object.assign({}, preparedRequest), { signal: controller.signal }))
199
205
  .then((checkout) => {
200
206
  if (!controller.signal.aborted) {
201
207
  setState({ state: 'ready', checkout, canResumeVerification: false });
@@ -211,7 +217,7 @@ export function SolidgateCheckoutDialog({ open, request, customerEmail, checkout
211
217
  }
212
218
  });
213
219
  return () => controller.abort();
214
- }, [committedEmail, normalizedRequest, open, requiresEmailCommit, setState, trackFailure]);
220
+ }, [checkoutKey, committedEmail, onPreparationStart, open, requiresEmailCommit, setState, trackFailure]);
215
221
  const paymentFormMounted = Boolean(view.checkout && mountedPaymentAttemptId === view.checkout.attemptId);
216
222
  const showPreparingLoader = view.state === 'loading'
217
223
  || (view.state === 'ready' && !paymentFormMounted);
@@ -395,8 +401,11 @@ export function SolidgateCheckoutDialog({ open, request, customerEmail, checkout
395
401
  setState(Object.assign(Object.assign({}, view), { state: 'recoverable_error', canResumeVerification: false }));
396
402
  trackFailure(copyRef.current.error, 'payment_form', (_a = view.checkout) === null || _a === void 0 ? void 0 : _a.attemptId);
397
403
  (_b = onErrorRef.current) === null || _b === void 0 ? void 0 : _b.call(onErrorRef, copyRef.current.error);
398
- } }, view.checkout.attemptId)) : null, presentation && view.checkout && showForm ? (_jsx("p", { className: 'solidgate-checkout-secure', children: presentation.secureLabel })) : null, statusMessage && view.state !== 'ready' ? (_jsx("div", { className: 'solidgate-checkout-status', children: _jsx("p", { role: 'status', "aria-live": 'polite', children: statusMessage }) })) : null, view.canResumeVerification ? (_jsx("button", { type: 'button', onClick: () => void verifyCanonicalStatus(), className: 'solidgate-checkout-action solidgate-checkout-action--spaced', children: copy.resumeVerification })) : null, (view.state === 'recoverable_error' || view.state === 'expired') && !view.canResumeVerification ? (_jsx("button", { type: 'button', onClick: onRetry || (() => setRetryRequest({
399
- originalKey: idempotencyKey,
400
- key: crypto.randomUUID(),
401
- })), className: 'solidgate-checkout-action solidgate-checkout-action--spaced', children: copy.retry })) : null] })) : null] }), _jsx("style", { children: solidgateCheckoutDialogStyles })] }));
404
+ } }, view.checkout.attemptId)) : null, presentation && view.checkout && showForm ? (_jsx("p", { className: 'solidgate-checkout-secure', children: presentation.secureLabel })) : null, statusMessage && view.state !== 'ready' ? (_jsx("div", { className: 'solidgate-checkout-status', children: _jsx("p", { role: 'status', "aria-live": 'polite', children: statusMessage }) })) : null, view.canResumeVerification ? (_jsx("button", { type: 'button', onClick: () => void verifyCanonicalStatus(), className: 'solidgate-checkout-action solidgate-checkout-action--spaced', children: copy.resumeVerification })) : null, (view.state === 'recoverable_error' || view.state === 'expired') && !view.canResumeVerification ? (_jsx("button", { type: 'button', onClick: onRetry || (() => {
405
+ preparedRequestRef.current = null;
406
+ setRetryRequest({
407
+ originalKey: idempotencyKey,
408
+ key: crypto.randomUUID(),
409
+ });
410
+ }), className: 'solidgate-checkout-action solidgate-checkout-action--spaced', children: copy.retry })) : null] })) : null] }), _jsx("style", { children: solidgateCheckoutDialogStyles })] }));
402
411
  }
@@ -1,4 +1,5 @@
1
1
  import type { FunnelSdkRuntimeConfigOverrides, FunnelStepType, RuntimeMode } from '@funnelsgrove/runtime';
2
+ import type { PublicAnalyticsFeatureFlags } from '@funnelsgrove/analytics';
2
3
  import type { PrepareSolidgateCheckoutInput, SolidgateCheckoutPreparation } from '../services/solidgate.service.js';
3
4
  export type SolidgateCheckoutDialogState = 'collecting_email' | 'loading' | 'ready' | 'submitting' | 'verifying' | 'success' | 'recoverable_error' | 'expired';
4
5
  export type SolidgateCheckoutDialogContent = {
@@ -52,11 +53,12 @@ export type SolidgateCheckoutDialogProps = {
52
53
  onSuccess?: (checkout: SolidgateCheckoutPreparation) => void | Promise<void>;
53
54
  onRetry?: () => void;
54
55
  onError?: (message: string) => void;
56
+ onPreparationStart?: () => void;
55
57
  onStateChange?: (state: SolidgateCheckoutDialogState) => void;
56
58
  };
57
59
  export type SolidgateCheckoutAnalyticsContext = {
58
60
  environment?: RuntimeMode;
59
- featureFlags?: Record<string, string | null | undefined>;
61
+ featureFlags?: PublicAnalyticsFeatureFlags;
60
62
  metadata?: Record<string, unknown> | null;
61
63
  runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
62
64
  stepId: string;
@@ -15,6 +15,8 @@ export type StripeOneTimeCheckoutSessionInput = {
15
15
  couponId?: string | null;
16
16
  customerEmail?: string | null;
17
17
  enabled?: boolean;
18
+ onPreparationStart?: () => void;
19
+ onReset?: () => void;
18
20
  onError?: (message: string | null) => void;
19
21
  plan?: StripeOneTimeCheckoutPlanInput | null;
20
22
  returnUrl: string;
@@ -52,4 +54,4 @@ export type StripeOneTimeCheckoutIntentKeyInput = Pick<StripeOneTimeCheckoutSess
52
54
  };
53
55
  export declare function buildStripeOneTimeCheckoutIntentKey({ runtimeConfigRevisionId, paymentProfileId, provider, checkoutMode, couponId, customerEmail, plan, returnUrl, runtimeConfig, stripePublishableKey, userId, }: StripeOneTimeCheckoutIntentKeyInput): string;
54
56
  export declare function shouldResetStripeOneTimeCheckoutSessionForIntentKey(previousIntentKey: string | null, nextIntentKey: string): boolean;
55
- export declare function useStripeOneTimeCheckoutSession({ runtimeConfigRevisionId, paymentProfileId, provider, analyticsMetadata, checkoutAnalytics, checkoutMode, couponId, customerEmail, enabled, onError, plan, returnUrl, runtimeConfig, stripePublishableKey, stripeLocale, userId, }: StripeOneTimeCheckoutSessionInput): StripeOneTimeCheckoutSession;
57
+ export declare function useStripeOneTimeCheckoutSession({ runtimeConfigRevisionId: nextRuntimeConfigRevisionId, paymentProfileId: nextPaymentProfileId, provider: nextProvider, analyticsMetadata, checkoutAnalytics, checkoutMode: nextCheckoutMode, couponId, customerEmail, enabled, onError, onPreparationStart, onReset, plan, returnUrl, runtimeConfig: nextRuntimeConfig, stripePublishableKey: nextStripePublishableKey, stripeLocale, userId, }: StripeOneTimeCheckoutSessionInput): StripeOneTimeCheckoutSession;
@@ -71,8 +71,20 @@ export function buildStripeOneTimeCheckoutIntentKey({ runtimeConfigRevisionId, p
71
71
  export function shouldResetStripeOneTimeCheckoutSessionForIntentKey(previousIntentKey, nextIntentKey) {
72
72
  return previousIntentKey !== null && previousIntentKey !== nextIntentKey;
73
73
  }
74
- export function useStripeOneTimeCheckoutSession({ runtimeConfigRevisionId, paymentProfileId, provider, analyticsMetadata, checkoutAnalytics, checkoutMode, couponId, customerEmail, enabled = true, onError, plan, returnUrl, runtimeConfig, stripePublishableKey, stripeLocale, userId, }) {
75
- var _a, _b, _c;
74
+ export function useStripeOneTimeCheckoutSession({ runtimeConfigRevisionId: nextRuntimeConfigRevisionId, paymentProfileId: nextPaymentProfileId, provider: nextProvider, analyticsMetadata, checkoutAnalytics, checkoutMode: nextCheckoutMode, couponId, customerEmail, enabled = true, onError, onPreparationStart, onReset, plan, returnUrl, runtimeConfig: nextRuntimeConfig, stripePublishableKey: nextStripePublishableKey, stripeLocale, userId, }) {
75
+ var _a, _b, _c, _d, _e;
76
+ const preparedSelectionRef = useRef(null);
77
+ const nextSelection = {
78
+ checkoutMode: nextCheckoutMode,
79
+ runtimeConfig: nextRuntimeConfig,
80
+ runtimeConfigRevisionId: nextRuntimeConfigRevisionId,
81
+ paymentProfileId: nextPaymentProfileId,
82
+ provider: nextProvider,
83
+ stripePublishableKey: nextStripePublishableKey,
84
+ };
85
+ const latestSelectionRef = useRef(nextSelection);
86
+ latestSelectionRef.current = nextSelection;
87
+ const { checkoutMode, runtimeConfig, runtimeConfigRevisionId, paymentProfileId, provider, stripePublishableKey, } = (_a = preparedSelectionRef.current) !== null && _a !== void 0 ? _a : nextSelection;
76
88
  const [clientSecret, setClientSecret] = useState(null);
77
89
  const [clientSecretIntentKey, setClientSecretIntentKey] = useState(null);
78
90
  const [checkoutSessionId, setCheckoutSessionId] = useState(null);
@@ -106,9 +118,10 @@ export function useStripeOneTimeCheckoutSession({ runtimeConfigRevisionId, payme
106
118
  userId,
107
119
  });
108
120
  const planKey = [
109
- (_a = plan === null || plan === void 0 ? void 0 : plan.id) !== null && _a !== void 0 ? _a : '',
110
- (_b = plan === null || plan === void 0 ? void 0 : plan.providerPlanId) !== null && _b !== void 0 ? _b : '',
111
- (_c = plan === null || plan === void 0 ? void 0 : plan.amountCents) !== null && _c !== void 0 ? _c : '',
121
+ (_b = plan === null || plan === void 0 ? void 0 : plan.offerSetId) !== null && _b !== void 0 ? _b : '',
122
+ (_c = plan === null || plan === void 0 ? void 0 : plan.id) !== null && _c !== void 0 ? _c : '',
123
+ (_d = plan === null || plan === void 0 ? void 0 : plan.providerPlanId) !== null && _d !== void 0 ? _d : '',
124
+ (_e = plan === null || plan === void 0 ? void 0 : plan.amountCents) !== null && _e !== void 0 ? _e : '',
112
125
  ].join('|');
113
126
  const activeClientSecret = clientSecretIntentKey === intentKey ? clientSecret : null;
114
127
  const activeCheckoutSessionId = clientSecretIntentKey === intentKey ? checkoutSessionId : null;
@@ -139,12 +152,17 @@ export function useStripeOneTimeCheckoutSession({ runtimeConfigRevisionId, payme
139
152
  onError === null || onError === void 0 ? void 0 : onError(message);
140
153
  }, [clearActiveCheckoutSession, onError]);
141
154
  const reset = useCallback(() => {
155
+ preparedSelectionRef.current = null;
142
156
  attemptRef.current = null;
143
157
  clearActiveCheckoutSession();
144
158
  setLoading(createEmptyCheckoutPreparationLoading());
145
159
  setRuntimeStripePublishableKey(null);
146
160
  setError(null);
147
161
  }, [clearActiveCheckoutSession, setError]);
162
+ const releaseAndReset = useCallback(() => {
163
+ reset();
164
+ onReset === null || onReset === void 0 ? void 0 : onReset();
165
+ }, [onReset, reset]);
148
166
  useIsomorphicLayoutEffect(() => {
149
167
  const previousIntentKey = previousIntentKeyRef.current;
150
168
  previousIntentKeyRef.current = intentKey;
@@ -278,9 +296,12 @@ export function useStripeOneTimeCheckoutSession({ runtimeConfigRevisionId, payme
278
296
  userId,
279
297
  ]);
280
298
  const prepareCheckout = useCallback(async (source, options = {}) => {
299
+ var _a;
281
300
  if (!configured || !plan) {
282
301
  return null;
283
302
  }
303
+ onPreparationStart === null || onPreparationStart === void 0 ? void 0 : onPreparationStart();
304
+ (_a = preparedSelectionRef.current) !== null && _a !== void 0 ? _a : (preparedSelectionRef.current = latestSelectionRef.current);
284
305
  if (!options.forceNew && activeClientSecret) {
285
306
  return activeClientSecret;
286
307
  }
@@ -297,6 +318,7 @@ export function useStripeOneTimeCheckoutSession({ runtimeConfigRevisionId, payme
297
318
  configured,
298
319
  createCheckoutSession,
299
320
  plan,
321
+ onPreparationStart,
300
322
  setError,
301
323
  setSourceLoading,
302
324
  ]);
@@ -314,7 +336,7 @@ export function useStripeOneTimeCheckoutSession({ runtimeConfigRevisionId, payme
314
336
  prepareCardCheckout,
315
337
  prepareWalletCheckout,
316
338
  provider: 'stripe',
317
- reset,
339
+ reset: releaseAndReset,
318
340
  restartCardCheckout,
319
341
  restartWalletCheckout,
320
342
  planKey,
@@ -330,7 +352,7 @@ export function useStripeOneTimeCheckoutSession({ runtimeConfigRevisionId, payme
330
352
  planKey,
331
353
  prepareCardCheckout,
332
354
  prepareWalletCheckout,
333
- reset,
355
+ releaseAndReset,
334
356
  restartCardCheckout,
335
357
  restartWalletCheckout,
336
358
  setError,
@@ -13,6 +13,8 @@ export type StripeSubscriptionCheckoutSessionInput = {
13
13
  customerEmail?: string | null;
14
14
  displayPlan?: PaywallDisplayPlan | null;
15
15
  enabled?: boolean;
16
+ onPreparationStart?: () => void;
17
+ onReset?: () => void;
16
18
  onError?: (message: string | null) => void;
17
19
  plan?: PaywallPlan | null;
18
20
  returnUrl: string;
@@ -59,4 +61,4 @@ export declare function shouldResetStripeSubscriptionCheckoutSessionForIntentKey
59
61
  export declare function isInactiveCheckoutSessionError(message?: string | null): boolean;
60
62
  export declare function shouldAutoRecoverStripeSubscriptionCheckoutSession(message: string | null | undefined, alreadyRecovered: boolean): boolean;
61
63
  export declare const shouldReportStripeCheckoutFailureShown: (message: string) => boolean;
62
- export declare function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMetadata, checkoutAnalytics, couponId, customerEmail, displayPlan, enabled, onError, plan, returnUrl, runtimeConfig, runtimeConfigRevisionId, paymentProfileId, provider, stripePublishableKey, stripeLocale, userId, }: StripeSubscriptionCheckoutSessionInput): StripeSubscriptionCheckoutSession;
64
+ export declare function useStripeSubscriptionCheckoutSession({ checkoutMode: nextCheckoutMode, analyticsMetadata, checkoutAnalytics, couponId, customerEmail, displayPlan, enabled, onError, onPreparationStart, onReset, plan, returnUrl, runtimeConfig: nextRuntimeConfig, runtimeConfigRevisionId: nextRuntimeConfigRevisionId, paymentProfileId: nextPaymentProfileId, provider: nextProvider, stripePublishableKey: nextStripePublishableKey, stripeLocale, userId, }: StripeSubscriptionCheckoutSessionInput): StripeSubscriptionCheckoutSession;
@@ -71,8 +71,20 @@ export function shouldAutoRecoverStripeSubscriptionCheckoutSession(message, alre
71
71
  return !alreadyRecovered && isInactiveCheckoutSessionError(message);
72
72
  }
73
73
  export const shouldReportStripeCheckoutFailureShown = (message) => !isInactiveCheckoutSessionError(message);
74
- export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMetadata, checkoutAnalytics, couponId, customerEmail, displayPlan, enabled = true, onError, plan, returnUrl, runtimeConfig, runtimeConfigRevisionId, paymentProfileId, provider, stripePublishableKey, stripeLocale, userId, }) {
75
- var _a, _b, _c, _d, _e;
74
+ export function useStripeSubscriptionCheckoutSession({ checkoutMode: nextCheckoutMode, analyticsMetadata, checkoutAnalytics, couponId, customerEmail, displayPlan, enabled = true, onError, onPreparationStart, onReset, plan, returnUrl, runtimeConfig: nextRuntimeConfig, runtimeConfigRevisionId: nextRuntimeConfigRevisionId, paymentProfileId: nextPaymentProfileId, provider: nextProvider, stripePublishableKey: nextStripePublishableKey, stripeLocale, userId, }) {
75
+ var _a, _b, _c, _d, _e, _f, _g;
76
+ const preparedSelectionRef = useRef(null);
77
+ const nextSelection = {
78
+ checkoutMode: nextCheckoutMode,
79
+ runtimeConfig: nextRuntimeConfig,
80
+ runtimeConfigRevisionId: nextRuntimeConfigRevisionId,
81
+ paymentProfileId: nextPaymentProfileId,
82
+ provider: nextProvider,
83
+ stripePublishableKey: nextStripePublishableKey,
84
+ };
85
+ const latestSelectionRef = useRef(nextSelection);
86
+ latestSelectionRef.current = nextSelection;
87
+ const { checkoutMode, runtimeConfig, runtimeConfigRevisionId, paymentProfileId, provider, stripePublishableKey, } = (_a = preparedSelectionRef.current) !== null && _a !== void 0 ? _a : nextSelection;
76
88
  const [clientSecret, setClientSecret] = useState(null);
77
89
  const [clientSecretIntentKey, setClientSecretIntentKey] = useState(null);
78
90
  const [checkoutSessionId, setCheckoutSessionId] = useState(null);
@@ -97,11 +109,12 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
97
109
  Boolean(normalizeRuntimeConfigValue(runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.funnelSdkPublishableKey)) ||
98
110
  Boolean(normalizeRuntimeConfigValue(stripePublishableKey)));
99
111
  const planKey = [
100
- (_a = plan === null || plan === void 0 ? void 0 : plan.id) !== null && _a !== void 0 ? _a : '',
101
- (_b = plan === null || plan === void 0 ? void 0 : plan.providerPlanId) !== null && _b !== void 0 ? _b : '',
102
- (_c = plan === null || plan === void 0 ? void 0 : plan.amountCents) !== null && _c !== void 0 ? _c : '',
103
- (_d = displayPlan === null || displayPlan === void 0 ? void 0 : displayPlan.id) !== null && _d !== void 0 ? _d : '',
104
- (_e = displayPlan === null || displayPlan === void 0 ? void 0 : displayPlan.amountCents) !== null && _e !== void 0 ? _e : '',
112
+ (_b = plan === null || plan === void 0 ? void 0 : plan.offerSetId) !== null && _b !== void 0 ? _b : '',
113
+ (_c = plan === null || plan === void 0 ? void 0 : plan.id) !== null && _c !== void 0 ? _c : '',
114
+ (_d = plan === null || plan === void 0 ? void 0 : plan.providerPlanId) !== null && _d !== void 0 ? _d : '',
115
+ (_e = plan === null || plan === void 0 ? void 0 : plan.amountCents) !== null && _e !== void 0 ? _e : '',
116
+ (_f = displayPlan === null || displayPlan === void 0 ? void 0 : displayPlan.id) !== null && _f !== void 0 ? _f : '',
117
+ (_g = displayPlan === null || displayPlan === void 0 ? void 0 : displayPlan.amountCents) !== null && _g !== void 0 ? _g : '',
105
118
  ].join('|');
106
119
  const intentKey = buildStripeSubscriptionCheckoutIntentKey({
107
120
  checkoutMode,
@@ -153,6 +166,7 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
153
166
  onError === null || onError === void 0 ? void 0 : onError(message);
154
167
  }, [clearActiveCheckoutSession, intentKey, onError]);
155
168
  const reset = useCallback(() => {
169
+ preparedSelectionRef.current = null;
156
170
  attemptRef.current = null;
157
171
  recoveredIntentKeyRef.current = null;
158
172
  handledRecoveryRequestRef.current = recoveryRequest;
@@ -161,6 +175,10 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
161
175
  setRuntimeStripePublishableKey(null);
162
176
  setError(null);
163
177
  }, [clearActiveCheckoutSession, recoveryRequest, setError]);
178
+ const releaseAndReset = useCallback(() => {
179
+ reset();
180
+ onReset === null || onReset === void 0 ? void 0 : onReset();
181
+ }, [onReset, reset]);
164
182
  useIsomorphicLayoutEffect(() => {
165
183
  const previousIntentKey = previousIntentKeyRef.current;
166
184
  previousIntentKeyRef.current = intentKey;
@@ -300,9 +318,12 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
300
318
  userId,
301
319
  ]);
302
320
  const prepareCheckout = useCallback(async (source, options = {}) => {
321
+ var _a;
303
322
  if (!configured || !plan || !displayPlan) {
304
323
  return null;
305
324
  }
325
+ onPreparationStart === null || onPreparationStart === void 0 ? void 0 : onPreparationStart();
326
+ (_a = preparedSelectionRef.current) !== null && _a !== void 0 ? _a : (preparedSelectionRef.current = latestSelectionRef.current);
306
327
  if (!options.forceNew && activeClientSecret) {
307
328
  return activeClientSecret;
308
329
  }
@@ -320,6 +341,7 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
320
341
  createCheckoutSubscription,
321
342
  displayPlan,
322
343
  plan,
344
+ onPreparationStart,
323
345
  setError,
324
346
  setSourceLoading,
325
347
  ]);
@@ -416,7 +438,7 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
416
438
  prepareCardCheckout,
417
439
  prepareWalletCheckout,
418
440
  provider: 'stripe',
419
- reset,
441
+ reset: releaseAndReset,
420
442
  restartCardCheckout,
421
443
  restartWalletCheckout,
422
444
  planKey,
@@ -434,7 +456,7 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
434
456
  planKey,
435
457
  prepareCardCheckout,
436
458
  prepareWalletCheckout,
437
- reset,
459
+ releaseAndReset,
438
460
  restartCardCheckout,
439
461
  restartWalletCheckout,
440
462
  setError,
@@ -3,6 +3,13 @@ export declare const toOneTimeCheckoutRequest: (input: CreateOneTimeCheckoutInpu
3
3
  environment: import("@funnelsgrove/runtime").RuntimeMode | undefined;
4
4
  runtimeConfig: import("@funnelsgrove/runtime").FunnelSdkRuntimeConfigOverrides | undefined;
5
5
  checkoutIdentityToken: string;
6
+ couponId: string | undefined;
7
+ analyticsMetadata: Record<string, unknown> | undefined;
8
+ customerEmail: string | undefined;
9
+ returnUrl: string;
10
+ userId: string | undefined;
11
+ currency?: string | undefined;
12
+ money?: import("@funnelsgrove/runtime").ProviderMoney | undefined;
6
13
  planKey: string;
7
14
  runtimeConfigRevisionId: string | null | undefined;
8
15
  paymentProfileId: string | null | undefined;
@@ -16,15 +23,17 @@ export declare const toOneTimeCheckoutRequest: (input: CreateOneTimeCheckoutInpu
16
23
  title: string;
17
24
  description: string | undefined;
18
25
  amountCents: number;
26
+ } | {
27
+ environment: import("@funnelsgrove/runtime").RuntimeMode | undefined;
28
+ runtimeConfig: import("@funnelsgrove/runtime").FunnelSdkRuntimeConfigOverrides | undefined;
29
+ checkoutIdentityToken?: undefined;
19
30
  couponId: string | undefined;
20
31
  analyticsMetadata: Record<string, unknown> | undefined;
21
32
  customerEmail: string | undefined;
22
33
  returnUrl: string;
23
34
  userId: string | undefined;
24
- } | {
25
- environment: import("@funnelsgrove/runtime").RuntimeMode | undefined;
26
- runtimeConfig: import("@funnelsgrove/runtime").FunnelSdkRuntimeConfigOverrides | undefined;
27
- checkoutIdentityToken?: undefined;
35
+ currency?: string | undefined;
36
+ money?: import("@funnelsgrove/runtime").ProviderMoney | undefined;
28
37
  planKey: string;
29
38
  runtimeConfigRevisionId: string | null | undefined;
30
39
  paymentProfileId: string | null | undefined;
@@ -38,29 +47,11 @@ export declare const toOneTimeCheckoutRequest: (input: CreateOneTimeCheckoutInpu
38
47
  title: string;
39
48
  description: string | undefined;
40
49
  amountCents: number;
41
- couponId: string | undefined;
42
- analyticsMetadata: Record<string, unknown> | undefined;
43
- customerEmail: string | undefined;
44
- returnUrl: string;
45
- userId: string | undefined;
46
50
  };
47
51
  export declare const toSubscriptionCheckoutRequest: (input: CreateSubscriptionCheckoutInput) => {
48
52
  environment: import("@funnelsgrove/runtime").RuntimeMode | undefined;
49
53
  runtimeConfig: import("@funnelsgrove/runtime").FunnelSdkRuntimeConfigOverrides | undefined;
50
54
  checkoutIdentityToken: string;
51
- offerSetId: string | undefined;
52
- offerSetKey: string | undefined;
53
- planId: string;
54
- planKey: string;
55
- providerPlanId: string | undefined;
56
- provider: "stripe" | null | undefined;
57
- paymentProfileId: string | undefined;
58
- runtimeConfigRevisionId: string | undefined;
59
- anonymousUserId: string | undefined;
60
- idempotencyKey: string | undefined;
61
- title: string;
62
- description: string | undefined;
63
- amountCents: number;
64
55
  billingInterval: import("../../../../index.js").BillingPlanInterval;
65
56
  billingIntervalCount: number;
66
57
  followUpProviderPlanId: string | undefined;
@@ -73,10 +64,8 @@ export declare const toSubscriptionCheckoutRequest: (input: CreateSubscriptionCh
73
64
  customerEmail: string | undefined;
74
65
  returnUrl: string;
75
66
  userId: string | undefined;
76
- } | {
77
- environment: import("@funnelsgrove/runtime").RuntimeMode | undefined;
78
- runtimeConfig: import("@funnelsgrove/runtime").FunnelSdkRuntimeConfigOverrides | undefined;
79
- checkoutIdentityToken?: undefined;
67
+ currency?: string | undefined;
68
+ money?: import("@funnelsgrove/runtime").ProviderMoney | undefined;
80
69
  offerSetId: string | undefined;
81
70
  offerSetKey: string | undefined;
82
71
  planId: string;
@@ -90,6 +79,10 @@ export declare const toSubscriptionCheckoutRequest: (input: CreateSubscriptionCh
90
79
  title: string;
91
80
  description: string | undefined;
92
81
  amountCents: number;
82
+ } | {
83
+ environment: import("@funnelsgrove/runtime").RuntimeMode | undefined;
84
+ runtimeConfig: import("@funnelsgrove/runtime").FunnelSdkRuntimeConfigOverrides | undefined;
85
+ checkoutIdentityToken?: undefined;
93
86
  billingInterval: import("../../../../index.js").BillingPlanInterval;
94
87
  billingIntervalCount: number;
95
88
  followUpProviderPlanId: string | undefined;
@@ -102,9 +95,8 @@ export declare const toSubscriptionCheckoutRequest: (input: CreateSubscriptionCh
102
95
  customerEmail: string | undefined;
103
96
  returnUrl: string;
104
97
  userId: string | undefined;
105
- };
106
- export declare const toSubscriptionCheckoutPlanRequest: (input: UpdateSubscriptionCheckoutPlanInput) => {
107
- checkoutSessionId: string;
98
+ currency?: string | undefined;
99
+ money?: import("@funnelsgrove/runtime").ProviderMoney | undefined;
108
100
  offerSetId: string | undefined;
109
101
  offerSetKey: string | undefined;
110
102
  planId: string;
@@ -113,9 +105,13 @@ export declare const toSubscriptionCheckoutPlanRequest: (input: UpdateSubscripti
113
105
  provider: "stripe" | null | undefined;
114
106
  paymentProfileId: string | undefined;
115
107
  runtimeConfigRevisionId: string | undefined;
108
+ anonymousUserId: string | undefined;
109
+ idempotencyKey: string | undefined;
116
110
  title: string;
117
111
  description: string | undefined;
118
112
  amountCents: number;
113
+ };
114
+ export declare const toSubscriptionCheckoutPlanRequest: (input: UpdateSubscriptionCheckoutPlanInput) => {
119
115
  billingInterval: import("../../../../index.js").BillingPlanInterval;
120
116
  billingIntervalCount: number;
121
117
  followUpProviderPlanId: string | undefined;
@@ -129,6 +125,20 @@ export declare const toSubscriptionCheckoutPlanRequest: (input: UpdateSubscripti
129
125
  userId: string | undefined;
130
126
  environment: import("@funnelsgrove/runtime").RuntimeMode | undefined;
131
127
  runtimeConfig: import("@funnelsgrove/runtime").FunnelSdkRuntimeConfigOverrides | undefined;
128
+ currency?: string | undefined;
129
+ money?: import("@funnelsgrove/runtime").ProviderMoney | undefined;
130
+ checkoutSessionId: string;
131
+ offerSetId: string | undefined;
132
+ offerSetKey: string | undefined;
133
+ planId: string;
134
+ planKey: string;
135
+ providerPlanId: string | undefined;
136
+ provider: "stripe" | null | undefined;
137
+ paymentProfileId: string | undefined;
138
+ runtimeConfigRevisionId: string | undefined;
139
+ title: string;
140
+ description: string | undefined;
141
+ amountCents: number;
132
142
  };
133
143
  export declare const toSubscriptionCheckoutStatusRequest: (input: VerifySubscriptionCheckoutPaymentInput) => {
134
144
  checkoutSessionId: string;
@@ -8,44 +8,24 @@ const toCheckoutIdentityRequestFields = (userId) => {
8
8
  return checkoutIdentityToken ? { checkoutIdentityToken } : {};
9
9
  };
10
10
  export const toOneTimeCheckoutRequest = (input) => {
11
- var _a, _b, _c, _d, _e, _f, _g, _h;
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 });
11
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
12
+ return Object.assign(Object.assign(Object.assign(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 }, (input.plan.money ? { money: input.plan.money } : {})), (((_f = input.plan.money) === null || _f === void 0 ? void 0 : _f.currency) || input.plan.currency
13
+ ? { currency: (_h = (_g = input.plan.money) === null || _g === void 0 ? void 0 : _g.currency) !== null && _h !== void 0 ? _h : input.plan.currency }
14
+ : {})), { couponId: ((_j = input.couponId) === null || _j === void 0 ? void 0 : _j.trim()) || undefined, analyticsMetadata: normalizeCheckoutAnalyticsMetadata(input.analyticsMetadata, input.plan), customerEmail: ((_k = input.customerEmail) === null || _k === void 0 ? void 0 : _k.trim()) || undefined, returnUrl: input.returnUrl, userId: ((_l = input.user_id) === null || _l === void 0 ? void 0 : _l.trim()) || undefined }), toCheckoutIdentityRequestFields(input.user_id)), { environment: input.environment, runtimeConfig: input.runtimeConfig });
13
15
  };
14
16
  export const toSubscriptionCheckoutRequest = (input) => {
15
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
17
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
16
18
  const recurring = requireCheckoutRecurringConfig(input.plan);
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 });
19
+ return Object.assign(Object.assign(Object.assign(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 }, (input.plan.money ? { money: input.plan.money } : {})), (((_k = input.plan.money) === null || _k === void 0 ? void 0 : _k.currency) || input.plan.currency
20
+ ? { currency: (_m = (_l = input.plan.money) === null || _l === void 0 ? void 0 : _l.currency) !== null && _m !== void 0 ? _m : input.plan.currency }
21
+ : {})), { billingInterval: recurring.interval, billingIntervalCount: recurring.intervalCount, followUpProviderPlanId: ((_o = input.plan.followUpProviderPlanId) === null || _o === void 0 ? void 0 : _o.trim()) || undefined, followUpPlanTitle: ((_p = input.plan.followUpPlanTitle) === null || _p === void 0 ? void 0 : _p.trim()) || undefined, followUpBillingInterval: input.plan.followUpBillingInterval, followUpBillingIntervalCount: input.plan.followUpBillingIntervalCount, introIterations: input.plan.introIterations, couponId: ((_q = input.couponId) === null || _q === void 0 ? void 0 : _q.trim()) || undefined, analyticsMetadata: normalizeCheckoutAnalyticsMetadata(input.analyticsMetadata, input.plan), customerEmail: ((_r = input.customerEmail) === null || _r === void 0 ? void 0 : _r.trim()) || undefined, returnUrl: input.returnUrl, userId: ((_s = input.user_id) === null || _s === void 0 ? void 0 : _s.trim()) || undefined }), toCheckoutIdentityRequestFields(input.user_id)), { environment: input.environment, runtimeConfig: input.runtimeConfig });
18
22
  };
19
23
  export const toSubscriptionCheckoutPlanRequest = (input) => {
20
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
24
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
21
25
  const recurring = requireCheckoutRecurringConfig(input.plan);
22
- return {
23
- checkoutSessionId: input.checkoutSessionId.trim(),
24
- offerSetId: ((_a = input.plan.offerSetId) === null || _a === void 0 ? void 0 : _a.trim()) || undefined,
25
- offerSetKey: ((_b = input.plan.offerSetKey) === null || _b === void 0 ? void 0 : _b.trim()) || undefined,
26
- planId: getPaywallPlanRequestId(input.plan),
27
- planKey: ((_c = input.plan.funnelPlanKey) === null || _c === void 0 ? void 0 : _c.trim()) || input.plan.id,
28
- providerPlanId: ((_d = input.plan.providerPlanId) === null || _d === void 0 ? void 0 : _d.trim()) || undefined,
29
- provider: input.provider,
30
- paymentProfileId: ((_e = input.paymentProfileId) === null || _e === void 0 ? void 0 : _e.trim()) || undefined,
31
- runtimeConfigRevisionId: ((_f = input.runtimeConfigRevisionId) === null || _f === void 0 ? void 0 : _f.trim()) || undefined,
32
- title: input.plan.title,
33
- description: ((_g = input.plan.description) === null || _g === void 0 ? void 0 : _g.trim()) || undefined,
34
- amountCents: input.plan.amountCents,
35
- billingInterval: recurring.interval,
36
- billingIntervalCount: recurring.intervalCount,
37
- followUpProviderPlanId: ((_h = input.plan.followUpProviderPlanId) === null || _h === void 0 ? void 0 : _h.trim()) || undefined,
38
- followUpPlanTitle: ((_j = input.plan.followUpPlanTitle) === null || _j === void 0 ? void 0 : _j.trim()) || undefined,
39
- followUpBillingInterval: input.plan.followUpBillingInterval,
40
- followUpBillingIntervalCount: input.plan.followUpBillingIntervalCount,
41
- introIterations: input.plan.introIterations,
42
- couponId: ((_k = input.couponId) === null || _k === void 0 ? void 0 : _k.trim()) || undefined,
43
- analyticsMetadata: normalizeCheckoutAnalyticsMetadata(input.analyticsMetadata, input.plan),
44
- customerEmail: ((_l = input.customerEmail) === null || _l === void 0 ? void 0 : _l.trim()) || undefined,
45
- userId: ((_m = input.user_id) === null || _m === void 0 ? void 0 : _m.trim()) || undefined,
46
- environment: input.environment,
47
- runtimeConfig: input.runtimeConfig,
48
- };
26
+ return Object.assign(Object.assign(Object.assign({ checkoutSessionId: input.checkoutSessionId.trim(), 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, title: input.plan.title, description: ((_g = input.plan.description) === null || _g === void 0 ? void 0 : _g.trim()) || undefined, amountCents: input.plan.amountCents }, (input.plan.money ? { money: input.plan.money } : {})), (((_h = input.plan.money) === null || _h === void 0 ? void 0 : _h.currency) || input.plan.currency
27
+ ? { currency: (_k = (_j = input.plan.money) === null || _j === void 0 ? void 0 : _j.currency) !== null && _k !== void 0 ? _k : input.plan.currency }
28
+ : {})), { billingInterval: recurring.interval, billingIntervalCount: recurring.intervalCount, followUpProviderPlanId: ((_l = input.plan.followUpProviderPlanId) === null || _l === void 0 ? void 0 : _l.trim()) || undefined, followUpPlanTitle: ((_m = input.plan.followUpPlanTitle) === null || _m === void 0 ? void 0 : _m.trim()) || undefined, followUpBillingInterval: input.plan.followUpBillingInterval, followUpBillingIntervalCount: input.plan.followUpBillingIntervalCount, introIterations: input.plan.introIterations, couponId: ((_o = input.couponId) === null || _o === void 0 ? void 0 : _o.trim()) || undefined, analyticsMetadata: normalizeCheckoutAnalyticsMetadata(input.analyticsMetadata, input.plan), customerEmail: ((_p = input.customerEmail) === null || _p === void 0 ? void 0 : _p.trim()) || undefined, userId: ((_q = input.user_id) === null || _q === void 0 ? void 0 : _q.trim()) || undefined, environment: input.environment, runtimeConfig: input.runtimeConfig });
49
29
  };
50
30
  export const toSubscriptionCheckoutStatusRequest = (input) => {
51
31
  const checkoutSessionId = requireCheckoutSessionId(input.checkoutSessionId);
@@ -1,9 +1,10 @@
1
1
  import type { FunnelStepType, RuntimeMode } from '@funnelsgrove/runtime';
2
+ import { type PublicAnalyticsFeatureFlags } from '@funnelsgrove/analytics';
2
3
  import type { StripeRuntimeConfigOverrides } from './shared/types.js';
3
4
  import type { ChargeOneClickPaymentResponse, CompleteStripeOneClickPaymentInput } from './oneClick/types.js';
4
5
  export type StripeSubscriptionCheckoutAnalyticsContext = {
5
6
  environment?: RuntimeMode;
6
- featureFlags?: Record<string, string | null | undefined>;
7
+ featureFlags?: PublicAnalyticsFeatureFlags;
7
8
  metadata?: Record<string, unknown> | null;
8
9
  runtimeConfig?: StripeRuntimeConfigOverrides;
9
10
  stepId: string;
@@ -176,7 +176,7 @@ export async function trackPaidStripeSubscriptionCheckoutCompleted(input) {
176
176
  }
177
177
  }
178
178
  export async function trackPaidStripeOneClickPaymentCompleted(input, payment) {
179
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
179
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
180
180
  const context = input.checkoutAnalytics;
181
181
  const paymentIntentId = (_a = payment.paymentIntentId) === null || _a === void 0 ? void 0 : _a.trim();
182
182
  if (!context || payment.status !== 'succeeded' || !paymentIntentId) {
@@ -202,7 +202,7 @@ export async function trackPaidStripeOneClickPaymentCompleted(input, payment) {
202
202
  stepContractVersion: context.stepContractVersion,
203
203
  featureFlags: context.featureFlags,
204
204
  metadata: Object.assign(Object.assign(Object.assign({}, asRecord(input.analyticsMetadata)), asRecord(context.metadata)), { userId,
205
- environment, amountCents: (_g = payment.amountCents) !== null && _g !== void 0 ? _g : input.amountCents, currency: (_j = (_h = payment.currency) !== null && _h !== void 0 ? _h : input.currency) !== null && _j !== void 0 ? _j : 'usd', planId: input.planId, providerPlanId: input.providerPlanId, payment_provider: 'stripe' }),
205
+ environment, amountCents: (_g = payment.amountCents) !== null && _g !== void 0 ? _g : input.amountCents, money: (_h = payment.money) !== null && _h !== void 0 ? _h : input.money, currency: (_o = (_l = (_k = (_j = payment.money) === null || _j === void 0 ? void 0 : _j.currency) !== null && _k !== void 0 ? _k : payment.currency) !== null && _l !== void 0 ? _l : (_m = input.money) === null || _m === void 0 ? void 0 : _m.currency) !== null && _o !== void 0 ? _o : input.currency, planId: input.planId, providerPlanId: input.providerPlanId, payment_provider: 'stripe' }),
206
206
  });
207
207
  if (eventId) {
208
208
  markCheckoutSessionTracked('checkout_completed', paymentIntentId, runtimeConfig);
@@ -1,5 +1,17 @@
1
1
  import type { CreateCheckoutSessionInput } from './types.js';
2
2
  export declare const toHostedCheckoutRequest: (input: CreateCheckoutSessionInput) => {
3
+ billingInterval: import("../../../../index.js").BillingPlanInterval;
4
+ billingIntervalCount: number;
5
+ couponId: string | undefined;
6
+ analyticsMetadata: Record<string, unknown> | undefined;
7
+ successUrl: string;
8
+ cancelUrl: string;
9
+ customerEmail: string | undefined;
10
+ userId: string | undefined;
11
+ environment: import("@funnelsgrove/runtime").RuntimeMode | undefined;
12
+ runtimeConfig: import("@funnelsgrove/runtime").FunnelSdkRuntimeConfigOverrides | undefined;
13
+ currency?: string | undefined;
14
+ money?: import("@funnelsgrove/runtime").ProviderMoney | undefined;
3
15
  planKey: string;
4
16
  paymentProfileId: string | null | undefined;
5
17
  provider: "stripe" | null | undefined;
@@ -14,14 +26,4 @@ export declare const toHostedCheckoutRequest: (input: CreateCheckoutSessionInput
14
26
  title: string;
15
27
  description: string | undefined;
16
28
  amountCents: number;
17
- billingInterval: import("../../../../index.js").BillingPlanInterval;
18
- billingIntervalCount: number;
19
- couponId: string | undefined;
20
- analyticsMetadata: Record<string, unknown> | undefined;
21
- successUrl: string;
22
- cancelUrl: string;
23
- customerEmail: string | undefined;
24
- userId: string | undefined;
25
- environment: import("@funnelsgrove/runtime").RuntimeMode | undefined;
26
- runtimeConfig: import("@funnelsgrove/runtime").FunnelSdkRuntimeConfigOverrides | undefined;
27
29
  };
@@ -2,32 +2,9 @@ import { requireCheckoutRecurringConfig } from '../shared/checkout.validator.js'
2
2
  import { getPaywallPlanRequestId } from '../../../../services/paywallPlans/selection.js';
3
3
  import { normalizeCheckoutAnalyticsMetadata } from '../shared/checkoutMetadata.js';
4
4
  export const toHostedCheckoutRequest = (input) => {
5
- var _a, _b, _c, _d, _e, _f, _g, _h;
5
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
6
6
  const recurring = requireCheckoutRecurringConfig(input.plan);
7
- return {
8
- planKey: ((_a = input.plan.funnelPlanKey) === null || _a === void 0 ? void 0 : _a.trim()) || input.plan.id,
9
- paymentProfileId: input.paymentProfileId,
10
- provider: input.provider,
11
- runtimeConfigRevisionId: input.runtimeConfigRevisionId,
12
- anonymousUserId: input.anonymousUserId,
13
- checkoutIdentityToken: input.checkoutIdentityToken,
14
- idempotencyKey: input.idempotencyKey,
15
- offerSetId: ((_b = input.plan.offerSetId) === null || _b === void 0 ? void 0 : _b.trim()) || undefined,
16
- offerSetKey: ((_c = input.plan.offerSetKey) === null || _c === void 0 ? void 0 : _c.trim()) || undefined,
17
- planId: getPaywallPlanRequestId(input.plan),
18
- providerPlanId: ((_d = input.plan.providerPlanId) === null || _d === void 0 ? void 0 : _d.trim()) || undefined,
19
- title: input.plan.title,
20
- description: ((_e = input.plan.description) === null || _e === void 0 ? void 0 : _e.trim()) || undefined,
21
- amountCents: input.plan.amountCents,
22
- billingInterval: recurring.interval,
23
- billingIntervalCount: recurring.intervalCount,
24
- couponId: ((_f = input.couponId) === null || _f === void 0 ? void 0 : _f.trim()) || undefined,
25
- analyticsMetadata: normalizeCheckoutAnalyticsMetadata(input.analyticsMetadata, input.plan),
26
- successUrl: input.successUrl,
27
- cancelUrl: input.cancelUrl,
28
- customerEmail: ((_g = input.customerEmail) === null || _g === void 0 ? void 0 : _g.trim()) || undefined,
29
- userId: ((_h = input.user_id) === null || _h === void 0 ? void 0 : _h.trim()) || undefined,
30
- environment: input.environment,
31
- runtimeConfig: input.runtimeConfig,
32
- };
7
+ return Object.assign(Object.assign(Object.assign({ planKey: ((_a = input.plan.funnelPlanKey) === null || _a === void 0 ? void 0 : _a.trim()) || input.plan.id, paymentProfileId: input.paymentProfileId, provider: input.provider, runtimeConfigRevisionId: input.runtimeConfigRevisionId, anonymousUserId: input.anonymousUserId, checkoutIdentityToken: input.checkoutIdentityToken, 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 }, (input.plan.money ? { money: input.plan.money } : {})), (((_f = input.plan.money) === null || _f === void 0 ? void 0 : _f.currency) || input.plan.currency
8
+ ? { currency: (_h = (_g = input.plan.money) === null || _g === void 0 ? void 0 : _g.currency) !== null && _h !== void 0 ? _h : input.plan.currency }
9
+ : {})), { billingInterval: recurring.interval, billingIntervalCount: recurring.intervalCount, couponId: ((_j = input.couponId) === null || _j === void 0 ? void 0 : _j.trim()) || undefined, analyticsMetadata: normalizeCheckoutAnalyticsMetadata(input.analyticsMetadata, input.plan), successUrl: input.successUrl, cancelUrl: input.cancelUrl, customerEmail: ((_k = input.customerEmail) === null || _k === void 0 ? void 0 : _k.trim()) || undefined, userId: ((_l = input.user_id) === null || _l === void 0 ? void 0 : _l.trim()) || undefined, environment: input.environment, runtimeConfig: input.runtimeConfig });
33
10
  };
@@ -1,13 +1,5 @@
1
1
  import type { ChargeOneClickPaymentInput } from './types.js';
2
2
  export declare const toOneClickPaymentRequest: (input: ChargeOneClickPaymentInput) => {
3
- planKey: string | null | undefined;
4
- offerSetId: string | null | undefined;
5
- runtimeConfigRevisionId: string | null | undefined;
6
- paymentProfileId: string | null | undefined;
7
- provider: "stripe" | null | undefined;
8
- planId: string;
9
- providerPlanId: string | undefined;
10
- amountCents: number;
11
3
  currency: string | undefined;
12
4
  description: string | undefined;
13
5
  analyticsMetadata: Record<string, unknown> | undefined;
@@ -17,4 +9,13 @@ export declare const toOneClickPaymentRequest: (input: ChargeOneClickPaymentInpu
17
9
  idempotencyKey: string | undefined;
18
10
  environment: import("@funnelsgrove/runtime").RuntimeMode | undefined;
19
11
  runtimeConfig: import("@funnelsgrove/runtime").FunnelSdkRuntimeConfigOverrides | undefined;
12
+ money?: import("@funnelsgrove/runtime").ProviderMoney | undefined;
13
+ planKey: string | null | undefined;
14
+ offerSetId: string | null | undefined;
15
+ runtimeConfigRevisionId: string | null | undefined;
16
+ paymentProfileId: string | null | undefined;
17
+ provider: "stripe" | null | undefined;
18
+ planId: string;
19
+ providerPlanId: string | undefined;
20
+ amountCents: number;
20
21
  };
@@ -1,23 +1,5 @@
1
1
  import { normalizeAnalyticsMetadata } from '../shared/checkoutMetadata.js';
2
2
  export const toOneClickPaymentRequest = (input) => {
3
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
4
- return {
5
- planKey: input.planKey,
6
- offerSetId: input.offerSetId,
7
- runtimeConfigRevisionId: input.runtimeConfigRevisionId,
8
- paymentProfileId: input.paymentProfileId,
9
- provider: input.provider,
10
- planId: input.planId,
11
- providerPlanId: ((_a = input.providerPlanId) === null || _a === void 0 ? void 0 : _a.trim()) || undefined,
12
- amountCents: input.amountCents,
13
- currency: ((_b = input.currency) === null || _b === void 0 ? void 0 : _b.trim()) || undefined,
14
- description: ((_c = input.description) === null || _c === void 0 ? void 0 : _c.trim()) || undefined,
15
- analyticsMetadata: normalizeAnalyticsMetadata(input.analyticsMetadata),
16
- userId: ((_d = input.userId) === null || _d === void 0 ? void 0 : _d.trim()) || ((_e = input.user_id) === null || _e === void 0 ? void 0 : _e.trim()) || undefined,
17
- stripeCustomerId: ((_f = input.stripeCustomerId) === null || _f === void 0 ? void 0 : _f.trim()) || ((_g = input.stripe_customer_id) === null || _g === void 0 ? void 0 : _g.trim()) || undefined,
18
- checkoutSessionId: ((_h = input.checkoutSessionId) === null || _h === void 0 ? void 0 : _h.trim()) || undefined,
19
- idempotencyKey: ((_j = input.idempotencyKey) === null || _j === void 0 ? void 0 : _j.trim()) || undefined,
20
- environment: input.environment,
21
- runtimeConfig: input.runtimeConfig,
22
- };
3
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
4
+ return Object.assign(Object.assign({ planKey: input.planKey, offerSetId: input.offerSetId, runtimeConfigRevisionId: input.runtimeConfigRevisionId, paymentProfileId: input.paymentProfileId, provider: input.provider, planId: input.planId, providerPlanId: ((_a = input.providerPlanId) === null || _a === void 0 ? void 0 : _a.trim()) || undefined, amountCents: input.amountCents }, (input.money ? { money: input.money } : {})), { currency: (_c = (_b = input.money) === null || _b === void 0 ? void 0 : _b.currency) !== null && _c !== void 0 ? _c : (((_d = input.currency) === null || _d === void 0 ? void 0 : _d.trim()) || undefined), description: ((_e = input.description) === null || _e === void 0 ? void 0 : _e.trim()) || undefined, analyticsMetadata: normalizeAnalyticsMetadata(input.analyticsMetadata), userId: ((_f = input.userId) === null || _f === void 0 ? void 0 : _f.trim()) || ((_g = input.user_id) === null || _g === void 0 ? void 0 : _g.trim()) || undefined, stripeCustomerId: ((_h = input.stripeCustomerId) === null || _h === void 0 ? void 0 : _h.trim()) || ((_j = input.stripe_customer_id) === null || _j === void 0 ? void 0 : _j.trim()) || undefined, checkoutSessionId: ((_k = input.checkoutSessionId) === null || _k === void 0 ? void 0 : _k.trim()) || undefined, idempotencyKey: ((_l = input.idempotencyKey) === null || _l === void 0 ? void 0 : _l.trim()) || undefined, environment: input.environment, runtimeConfig: input.runtimeConfig });
23
5
  };
@@ -1,4 +1,4 @@
1
- import type { RuntimeMode } from '@funnelsgrove/runtime';
1
+ import type { ProviderMoney, RuntimeMode } from '@funnelsgrove/runtime';
2
2
  import type { StripeRuntimeConfigOverrides } from '../shared/types.js';
3
3
  import type { StripeSubscriptionCheckoutAnalyticsContext } from '../checkoutCompletionAnalytics.service.js';
4
4
  export type ChargeOneClickPaymentInput = {
@@ -10,6 +10,7 @@ export type ChargeOneClickPaymentInput = {
10
10
  provider?: 'stripe' | null;
11
11
  providerPlanId?: string | null;
12
12
  amountCents: number;
13
+ money?: ProviderMoney;
13
14
  analyticsMetadata?: Record<string, unknown> | null;
14
15
  checkoutSessionId?: string | null;
15
16
  currency?: string | null;
@@ -28,6 +29,7 @@ export type ChargeOneClickPaymentResponse = {
28
29
  paymentIntentId: string;
29
30
  status: string;
30
31
  amountCents?: number;
32
+ money?: ProviderMoney;
31
33
  clientSecret?: string;
32
34
  currency?: string;
33
35
  environment?: 'test' | 'live';
@@ -1,7 +1,8 @@
1
+ import { type PublicAnalyticsFeatureFlags } from '@funnelsgrove/analytics';
1
2
  import { type FunnelSdkRuntimeConfigOverrides, type FunnelStepType, type RuntimeMode } from '@funnelsgrove/runtime';
2
3
  export type CheckoutObservabilityAnalyticsContext = {
3
4
  environment?: RuntimeMode;
4
- featureFlags?: Record<string, string | null | undefined>;
5
+ featureFlags?: PublicAnalyticsFeatureFlags;
5
6
  metadata?: Record<string, unknown> | null;
6
7
  runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
7
8
  stepId?: string;
@@ -1,4 +1,4 @@
1
- import { publicAnalyticsSdk } from '@funnelsgrove/analytics';
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
4
  const asNonEmptyString = (value) => typeof value === 'string' && value.trim() ? value.trim() : null;
@@ -1,3 +1,5 @@
1
+ import { formatProviderMoney } from '@funnelsgrove/runtime';
2
+ const withAmountMinor = (money, amountMinor) => (Object.assign(Object.assign({}, money), { amountMinor }));
1
3
  const isBillingDiscountList = (discounts) => {
2
4
  return Array.isArray(discounts);
3
5
  };
@@ -259,21 +261,30 @@ export const buildDiscountedPaywallPlans = (input) => {
259
261
  const currency = ((_a = input.currency) === null || _a === void 0 ? void 0 : _a.trim()) || 'USD';
260
262
  const locale = ((_b = input.locale) === null || _b === void 0 ? void 0 : _b.trim()) || 'en-US';
261
263
  return input.plans.map((plan) => {
262
- var _a, _b;
264
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
263
265
  const billingDays = getBillingDays(plan);
266
+ const planCurrency = ((_b = (_a = plan.money) === null || _a === void 0 ? void 0 : _a.currency) !== null && _b !== void 0 ? _b : (_c = plan.currency) === null || _c === void 0 ? void 0 : _c.trim()) || currency;
264
267
  const fixed = input.amountOffMinor !== undefined;
265
268
  const hasActiveDiscount = Boolean(input.couponId) && (fixed
266
269
  ? Number.isSafeInteger(input.amountOffMinor) && input.amountOffMinor > 0
267
- && ((_a = input.discountCurrency) === null || _a === void 0 ? void 0 : _a.toUpperCase()) === currency.toUpperCase()
270
+ && ((_d = input.discountCurrency) === null || _d === void 0 ? void 0 : _d.toUpperCase()) === planCurrency.toUpperCase()
268
271
  : typeof input.discountPercent === 'number');
269
- const discountedAmountCents = !hasActiveDiscount ? plan.amountCents : fixed
270
- ? Math.max(0, plan.amountCents - input.amountOffMinor)
271
- : getDiscountedAmountCents(plan.amountCents, (_b = input.discountPercent) !== null && _b !== void 0 ? _b : 0);
272
+ const baseAmountMinor = (_f = (_e = plan.money) === null || _e === void 0 ? void 0 : _e.amountMinor) !== null && _f !== void 0 ? _f : plan.amountCents;
273
+ const discountedAmountCents = !hasActiveDiscount ? baseAmountMinor : fixed
274
+ ? Math.max(0, baseAmountMinor - input.amountOffMinor)
275
+ : getDiscountedAmountCents(baseAmountMinor, (_g = input.discountPercent) !== null && _g !== void 0 ? _g : 0);
276
+ const discountedMoney = plan.money
277
+ ? withAmountMinor(plan.money, discountedAmountCents)
278
+ : undefined;
272
279
  const discountedPerDayAmount = billingDays && billingDays > 0
273
- ? formatMoneyFromCents(Math.round(discountedAmountCents / billingDays), currency, locale)
280
+ ? discountedMoney
281
+ ? formatProviderMoney(withAmountMinor(discountedMoney, Math.round(discountedMoney.amountMinor / billingDays)), locale)
282
+ : formatMoneyFromCents(Math.round(discountedAmountCents / billingDays), planCurrency, locale)
274
283
  : plan.perDayAmount;
275
- return Object.assign(Object.assign({}, plan), { amountCents: discountedAmountCents, priceLabel: hasActiveDiscount
276
- ? formatMoneyFromCents(discountedAmountCents, currency, locale)
284
+ return Object.assign(Object.assign({}, plan), { amountCents: discountedAmountCents, money: discountedMoney, currency: (_j = (_h = plan.money) === null || _h === void 0 ? void 0 : _h.currency) !== null && _j !== void 0 ? _j : plan.currency, priceLabel: hasActiveDiscount
285
+ ? discountedMoney
286
+ ? formatProviderMoney(discountedMoney, locale)
287
+ : formatMoneyFromCents(discountedAmountCents, planCurrency, locale)
277
288
  : plan.priceLabel, oldPriceLabel: hasActiveDiscount ? plan.priceLabel : undefined, perDayAmount: hasActiveDiscount ? discountedPerDayAmount : plan.perDayAmount, baseAmountCents: plan.amountCents, basePriceLabel: plan.priceLabel, basePerDayAmount: plan.perDayAmount, oldPerDayAmount: hasActiveDiscount ? plan.perDayAmount : null, couponId: hasActiveDiscount ? input.couponId : null, hasActiveDiscount });
278
289
  });
279
290
  };
@@ -1,5 +1,5 @@
1
1
  import type { BillingFallbackPlanConfig, BillingPlanInterval } from '../../config/billing.config.js';
2
- import type { ResolvedCountryPricing } from '@funnelsgrove/runtime';
2
+ import type { ProviderMoney, ResolvedCountryPricing } from '@funnelsgrove/runtime';
3
3
  export type PaywallPlan = {
4
4
  id: string;
5
5
  funnelPlanKey?: string;
@@ -15,6 +15,8 @@ export type PaywallPlan = {
15
15
  perDayAmount: string;
16
16
  perDayLabel: string;
17
17
  amountCents: number;
18
+ money?: ProviderMoney;
19
+ currency?: string;
18
20
  analyticsPurchaseValue?: number;
19
21
  comparison?: BillingFallbackPlanConfig['comparison'];
20
22
  checkoutOriginalAmountCents?: number;
@@ -32,7 +34,7 @@ export type PaywallPlanResolutionOptions = {
32
34
  locale?: string | null;
33
35
  pricing?: ResolvedCountryPricing<string> | null;
34
36
  };
35
- export type CheckoutSessionPlanInput = Pick<PaywallPlan, 'amountCents' | 'analyticsPurchaseValue' | 'billingInterval' | 'billingIntervalCount' | 'description' | 'funnelPlanKey' | 'id' | 'providerPlanId' | 'title' | 'followUpProviderPlanId' | 'followUpPlanTitle' | 'followUpBillingInterval' | 'followUpBillingIntervalCount' | 'introIterations' | 'offerSetId' | 'offerSetKey'>;
37
+ export type CheckoutSessionPlanInput = Pick<PaywallPlan, 'amountCents' | 'money' | 'currency' | 'analyticsPurchaseValue' | 'billingInterval' | 'billingIntervalCount' | 'description' | 'funnelPlanKey' | 'id' | 'providerPlanId' | 'title' | 'followUpProviderPlanId' | 'followUpPlanTitle' | 'followUpBillingInterval' | 'followUpBillingIntervalCount' | 'introIterations' | 'offerSetId' | 'offerSetKey'>;
36
38
  export type CheckoutPlanRecurringConfig = {
37
39
  interval: BillingPlanInterval;
38
40
  intervalCount: number;
@@ -4,6 +4,7 @@ import { type PaywallPlan } from './paywallPlans.service.js';
4
4
  export type PublishedBillingPlan = BillingFallbackPlanConfig & OfferSetRuntimePlanOverrides;
5
5
  export type ResolvedPublishedBillingRuntime = {
6
6
  revisionId: string | null;
7
+ sourceVersionId: string | null;
7
8
  paymentsApiVersion: 'v2';
8
9
  mode: RuntimeMode;
9
10
  offerSetId: string;
@@ -12,6 +13,8 @@ export type ResolvedPublishedBillingRuntime = {
12
13
  stripePublishableKey: string | null;
13
14
  experimentId: string | null;
14
15
  variantKey: string | null;
16
+ locale: string | null;
17
+ featureFlags: Readonly<Record<string, string | boolean>>;
15
18
  experiments: readonly FunnelExperimentDefinition[];
16
19
  flowExperiments: readonly FunnelManifestExperiment[];
17
20
  plans: readonly PaywallPlan[];
@@ -20,14 +23,32 @@ export type ResolvedPublishedBillingRuntime = {
20
23
  discounts: BillingDiscountCatalog | null;
21
24
  upsells: readonly PublishedBillingPlan[];
22
25
  downsells: readonly PublishedBillingPlan[];
26
+ /** Published plan keys withheld because this mode has no chargeable price for them. */
27
+ unavailablePlanKeys: readonly string[];
23
28
  };
24
- export declare const resolvePublishedBillingRuntime: ({ config, featureFlags, mode, search, defaultOfferSetId, baseCatalogsByMode, fallbackDiscounts, resolvedExperiments, }: {
29
+ export declare const EMPTY_PUBLISHED_BILLING_RUNTIME: ResolvedPublishedBillingRuntime;
30
+ export declare const resolvePublishedBillingRuntime: ({ config, featureFlags, mode, search, defaultOfferSetId, locale, baseCatalogsByMode, fallbackDiscounts, resolvedExperiments, }: {
25
31
  config: ResolvedFunnelRuntimeConfig | null;
26
- featureFlags: Readonly<Record<string, string>>;
32
+ featureFlags: Readonly<Record<string, string | boolean>>;
27
33
  mode: RuntimeMode;
28
34
  search?: string;
29
35
  defaultOfferSetId?: string;
36
+ locale?: string | null;
30
37
  baseCatalogsByMode?: Partial<Record<RuntimeMode, BillingPlanCatalog>>;
31
38
  fallbackDiscounts?: BillingDiscountList;
32
39
  resolvedExperiments?: ResolvedPublishedExperiments;
33
40
  }) => ResolvedPublishedBillingRuntime;
41
+ export type PublishedBillingRuntimeResolution = {
42
+ status: 'pending';
43
+ runtime: null;
44
+ error: null;
45
+ } | {
46
+ status: 'resolved';
47
+ runtime: ResolvedPublishedBillingRuntime;
48
+ error: null;
49
+ } | {
50
+ status: 'error';
51
+ runtime: null;
52
+ error: Error;
53
+ };
54
+ export declare const resolvePublishedBillingRuntimeState: (input: Parameters<typeof resolvePublishedBillingRuntime>[0], ready?: boolean) => PublishedBillingRuntimeResolution;
@@ -1,8 +1,16 @@
1
1
  import { resolveGeneratedOfferSetRuntime, resolvePublishedOfferSetSelection, resolvePublishedRuntimeExperiments, } from '@funnelsgrove/runtime';
2
2
  import { buildBillingDiscountCatalog } from './paywallOffer.service.js';
3
3
  import { buildConfigPaywallPlans, } from './paywallPlans.service.js';
4
- export const resolvePublishedBillingRuntime = ({ config, featureFlags, mode, search = '', defaultOfferSetId, baseCatalogsByMode, fallbackDiscounts, resolvedExperiments = resolvePublishedRuntimeExperiments(config), }) => {
5
- var _a, _b, _c, _d, _e, _f, _g;
4
+ export const EMPTY_PUBLISHED_BILLING_RUNTIME = Object.freeze({
5
+ revisionId: null, sourceVersionId: null, paymentsApiVersion: 'v2', mode: 'live',
6
+ offerSetId: '', paymentProfileId: null, provider: 'stripe', stripePublishableKey: null,
7
+ experimentId: null, variantKey: null, locale: null, experiments: [], flowExperiments: [],
8
+ featureFlags: Object.freeze({}),
9
+ plans: [], planCatalog: {}, defaultPlanKey: '', discounts: null, upsells: [], downsells: [],
10
+ unavailablePlanKeys: [],
11
+ });
12
+ export const resolvePublishedBillingRuntime = ({ config, featureFlags, mode, search = '', defaultOfferSetId, locale, baseCatalogsByMode, fallbackDiscounts, resolvedExperiments = resolvePublishedRuntimeExperiments(config), }) => {
13
+ var _a, _b, _c, _d, _e, _f, _g, _h;
6
14
  const offerSets = (_a = config === null || config === void 0 ? void 0 : config.offerSets) !== null && _a !== void 0 ? _a : [];
7
15
  const selection = resolvePublishedOfferSetSelection({
8
16
  offerSets,
@@ -10,7 +18,12 @@ export const resolvePublishedBillingRuntime = ({ config, featureFlags, mode, sea
10
18
  featureFlags,
11
19
  search,
12
20
  defaultOfferSetId: defaultOfferSetId || (config === null || config === void 0 ? void 0 : config.defaultOfferSetId) || undefined,
21
+ locale,
22
+ localeContractVersion: config === null || config === void 0 ? void 0 : config.localeContractVersion,
13
23
  });
24
+ if ((config === null || config === void 0 ? void 0 : config.localeContractVersion) === 1 && offerSets.length > 0 && !selection.offerSetId) {
25
+ throw new Error('Published billing runtime has no valid offer set selection');
26
+ }
14
27
  const runtime = resolveGeneratedOfferSetRuntime({
15
28
  offerSets,
16
29
  offerSetId: selection.offerSetId,
@@ -24,18 +37,24 @@ export const resolvePublishedBillingRuntime = ({ config, featureFlags, mode, sea
24
37
  const discountList = runtime.getDiscountList(mode);
25
38
  const provider = runtime.getPaymentProvider(mode);
26
39
  const paymentProviderConfig = (_c = (_b = runtime.offerSet) === null || _b === void 0 ? void 0 : _b.paymentProviderConfigByMode) === null || _c === void 0 ? void 0 : _c[mode];
40
+ const pinnedFeatureFlags = Object.assign(Object.assign({}, featureFlags), (selection.experimentId && selection.variantKey
41
+ ? { [selection.experimentId]: selection.variantKey }
42
+ : {}));
27
43
  return {
28
44
  revisionId: (_d = config === null || config === void 0 ? void 0 : config.revisionId) !== null && _d !== void 0 ? _d : null,
45
+ sourceVersionId: (_e = config === null || config === void 0 ? void 0 : config.sourceVersionId) !== null && _e !== void 0 ? _e : null,
29
46
  paymentsApiVersion: 'v2',
30
47
  mode,
31
48
  offerSetId: selection.offerSetId,
32
- paymentProfileId: ((_f = (_e = runtime.offerSet) === null || _e === void 0 ? void 0 : _e.paymentProfileId) === null || _f === void 0 ? void 0 : _f.trim()) || null,
49
+ paymentProfileId: ((_g = (_f = runtime.offerSet) === null || _f === void 0 ? void 0 : _f.paymentProfileId) === null || _g === void 0 ? void 0 : _g.trim()) || null,
33
50
  provider,
34
51
  stripePublishableKey: provider === 'stripe' && (paymentProviderConfig === null || paymentProviderConfig === void 0 ? void 0 : paymentProviderConfig.provider) === 'stripe'
35
- ? ((_g = paymentProviderConfig === null || paymentProviderConfig === void 0 ? void 0 : paymentProviderConfig.stripePublishableKey) === null || _g === void 0 ? void 0 : _g.trim()) || null
52
+ ? ((_h = paymentProviderConfig === null || paymentProviderConfig === void 0 ? void 0 : paymentProviderConfig.stripePublishableKey) === null || _h === void 0 ? void 0 : _h.trim()) || null
36
53
  : null,
37
54
  experimentId: selection.experimentId,
38
55
  variantKey: selection.variantKey,
56
+ locale: selection.locale,
57
+ featureFlags: Object.freeze(pinnedFeatureFlags),
39
58
  experiments: resolvedExperiments.experiments,
40
59
  flowExperiments: resolvedExperiments.flowExperiments,
41
60
  plans: buildConfigPaywallPlans(planCatalog, runtime.planKeys),
@@ -44,5 +63,20 @@ export const resolvePublishedBillingRuntime = ({ config, featureFlags, mode, sea
44
63
  discounts: discountList.length > 0 ? buildBillingDiscountCatalog(discountList) : null,
45
64
  upsells: getPlans(runtime.upsellPlanKeys),
46
65
  downsells: getPlans(runtime.downsellPlanKeys),
66
+ unavailablePlanKeys: runtime.getUnavailablePlanKeys(mode),
47
67
  };
48
68
  };
69
+ export const resolvePublishedBillingRuntimeState = (input, ready = true) => {
70
+ if (!ready)
71
+ return { status: 'pending', runtime: null, error: null };
72
+ try {
73
+ return { status: 'resolved', runtime: resolvePublishedBillingRuntime(input), error: null };
74
+ }
75
+ catch (error) {
76
+ return {
77
+ status: 'error',
78
+ runtime: null,
79
+ error: error instanceof Error ? error : new Error('Unable to resolve published billing runtime'),
80
+ };
81
+ }
82
+ };
@@ -0,0 +1,10 @@
1
+ import type { PublishedBillingRuntimeResolution, ResolvedPublishedBillingRuntime } from './publishedBillingRuntime.service.js';
2
+ export type PinnedPublishedBillingRuntime = {
3
+ runtime: ResolvedPublishedBillingRuntime | null;
4
+ resolution: PublishedBillingRuntimeResolution;
5
+ pinned: boolean;
6
+ pin: () => void;
7
+ reset: () => void;
8
+ };
9
+ /** Keeps every visible and billable field on one published snapshot once preparation starts. */
10
+ export declare const usePinnedPublishedBillingRuntime: (current: PublishedBillingRuntimeResolution) => PinnedPublishedBillingRuntime;
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+ import { useCallback, useState } from 'react';
3
+ /** Keeps every visible and billable field on one published snapshot once preparation starts. */
4
+ export const usePinnedPublishedBillingRuntime = (current) => {
5
+ const [pinnedRuntime, setPinnedRuntime] = useState(null);
6
+ const pin = useCallback(() => {
7
+ if (current.status !== 'resolved')
8
+ return;
9
+ setPinnedRuntime((existing) => existing !== null && existing !== void 0 ? existing : current.runtime);
10
+ }, [current]);
11
+ const reset = useCallback(() => {
12
+ setPinnedRuntime(null);
13
+ }, []);
14
+ return {
15
+ runtime: pinnedRuntime !== null && pinnedRuntime !== void 0 ? pinnedRuntime : current.runtime,
16
+ resolution: pinnedRuntime
17
+ ? { status: 'resolved', runtime: pinnedRuntime, error: null }
18
+ : current,
19
+ pinned: pinnedRuntime !== null,
20
+ pin,
21
+ reset,
22
+ };
23
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnelsgrove/payments",
3
- "version": "0.22.6",
3
+ "version": "0.23.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/The-Solid-Grove/funnelsgrove.git",
@@ -46,11 +46,11 @@
46
46
  "jsdom": "20.0.3",
47
47
  "typescript": "^5",
48
48
  "vitest": "^3.2.4",
49
- "@funnelsgrove/runtime": "0.20.3",
50
- "@funnelsgrove/analytics": "0.3.0"
49
+ "@funnelsgrove/runtime": "0.21.0",
50
+ "@funnelsgrove/analytics": "0.4.0"
51
51
  },
52
52
  "peerDependencies": {
53
- "@funnelsgrove/runtime": "^0.19.1 || ^0.20.0",
54
- "@funnelsgrove/analytics": "^0.1.111 || ^0.2.0 || ^0.3.0"
53
+ "@funnelsgrove/runtime": "^0.21.0",
54
+ "@funnelsgrove/analytics": "^0.4.0"
55
55
  }
56
56
  }