@funnelsgrove/payments 0.1.49 → 0.1.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,6 +22,11 @@ Use these for new subscription paywalls:
22
22
  - `WalletSubscriptionCheckoutSlot`
23
23
  - `trackPaidStripeSubscriptionCheckoutCompleted`
24
24
 
25
+ Use these for new one-time paywall plans:
26
+
27
+ - `createStripeOneTimeCheckout`
28
+ - `SharedStripeCheckoutV2Dialog`
29
+
25
30
  Use `chargeStripeOneClickPayment` for post-checkout one-click upsells when the shared client helper fits the funnel. It delegates to `funnelSdkService.chargeOneClickPayment`.
26
31
 
27
32
  ## Compatibility-Only Surfaces
@@ -64,7 +69,7 @@ When a live funnel still uses one of these, migrate the funnel deliberately with
64
69
  - `services/planCatalog.service`: plan resolution and selection helpers.
65
70
  - `services/runtimeBillingPlanCatalog.service`: test/live runtime catalog selection.
66
71
  - `services/paywallOffer.service`: timed discount and display-plan helpers.
67
- - `services/stripe.service`: Stripe loader, checkout-session, hosted checkout, one-click, and compatibility PaymentIntent clients.
72
+ - `services/stripe.service`: Stripe loader, Checkout Session, hosted checkout, one-click, one-time Checkout Session clients, and deprecated PaymentIntent compatibility clients.
68
73
  - `providers/*`: provider-neutral checkout state and Stripe subscription checkout slots.
69
74
  - `components/shared/*`: plan selector, checkout dialogs, Express Checkout wrappers, wallet placeholders, and trust assets.
70
75
  - `testing/walletCheckoutSmoke`: wallet checkout smoke assertions.
@@ -74,7 +79,9 @@ When a live funnel still uses one of these, migrate the funnel deliberately with
74
79
  - Keep provider-specific implementation under `src/providers/<provider>`.
75
80
  - Stripe owns real card fields and wallet controls. Local wallet buttons are placeholders until Stripe confirms availability.
76
81
  - Wallet unavailable state must fall back to card/manual checkout or disappear.
77
- - Do not create checkout sessions until the visitor taps a visible checkout CTA.
82
+ - Do not count checkout intent until the visitor taps or confirms a visible checkout CTA.
83
+ - Wallet slots may create render-only Checkout Sessions on mount so Apple Pay and Google Pay render before the visitor taps. These sessions must use `checkoutStartSource: 'wallet_render'`; trusted server `checkout_started` analytics ignores that source.
84
+ - Funnel paywalls should style wallet buttons through the shared slot API: `className` for the Stripe button/placeholder, `slotClassName` for the outer slot, `appearance` for Stripe Elements appearance, and `options` for Stripe Express Checkout options.
78
85
  - Keep discount math reusable here, but keep offer activation timing in funnel code.
79
86
  - Checkout metadata must avoid sensitive raw query params and use `@funnelsgrove/analytics` metadata helpers.
80
87
 
@@ -59,7 +59,7 @@ function getFriendlyCardErrorMessage(error) {
59
59
  ? error.message
60
60
  : 'Payment failed. Please review your card details and try again.';
61
61
  }
