@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 +9 -2
- package/dist/components/shared/SharedStripeCheckoutV2Dialog.js +10 -6
- package/dist/providers/stripe/ApplePaySubscriptionCheckoutSlot.d.ts +4 -2
- package/dist/providers/stripe/ApplePaySubscriptionCheckoutSlot.js +26 -2
- package/dist/providers/stripe/WalletSubscriptionCheckoutSlot.d.ts +4 -2
- package/dist/providers/stripe/WalletSubscriptionCheckoutSlot.js +43 -4
- package/dist/providers/stripe/useStripeSubscriptionCheckoutSession.d.ts +8 -4
- package/dist/providers/stripe/useStripeSubscriptionCheckoutSession.js +17 -7
- package/dist/services/stripe.service.d.ts +20 -0
- package/dist/services/stripe.service.js +20 -0
- package/package.json +2 -2
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
310
|
-
|
|
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(
|
|
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
|
|
18
|
-
|
|
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 (
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|