@funnelsgrove/payments 0.1.35 → 0.1.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,146 @@
1
+ import { type FormEvent } from 'react';
2
+ import type { RuntimeMode } from '@funnelsgrove/runtime';
3
+ import type { StripeRuntimeConfigOverrides, PaywallPlan } from '../../services/stripe.service.js';
4
+ import type { PaywallDisplayPlan } from '../../services/paywallOffer.service.js';
5
+ import { type PlatformWalletPaymentMethod } from './walletPlatform.js';
6
+ import { type StripeSubscriptionCheckoutSession } from '../../providers/stripe/useStripeSubscriptionCheckoutSession.js';
7
+ export type PaywallSubscriptionCheckoutContent = {
8
+ title: string;
9
+ closeAriaLabel: string;
10
+ walletButtonLabel: string;
11
+ customerEmailLabel: string;
12
+ customerEmailPlaceholder: string;
13
+ customerEmailInvalidMessage: string;
14
+ walletEmailPromptTitle: string;
15
+ walletEmailPromptDescription: string;
16
+ walletEmailPromptSubmitLabel: string;
17
+ walletEmailPromptCancelLabel: string;
18
+ countdownLabel: string;
19
+ satisfactionPercent: string;
20
+ satisfactionLabel: string;
21
+ originalPriceLabel: string;
22
+ discountRowLabelTemplate: string;
23
+ promoCodeLabel: string;
24
+ savedLabelTemplate: string;
25
+ totalLabel: string;
26
+ cardSubmitLabel: string;
27
+ paypalLabel: string;
28
+ paypalButtonLabel: string;
29
+ processingLabel: string;
30
+ secureLabel: string;
31
+ paymentMethodLabels: readonly string[];
32
+ specialOffer: {
33
+ image?: {
34
+ src?: string;
35
+ alt?: string;
36
+ };
37
+ discountLabel: string;
38
+ title: string;
39
+ description: string;
40
+ buttonLabel: string;
41
+ };
42
+ };
43
+ export type PaywallSubscriptionPaymentStackContent = {
44
+ cardButtonLabel: string;
45
+ googlePayPlaceholderLabel: string;
46
+ metaLabels: readonly string[];
47
+ stripeUnavailableNote: string;
48
+ supportText: string;
49
+ walletPlaceholderLabel: string;
50
+ };
51
+ type PaywallCheckoutCloseDiscountState = {
52
+ stage: string;
53
+ status: string;
54
+ };
55
+ export type UsePaywallSubscriptionCheckoutControllerInput = {
56
+ analyticsMetadata?: Record<string, unknown> | null;
57
+ appliedDiscountAmountCents: number;
58
+ checkoutDiscountPercent: number;
59
+ checkoutMode: RuntimeMode;
60
+ checkoutPromoActive: boolean;
61
+ content: PaywallSubscriptionCheckoutContent;
62
+ customerEmailEditable: boolean;
63
+ customerName?: string | null;
64
+ discountState?: PaywallCheckoutCloseDiscountState | null;
65
+ initialCustomerEmail?: string | null;
66
+ onCheckoutCompleted?: () => Promise<void> | void;
67
+ onCheckoutStarted?: () => Promise<void> | void;
68
+ onCustomerEmailCommit: (email: string) => Promise<string>;
69
+ onFirstCheckoutClosed?: () => void;
70
+ plan: PaywallPlan | null;
71
+ returnUrl: string;
72
+ runtimeConfig?: StripeRuntimeConfigOverrides;
73
+ selectedDisplayPlan: PaywallDisplayPlan | null;
74
+ supportEmail: string;
75
+ userId?: string | null;
76
+ getReturnUrl?: (checkoutSessionId?: string | null) => string;
77
+ };
78
+ export type PaywallSubscriptionCheckoutController = {
79
+ checkoutDialogOpen: boolean;
80
+ checkoutDialogReady: boolean;
81
+ checkoutDisabled: boolean;
82
+ checkoutDiscountAmountLabel: string;
83
+ checkoutDiscountLabel: string;
84
+ checkoutInitialWalletAvailable: boolean | null;
85
+ checkoutSavedLabel: string;
86
+ checkoutSession: StripeSubscriptionCheckoutSession;
87
+ checkoutSuccessReturnUrl: string;
88
+ checkoutSummaryLabel: string;
89
+ checkoutTotalValue: string;
90
+ closeCheckoutDialog: () => void;
91
+ commitCheckoutEmail: (draftEmail: string) => Promise<string>;
92
+ customerEmail: string;
93
+ customerEmailEditable: boolean;
94
+ customerName: string;
95
+ error: string | null;
96
+ handleApplePayAvailabilityChange: (available: boolean) => void;
97
+ handleGooglePayAvailabilityChange: (available: boolean) => void;
98
+ openCardCheckoutFromUnavailableWallet: () => Promise<void>;
99
+ preparePaywallWalletCheckout: () => Promise<string | null>;
100
+ setCheckoutEmail: (value: string) => void;
101
+ startCheckout: () => Promise<void>;
102
+ supportEmail: string;
103
+ trackCheckoutCompleted: () => Promise<void>;
104
+ walletEmailPromptCancel: () => void;
105
+ walletEmailPromptDraft: string;
106
+ walletEmailPromptError: string | null;
107
+ walletEmailPromptOpen: boolean;
108
+ walletEmailPromptSubmit: (event: FormEvent<HTMLFormElement>) => Promise<void>;
109
+ walletPaymentMethods: readonly PlatformWalletPaymentMethod[];
110
+ setWalletEmailPromptDraft: (value: string) => void;
111
+ };
112
+ export declare function usePaywallSubscriptionCheckoutController({ analyticsMetadata, appliedDiscountAmountCents, checkoutDiscountPercent, checkoutMode, checkoutPromoActive, content, customerEmailEditable, customerName, discountState, getReturnUrl, initialCustomerEmail, onCheckoutCompleted, onCheckoutStarted, onCustomerEmailCommit, onFirstCheckoutClosed, plan, returnUrl, runtimeConfig, selectedDisplayPlan, supportEmail, userId, }: UsePaywallSubscriptionCheckoutControllerInput): PaywallSubscriptionCheckoutController;
113
+ export type PaywallSubscriptionCheckoutDialogsProps = {
114
+ buttonColor?: string;
115
+ content: PaywallSubscriptionCheckoutContent;
116
+ controller: PaywallSubscriptionCheckoutController;
117
+ discountPercent: number;
118
+ promoActive: boolean;
119
+ promoDisplayName: string;
120
+ remainingSeconds: number;
121
+ selectedDisplayPlan: PaywallDisplayPlan | null;
122
+ specialOfferDialogOpen: boolean;
123
+ themeColor?: string;
124
+ onSpecialOfferAccept: () => void;
125
+ };
126
+ export declare function PaywallSubscriptionCheckoutDialogs({ buttonColor, content, controller, discountPercent, promoActive, promoDisplayName, remainingSeconds, selectedDisplayPlan, specialOfferDialogOpen, themeColor, onSpecialOfferAccept, }: PaywallSubscriptionCheckoutDialogsProps): import("react/jsx-runtime").JSX.Element;
127
+ export type PaywallSubscriptionPaymentStackProps = {
128
+ applePayButtonClassName?: string;
129
+ applePayShellClassName?: string;
130
+ cardButtonClassName?: string;
131
+ className?: string;
132
+ content: PaywallSubscriptionPaymentStackContent;
133
+ controller: PaywallSubscriptionCheckoutController;
134
+ disclaimerClassName?: string;
135
+ errorClassName?: string;
136
+ googlePayButtonClassName?: string;
137
+ googlePayShellClassName?: string;
138
+ metaClassName?: string;
139
+ noteClassName?: string;
140
+ paymentDisclaimer: string;
141
+ selectedDisplayPlan: PaywallDisplayPlan | null;
142
+ supportClassName?: string;
143
+ walletSuspended: boolean;
144
+ };
145
+ export declare function PaywallSubscriptionPaymentStack({ applePayButtonClassName, applePayShellClassName, cardButtonClassName, className, content, controller, disclaimerClassName, errorClassName, googlePayButtonClassName, googlePayShellClassName, metaClassName, noteClassName, paymentDisclaimer, selectedDisplayPlan, supportClassName, walletSuspended, }: PaywallSubscriptionPaymentStackProps): import("react/jsx-runtime").JSX.Element;
146
+ export {};
@@ -0,0 +1,340 @@
1
+ 'use client';
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
+ import { ApplePaySubscriptionCheckoutSlot, } from '../../providers/stripe/ApplePaySubscriptionCheckoutSlot.js';
5
+ import { GooglePaySubscriptionCheckoutSlot, } from '../../providers/stripe/GooglePaySubscriptionCheckoutSlot.js';
6
+ import { SharedCheckoutSpecialOfferDialog, SharedStripeCheckoutV2Dialog, } from './SharedStripeCheckoutV2Dialog.js';
7
+ import { usePlatformWalletPaymentMethods } from './walletPlatform.js';
8
+ import { useStripeSubscriptionCheckoutSession, } from '../../providers/stripe/useStripeSubscriptionCheckoutSession.js';
9
+ const checkoutCurrencyFormatter = new Intl.NumberFormat('en-US', {
10
+ currency: 'USD',
11
+ maximumFractionDigits: 2,
12
+ minimumFractionDigits: 2,
13
+ style: 'currency',
14
+ });
15
+ function formatCheckoutAmount(amountCents) {
16
+ return checkoutCurrencyFormatter.format(amountCents / 100);
17
+ }
18
+ function getErrorMessage(error, fallback) {
19
+ return error instanceof Error && error.message ? error.message : fallback;
20
+ }
21
+ export function usePaywallSubscriptionCheckoutController({ analyticsMetadata, appliedDiscountAmountCents, checkoutDiscountPercent, checkoutMode, checkoutPromoActive, content, customerEmailEditable, customerName, discountState, getReturnUrl, initialCustomerEmail, onCheckoutCompleted, onCheckoutStarted, onCustomerEmailCommit, onFirstCheckoutClosed, plan, returnUrl, runtimeConfig, selectedDisplayPlan, supportEmail, userId, }) {
22
+ var _a, _b, _c, _d;
23
+ const walletPaymentMethods = usePlatformWalletPaymentMethods();
24
+ const [checkoutDialogOpen, setCheckoutDialogOpen] = useState(false);
25
+ const [checkoutEmail, setCheckoutEmail] = useState((_a = initialCustomerEmail === null || initialCustomerEmail === void 0 ? void 0 : initialCustomerEmail.trim()) !== null && _a !== void 0 ? _a : '');
26
+ const [error, setError] = useState(null);
27
+ const [paywallWalletAvailability, setPaywallWalletAvailability] = useState({
28
+ intentKey: '',
29
+ methods: {},
30
+ });
31
+ const [walletEmailPromptOpen, setWalletEmailPromptOpen] = useState(false);
32
+ const [walletEmailPromptDraft, setWalletEmailPromptDraft] = useState('');
33
+ const [walletEmailPromptError, setWalletEmailPromptError] = useState(null);
34
+ const walletEmailResolverRef = useRef(null);
35
+ const checkoutSummaryLabel = ((_b = selectedDisplayPlan === null || selectedDisplayPlan === void 0 ? void 0 : selectedDisplayPlan.checkoutSummaryLabel) === null || _b === void 0 ? void 0 : _b.trim()) || (selectedDisplayPlan === null || selectedDisplayPlan === void 0 ? void 0 : selectedDisplayPlan.title) || 'Selected plan';
36
+ const checkoutDiscountLabel = checkoutPromoActive
37
+ ? content.discountRowLabelTemplate.replace('{percent}', String(checkoutDiscountPercent))
38
+ : '';
39
+ const checkoutDiscountAmountLabel = checkoutPromoActive
40
+ ? `-${formatCheckoutAmount(appliedDiscountAmountCents)}`
41
+ : '';
42
+ const checkoutSavedLabel = checkoutPromoActive
43
+ ? content.savedLabelTemplate
44
+ .replace('{amount}', formatCheckoutAmount(appliedDiscountAmountCents))
45
+ .replace('{percent}', String(checkoutDiscountPercent))
46
+ : '';
47
+ const checkoutTotalValue = useMemo(() => {
48
+ var _a;
49
+ if (!selectedDisplayPlan) {
50
+ return '';
51
+ }
52
+ return ((_a = selectedDisplayPlan.priceLabel.split('/')[0]) === null || _a === void 0 ? void 0 : _a.trim()) || selectedDisplayPlan.priceLabel;
53
+ }, [selectedDisplayPlan]);
54
+ const successReturnUrl = useMemo(() => (getReturnUrl === null || getReturnUrl === void 0 ? void 0 : getReturnUrl()) || returnUrl, [getReturnUrl, returnUrl]);
55
+ const checkoutSession = useStripeSubscriptionCheckoutSession({
56
+ analyticsMetadata,
57
+ checkoutMode,
58
+ couponId: checkoutPromoActive ? (_c = selectedDisplayPlan === null || selectedDisplayPlan === void 0 ? void 0 : selectedDisplayPlan.couponId) !== null && _c !== void 0 ? _c : null : null,
59
+ customerEmail: checkoutEmail || initialCustomerEmail,
60
+ displayPlan: selectedDisplayPlan,
61
+ onError: setError,
62
+ plan,
63
+ returnUrl: successReturnUrl,
64
+ runtimeConfig,
65
+ userId,
66
+ });
67
+ const checkoutDisabled = !checkoutSession.configured || !plan || !selectedDisplayPlan || !userId;
68
+ const checkoutDialogReady = checkoutDialogOpen &&
69
+ Boolean(checkoutSession.activeClientSecret) &&
70
+ Boolean(checkoutSession.stripePromise) &&
71
+ Boolean(selectedDisplayPlan);
72
+ const checkoutSuccessReturnUrl = useMemo(() => (getReturnUrl === null || getReturnUrl === void 0 ? void 0 : getReturnUrl(checkoutSession.checkoutSessionId)) || returnUrl, [checkoutSession.checkoutSessionId, getReturnUrl, returnUrl]);
73
+ const currentPaywallWalletAvailability = paywallWalletAvailability.intentKey === checkoutSession.intentKey
74
+ ? paywallWalletAvailability.methods
75
+ : {};
76
+ const checkoutInitialWalletAvailable = walletPaymentMethods.some((method) => currentPaywallWalletAvailability[method] === true)
77
+ ? true
78
+ : null;
79
+ useEffect(() => {
80
+ const timeoutId = window.setTimeout(() => {
81
+ var _a;
82
+ setCheckoutEmail((_a = initialCustomerEmail === null || initialCustomerEmail === void 0 ? void 0 : initialCustomerEmail.trim()) !== null && _a !== void 0 ? _a : '');
83
+ }, 0);
84
+ return () => window.clearTimeout(timeoutId);
85
+ }, [initialCustomerEmail]);
86
+ useEffect(() => {
87
+ return () => {
88
+ var _a;
89
+ (_a = walletEmailResolverRef.current) === null || _a === void 0 ? void 0 : _a.call(walletEmailResolverRef, null);
90
+ walletEmailResolverRef.current = null;
91
+ };
92
+ }, []);
93
+ const updatePaywallWalletAvailability = (method, available) => {
94
+ setPaywallWalletAvailability((current) => {
95
+ const currentMethods = current.intentKey === checkoutSession.intentKey ? current.methods : {};
96
+ return {
97
+ intentKey: checkoutSession.intentKey,
98
+ methods: Object.assign(Object.assign({}, currentMethods), { [method]: available }),
99
+ };
100
+ });
101
+ };
102
+ const handleApplePayAvailabilityChange = (available) => updatePaywallWalletAvailability('applePay', available);
103
+ const handleGooglePayAvailabilityChange = (available) => updatePaywallWalletAvailability('googlePay', available);
104
+ const trackCheckoutStarted = async () => {
105
+ await (onCheckoutStarted === null || onCheckoutStarted === void 0 ? void 0 : onCheckoutStarted());
106
+ };
107
+ const startCheckout = async () => {
108
+ if (checkoutDisabled || checkoutSession.loading.card) {
109
+ return;
110
+ }
111
+ await trackCheckoutStarted();
112
+ if (checkoutSession.activeClientSecret) {
113
+ setCheckoutDialogOpen(true);
114
+ return;
115
+ }
116
+ const readyClientSecret = await checkoutSession.prepareCardCheckout();
117
+ if (readyClientSecret) {
118
+ setCheckoutDialogOpen(true);
119
+ }
120
+ };
121
+ const trackCheckoutCompleted = async () => {
122
+ await (onCheckoutCompleted === null || onCheckoutCompleted === void 0 ? void 0 : onCheckoutCompleted());
123
+ };
124
+ const commitCheckoutEmail = async (draftEmail) => {
125
+ var _a;
126
+ const normalizedEmail = draftEmail.trim();
127
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalizedEmail)) {
128
+ throw new Error(content.customerEmailInvalidMessage);
129
+ }
130
+ if (normalizedEmail === ((_a = initialCustomerEmail === null || initialCustomerEmail === void 0 ? void 0 : initialCustomerEmail.trim()) !== null && _a !== void 0 ? _a : '')) {
131
+ setCheckoutEmail(normalizedEmail);
132
+ return normalizedEmail;
133
+ }
134
+ const syncedEmail = await onCustomerEmailCommit(normalizedEmail);
135
+ const committedEmail = syncedEmail.trim() || normalizedEmail;
136
+ setCheckoutEmail(committedEmail);
137
+ return committedEmail;
138
+ };
139
+ const resolveWalletEmailPrompt = useCallback((email) => {
140
+ var _a;
141
+ (_a = walletEmailResolverRef.current) === null || _a === void 0 ? void 0 : _a.call(walletEmailResolverRef, email);
142
+ walletEmailResolverRef.current = null;
143
+ }, []);
144
+ const requestWalletEmail = useCallback(async () => {
145
+ setWalletEmailPromptDraft(checkoutEmail.trim() || (initialCustomerEmail === null || initialCustomerEmail === void 0 ? void 0 : initialCustomerEmail.trim()) || '');
146
+ setWalletEmailPromptError(null);
147
+ setWalletEmailPromptOpen(true);
148
+ return await new Promise((resolve) => {
149
+ walletEmailResolverRef.current = resolve;
150
+ });
151
+ }, [checkoutEmail, initialCustomerEmail]);
152
+ const walletEmailPromptSubmit = async (event) => {
153
+ event.preventDefault();
154
+ try {
155
+ const committedEmail = await commitCheckoutEmail(walletEmailPromptDraft);
156
+ setWalletEmailPromptOpen(false);
157
+ setWalletEmailPromptError(null);
158
+ resolveWalletEmailPrompt(committedEmail);
159
+ }
160
+ catch (submitError) {
161
+ setWalletEmailPromptError(getErrorMessage(submitError, content.customerEmailInvalidMessage));
162
+ }
163
+ };
164
+ const walletEmailPromptCancel = () => {
165
+ setWalletEmailPromptOpen(false);
166
+ setWalletEmailPromptError(null);
167
+ resolveWalletEmailPrompt(null);
168
+ };
169
+ const preparePaywallWalletCheckout = async () => {
170
+ await trackCheckoutStarted();
171
+ const normalizedEmail = checkoutEmail.trim() || (initialCustomerEmail === null || initialCustomerEmail === void 0 ? void 0 : initialCustomerEmail.trim()) || '';
172
+ if (!normalizedEmail) {
173
+ return await requestWalletEmail();
174
+ }
175
+ return normalizedEmail;
176
+ };
177
+ const openCardCheckoutFromUnavailableWallet = async () => {
178
+ var _a;
179
+ if (checkoutDisabled || checkoutSession.loading.card) {
180
+ return;
181
+ }
182
+ await trackCheckoutStarted();
183
+ const readyClientSecret = (_a = checkoutSession.activeClientSecret) !== null && _a !== void 0 ? _a : await checkoutSession.prepareCardCheckout();
184
+ if (readyClientSecret) {
185
+ setCheckoutDialogOpen(true);
186
+ }
187
+ };
188
+ const closeCheckoutDialog = () => {
189
+ setCheckoutDialogOpen(false);
190
+ setError(null);
191
+ if ((discountState === null || discountState === void 0 ? void 0 : discountState.stage) === 'first' && discountState.status === 'active') {
192
+ onFirstCheckoutClosed === null || onFirstCheckoutClosed === void 0 ? void 0 : onFirstCheckoutClosed();
193
+ }
194
+ };
195
+ return {
196
+ checkoutDialogOpen,
197
+ checkoutDialogReady,
198
+ checkoutDisabled,
199
+ checkoutDiscountAmountLabel,
200
+ checkoutDiscountLabel,
201
+ checkoutInitialWalletAvailable,
202
+ checkoutSavedLabel,
203
+ checkoutSession,
204
+ checkoutSuccessReturnUrl,
205
+ checkoutSummaryLabel,
206
+ checkoutTotalValue,
207
+ closeCheckoutDialog,
208
+ commitCheckoutEmail,
209
+ customerEmail: checkoutEmail,
210
+ customerEmailEditable,
211
+ customerName: (_d = customerName === null || customerName === void 0 ? void 0 : customerName.trim()) !== null && _d !== void 0 ? _d : '',
212
+ error,
213
+ handleApplePayAvailabilityChange,
214
+ handleGooglePayAvailabilityChange,
215
+ openCardCheckoutFromUnavailableWallet,
216
+ preparePaywallWalletCheckout,
217
+ setCheckoutEmail,
218
+ startCheckout,
219
+ supportEmail,
220
+ trackCheckoutCompleted,
221
+ walletEmailPromptCancel,
222
+ walletEmailPromptDraft,
223
+ walletEmailPromptError,
224
+ walletEmailPromptOpen,
225
+ walletEmailPromptSubmit,
226
+ walletPaymentMethods,
227
+ setWalletEmailPromptDraft,
228
+ };
229
+ }
230
+ export function PaywallSubscriptionCheckoutDialogs({ buttonColor = 'var(--color-secondary-text)', content, controller, discountPercent, promoActive, promoDisplayName, remainingSeconds, selectedDisplayPlan, specialOfferDialogOpen, themeColor = 'var(--color-secondary-text)', onSpecialOfferAccept, }) {
231
+ var _a, _b;
232
+ return (_jsxs(_Fragment, { children: [controller.checkoutDialogOpen &&
233
+ controller.checkoutSession.activeClientSecret &&
234
+ controller.checkoutSession.stripePromise &&
235
+ selectedDisplayPlan ? (_jsx(SharedStripeCheckoutV2Dialog, { amountCents: selectedDisplayPlan.amountCents, buttonColor: buttonColor, cardSubmitLabel: content.cardSubmitLabel, clientSecret: controller.checkoutSession.activeClientSecret, closeAriaLabel: content.closeAriaLabel, countdownLabel: content.countdownLabel, customerEmail: controller.customerEmail, customerEmailEditable: controller.customerEmailEditable, customerEmailLabel: content.customerEmailLabel, customerEmailPlaceholder: content.customerEmailPlaceholder, customerEmailInvalidMessage: content.customerEmailInvalidMessage, customerName: controller.customerName, discountAmountLabel: controller.checkoutDiscountAmountLabel, discountLabel: controller.checkoutDiscountLabel, discountPercent: discountPercent, initialWalletAvailable: controller.checkoutInitialWalletAvailable, onClose: controller.closeCheckoutDialog, onCustomerEmailChange: controller.setCheckoutEmail, onCustomerEmailCommit: controller.commitCheckoutEmail, onError: controller.checkoutSession.setError, onSuccess: controller.trackCheckoutCompleted, paymentMethodLabels: content.paymentMethodLabels, paypalButtonLabel: content.paypalButtonLabel, paypalLabel: content.paypalLabel, promoActive: promoActive, promoCode: promoDisplayName, promoCodeLabel: content.promoCodeLabel, processingLabel: content.processingLabel, remainingSeconds: remainingSeconds, returnUrl: controller.checkoutSuccessReturnUrl, savedLabel: controller.checkoutSavedLabel, secureLabel: content.secureLabel, satisfactionLabel: content.satisfactionLabel, satisfactionPercent: content.satisfactionPercent, stripePromise: controller.checkoutSession.stripePromise, summaryLabel: controller.checkoutSummaryLabel, themeColor: themeColor, title: content.title, totalLabel: content.totalLabel, totalValue: controller.checkoutTotalValue, walletButtonLabel: content.walletButtonLabel, originalPriceLabel: content.originalPriceLabel, originalPriceValue: selectedDisplayPlan.basePriceLabel })) : null, specialOfferDialogOpen ? (_jsx(SharedCheckoutSpecialOfferDialog, { buttonColor: buttonColor, buttonLabel: content.specialOffer.buttonLabel, description: content.specialOffer.description, discountLabel: content.specialOffer.discountLabel, imageAlt: (_a = content.specialOffer.image) === null || _a === void 0 ? void 0 : _a.alt, imageSrc: (_b = content.specialOffer.image) === null || _b === void 0 ? void 0 : _b.src, onAccept: onSpecialOfferAccept, themeColor: themeColor, title: content.specialOffer.title })) : null, controller.walletEmailPromptOpen ? (_jsx(PaywallWalletEmailPromptDialog, { content: content, draftEmail: controller.walletEmailPromptDraft, error: controller.walletEmailPromptError, onCancel: controller.walletEmailPromptCancel, onChange: controller.setWalletEmailPromptDraft, onSubmit: controller.walletEmailPromptSubmit })) : null] }));
236
+ }
237
+ export function PaywallSubscriptionPaymentStack({ applePayButtonClassName, applePayShellClassName, cardButtonClassName, className, content, controller, disclaimerClassName, errorClassName, googlePayButtonClassName, googlePayShellClassName, metaClassName, noteClassName, paymentDisclaimer, selectedDisplayPlan, supportClassName, walletSuspended, }) {
238
+ const suspended = controller.checkoutDialogReady || walletSuspended;
239
+ return (_jsxs("section", { className: className, children: [controller.walletPaymentMethods.includes('applePay') ? (_jsx("div", { className: applePayShellClassName, children: selectedDisplayPlan ? (_jsx(ApplePaySubscriptionCheckoutSlot, { amountCents: selectedDisplayPlan.amountCents, beforeConfirm: controller.preparePaywallWalletCheckout, className: applePayButtonClassName, customerEmail: controller.customerEmail, customerName: controller.customerName, disabled: controller.checkoutDisabled, onAvailabilityChange: controller.handleApplePayAvailabilityChange, onSuccess: controller.trackCheckoutCompleted, onUnavailableClick: controller.openCardCheckoutFromUnavailableWallet, placeholderLabel: content.walletPlaceholderLabel, returnUrl: controller.checkoutSuccessReturnUrl, session: controller.checkoutSession, summaryLabel: controller.checkoutSummaryLabel, suspended: suspended })) : null })) : null, controller.walletPaymentMethods.includes('googlePay') ? (_jsx("div", { className: googlePayShellClassName, children: selectedDisplayPlan ? (_jsx(GooglePaySubscriptionCheckoutSlot, { amountCents: selectedDisplayPlan.amountCents, beforeConfirm: controller.preparePaywallWalletCheckout, className: googlePayButtonClassName, customerEmail: controller.customerEmail, customerName: controller.customerName, disabled: controller.checkoutDisabled, onAvailabilityChange: controller.handleGooglePayAvailabilityChange, onSuccess: controller.trackCheckoutCompleted, onUnavailableClick: controller.openCardCheckoutFromUnavailableWallet, placeholderLabel: content.googlePayPlaceholderLabel, returnUrl: controller.checkoutSuccessReturnUrl, session: controller.checkoutSession, summaryLabel: controller.checkoutSummaryLabel, suspended: suspended })) : null })) : null, _jsx("button", { type: 'button', className: cardButtonClassName, onClick: () => void controller.startCheckout(), disabled: controller.checkoutDisabled || controller.checkoutSession.loading.card, children: content.cardButtonLabel }), _jsx("p", { className: metaClassName, children: content.metaLabels.join(' · ') }), _jsx("p", { className: disclaimerClassName, children: paymentDisclaimer }), _jsxs("p", { className: supportClassName, children: [content.supportText, ' ', _jsx("a", { href: `mailto:${controller.supportEmail}`, children: controller.supportEmail })] }), !controller.checkoutSession.configured ? (_jsx("p", { className: noteClassName, children: content.stripeUnavailableNote })) : null, controller.error ? _jsx("p", { className: errorClassName, children: controller.error }) : null] }));
240
+ }
241
+ function PaywallWalletEmailPromptDialog({ content, draftEmail, error, onCancel, onChange, onSubmit, }) {
242
+ return (_jsxs("div", { className: 'paywall-wallet-email-prompt-backdrop', role: 'presentation', children: [_jsxs("form", { className: 'paywall-wallet-email-prompt', role: 'dialog', "aria-modal": 'true', "aria-labelledby": 'paywall-wallet-email-prompt-title', onSubmit: (event) => void onSubmit(event), children: [_jsx("h2", { id: 'paywall-wallet-email-prompt-title', children: content.walletEmailPromptTitle }), _jsx("p", { children: content.walletEmailPromptDescription }), _jsxs("label", { children: [_jsx("span", { children: content.customerEmailLabel }), _jsx("input", { type: 'email', inputMode: 'email', autoComplete: 'email', value: draftEmail, onChange: (event) => onChange(event.currentTarget.value), placeholder: content.customerEmailPlaceholder, autoFocus: true })] }), error ? _jsx("p", { className: 'paywall-wallet-email-prompt-error', children: error }) : null, _jsxs("div", { className: 'paywall-wallet-email-prompt-actions', children: [_jsx("button", { type: 'button', onClick: onCancel, children: content.walletEmailPromptCancelLabel }), _jsx("button", { type: 'submit', children: content.walletEmailPromptSubmitLabel })] })] }), _jsx("style", { children: paywallWalletEmailPromptStyles })] }));
243
+ }
244
+ const paywallWalletEmailPromptStyles = `
245
+ .paywall-wallet-email-prompt-backdrop {
246
+ position: fixed;
247
+ inset: 0;
248
+ z-index: 1000;
249
+ display: flex;
250
+ align-items: center;
251
+ justify-content: center;
252
+ padding: 20px;
253
+ background: rgb(13 17 32 / 42%);
254
+ }
255
+
256
+ .paywall-wallet-email-prompt {
257
+ box-sizing: border-box;
258
+ width: min(100%, 360px);
259
+ border-radius: 20px;
260
+ background: #fff;
261
+ padding: 22px;
262
+ box-shadow: 0 18px 48px rgb(19 27 47 / 26%);
263
+ color: #111827;
264
+ }
265
+
266
+ .paywall-wallet-email-prompt h2 {
267
+ margin: 0;
268
+ color: #111827;
269
+ font-size: 22px;
270
+ font-weight: 800;
271
+ line-height: 1.15;
272
+ letter-spacing: 0;
273
+ }
274
+
275
+ .paywall-wallet-email-prompt p {
276
+ margin: 10px 0 0;
277
+ color: #4b5563;
278
+ font-size: 15px;
279
+ font-weight: 500;
280
+ line-height: 1.45;
281
+ }
282
+
283
+ .paywall-wallet-email-prompt label {
284
+ display: flex;
285
+ flex-direction: column;
286
+ gap: 8px;
287
+ margin-top: 18px;
288
+ color: #374151;
289
+ font-size: 13px;
290
+ font-weight: 700;
291
+ line-height: 1.2;
292
+ }
293
+
294
+ .paywall-wallet-email-prompt input {
295
+ min-height: 48px;
296
+ border: 1px solid #d8deea;
297
+ border-radius: 12px;
298
+ padding: 0 14px;
299
+ color: #111827;
300
+ font: inherit;
301
+ font-size: 16px;
302
+ font-weight: 500;
303
+ outline: none;
304
+ }
305
+
306
+ .paywall-wallet-email-prompt input:focus {
307
+ border-color: var(--color-secondary-text);
308
+ box-shadow: 0 0 0 3px rgb(63 81 181 / 12%);
309
+ }
310
+
311
+ .paywall-wallet-email-prompt-error {
312
+ color: #c03232 !important;
313
+ }
314
+
315
+ .paywall-wallet-email-prompt-actions {
316
+ display: grid;
317
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
318
+ gap: 10px;
319
+ margin-top: 18px;
320
+ }
321
+
322
+ .paywall-wallet-email-prompt-actions button {
323
+ min-height: 48px;
324
+ border-radius: 14px;
325
+ border: 0;
326
+ font-size: 15px;
327
+ font-weight: 800;
328
+ cursor: pointer;
329
+ }
330
+
331
+ .paywall-wallet-email-prompt-actions button[type='button'] {
332
+ background: #f2f5fb;
333
+ color: #4b5563;
334
+ }
335
+
336
+ .paywall-wallet-email-prompt-actions button[type='submit'] {
337
+ background: var(--color-secondary-text);
338
+ color: #fff;
339
+ }
340
+ `;
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@ export * from './providers/stripe/GooglePaySubscriptionCheckoutSlot.js';
14
14
  export { ManageSubscriptionScreen, type ManageSubscriptionContent, type ManageSubscriptionScreenProps, } from './components/ManageSubscriptionScreen.js';