62
- function SharedStripeCheckoutV2Form({ amountCents, buttonColor, cardSubmitLabel, closeAriaLabel, countdownLabel, checkoutAnalytics, checkoutSessionId, customerEmail, customerEmailEditable = false, customerEmailLabel = 'Email', customerEmailPlaceholder = 'Enter your email', customerEmailInvalidMessage = 'Enter a valid email address.', customerName, discountAmountLabel, discountLabel, discountPercent, initialWalletAvailable, onClose, onCustomerEmailChange, onCustomerEmailCommit, onError, onInactiveCheckoutSession, onPaymentInfoSubmitted, onSuccess, paymentMethodLabels = defaultPaymentMethodLabels, promoActive, processingLabel, promoCode, promoCodeLabel, remainingSeconds, returnUrl, savedLabel, secureLabel, satisfactionLabel, satisfactionPercent, summaryLabel, supportedCountries, themeColor, title, totalLabel, totalValue, unsupportedCountryMessage, walletButtonLabel, originalPriceLabel, originalPriceValue, }) {
62
+ function SharedStripeCheckoutV2CheckoutSessionForm({ amountCents, buttonColor, cardSubmitLabel, closeAriaLabel, countdownLabel, checkoutAnalytics, checkoutSessionId, customerEmail, customerEmailEditable = false, customerEmailLabel = 'Email', customerEmailPlaceholder = 'Enter your email', customerEmailInvalidMessage = 'Enter a valid email address.', customerName, discountAmountLabel, discountLabel, discountPercent, initialWalletAvailable, onClose, onCustomerEmailChange, onCustomerEmailCommit, onError, onInactiveCheckoutSession, onPaymentInfoSubmitted, onSuccess, paymentMethodLabels = defaultPaymentMethodLabels, promoActive, processingLabel, promoCode, promoCodeLabel, remainingSeconds, returnUrl, savedLabel, secureLabel, satisfactionLabel, satisfactionPercent, summaryLabel, supportedCountries, themeColor, title, totalLabel, totalValue, unsupportedCountryMessage, walletButtonLabel, originalPriceLabel, originalPriceValue, }) {
63
63
  const checkoutState = useCheckout();
64
64
  const walletPaymentMethods = usePlatformWalletPaymentMethods();
65
65
  const [selectedMethod, setSelectedMethod] = useState('wallet');
@@ -306,10 +306,8 @@ function SharedStripeCheckoutV2Form({ amountCents, buttonColor, cardSubmitLabel,
306
306
  onCustomerEmailChange === null || onCustomerEmailChange === void 0 ? void 0 : onCustomerEmailChange(event.target.value);
307
307
  } }) })] }), _jsx(PaymentElement, { options: paymentElementOptions, onChange: handlePaymentElementChange }), fixedBillingCountry && fixedBillingCountryLabel ? (_jsxs("label", { className: 'shared-checkout-v2-country-field', children: [_jsx("span", { className: 'shared-checkout-v2-country-label', children: "Country" }), _jsx("span", { className: 'shared-checkout-v2-country-control', children: _jsx("select", { "aria-label": 'Country', className: 'shared-checkout-v2-country-select', value: fixedBillingCountry, onChange: () => setBillingCountry(fixedBillingCountry), children: _jsx("option", { value: fixedBillingCountry, children: fixedBillingCountryLabel }) }) })] })) : null] }), visibleError ? _jsx("p", { className: 'shared-checkout-v2-error', children: visibleError }) : null, _jsxs("button", { type: 'submit', className: 'shared-checkout-v2-card-submit', disabled: submitting, children: [_jsx(SecurityLockIcon, { className: 'shared-checkout-v2-card-submit-icon' }), submitting ? processingLabel : cardSubmitLabel] }), _jsx("div", { className: 'shared-checkout-v2-card-brands', "aria-hidden": 'true', children: cardBrandLabels.map((label) => (_jsx(PaymentBrandMark, { label: label, className: 'shared-checkout-v2-card-brand' }, label))) }), _jsxs("p", { className: 'shared-checkout-v2-secure-pill shared-checkout-v2-secure-pill--card', children: [_jsx(SecurityLockIcon, { className: 'shared-checkout-v2-secure-pill-icon' }), secureLabel] })] })] })] }));
308
308
  }
309
- export function SharedStripeCheckoutV2Dialog(_a) {
310
- var { checkoutAnalytics, checkoutSessionId, clientSecret, onClose, stripePromise, supportedCountries } = _a, props = __rest(_a, ["checkoutAnalytics", "checkoutSessionId", "clientSecret", "onClose", "stripePromise", "supportedCountries"]);
311
- const normalizedSupportedCountries = useMemo(() => normalizeSupportedCheckoutCountries(supportedCountries), [supportedCountries]);
312
- const fixedBillingCountry = getFixedSupportedBillingCountry(normalizedSupportedCountries);
309
+ function CheckoutSessionProvider({ children, clientSecret, fixedBillingCountry, stripePromise, }) {
310
+ const providerChildren = children;
313
311
  const checkoutProviderOptions = useMemo(() => (Object.assign(Object.assign({ clientSecret }, (fixedBillingCountry
314
312
  ? {
315
313
  defaultValues: {
@@ -323,6 +321,12 @@ export function SharedStripeCheckoutV2Dialog(_a) {
323
321
  : {})), { elementsOptions: {
324
322
  appearance: stripeCheckoutV2Appearance,
325
323
  } })), [clientSecret, fixedBillingCountry]);
324
+ return (_jsx(CheckoutProvider, { stripe: stripePromise, options: checkoutProviderOptions, children: providerChildren }, clientSecret));
325
+ }
326
+ export function SharedStripeCheckoutV2Dialog(_a) {
327
+ var { checkoutAnalytics, checkoutSessionId, clientSecret, onClose, stripePromise, supportedCountries } = _a, props = __rest(_a, ["checkoutAnalytics", "checkoutSessionId", "clientSecret", "onClose", "stripePromise", "supportedCountries"]);
328
+ const normalizedSupportedCountries = useMemo(() => normalizeSupportedCheckoutCountries(supportedCountries), [supportedCountries]);
329
+ const fixedBillingCountry = getFixedSupportedBillingCountry(normalizedSupportedCountries);
326
330
  useEffect(() => {
327
331
  if (!checkoutAnalytics) {
328
332
  return;
@@ -338,7 +342,7 @@ export function SharedStripeCheckoutV2Dialog(_a) {
338
342
  window.addEventListener('keydown', handleKeyDown);
339
343
  return () => window.removeEventListener('keydown', handleKeyDown);
340
344
  }, [onClose]);
341
- return (_jsxs(_Fragment, { children: [_jsx("div", { className: 'shared-checkout-v2-overlay', children: _jsx("div", { className: 'shared-checkout-v2-dialog', role: 'dialog', "aria-modal": 'true', "aria-labelledby": 'shared-checkout-v2-title', children: _jsx(CheckoutProvider, { stripe: stripePromise, options: checkoutProviderOptions, children: _jsx(SharedStripeCheckoutV2Form, Object.assign({}, props, { checkoutAnalytics: checkoutAnalytics, checkoutSessionId: checkoutSessionId, clientSecret: clientSecret, onClose: onClose, supportedCountries: normalizedSupportedCountries })) }, clientSecret) }) }), _jsx("style", { children: sharedStripeCheckoutV2Styles })] }));
345
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: 'shared-checkout-v2-overlay', children: _jsx("div", { className: 'shared-checkout-v2-dialog', role: 'dialog', "aria-modal": 'true', "aria-labelledby": 'shared-checkout-v2-title', children: _jsx(CheckoutSessionProvider, { clientSecret: clientSecret, fixedBillingCountry: fixedBillingCountry, stripePromise: stripePromise, children: _jsx(SharedStripeCheckoutV2CheckoutSessionForm, Object.assign({}, props, { checkoutAnalytics: checkoutAnalytics, checkoutSessionId: checkoutSessionId, clientSecret: clientSecret, onClose: onClose, supportedCountries: normalizedSupportedCountries })) }) }) }), _jsx("style", { children: sharedStripeCheckoutV2Styles })] }));
342
346
  }