15
15
  export * from './components/shared/SharedStripeCheckoutDialog.js';
16
16
  export * from './components/shared/SharedStripeCheckoutV2Dialog.js';
17
+ export * from './components/shared/PaywallSubscriptionCheckout.js';
17
18
  export * from './components/shared/ApplePaySubscribeButton.js';
18
19
  export * from './components/shared/GooglePaySubscribeButton.js';
19
20
  export * from './components/shared/StripeCheckoutExpressCheckoutButton.js';
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ export * from './providers/stripe/GooglePaySubscriptionCheckoutSlot.js';
14
14
  export { ManageSubscriptionScreen, } from './components/ManageSubscriptionScreen.js';
15
15
  export * from './components/shared/SharedStripeCheckoutDialog.js';
16
16
  export * from './components/shared/SharedStripeCheckoutV2Dialog.js';
17
+ export * from './components/shared/PaywallSubscriptionCheckout.js';
17
18
  export * from './components/shared/ApplePaySubscribeButton.js';
18
19
  export * from './components/shared/GooglePaySubscribeButton.js';
19
20
  export * from './components/shared/StripeCheckoutExpressCheckoutButton.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnelsgrove/payments",
3
- "version": "0.1.35",
3
+ "version": "0.1.36",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "main": "./dist/index.js",