343
347
  function SharedCheckoutSpecialOfferGift({ discountLabel, imageAlt, imageSrc, }) {
344
348
  if (imageSrc) {
@@ -1,5 +1,7 @@
1
+ import type { StripeCheckoutExpressCheckoutElementOptions } from '@stripe/stripe-js';
1
2
  import { type WalletSubscriptionCheckoutSlotProps } from './WalletSubscriptionCheckoutSlot.js';
2
- export type ApplePaySubscriptionCheckoutSlotProps = Omit<WalletSubscriptionCheckoutSlotProps, 'availabilityPaymentMethod' | 'expressCheckoutAllowed' | 'placeholderLabel' | 'renderPlaceholder' | 'renderPreparingPlaceholder'> & {
3
+ export type ApplePaySubscriptionCheckoutSlotProps = Omit<WalletSubscriptionCheckoutSlotProps, 'availabilityPaymentMethod' | 'expressCheckoutAllowed' | 'options' | 'placeholderLabel' | 'renderPlaceholder' | 'renderPreparingPlaceholder'> & {
4
+ options?: Partial<StripeCheckoutExpressCheckoutElementOptions>;
3
5
  placeholderLabel?: string;
4
6
  };
5
- export declare function ApplePaySubscriptionCheckoutSlot({ placeholderLabel, ...slotProps }: ApplePaySubscriptionCheckoutSlotProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function ApplePaySubscriptionCheckoutSlot({ options, placeholderLabel, ...slotProps }: ApplePaySubscriptionCheckoutSlotProps): import("react/jsx-runtime").JSX.Element;
@@ -13,7 +13,31 @@ var __rest = (this && this.__rest) || function (s, e) {
13
13
  import { jsx as _jsx } from "react/jsx-runtime";
14
14
  import { ApplePaySubscribeButton } from '../../components/shared/ApplePaySubscribeButton.js';
15
15
  import { WalletSubscriptionCheckoutLoadingPlaceholder, WalletSubscriptionCheckoutSlot, } from './WalletSubscriptionCheckoutSlot.js';
16
+ const applePayExpressCheckoutOptions = {
17
+ buttonHeight: 55,
18
+ buttonTheme: {
19
+ applePay: 'black',
20
+ },
21
+ buttonType: {
22
+ applePay: 'subscribe',
23
+ },
24
+ layout: {
25
+ maxColumns: 1,
26
+ maxRows: 1,
27
+ overflow: 'auto',
28
+ },
29
+ paymentMethodOrder: ['apple_pay'],
30
+ paymentMethods: {
31
+ amazonPay: 'never',
32
+ applePay: 'always',
33
+ googlePay: 'never',
34
+ klarna: 'never',
35
+ link: 'never',
36
+ paypal: 'never',
37
+ },
38
+ };
16
39
  export function ApplePaySubscriptionCheckoutSlot(_a) {
17
- var { placeholderLabel = 'Subscribe with Apple Pay' } = _a, slotProps = __rest(_a, ["placeholderLabel"]);
18
- return (_jsx(WalletSubscriptionCheckoutSlot, Object.assign({}, slotProps, { availabilityPaymentMethod: 'applePay', placeholderLabel: placeholderLabel, renderPreparingPlaceholder: (placeholderProps) => (_jsx(WalletSubscriptionCheckoutLoadingPlaceholder, Object.assign({}, placeholderProps, { label: 'Loading Apple Pay' }))), renderPlaceholder: (placeholderProps) => (_jsx(ApplePaySubscribeButton, Object.assign({}, placeholderProps))) })));
40
+ var _b, _c, _d, _e, _f, _g, _h, _j, _k;
41
+ var { options, placeholderLabel = 'Subscribe with Apple Pay' } = _a, slotProps = __rest(_a, ["options", "placeholderLabel"]);
42
+ return (_jsx(WalletSubscriptionCheckoutSlot, Object.assign({}, slotProps, { availabilityPaymentMethod: 'applePay', options: Object.assign(Object.assign(Object.assign({}, applePayExpressCheckoutOptions), (options !== null && options !== void 0 ? options : {})), { buttonTheme: Object.assign(Object.assign({}, ((_b = applePayExpressCheckoutOptions.buttonTheme) !== null && _b !== void 0 ? _b : {})), ((_c = options === null || options === void 0 ? void 0 : options.buttonTheme) !== null && _c !== void 0 ? _c : {})), buttonType: Object.assign(Object.assign({}, ((_d = applePayExpressCheckoutOptions.buttonType) !== null && _d !== void 0 ? _d : {})), ((_e = options === null || options === void 0 ? void 0 : options.buttonType) !== null && _e !== void 0 ? _e : {})), layout: Object.assign(Object.assign({}, ((_f = applePayExpressCheckoutOptions.layout) !== null && _f !== void 0 ? _f : {})), ((_g = options === null || options === void 0 ? void 0 : options.layout) !== null && _g !== void 0 ? _g : {})), paymentMethods: Object.assign(Object.assign({}, ((_h = applePayExpressCheckoutOptions.paymentMethods) !== null && _h !== void 0 ? _h : {})), ((_j = options === null || options === void 0 ? void 0 : options.paymentMethods) !== null && _j !== void 0 ? _j : {})), paymentMethodOrder: (_k = options === null || options === void 0 ? void 0 : options.paymentMethodOrder) !== null && _k !== void 0 ? _k : applePayExpressCheckoutOptions.paymentMethodOrder }), placeholderLabel: placeholderLabel, renderPreparingPlaceholder: (placeholderProps) => (_jsx(WalletSubscriptionCheckoutLoadingPlaceholder, Object.assign({}, placeholderProps, { label: 'Loading Apple Pay' }))), renderPlaceholder: (placeholderProps) => (_jsx(ApplePaySubscribeButton, Object.assign({}, placeholderProps))) })));
19
43
  }
@@ -1,5 +1,5 @@
1
1
  import { type ReactNode } from 'react';
2
- import type { AvailablePaymentMethods, StripeCheckoutExpressCheckoutElementOptions } from '@stripe/stripe-js';
2
+ import type { Appearance, AvailablePaymentMethods, StripeCheckoutExpressCheckoutElementOptions } from '@stripe/stripe-js';
3
3
  import { type StripeSubscriptionCheckoutAnalyticsContext } from '../../services/checkoutCompletionAnalytics.service.js';
4
4
  import { type StripeSubscriptionCheckoutSession } from './useStripeSubscriptionCheckoutSession.js';
5
5
  export type WalletSubscriptionCheckoutRenderStateInput = {
@@ -27,6 +27,7 @@ export type WalletSubscriptionCheckoutSlotProps = {
27
27
  availabilityPaymentMethod: keyof AvailablePaymentMethods;
28
28
  beforeConfirm?: () => Promise<string | null>;
29
29
  checkoutAnalytics?: StripeSubscriptionCheckoutAnalyticsContext | null;
30
+ appearance?: Appearance;
30
31
  className?: string;
31
32
  customerEmail?: string | null;
32
33
  customerName?: string | null;
@@ -44,9 +45,10 @@ export type WalletSubscriptionCheckoutSlotProps = {
44
45
  renderPlaceholder: (props: WalletPlaceholderButtonProps) => ReactNode;
45
46
  returnUrl: string;
46
47
  session: StripeSubscriptionCheckoutSession;
48
+ slotClassName?: string;
47
49
  summaryLabel: string;
48
50
  suspended?: boolean;
49
51
  };
50
52
  export declare function WalletSubscriptionCheckoutLoadingPlaceholder({ className, label, }: Pick<WalletPlaceholderButtonProps, 'className' | 'label'>): import("react/jsx-runtime").JSX.Element;
51
- export declare function WalletSubscriptionCheckoutSlot({ amountCents, availabilityPaymentMethod, beforeConfirm, checkoutAnalytics, className, customerEmail, customerName, disabled, expressCheckoutAllowed, onAvailabilityChange, onCancel, onError, onPaymentInfoSubmitted, onSuccess, onUnavailableClick, options, placeholderLabel, renderPreparingPlaceholder, renderPlaceholder, returnUrl, session, summaryLabel, suspended, }: WalletSubscriptionCheckoutSlotProps): import("react/jsx-runtime").JSX.Element;
53
+ export declare function WalletSubscriptionCheckoutSlot({ amountCents, appearance, availabilityPaymentMethod, beforeConfirm, checkoutAnalytics, className, customerEmail, customerName, disabled, expressCheckoutAllowed, onAvailabilityChange, onCancel, onError, onPaymentInfoSubmitted, onSuccess, onUnavailableClick, options, placeholderLabel, renderPreparingPlaceholder, renderPlaceholder, returnUrl, session, slotClassName, summaryLabel, suspended, }: WalletSubscriptionCheckoutSlotProps): import("react/jsx-runtime").JSX.Element;
52
54
  export {};
@@ -28,12 +28,13 @@ export function WalletSubscriptionCheckoutLoadingPlaceholder({ className, label,
28
28
  .filter(Boolean)
29
29
  .join(' '), "aria-label": label, role: 'status', children: [_jsx("span", { className: 'wallet-subscription-checkout-slot__loading-bar' }), _jsx("style", { children: walletSubscriptionCheckoutLoadingPlaceholderStyles })] }));
30
30
  }
31
- export function WalletSubscriptionCheckoutSlot({ amountCents, availabilityPaymentMethod, beforeConfirm, checkoutAnalytics, className, customerEmail, customerName, disabled = false, expressCheckoutAllowed = true, onAvailabilityChange, onCancel, onError, onPaymentInfoSubmitted, onSuccess, onUnavailableClick, options, placeholderLabel, renderPreparingPlaceholder, renderPlaceholder, returnUrl, session, summaryLabel, suspended = false, }) {
31
+ export function WalletSubscriptionCheckoutSlot({ amountCents, appearance, availabilityPaymentMethod, beforeConfirm, checkoutAnalytics, className, customerEmail, customerName, disabled = false, expressCheckoutAllowed = true, onAvailabilityChange, onCancel, onError, onPaymentInfoSubmitted, onSuccess, onUnavailableClick, options, placeholderLabel, renderPreparingPlaceholder, renderPlaceholder, returnUrl, session, slotClassName, summaryLabel, suspended = false, }) {
32
32
  const [walletAvailability, setWalletAvailability] = useState({
33
33
  available: null,
34
34
  intentKey: session.intentKey,
35
35
  });
36
36
  const [walletCheckoutStartedAfterClickIntentKey, setWalletCheckoutStartedAfterClickIntentKey] = useState(null);
37
+ const autoWalletPreparationIntentKeyRef = useRef(null);
37
38
  const walletCheckoutStartedAfterClickSessionIdRef = useRef(null);
38
39
  const slotDisabled = disabled || !session.configured;
39
40
  const walletAvailable = walletAvailability.intentKey === session.intentKey
@@ -67,6 +68,39 @@ export function WalletSubscriptionCheckoutSlot({ amountCents, availabilityPaymen
67
68
  intentKey,
68
69
  });
69
70
  }, []);
71
+ useEffect(() => {
72
+ if (slotDisabled ||
73
+ suspended ||
74
+ !expressCheckoutAllowed ||
75
+ walletAvailable === false ||
76
+ session.loading.wallet ||
77
+ session.activeClientSecret) {
78
+ return;
79
+ }
80
+ if (autoWalletPreparationIntentKeyRef.current === session.intentKey) {
81
+ return;
82
+ }
83
+ autoWalletPreparationIntentKeyRef.current = session.intentKey;
84
+ void session
85
+ .prepareWalletCheckout({
86
+ checkoutStartSource: 'wallet_render',
87
+ })
88
+ .then((readyClientSecret) => {
89
+ if (!readyClientSecret) {
90
+ reportWalletPreparationFailure(session.intentKey);
91
+ }
92
+ });
93
+ }, [
94
+ expressCheckoutAllowed,
95
+ reportWalletPreparationFailure,
96
+ session.activeClientSecret,
97
+ session.intentKey,
98
+ session.loading.wallet,
99
+ session.prepareWalletCheckout,
100
+ slotDisabled,
101
+ suspended,
102
+ walletAvailable,
103
+ ]);
70
104
  useEffect(() => {
71
105
  if (!walletCheckoutStartedAfterClickIntentKey ||
72
106
  walletCheckoutStartedAfterClickIntentKey !== session.intentKey ||
@@ -91,7 +125,9 @@ export function WalletSubscriptionCheckoutSlot({ amountCents, availabilityPaymen
91
125
  available: null,
92
126
  intentKey: session.intentKey,
93
127
  });
94
- void session.restartWalletCheckout();
128
+ void session.restartWalletCheckout({
129
+ checkoutStartSource: 'wallet_render',
130
+ });
95
131
  return;
96
132
  }
97
133
  session.setError(message);
@@ -109,7 +145,9 @@ export function WalletSubscriptionCheckoutSlot({ amountCents, availabilityPaymen
109
145
  return;
110
146
  }
111
147
  setWalletCheckoutStartedAfterClickIntentKey(session.intentKey);
112
- const readyClientSecret = await session.prepareWalletCheckout();
148
+ const readyClientSecret = await session.prepareWalletCheckout({
149
+ checkoutStartSource: 'wallet_render',
150
+ });
113
151
  if (!readyClientSecret) {
114
152
  setWalletCheckoutStartedAfterClickIntentKey(null);
115
153
  reportWalletPreparationFailure(session.intentKey);
@@ -120,13 +158,14 @@ export function WalletSubscriptionCheckoutSlot({ amountCents, availabilityPaymen
120
158
  : renderPlaceholder;
121
159
  return (_jsxs(_Fragment, { children: [shouldRenderPlaceholder || shouldRenderExpressCheckout ? (_jsxs("div", { className: [
122
160
  'wallet-subscription-checkout-slot',
161
+ slotClassName !== null && slotClassName !== void 0 ? slotClassName : '',
123
162
  placeholderIsPreparing ? 'is-loading' : '',
124
163
  shouldRenderExpressCheckout ? 'has-express-checkout' : '',
125
164
  ]
126
165
  .filter(Boolean)
127
166
  .join(' '), "aria-busy": placeholderIsPreparing || undefined, children: [shouldRenderExpressCheckout &&
128
167
  session.activeClientSecret &&
129
- session.stripePromise ? (_jsx(StripeExpressCheckoutElement, { amountCents: amountCents, availabilityPaymentMethods: [availabilityPaymentMethod], beforeConfirm: beforeConfirm, checkoutAnalytics: checkoutAnalytics, checkoutSessionId: session.checkoutSessionId, className: className, clientSecret: session.activeClientSecret, customerEmail: customerEmail, customerName: customerName, onAvailabilityChange: handleAvailabilityChange, onCancel: onCancel, onError: handleError, onPaymentInfoSubmitted: onPaymentInfoSubmitted, onSuccess: onSuccess, options: options, returnUrl: returnUrl, serverUpdateKey: serverUpdateKey, stripePromise: session.stripePromise, summaryLabel: summaryLabel, onServerUpdate: session.updateCheckoutSessionPlan })) : null, shouldRenderPlaceholder ? (_jsx("div", { className: 'wallet-subscription-checkout-slot__placeholder', children: placeholderRenderer({
168
+ session.stripePromise ? (_jsx(StripeExpressCheckoutElement, { amountCents: amountCents, appearance: appearance, availabilityPaymentMethods: [availabilityPaymentMethod], beforeConfirm: beforeConfirm, checkoutAnalytics: checkoutAnalytics, checkoutSessionId: session.checkoutSessionId, className: className, clientSecret: session.activeClientSecret, customerEmail: customerEmail, customerName: customerName, onAvailabilityChange: handleAvailabilityChange, onCancel: onCancel, onError: handleError, onPaymentInfoSubmitted: onPaymentInfoSubmitted, onSuccess: onSuccess, options: options, returnUrl: returnUrl, serverUpdateKey: serverUpdateKey, stripePromise: session.stripePromise, summaryLabel: summaryLabel, onServerUpdate: session.updateCheckoutSessionPlan })) : null, shouldRenderPlaceholder ? (_jsx("div", { className: 'wallet-subscription-checkout-slot__placeholder', children: placeholderRenderer({
130
169
  className,
131
170
  disabled: slotDisabled,
132
171
  label: placeholderLabel,
@@ -16,6 +16,10 @@ export type StripeSubscriptionCheckoutSessionInput = {
16
16
  runtimeConfig?: StripeRuntimeConfigOverrides;
17
17
  userId?: string | null;
18
18
  };
19
+ export type StripeSubscriptionCheckoutPreparationOptions = {
20
+ checkoutStartSource?: 'card_click' | 'wallet_click' | 'wallet_render' | (string & {});
21
+ forceNew?: boolean;
22
+ };
19
23
  export type StripeSubscriptionCheckoutSession = {
20
24
  activeClientSecret: string | null;
21
25
  activePlanKey: string | null;
@@ -24,12 +28,12 @@ export type StripeSubscriptionCheckoutSession = {
24
28
  error: string | null;
25
29
  intentKey: string;
26
30
  loading: CheckoutPreparationLoading;
27
- prepareCardCheckout: () => Promise<string | null>;
28
- prepareWalletCheckout: () => Promise<string | null>;
31
+ prepareCardCheckout: (options?: StripeSubscriptionCheckoutPreparationOptions) => Promise<string | null>;
32
+ prepareWalletCheckout: (options?: StripeSubscriptionCheckoutPreparationOptions) => Promise<string | null>;
29
33
  provider: 'stripe';
30
34
  reset: () => void;
31
- restartCardCheckout: () => Promise<string | null>;
32
- restartWalletCheckout: () => Promise<string | null>;
35
+ restartCardCheckout: (options?: StripeSubscriptionCheckoutPreparationOptions) => Promise<string | null>;
36
+ restartWalletCheckout: (options?: StripeSubscriptionCheckoutPreparationOptions) => Promise<string | null>;
33
37
  planKey: string;
34
38
  setError: (message: string | null) => void;
35
39
  stripePromise: Promise<Stripe | null> | null;
@@ -10,6 +10,14 @@ const getFriendlyStripeCheckoutError = (error) => {
10
10
  };
11
11
  const normalizeRuntimeConfigValue = (value) => (value === null || value === void 0 ? void 0 : value.trim()) || '';
12
12
  const useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;
13
+ const buildCheckoutPreparationAnalyticsMetadata = (analyticsMetadata, options = {}) => {
14
+ var _a;
15
+ const checkoutStartSource = (_a = options.checkoutStartSource) === null || _a === void 0 ? void 0 : _a.trim();
16
+ if (!checkoutStartSource) {
17
+ return analyticsMetadata;
18
+ }
19
+ return Object.assign(Object.assign({}, (analyticsMetadata !== null && analyticsMetadata !== void 0 ? analyticsMetadata : {})), { checkoutStartSource });
20
+ };
13
21
  export function buildStripeSubscriptionCheckoutIntentKey({ checkoutMode, couponId, customerEmail, returnUrl, runtimeConfig, userId, }) {
14
22
  const runtimeConfigSignature = [
15
23
  normalizeRuntimeConfigValue(runtimeConfig === null || runtimeConfig === void 0 ? void 0 : runtimeConfig.apiBaseUrl),
@@ -110,8 +118,9 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
110
118
  ? current
111
119
  : Object.assign(Object.assign({}, current), { [source]: value }));
112
120
  }, []);
113
- const createCheckoutSubscription = useCallback(async (forceNew = false) => {
121
+ const createCheckoutSubscription = useCallback(async (options = {}) => {
114
122
  var _a;
123
+ const forceNew = options.forceNew === true;
115
124
  if (!configured || !plan || !displayPlan) {
116
125
  return null;
117
126
  }
@@ -133,7 +142,7 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
133
142
  try {
134
143
  const response = await createStripeSubscriptionCheckout({
135
144
  plan,
136
- analyticsMetadata: analyticsMetadataRef.current,
145
+ analyticsMetadata: buildCheckoutPreparationAnalyticsMetadata(analyticsMetadataRef.current, options),
137
146
  couponId,
138
147
  customerEmail,
139
148
  returnUrl,
@@ -192,6 +201,7 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
192
201
  userId,
193
202
  ]);
194
203
  const prepareCheckout = useCallback(async (source, options = {}) => {
204
+ var _a;
195
205
  if (!configured || !plan || !displayPlan) {
196
206
  return null;
197
207
  }
@@ -201,7 +211,7 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
201
211
  setSourceLoading(source, true);
202
212
  setError(null);
203
213
  try {
204
- return await createCheckoutSubscription(options.forceNew === true);
214
+ return await createCheckoutSubscription(Object.assign(Object.assign({}, options), { checkoutStartSource: (_a = options.checkoutStartSource) !== null && _a !== void 0 ? _a : (source === 'wallet' ? 'wallet_render' : 'card_click') }));
205
215
  }
206
216
  finally {
207
217
  setSourceLoading(source, false);
@@ -215,10 +225,10 @@ export function useStripeSubscriptionCheckoutSession({ checkoutMode, analyticsMe
215
225
  setError,
216
226
  setSourceLoading,
217
227
  ]);
218
- const prepareCardCheckout = useCallback(() => prepareCheckout('card'), [prepareCheckout]);
219
- const prepareWalletCheckout = useCallback(() => prepareCheckout('wallet'), [prepareCheckout]);
220
- const restartCardCheckout = useCallback(() => prepareCheckout('card', { forceNew: true }), [prepareCheckout]);
221
- const restartWalletCheckout = useCallback(() => prepareCheckout('wallet', { forceNew: true }), [prepareCheckout]);
228
+ const prepareCardCheckout = useCallback((options = {}) => prepareCheckout('card', options), [prepareCheckout]);
229
+ const prepareWalletCheckout = useCallback((options = {}) => prepareCheckout('wallet', options), [prepareCheckout]);
230
+ const restartCardCheckout = useCallback((options = {}) => prepareCheckout('card', Object.assign(Object.assign({}, options), { forceNew: true })), [prepareCheckout]);
231
+ const restartWalletCheckout = useCallback((options = {}) => prepareCheckout('wallet', Object.assign(Object.assign({}, options), { forceNew: true })), [prepareCheckout]);
222
232
  const updateCheckoutSessionPlan = useCallback(async () => {
223
233
  if (!configured || !plan || !displayPlan || !activeCheckoutSessionId) {
224
234
  return false;
@@ -60,6 +60,26 @@ type CreatePaymentIntentResponse = {
60
60
  stripePublishableKey?: string;
61
61
  environment?: 'test' | 'live';
62
62
  };
63
+ type CreateOneTimeCheckoutInput = {
64
+ plan: CheckoutSessionPlanInput;
65
+ couponId?: string | null;
66
+ analyticsMetadata?: Record<string, unknown> | null;
67
+ customerEmail?: string | null;
68
+ returnUrl: string;
69
+ user_id?: string | null;
70
+ environment?: RuntimeMode;
71
+ runtimeConfig?: StripeRuntimeConfigOverrides;
72
+ };
73
+ type CreateOneTimeCheckoutResponse = {
74
+ clientSecret: string;
75
+ checkoutSessionId?: string;
76
+ stripePublishableKey?: string;
77
+ environment?: 'test' | 'live';
78
+ };
79
+ export declare function createStripeOneTimeCheckout(input: CreateOneTimeCheckoutInput): Promise<CreateOneTimeCheckoutResponse>;
80
+ /**
81
+ * @deprecated Use createStripeOneTimeCheckout for one-time paywall checkout.
82
+ */
63
83
  export declare function createStripePaymentIntent(input: CreatePaymentIntentInput): Promise<CreatePaymentIntentResponse>;
64
84
  type ChargeOneClickPaymentInput = {
65
85
  planId: string;
@@ -531,6 +531,26 @@ const normalizeCheckoutAnalyticsMetadata = (metadata, plan) => {
531
531
  }
532
532
  return Object.assign(Object.assign({}, (normalized !== null && normalized !== void 0 ? normalized : {})), { analyticsPurchaseValue });
533
533
  };
534
+ export async function createStripeOneTimeCheckout(input) {
535
+ var _a, _b, _c, _d, _e;
536
+ return funnelSdkService.createOneTimeCheckout({
537
+ planId: getPaywallPlanSelectionValue(input.plan),
538
+ providerPlanId: ((_a = input.plan.providerPlanId) === null || _a === void 0 ? void 0 : _a.trim()) || undefined,
539
+ title: input.plan.title,
540
+ description: ((_b = input.plan.description) === null || _b === void 0 ? void 0 : _b.trim()) || undefined,
541
+ amountCents: input.plan.amountCents,
542
+ couponId: ((_c = input.couponId) === null || _c === void 0 ? void 0 : _c.trim()) || undefined,
543
+ analyticsMetadata: normalizeCheckoutAnalyticsMetadata(input.analyticsMetadata, input.plan),
544
+ customerEmail: ((_d = input.customerEmail) === null || _d === void 0 ? void 0 : _d.trim()) || undefined,
545
+ returnUrl: input.returnUrl,
546
+ userId: ((_e = input.user_id) === null || _e === void 0 ? void 0 : _e.trim()) || undefined,
547
+ environment: input.environment,
548
+ runtimeConfig: input.runtimeConfig,
549
+ });
550
+ }
551
+ /**
552
+ * @deprecated Use createStripeOneTimeCheckout for one-time paywall checkout.
553
+ */
534
554
  export async function createStripePaymentIntent(input) {
535
555
  var _a, _b;
536
556
  return funnelSdkService.createOneTimePaymentIntent({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnelsgrove/payments",
3
- "version": "0.1.49",
3
+ "version": "0.1.51",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "main": "./dist/index.js",
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@funnelsgrove/analytics": "^0.1.28",
31
- "@funnelsgrove/runtime": "^0.1.46",
31
+ "@funnelsgrove/runtime": "^0.1.54",
32
32
  "@stripe/react-stripe-js": "^5.6.0",
33
33
  "@stripe/stripe-js": "^8.7.0",
34
34
  "react": "19.2.3",