@funnelsgrove/payments 0.32.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/components/ManageSubscriptionScreen.d.ts +1 -0
- package/dist/components/ManageSubscriptionScreen.js +3 -1
- package/dist/generated/package-version.d.ts +1 -1
- package/dist/generated/package-version.js +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/providers/paypal/PayPalCheckout.d.ts +8 -0
- package/dist/providers/paypal/PayPalCheckout.js +210 -0
- package/dist/providers/paypal/PayPalCheckoutDialog.d.ts +8 -0
- package/dist/providers/paypal/PayPalCheckoutDialog.js +16 -0
- package/dist/providers/paypal/paypalCheckout.service.d.ts +35 -0
- package/dist/providers/paypal/paypalCheckout.service.js +87 -0
- package/dist/providers/paypal/paypalSdk.d.ts +25 -0
- package/dist/providers/paypal/paypalSdk.js +33 -0
- package/dist/providers/stripe/components/SharedCheckoutPaymentMethods.internal.d.ts +18 -0
- package/dist/providers/stripe/components/SharedCheckoutPaymentMethods.internal.js +15 -0
- package/dist/providers/stripe/components/SharedStripeCheckoutV2.styles.d.ts +1 -1
- package/dist/providers/stripe/components/SharedStripeCheckoutV2.styles.js +15 -0
- package/dist/providers/stripe/components/SharedStripeCheckoutV2Dialog.d.ts +4 -1
- package/dist/providers/stripe/components/SharedStripeCheckoutV2Form.internal.d.ts +1 -1
- package/dist/providers/stripe/components/SharedStripeCheckoutV2Form.internal.js +20 -17
- package/dist/services/publishedBillingRuntime.service.d.ts +10 -1
- package/dist/services/publishedBillingRuntime.service.js +22 -5
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -41,6 +41,15 @@ mobile remains full-screen and all checkout analytics and verified-success behav
|
|
|
41
41
|
|
|
42
42
|
Pass `presentation='inline'` to embed a card-only form in the paywall. The host owns the offer summary and renewal disclosure. Inline checkout has no modal semantics, close button, Escape dismissal, or shared wallet session; analytics and verified-success callbacks are unchanged.
|
|
43
43
|
|
|
44
|
+
For a mixed Stripe/PayPal Offer Set, pass the selected binding's `PayPalCheckout`
|
|
45
|
+
as `paypalCheckout` to `SharedStripeCheckoutV2Dialog`. It appears in the checkout
|
|
46
|
+
method picker alongside available wallets and card. Forward `onPendingChange`
|
|
47
|
+
to `paypalPending` so an unresolved PayPal purchase keeps its selected method.
|
|
48
|
+
The PayPal panel shows its native billing terms and hides the Stripe discount
|
|
49
|
+
summary. This slot applies only to dialog presentation. A PayPal-only Offer Set
|
|
50
|
+
uses `PayPalCheckoutDialog` around the same native checkout, without creating a
|
|
51
|
+
Stripe session. The funnel owns opening the dialog and post-success navigation.
|
|
52
|
+
|
|
44
53
|
Use `completeStripeOneClickPayment` for normal post-checkout one-click upsells. It delegates the charge to `funnelSdkService.chargeOneClickPayment` and owns any required Stripe confirmation. Pass its optional `checkoutAnalytics` context to let the shared package emit `checkout_completed` only after `succeeded`, including after required confirmation. The context uses `StripeSubscriptionCheckoutAnalyticsContext`; completion is deduplicated by PaymentIntent ID, and analytics failures do not fail the payment. Funnel code supplies the context and completes its step; the shared package owns the analytics callback. `chargeStripeOneClickPayment` remains the lower-level charge-only primitive.
|
|
45
54
|
|
|
46
55
|
Solidgate browser checkout is available for controlled provider-routed funnels through
|
|
@@ -46,6 +46,7 @@ type ManageSubscription = RuntimeSubscription & {
|
|
|
46
46
|
};
|
|
47
47
|
export declare function ManageSubscriptionScreen({ stepId, content, homeStepId, nodeId, plans, }: ManageSubscriptionScreenProps): import("react/jsx-runtime").JSX.Element;
|
|
48
48
|
export declare const __manageSubscriptionScreenTestables: {
|
|
49
|
+
isCancelledActiveSubscription: (subscription: ManageSubscription) => boolean;
|
|
49
50
|
resolveBillingDateLabel: (subscription: Pick<ManageSubscription, "cancelAtPeriodEnd" | "currentPeriodEnd" | "status">, options?: {
|
|
50
51
|
cancelled?: boolean;
|
|
51
52
|
past?: boolean;
|
|
@@ -8,7 +8,8 @@ const pastSubscriptionsNote = 'Past subscriptions are already canceled or expire
|
|
|
8
8
|
const cancelledSubscriptionsNote = 'These subscriptions are cancelled and remain active until the date shown below.';
|
|
9
9
|
const getNormalizedStatus = (subscription) => subscription.status.trim().toLowerCase();
|
|
10
10
|
const isCancelledActiveSubscription = (subscription) => {
|
|
11
|
-
return subscription.cancelAtPeriodEnd && getNormalizedStatus(subscription) !== 'canceled'
|
|
11
|
+
return subscription.cancelAtPeriodEnd && (getNormalizedStatus(subscription) !== 'canceled'
|
|
12
|
+
|| Date.parse(subscription.currentPeriodEnd || '') > Date.now());
|
|
12
13
|
};
|
|
13
14
|
const isCancellableSubscription = (subscription) => {
|
|
14
15
|
return getNormalizedStatus(subscription) !== 'canceled' && !subscription.cancelAtPeriodEnd;
|
|
@@ -243,6 +244,7 @@ function SubscriptionSection({ title, subscriptions, plans, onSelect, descriptio
|
|
|
243
244
|
}) })] }));
|
|
244
245
|
}
|
|
245
246
|
export const __manageSubscriptionScreenTestables = {
|
|
247
|
+
isCancelledActiveSubscription,
|
|
246
248
|
resolveBillingDateLabel,
|
|
247
249
|
resolveSubscriptionStatusLabel,
|
|
248
250
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PAYMENTS_PACKAGE_VERSION = "0.
|
|
1
|
+
export declare const PAYMENTS_PACKAGE_VERSION = "0.34.0";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by scripts/generate-package-version.mjs.
|
|
2
|
-
export const PAYMENTS_PACKAGE_VERSION = "0.
|
|
2
|
+
export const PAYMENTS_PACKAGE_VERSION = "0.34.0";
|
package/dist/index.d.ts
CHANGED
|
@@ -15,3 +15,6 @@ export * from './providers/solidgate/index.js';
|
|
|
15
15
|
export { ManageSubscriptionScreen, type ManageSubscriptionContent, type ManageSubscriptionScreenProps, type ManageSubscriptionLabels, } from './components/ManageSubscriptionScreen.js';
|
|
16
16
|
export { isCheckoutEmailError, isValidCheckoutEmail } from './services/checkoutEmail.js';
|
|
17
17
|
export * from './components/CheckoutEmailDialog.js';
|
|
18
|
+
export { PayPalCheckoutDialog } from './providers/paypal/PayPalCheckoutDialog.js';
|
|
19
|
+
export { PayPalCheckout, type PayPalCheckoutProps } from './providers/paypal/PayPalCheckout.js';
|
|
20
|
+
export { readPendingPayPalCheckout } from './providers/paypal/paypalCheckout.service.js';
|
package/dist/index.js
CHANGED
|
@@ -18,3 +18,6 @@ export * from './providers/solidgate/index.js';
|
|
|
18
18
|
export { ManageSubscriptionScreen, } from './components/ManageSubscriptionScreen.js';
|
|
19
19
|
export { isCheckoutEmailError, isValidCheckoutEmail } from './services/checkoutEmail.js';
|
|
20
20
|
export * from './components/CheckoutEmailDialog.js';
|
|
21
|
+
export { PayPalCheckoutDialog } from './providers/paypal/PayPalCheckoutDialog.js';
|
|
22
|
+
export { PayPalCheckout } from './providers/paypal/PayPalCheckout.js';
|
|
23
|
+
export { readPendingPayPalCheckout } from './providers/paypal/paypalCheckout.service.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type PayPalCheckoutInput } from './paypalCheckout.service.js';
|
|
2
|
+
export type PayPalCheckoutProps = PayPalCheckoutInput & {
|
|
3
|
+
onSuccess: () => void;
|
|
4
|
+
onPreparationStart?: () => void;
|
|
5
|
+
onPendingChange?: (pending: boolean) => void;
|
|
6
|
+
};
|
|
7
|
+
/** A keyed instance owns one merchant/plan selection throughout buyer approval. */
|
|
8
|
+
export declare function PayPalCheckout(props: PayPalCheckoutProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { completePaywallCheckout, retainPaywallCheckout } from '@funnelsgrove/runtime';
|
|
4
|
+
import { loadPayPalSdk } from './paypalSdk.js';
|
|
5
|
+
import { PayPalCheckoutRequestError, readPendingPayPalCheckout, storePendingPayPalCheckout, preparePayPalCheckout, verifyPayPalCheckout } from './paypalCheckout.service.js';
|
|
6
|
+
/** A keyed instance owns one merchant/plan selection throughout buyer approval. */
|
|
7
|
+
export function PayPalCheckout(props) {
|
|
8
|
+
const container = useRef(null);
|
|
9
|
+
const attemptKey = useRef(crypto.randomUUID());
|
|
10
|
+
const callbacks = useRef(props);
|
|
11
|
+
callbacks.current = props;
|
|
12
|
+
const [error, setError] = useState(null);
|
|
13
|
+
const [state, setState] = useState('loading');
|
|
14
|
+
const [retry, setRetry] = useState(0);
|
|
15
|
+
const [summary, setSummary] = useState(props.option.summary);
|
|
16
|
+
const verifyRef = useRef(null);
|
|
17
|
+
const inputKey = JSON.stringify([props.option, props.environment, props.funnelEndUserId, props.offerSetId, props.funnelPlanKey, props.runtimeConfig]);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
var _a, _b, _c, _d, _e;
|
|
20
|
+
const pending = readPendingPayPalCheckout(callbacks.current);
|
|
21
|
+
const input = pending ? Object.assign(Object.assign({}, callbacks.current), { option: pending.option, offerSetId: pending.offerSetId, funnelPlanKey: pending.funnelPlanKey }) : callbacks.current;
|
|
22
|
+
setSummary(input.option.summary);
|
|
23
|
+
const controller = new AbortController();
|
|
24
|
+
let button;
|
|
25
|
+
let attempt = null;
|
|
26
|
+
let verifying = false;
|
|
27
|
+
let approved = false;
|
|
28
|
+
let blocked = false;
|
|
29
|
+
let completed = false;
|
|
30
|
+
let idempotencyKey = attemptKey.current;
|
|
31
|
+
const storageKey = `fg-paypal:${input.environment}:${((_a = input.runtimeConfig) === null || _a === void 0 ? void 0 : _a.funnelId) || ''}:${input.funnelEndUserId}:${input.offerSetId}:${input.funnelPlanKey}:${input.option.paymentProfileId}:${input.option.providerPlanId}`;
|
|
32
|
+
try {
|
|
33
|
+
const saved = sessionStorage.getItem(storageKey);
|
|
34
|
+
if (saved === null || saved === void 0 ? void 0 : saved.startsWith('{')) {
|
|
35
|
+
const record = JSON.parse(saved);
|
|
36
|
+
if (typeof record.key === 'string')
|
|
37
|
+
idempotencyKey = record.key;
|
|
38
|
+
if (typeof ((_b = record.attempt) === null || _b === void 0 ? void 0 : _b.attemptId) === 'string' && typeof ((_c = record.attempt) === null || _c === void 0 ? void 0 : _c.subscriptionId) === 'string') {
|
|
39
|
+
attempt = record.attempt;
|
|
40
|
+
approved = record.approved === true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else if (saved)
|
|
44
|
+
idempotencyKey = saved;
|
|
45
|
+
}
|
|
46
|
+
catch ( /* In-memory key still protects retries when browser storage is disabled. */_f) { /* In-memory key still protects retries when browser storage is disabled. */ }
|
|
47
|
+
if (pending) {
|
|
48
|
+
attempt = pending.attempt;
|
|
49
|
+
approved = pending.approved !== false;
|
|
50
|
+
idempotencyKey = pending.idempotencyKey;
|
|
51
|
+
}
|
|
52
|
+
const persist = () => {
|
|
53
|
+
try {
|
|
54
|
+
sessionStorage.setItem(storageKey, JSON.stringify({ key: idempotencyKey, attempt, approved }));
|
|
55
|
+
}
|
|
56
|
+
catch ( /* Optional persistence. */_a) { /* Optional persistence. */ }
|
|
57
|
+
if (attempt)
|
|
58
|
+
storePendingPayPalCheckout(input, { option: input.option, offerSetId: input.offerSetId, funnelPlanKey: input.funnelPlanKey, attempt, idempotencyKey, approved });
|
|
59
|
+
};
|
|
60
|
+
persist();
|
|
61
|
+
const showError = (message) => {
|
|
62
|
+
var _a, _b;
|
|
63
|
+
if (controller.signal.aborted || blocked || completed)
|
|
64
|
+
return;
|
|
65
|
+
setError(message);
|
|
66
|
+
setState(attempt ? 'pending' : 'ready');
|
|
67
|
+
if (!attempt)
|
|
68
|
+
(_b = (_a = callbacks.current).onPendingChange) === null || _b === void 0 ? void 0 : _b.call(_a, false);
|
|
69
|
+
};
|
|
70
|
+
const showRequestError = (error, fallback) => {
|
|
71
|
+
var _a, _b;
|
|
72
|
+
if (controller.signal.aborted || completed)
|
|
73
|
+
return;
|
|
74
|
+
if (error instanceof PayPalCheckoutRequestError) {
|
|
75
|
+
blocked = true;
|
|
76
|
+
setError(error.message);
|
|
77
|
+
setState('blocked');
|
|
78
|
+
(_b = (_a = callbacks.current).onPendingChange) === null || _b === void 0 ? void 0 : _b.call(_a, true);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
showError(fallback);
|
|
82
|
+
};
|
|
83
|
+
const verify = async () => {
|
|
84
|
+
var _a, _b;
|
|
85
|
+
if (!attempt || verifying || controller.signal.aborted)
|
|
86
|
+
return;
|
|
87
|
+
verifying = true;
|
|
88
|
+
setError(null);
|
|
89
|
+
setState('verifying');
|
|
90
|
+
try {
|
|
91
|
+
// Polling is bounded; a pending payment retains the same attempt for a manual recheck.
|
|
92
|
+
for (let index = 0; index < 20 && !controller.signal.aborted; index += 1) {
|
|
93
|
+
const status = await verifyPayPalCheckout(input, attempt, controller.signal);
|
|
94
|
+
if (controller.signal.aborted)
|
|
95
|
+
return;
|
|
96
|
+
if (status === 'succeeded') {
|
|
97
|
+
const embedded = await completePaywallCheckout('paypal', attempt.attemptId, { signal: controller.signal });
|
|
98
|
+
if (controller.signal.aborted)
|
|
99
|
+
return;
|
|
100
|
+
completed = true;
|
|
101
|
+
setState('success');
|
|
102
|
+
storePendingPayPalCheckout(input, null);
|
|
103
|
+
try {
|
|
104
|
+
sessionStorage.removeItem(storageKey);
|
|
105
|
+
}
|
|
106
|
+
catch ( /* Optional persistence. */_c) { /* Optional persistence. */ }
|
|
107
|
+
(_b = (_a = callbacks.current).onPendingChange) === null || _b === void 0 ? void 0 : _b.call(_a, false);
|
|
108
|
+
if (!embedded)
|
|
109
|
+
callbacks.current.onSuccess();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (status === 'requires_payment' && !approved) {
|
|
113
|
+
await renderButton();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
await new Promise(resolve => {
|
|
117
|
+
const done = () => { clearTimeout(timeout); controller.signal.removeEventListener('abort', done); resolve(); };
|
|
118
|
+
const timeout = setTimeout(done, 1500);
|
|
119
|
+
controller.signal.addEventListener('abort', done, { once: true });
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
showError('PayPal is still confirming your subscription. Check its status before trying another purchase.');
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
showRequestError(error, 'Unable to confirm your subscription yet. Check payment status to continue.');
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
verifying = false;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
verifyRef.current = verify;
|
|
132
|
+
setState('loading');
|
|
133
|
+
setError(null);
|
|
134
|
+
const renderButton = async () => {
|
|
135
|
+
if (controller.signal.aborted)
|
|
136
|
+
return;
|
|
137
|
+
if (button) {
|
|
138
|
+
setState('ready');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const sdk = await loadPayPalSdk(input.option.clientId, input.environment, input.option.currency);
|
|
142
|
+
if (controller.signal.aborted || !container.current)
|
|
143
|
+
return;
|
|
144
|
+
button = sdk.Buttons({
|
|
145
|
+
fundingSource: sdk.FUNDING.PAYPAL, style: { label: 'paypal', layout: 'vertical', height: 48 },
|
|
146
|
+
createSubscription: async () => {
|
|
147
|
+
var _a, _b, _c, _d;
|
|
148
|
+
if (controller.signal.aborted || completed || blocked)
|
|
149
|
+
throw new Error('This PayPal checkout is no longer accepting approval.');
|
|
150
|
+
(_b = (_a = callbacks.current).onPreparationStart) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
151
|
+
(_d = (_c = callbacks.current).onPendingChange) === null || _d === void 0 ? void 0 : _d.call(_c, true);
|
|
152
|
+
setError(null);
|
|
153
|
+
setState('approving');
|
|
154
|
+
try {
|
|
155
|
+
if (attempt) {
|
|
156
|
+
const status = await verifyPayPalCheckout(input, attempt, controller.signal);
|
|
157
|
+
if (status !== 'requires_payment') {
|
|
158
|
+
await verify();
|
|
159
|
+
throw new Error('Check payment status before approving this subscription.');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
attempt = await preparePayPalCheckout(input, idempotencyKey, controller.signal);
|
|
164
|
+
}
|
|
165
|
+
persist();
|
|
166
|
+
retainPaywallCheckout('paypal', attempt.attemptId, input.option.paymentProfileId);
|
|
167
|
+
return attempt.subscriptionId;
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
showRequestError(error, 'PayPal is temporarily unavailable. Please try again.');
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
onApprove: async (data) => {
|
|
175
|
+
if (controller.signal.aborted || completed || blocked)
|
|
176
|
+
return;
|
|
177
|
+
if (!attempt || data.subscriptionID !== attempt.subscriptionId) {
|
|
178
|
+
showError('Unable to match this PayPal approval. Check payment status.');
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
approved = true;
|
|
182
|
+
persist();
|
|
183
|
+
await verify();
|
|
184
|
+
},
|
|
185
|
+
onCancel: () => { var _a, _b; if (!controller.signal.aborted && !approved && !completed && !blocked) {
|
|
186
|
+
attempt = null;
|
|
187
|
+
persist();
|
|
188
|
+
storePendingPayPalCheckout(input, null);
|
|
189
|
+
setState('ready');
|
|
190
|
+
(_b = (_a = callbacks.current).onPendingChange) === null || _b === void 0 ? void 0 : _b.call(_a, false);
|
|
191
|
+
} },
|
|
192
|
+
onError: () => showError('PayPal is temporarily unavailable. Please try again.'),
|
|
193
|
+
});
|
|
194
|
+
await button.render(container.current);
|
|
195
|
+
if (!controller.signal.aborted)
|
|
196
|
+
setState('ready');
|
|
197
|
+
};
|
|
198
|
+
if (attempt) {
|
|
199
|
+
(_e = (_d = callbacks.current).onPendingChange) === null || _e === void 0 ? void 0 : _e.call(_d, true);
|
|
200
|
+
retainPaywallCheckout('paypal', attempt.attemptId, input.option.paymentProfileId);
|
|
201
|
+
void verify();
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
void renderButton().catch(() => showError('Unable to load PayPal. Please try again.'));
|
|
205
|
+
}
|
|
206
|
+
return () => { controller.abort(); verifyRef.current = null; void (button === null || button === void 0 ? void 0 : button.close().catch(() => undefined)); };
|
|
207
|
+
}, [inputKey, retry]);
|
|
208
|
+
return _jsxs("section", { "aria-label": 'PayPal payment', className: 'space-y-3', children: [_jsxs("p", { className: 'text-sm', children: ["PayPal \u00B7 ", summary] }), _jsx("div", { ref: container, hidden: state === 'blocked' || state === 'pending' || state === 'verifying' || state === 'success' }), state === 'loading' ? _jsx("p", { role: 'status', children: "Loading PayPal\u2026" }) : null, state === 'verifying' ? _jsx("p", { role: 'status', children: "Confirming your subscription\u2026" }) : null, state === 'success' ? _jsx("p", { role: 'status', children: "Subscription confirmed." }) : null, error ? _jsx("p", { role: 'alert', children: error }) : null, state === 'pending' ? _jsx("button", { type: 'button', onClick: () => { var _a; return void ((_a = verifyRef.current) === null || _a === void 0 ? void 0 : _a.call(verifyRef)); }, children: "Check payment status" })
|
|
209
|
+
: error && state !== 'blocked' ? _jsx("button", { type: 'button', onClick: () => setRetry(value => value + 1), children: "Retry PayPal" }) : null] });
|
|
210
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/** The same checkout surface for an Offer Set with only a PayPal binding. */
|
|
3
|
+
export declare function PayPalCheckoutDialog({ children, title, closeAriaLabel, onClose }: {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
title: string;
|
|
6
|
+
closeAriaLabel: string;
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId } from 'react';
|
|
4
|
+
import { sharedStripeCheckoutV2Styles } from '../stripe/components/SharedStripeCheckoutV2.styles.js';
|
|
5
|
+
import { PaymentBrandMark } from '../stripe/components/CheckoutBrandAssets.js';
|
|
6
|
+
/** The same checkout surface for an Offer Set with only a PayPal binding. */
|
|
7
|
+
export function PayPalCheckoutDialog({ children, title, closeAriaLabel, onClose }) {
|
|
8
|
+
const titleId = useId();
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const handleKey = (event) => { if (event.key === 'Escape')
|
|
11
|
+
onClose(); };
|
|
12
|
+
window.addEventListener('keydown', handleKey);
|
|
13
|
+
return () => window.removeEventListener('keydown', handleKey);
|
|
14
|
+
}, [onClose]);
|
|
15
|
+
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": titleId, children: _jsxs("div", { className: 'shared-checkout-v2-shell', children: [_jsxs("header", { className: 'shared-checkout-v2-header', children: [_jsx("button", { type: 'button', className: 'shared-checkout-v2-close', "aria-label": closeAriaLabel, onClick: onClose, children: _jsx("span", { "aria-hidden": 'true' }) }), _jsx("h2", { id: titleId, children: title })] }), _jsxs("div", { className: 'shared-checkout-v2-payment', children: [_jsxs("div", { className: 'shared-checkout-v2-method is-selected', "aria-label": 'PayPal', children: [_jsx(PaymentBrandMark, { label: 'PayPal', className: 'shared-checkout-v2-tab-wallet' }), _jsx("span", { children: "PayPal" })] }), children] })] }) }) }), _jsx("style", { children: sharedStripeCheckoutV2Styles })] });
|
|
16
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type FunnelSdkRuntimeConfigOverrides } from '@funnelsgrove/runtime';
|
|
2
|
+
import type { PayPalBillingOption } from '../../services/publishedBillingRuntime.service.js';
|
|
3
|
+
export type PayPalCheckoutInput = {
|
|
4
|
+
option: PayPalBillingOption;
|
|
5
|
+
environment: 'test' | 'live';
|
|
6
|
+
funnelEndUserId: string;
|
|
7
|
+
offerSetId: string;
|
|
8
|
+
funnelPlanKey: string;
|
|
9
|
+
runtimeConfig?: FunnelSdkRuntimeConfigOverrides;
|
|
10
|
+
};
|
|
11
|
+
export type PayPalCheckoutAttempt = {
|
|
12
|
+
attemptId: string;
|
|
13
|
+
subscriptionId: string;
|
|
14
|
+
};
|
|
15
|
+
declare const checkoutErrorMessages: {
|
|
16
|
+
checkout_unavailable: string;
|
|
17
|
+
checkout_outcome_unresolved: string;
|
|
18
|
+
already_subscribed_to_product: string;
|
|
19
|
+
};
|
|
20
|
+
export declare class PayPalCheckoutRequestError extends Error {
|
|
21
|
+
readonly code: keyof typeof checkoutErrorMessages;
|
|
22
|
+
constructor(code: keyof typeof checkoutErrorMessages);
|
|
23
|
+
}
|
|
24
|
+
export declare function preparePayPalCheckout(input: PayPalCheckoutInput, idempotencyKey: string, signal: AbortSignal): Promise<PayPalCheckoutAttempt>;
|
|
25
|
+
export declare function verifyPayPalCheckout(input: PayPalCheckoutInput, attempt: PayPalCheckoutAttempt, signal: AbortSignal): Promise<'succeeded' | 'requires_payment' | 'verifying' | 'preparing'>;
|
|
26
|
+
export type PendingPayPalCheckout = Pick<PayPalCheckoutInput, 'option' | 'offerSetId' | 'funnelPlanKey'> & {
|
|
27
|
+
attempt: PayPalCheckoutAttempt;
|
|
28
|
+
idempotencyKey: string;
|
|
29
|
+
approved?: boolean;
|
|
30
|
+
};
|
|
31
|
+
type PendingScope = Pick<PayPalCheckoutInput, 'runtimeConfig' | 'environment' | 'funnelEndUserId'>;
|
|
32
|
+
/** A prepared checkout must survive a lost approval callback or default-plan change. */
|
|
33
|
+
export declare const readPendingPayPalCheckout: (scope: PendingScope) => PendingPayPalCheckout | null;
|
|
34
|
+
export declare const storePendingPayPalCheckout: (scope: PendingScope, value: PendingPayPalCheckout | null) => void;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { FUNNEL_ID, FUNNEL_SDK_PUBLISHABLE_KEY, buildMainApiUrl, resolvePreviewPaymentPublishableKey } from '@funnelsgrove/runtime';
|
|
2
|
+
const record = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
3
|
+
const checkoutErrorMessages = {
|
|
4
|
+
checkout_unavailable: 'This PayPal checkout is no longer available. Please contact support before starting another purchase.',
|
|
5
|
+
checkout_outcome_unresolved: 'We are still checking this PayPal checkout. Please wait or contact support before starting another purchase.',
|
|
6
|
+
already_subscribed_to_product: 'You already have an active subscription to this product.',
|
|
7
|
+
};
|
|
8
|
+
export class PayPalCheckoutRequestError extends Error {
|
|
9
|
+
constructor(code) {
|
|
10
|
+
super(checkoutErrorMessages[code]);
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.name = 'PayPalCheckoutRequestError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function request(input, path, signal, body) {
|
|
16
|
+
var _a, _b, _c, _d;
|
|
17
|
+
const key = resolvePreviewPaymentPublishableKey((_b = (_a = input.runtimeConfig) === null || _a === void 0 ? void 0 : _a.funnelSdkPublishableKey) !== null && _b !== void 0 ? _b : FUNNEL_SDK_PUBLISHABLE_KEY);
|
|
18
|
+
const base = (_d = (_c = input.runtimeConfig) === null || _c === void 0 ? void 0 : _c.apiBaseUrl) === null || _d === void 0 ? void 0 : _d.trim().replace(/\/+$/, '');
|
|
19
|
+
const response = await fetch(base ? `${base}${path}` : buildMainApiUrl(path), Object.assign({ method: body ? 'POST' : 'GET', signal, headers: Object.assign(Object.assign({}, (body ? { 'content-type': 'application/json' } : {})), (key ? { 'x-sdk-publishable-key': key } : {})) }, (body ? { body: JSON.stringify(body) } : {})));
|
|
20
|
+
if (!response.ok) {
|
|
21
|
+
const failure = await response.json().catch(() => null);
|
|
22
|
+
if (record(failure) && (failure.code === 'checkout_unavailable'
|
|
23
|
+
|| failure.code === 'checkout_outcome_unresolved' || failure.code === 'already_subscribed_to_product')) {
|
|
24
|
+
throw new PayPalCheckoutRequestError(failure.code);
|
|
25
|
+
}
|
|
26
|
+
throw new Error('Unable to confirm with PayPal. Please try again.');
|
|
27
|
+
}
|
|
28
|
+
const result = await response.json();
|
|
29
|
+
if (!record(result))
|
|
30
|
+
throw new Error('Invalid PayPal checkout response.');
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
export async function preparePayPalCheckout(input, idempotencyKey, signal) {
|
|
34
|
+
var _a;
|
|
35
|
+
const result = await request(input, '/sdk/public/v2/payments/checkout-sessions', signal, {
|
|
36
|
+
provider: 'paypal', checkoutType: 'subscription', uiMode: 'custom', environment: input.environment,
|
|
37
|
+
paymentProfileId: input.option.paymentProfileId, providerPlanId: input.option.providerPlanId,
|
|
38
|
+
funnelId: ((_a = input.runtimeConfig) === null || _a === void 0 ? void 0 : _a.funnelId) || FUNNEL_ID, funnelEndUserId: input.funnelEndUserId,
|
|
39
|
+
offerSetId: input.offerSetId, planKey: input.funnelPlanKey, idempotencyKey,
|
|
40
|
+
returnUrl: window.location.href, successUrl: window.location.href, failUrl: window.location.href,
|
|
41
|
+
});
|
|
42
|
+
const initialization = result.initialization;
|
|
43
|
+
if (typeof result.attemptId !== 'string' || !result.attemptId || !record(initialization)
|
|
44
|
+
|| initialization.kind !== 'paypal' || initialization.clientId !== input.option.clientId
|
|
45
|
+
|| typeof initialization.subscriptionId !== 'string' || !initialization.subscriptionId)
|
|
46
|
+
throw new Error('Invalid PayPal checkout response.');
|
|
47
|
+
return { attemptId: result.attemptId, subscriptionId: initialization.subscriptionId };
|
|
48
|
+
}
|
|
49
|
+
export async function verifyPayPalCheckout(input, attempt, signal) {
|
|
50
|
+
var _a;
|
|
51
|
+
const query = new URLSearchParams({ provider: 'paypal', funnelId: ((_a = input.runtimeConfig) === null || _a === void 0 ? void 0 : _a.funnelId) || FUNNEL_ID, funnelEndUserId: input.funnelEndUserId });
|
|
52
|
+
const result = await request(input, `/sdk/public/v2/payments/checkout-sessions/${encodeURIComponent(attempt.attemptId)}?${query}`, signal);
|
|
53
|
+
if (result.attemptId !== attempt.attemptId)
|
|
54
|
+
throw new Error('Invalid PayPal checkout response.');
|
|
55
|
+
if (result.status === 'failed' || result.status === 'expired')
|
|
56
|
+
throw new PayPalCheckoutRequestError('checkout_unavailable');
|
|
57
|
+
if (result.status !== 'succeeded' && result.status !== 'requires_payment'
|
|
58
|
+
&& result.status !== 'verifying' && result.status !== 'preparing')
|
|
59
|
+
throw new Error('Invalid PayPal checkout response.');
|
|
60
|
+
return result.status;
|
|
61
|
+
}
|
|
62
|
+
const pendingKey = (scope) => { var _a; return `fg-paypal-pending:${scope.environment}:${((_a = scope.runtimeConfig) === null || _a === void 0 ? void 0 : _a.funnelId) || FUNNEL_ID}:${scope.funnelEndUserId}`; };
|
|
63
|
+
/** A prepared checkout must survive a lost approval callback or default-plan change. */
|
|
64
|
+
export const readPendingPayPalCheckout = (scope) => {
|
|
65
|
+
try {
|
|
66
|
+
const value = JSON.parse(sessionStorage.getItem(pendingKey(scope)) || 'null');
|
|
67
|
+
if (!record(value) || !record(value.attempt) || !record(value.option)
|
|
68
|
+
|| ![value.offerSetId, value.funnelPlanKey, value.idempotencyKey, value.attempt.attemptId, value.attempt.subscriptionId,
|
|
69
|
+
value.option.paymentProfileId, value.option.providerPlanId, value.option.clientId, value.option.currency, value.option.summary]
|
|
70
|
+
.every(field => typeof field === 'string' && field.length > 0)
|
|
71
|
+
|| typeof value.option.amountMinor !== 'number')
|
|
72
|
+
return null;
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
catch (_a) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
export const storePendingPayPalCheckout = (scope, value) => {
|
|
80
|
+
try {
|
|
81
|
+
if (value)
|
|
82
|
+
sessionStorage.setItem(pendingKey(scope), JSON.stringify(value));
|
|
83
|
+
else
|
|
84
|
+
sessionStorage.removeItem(pendingKey(scope));
|
|
85
|
+
}
|
|
86
|
+
catch ( /* Browser storage can be unavailable; the mounted checkout still retains ownership. */_a) { /* Browser storage can be unavailable; the mounted checkout still retains ownership. */ }
|
|
87
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
type PayPalButton = {
|
|
2
|
+
render: (container: HTMLElement) => Promise<void>;
|
|
3
|
+
close: () => Promise<void>;
|
|
4
|
+
};
|
|
5
|
+
export type PayPalSdk = {
|
|
6
|
+
FUNDING: {
|
|
7
|
+
PAYPAL: string;
|
|
8
|
+
};
|
|
9
|
+
Buttons: (options: {
|
|
10
|
+
fundingSource: string;
|
|
11
|
+
style: {
|
|
12
|
+
label: string;
|
|
13
|
+
layout: string;
|
|
14
|
+
height: number;
|
|
15
|
+
};
|
|
16
|
+
createSubscription: () => Promise<string>;
|
|
17
|
+
onApprove: (data: {
|
|
18
|
+
subscriptionID: string;
|
|
19
|
+
}) => Promise<void>;
|
|
20
|
+
onCancel: () => void;
|
|
21
|
+
onError: () => void;
|
|
22
|
+
}) => PayPalButton;
|
|
23
|
+
};
|
|
24
|
+
export declare function loadPayPalSdk(clientId: string, environment: 'test' | 'live', currency: string): Promise<PayPalSdk>;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const instances = new Map();
|
|
2
|
+
let sequence = 0;
|
|
3
|
+
export function loadPayPalSdk(clientId, environment, currency) {
|
|
4
|
+
const key = JSON.stringify([clientId, environment, currency]);
|
|
5
|
+
const existing = instances.get(key);
|
|
6
|
+
if (existing)
|
|
7
|
+
return existing;
|
|
8
|
+
const namespace = `fgPayPal${++sequence}`;
|
|
9
|
+
const pending = new Promise((resolve, reject) => {
|
|
10
|
+
const script = document.createElement('script');
|
|
11
|
+
const query = new URLSearchParams({ 'client-id': clientId, components: 'buttons', intent: 'subscription', vault: 'true', currency,
|
|
12
|
+
'integration-date': '2026-09-30' });
|
|
13
|
+
script.src = `https://www.paypal.com/sdk/js?${query}`;
|
|
14
|
+
script.dataset.namespace = namespace;
|
|
15
|
+
script.async = true;
|
|
16
|
+
const failed = () => { clearTimeout(timeout); script.remove(); reject(new Error('Unable to load PayPal. Please try again.')); };
|
|
17
|
+
const timeout = setTimeout(failed, 20000);
|
|
18
|
+
script.onerror = failed;
|
|
19
|
+
script.onload = () => {
|
|
20
|
+
clearTimeout(timeout);
|
|
21
|
+
const sdk = window[namespace];
|
|
22
|
+
if (!(sdk === null || sdk === void 0 ? void 0 : sdk.Buttons)) {
|
|
23
|
+
failed();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
resolve(sdk);
|
|
27
|
+
};
|
|
28
|
+
document.head.appendChild(script);
|
|
29
|
+
});
|
|
30
|
+
instances.set(key, pending);
|
|
31
|
+
void pending.catch(() => instances.delete(key));
|
|
32
|
+
return pending;
|
|
33
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PlatformWalletPaymentMethod } from './walletPlatform.js';
|
|
2
|
+
type Props = {
|
|
3
|
+
showWalletMethod: boolean;
|
|
4
|
+
showPayPalMethod: boolean;
|
|
5
|
+
selectedMethod: 'wallet' | 'card' | 'paypal';
|
|
6
|
+
disabled: boolean;
|
|
7
|
+
paymentMethodLabel: string;
|
|
8
|
+
cardLabel: string;
|
|
9
|
+
paypalLabel: string;
|
|
10
|
+
walletAriaLabel: string;
|
|
11
|
+
walletPaymentMethods: readonly PlatformWalletPaymentMethod[];
|
|
12
|
+
cardBrandLabels: readonly string[];
|
|
13
|
+
onWalletSelect: () => void;
|
|
14
|
+
onCardSelect: () => void;
|
|
15
|
+
onPayPalSelect: () => void;
|
|
16
|
+
};
|
|
17
|
+
export declare function SharedCheckoutPaymentMethods({ showWalletMethod, showPayPalMethod, selectedMethod, disabled, paymentMethodLabel, cardLabel, paypalLabel, walletAriaLabel, walletPaymentMethods, cardBrandLabels, onWalletSelect, onCardSelect, onPayPalSelect }: Props): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PaymentBrandMark } from './CheckoutBrandAssets.js';
|
|
3
|
+
export function SharedCheckoutPaymentMethods({ showWalletMethod, showPayPalMethod, selectedMethod, disabled, paymentMethodLabel, cardLabel, paypalLabel, walletAriaLabel, walletPaymentMethods, cardBrandLabels, onWalletSelect, onCardSelect, onPayPalSelect }) {
|
|
4
|
+
return (_jsxs("div", { className: 'shared-checkout-v2-methods', "data-paypal": showPayPalMethod ? 'true' : undefined, role: 'tablist', "aria-label": paymentMethodLabel, children: [showWalletMethod ? _jsxs("button", { type: 'button', disabled: disabled, className: [
|
|
5
|
+
'shared-checkout-v2-method',
|
|
6
|
+
selectedMethod === 'wallet' ? 'is-selected' : '',
|
|
7
|
+
]
|
|
8
|
+
.filter(Boolean)
|
|
9
|
+
.join(' '), onClick: onWalletSelect, role: 'tab', "aria-selected": selectedMethod === 'wallet', children: [_jsxs("span", { className: 'shared-checkout-v2-tab-wallets', "aria-hidden": 'true', children: [walletPaymentMethods.includes('applePay') ? (_jsx(PaymentBrandMark, { label: 'Apple Pay', className: 'shared-checkout-v2-tab-wallet' })) : null, walletPaymentMethods.includes('googlePay') ? (_jsx(PaymentBrandMark, { label: 'Google Pay', className: 'shared-checkout-v2-tab-wallet' })) : null] }), _jsx("span", { className: 'shared-checkout-v2-sr-only', children: walletAriaLabel })] }) : null, showPayPalMethod ? _jsxs("button", { type: 'button', role: 'tab', "aria-label": paypalLabel, "aria-selected": selectedMethod === 'paypal', disabled: disabled, onClick: onPayPalSelect, className: `shared-checkout-v2-method ${selectedMethod === 'paypal' ? 'is-selected' : ''}`, children: [_jsx(PaymentBrandMark, { label: 'PayPal', className: 'shared-checkout-v2-tab-wallet' }), _jsx("span", { children: paypalLabel })] }) : null, _jsxs("button", { type: 'button', className: [
|
|
10
|
+
'shared-checkout-v2-method',
|
|
11
|
+
selectedMethod === 'card' ? 'is-selected' : '',
|
|
12
|
+
]
|
|
13
|
+
.filter(Boolean)
|
|
14
|
+
.join(' '), disabled: disabled, onClick: onCardSelect, role: 'tab', "aria-selected": selectedMethod === 'card', children: [_jsx("strong", { children: cardLabel }), _jsx("span", { className: 'shared-checkout-v2-tab-brands', "aria-hidden": 'true', children: cardBrandLabels.slice(0, 4).map((label) => (_jsx(PaymentBrandMark, { label: label, className: 'shared-checkout-v2-tab-brand' }, label))) })] })] }));
|
|
15
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const sharedStripeCheckoutV2Styles = "\n.shared-checkout-v2-overlay {\n position: fixed;\n inset: 0;\n z-index: 110;\n display: flex;\n align-items: stretch;\n justify-content: center;\n overflow: hidden;\n background: #fff;\n}\n\n.shared-checkout-v2-dialog {\n width: min(100%, 430px);\n height: 100svh;\n max-height: 100svh;\n overflow: hidden;\n background: #fff;\n}\n\n@media (min-width: 640px) {\n .shared-checkout-v2-overlay:has(.shared-checkout-v2-dialog[data-variant='compact']) {\n align-items: center;\n padding: 24px;\n background: rgb(0 0 0 / 48%);\n }\n\n .shared-checkout-v2-dialog[data-variant='compact'] {\n width: min(100%, 520px);\n height: auto;\n max-height: calc(100svh - 48px);\n border-radius: 24px;\n box-shadow: 0 24px 70px rgb(0 0 0 / 28%);\n }\n}\n\n.shared-checkout-v2-shell[data-variant='compact'] {\n height: auto;\n max-height: min(100svh, 760px);\n}\n\n.shared-checkout-v2-shell[data-variant='compact'] .shared-checkout-v2-header {\n min-height: 88px;\n padding: 0 72px;\n}\n\n.shared-checkout-v2-shell[data-variant='compact'] .shared-checkout-v2-header h2 {\n font-size: 27px;\n}\n\n.shared-checkout-v2-shell[data-variant='compact'] .shared-checkout-v2-close {\n right: 22px;\n left: auto;\n}\n\n.shared-checkout-v2-shell[data-variant='compact'] .shared-checkout-v2-summary {\n padding: 0 36px;\n}\n\n.shared-checkout-v2-shell[data-variant='compact'] .shared-checkout-v2-total {\n min-height: 82px;\n border: 1px solid #ececef;\n border-radius: 16px;\n padding: 0 22px;\n background: #fbfbfc;\n}\n\n.shared-checkout-v2-shell[data-variant='compact'] .shared-checkout-v2-total svg {\n display: none;\n}\n\n.shared-checkout-v2-shell[data-variant='compact'] .shared-checkout-v2-payment {\n padding-right: 36px;\n padding-left: 36px;\n}\n\n.shared-checkout-v2-shell[data-variant='compact'] .shared-checkout-v2-card-submit {\n border-radius: 14px;\n background: var(--shared-checkout-v2-button-color);\n text-transform: none;\n}\n\n.shared-checkout-v2-shell {\n height: 100%;\n max-height: 100svh;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n overflow-x: hidden;\n -webkit-overflow-scrolling: touch;\n background: #fff;\n color: #303136;\n font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n}\n\n.shared-checkout-v2-verifying,\n.shared-checkout-v2-confirmed {\n box-sizing: border-box;\n min-height: 100%;\n margin: 0;\n padding: 32px 24px;\n background: #fff;\n color: #303136;\n font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n font-size: 16px;\n line-height: 1.5;\n}\n\n.shared-checkout-v2-verifying p {\n margin: 0 0 24px;\n}\n\n.shared-checkout-v2-status-button {\n min-height: 48px;\n border: 0;\n border-radius: 11px;\n padding: 12px 18px;\n background: var(--shared-checkout-v2-button-color);\n color: #fff;\n font: inherit;\n font-weight: 700;\n cursor: pointer;\n}\n\n.shared-checkout-v2-status-button:disabled {\n cursor: wait;\n opacity: 0.72;\n}\n\n.shared-checkout-v2-header {\n position: relative;\n min-height: 70px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0 56px;\n background: #fff;\n}\n\n.shared-checkout-v2-header h2 {\n margin: 0;\n color: #020203;\n font-size: 24px;\n font-weight: 900;\n line-height: 1.15;\n letter-spacing: 0;\n text-align: center;\n}\n\n.shared-checkout-v2-close {\n position: absolute;\n left: 16px;\n top: 50%;\n width: 40px;\n height: 40px;\n border: 0;\n border-radius: 999px;\n background: #f2f2f3;\n color: #aaa;\n transform: translateY(-50%);\n cursor: pointer;\n}\n\n.shared-checkout-v2-close span,\n.shared-checkout-v2-close span::after {\n position: absolute;\n left: 50%;\n top: 50%;\n width: 28px;\n height: 4px;\n border-radius: 999px;\n background: currentColor;\n content: '';\n}\n\n.shared-checkout-v2-close span {\n transform: translate(-50%, -50%) rotate(45deg);\n}\n\n.shared-checkout-v2-close span::after {\n transform: translate(-50%, -50%) rotate(90deg);\n}\n\n.shared-checkout-v2-summary {\n padding: 0 17px 0;\n background: #fff;\n}\n\n.shared-checkout-v2-countdown {\n margin: 0;\n min-height: 37px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 12px;\n background: color-mix(in srgb, var(--shared-checkout-v2-theme-color) 10%, #fff);\n color: var(--shared-checkout-v2-theme-color);\n font-size: 16px;\n font-weight: 900;\n line-height: 1.2;\n letter-spacing: 0;\n text-align: center;\n}\n\n.shared-checkout-v2-satisfaction {\n margin: 17px 0 21px;\n color: #2f3034;\n font-size: 19px;\n font-weight: 800;\n line-height: 1.15;\n letter-spacing: 0;\n text-align: center;\n}\n\n.shared-checkout-v2-satisfaction strong {\n color: var(--shared-checkout-v2-theme-color);\n}\n\n.shared-checkout-v2-price-stack {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.shared-checkout-v2-price-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n font-size: 18px;\n line-height: 1.2;\n letter-spacing: 0;\n}\n\n.shared-checkout-v2-price-row.is-muted {\n color: #9d9d9f;\n font-size: 17px;\n font-weight: 500;\n}\n\n.shared-checkout-v2-strike {\n text-decoration-line: line-through;\n text-decoration-thickness: 2px;\n}\n\n.shared-checkout-v2-price-row.is-discount {\n color: #040508;\n font-size: 17px;\n font-weight: 900;\n}\n\n.shared-checkout-v2-price-row.is-discount strong:last-child {\n color: #ff2544;\n}\n\n.shared-checkout-v2-promo {\n min-height: 42px;\n box-sizing: border-box;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n border-radius: 13px;\n background: #f0f0f0;\n padding: 0 14px;\n color: #8d8d90;\n font-size: 16px;\n font-weight: 500;\n line-height: 1.1;\n}\n\n.shared-checkout-v2-promo strong {\n color: #050609;\n font-weight: 900;\n}\n\n.shared-checkout-v2-total {\n margin-top: 20px;\n min-height: 76px;\n border-top: 1px solid #dedede;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n color: #303136;\n}\n\n.shared-checkout-v2-total span {\n font-size: 23px;\n font-weight: 800;\n line-height: 1;\n}\n\n.shared-checkout-v2-total strong {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n color: #303136;\n font-size: 25px;\n font-weight: 900;\n line-height: 1;\n}\n\n.shared-checkout-v2-total svg {\n width: 25px;\n height: 25px;\n fill: none;\n stroke: currentColor;\n stroke-width: 2;\n stroke-linejoin: round;\n}\n\n.shared-checkout-v2-total.is-plain {\n margin-top: 0;\n}\n\n.shared-checkout-v2-saved {\n width: fit-content;\n min-height: 36px;\n box-sizing: border-box;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n margin: -8px 0 9px auto;\n border-radius: 12px;\n background: #ffdce3;\n padding: 0 14px;\n color: #ff2544;\n font-size: 14px;\n font-weight: 900;\n line-height: 1.1;\n letter-spacing: 0;\n}\n\n.shared-checkout-v2-payment {\n min-height: 0;\n display: flex;\n flex-direction: column;\n gap: 12px;\n background: #f2f2f3;\n padding: 9px 17px 32px;\n}\n\n.shared-checkout-v2-methods {\n display: grid;\n grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);\n gap: 42px;\n}\n\n.shared-checkout-v2-method {\n min-width: 0;\n min-height: 74px;\n box-sizing: border-box;\n border: 2px solid #e6e7eb;\n border-radius: 13px;\n background: #fff;\n color: #344376;\n padding: 9px 8px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 6px;\n cursor: pointer;\n}\n\n.shared-checkout-v2-method.is-selected {\n border: 3px solid #050505;\n}\n\n.shared-checkout-v2-tab-wallet {\n width: 58px;\n height: 36px;\n}\n\n.shared-checkout-v2-tab-wallet svg {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n.shared-checkout-v2-tab-wallets {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n}\n\n.shared-checkout-v2-method strong {\n color: #344376;\n font-size: 20px;\n font-weight: 900;\n line-height: 1;\n letter-spacing: 0;\n}\n\n.shared-checkout-v2-tab-brands {\n display: flex;\n align-items: center;\n justify-content: center;\n max-width: 100%;\n gap: 4px;\n}\n\n.shared-checkout-v2-tab-brand {\n min-width: 0;\n width: 24px;\n height: 15px;\n}\n\n.shared-checkout-v2-tab-brand svg {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n.shared-checkout-v2-wallet-panel,\n.shared-checkout-v2-card-panel {\n display: flex;\n flex-direction: column;\n height: 0;\n overflow: hidden;\n opacity: 0;\n pointer-events: none;\n}\n\n.shared-checkout-v2-wallet-panel[hidden],\n.shared-checkout-v2-card-panel[hidden] {\n display: none;\n}\n\n.shared-checkout-v2-wallet-panel {\n align-items: center;\n gap: 22px;\n padding-top: 5px;\n}\n\n.shared-checkout-v2-secure-pill {\n width: fit-content;\n min-height: 47px;\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n margin: 0 auto;\n border-radius: 12px;\n background: #dcffd0;\n padding: 0 22px;\n color: #207733;\n font-size: 18px;\n font-weight: 600;\n line-height: 1.1;\n letter-spacing: 0;\n}\n\n.shared-checkout-v2-secure-pill-icon {\n width: 24px;\n height: 24px;\n flex: none;\n color: #147232;\n}\n\n.shared-checkout-v2-wallet-shell {\n width: 100%;\n min-height: 124px;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.shared-checkout-v2-express-buttons {\n width: 100%;\n}\n\n.shared-checkout-v2-card-panel {\n position: relative;\n gap: 10px;\n margin-inline: -17px;\n background: #f2f2f3;\n padding: 16px 32px 92px;\n}\n\n.shared-checkout-v2-wallet-panel.is-visible,\n.shared-checkout-v2-card-panel.is-visible {\n height: auto;\n overflow: visible;\n opacity: 1;\n pointer-events: auto;\n}\n\n.shared-checkout-v2-wallet-panel:not(.is-visible),\n.shared-checkout-v2-card-panel:not(.is-visible) {\n padding-top: 0;\n padding-bottom: 0;\n}\n\n.shared-checkout-v2-email-field {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 7px;\n border: 0;\n border-radius: 0;\n background: transparent;\n margin-bottom: 14px;\n padding: 0;\n color: #303136;\n font-size: 14px;\n font-weight: 400;\n line-height: 1.2;\n}\n\n.shared-checkout-v2-email-field.is-disabled {\n cursor: default;\n}\n\n.shared-checkout-v2-email-label {\n padding: 0 14px;\n color: #303136;\n font-size: 14px;\n font-weight: 400;\n line-height: 1.2;\n}\n\n.shared-checkout-v2-email-control {\n min-height: 50px;\n box-sizing: border-box;\n display: flex;\n align-items: center;\n border: 1px solid #dedede;\n background: #fff;\n box-shadow: 0 1px 2px rgb(16 24 40 / 8%);\n padding: 0 14px;\n}\n\n.shared-checkout-v2-email-input {\n width: 100%;\n min-height: 48px;\n box-sizing: border-box;\n border: 0;\n background: transparent;\n padding: 0;\n color: #303136;\n font: inherit;\n font-size: 18px;\n font-weight: 400;\n line-height: 1.2;\n outline: none;\n}\n\n.shared-checkout-v2-email-input:disabled {\n color: #74767c;\n opacity: 1;\n -webkit-text-fill-color: #74767c;\n}\n\n.shared-checkout-v2-email-input::placeholder {\n color: #8c909a;\n}\n\n.shared-checkout-v2-card-box {\n overflow: visible;\n border: 0;\n border-radius: 6px;\n background: transparent;\n box-shadow: none;\n}\n\n.shared-checkout-v2-country-field {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 7px;\n margin: 14px 0 0;\n color: #303136;\n font-size: 14px;\n font-weight: 400;\n line-height: 1.2;\n}\n\n.shared-checkout-v2-country-label {\n padding: 0 14px;\n}\n\n.shared-checkout-v2-country-control {\n position: relative;\n display: block;\n}\n\n.shared-checkout-v2-country-control::after {\n position: absolute;\n right: 17px;\n top: 50%;\n width: 8px;\n height: 8px;\n border-right: 2px solid #6f7380;\n border-bottom: 2px solid #6f7380;\n content: '';\n pointer-events: none;\n transform: translateY(-65%) rotate(45deg);\n}\n\n.shared-checkout-v2-country-select {\n width: 100%;\n min-height: 50px;\n box-sizing: border-box;\n appearance: none;\n border: 1px solid #dedede;\n border-radius: 0;\n background: #fff;\n box-shadow: 0 1px 2px rgb(16 24 40 / 8%);\n color: #303136;\n font: inherit;\n font-size: 18px;\n font-weight: 400;\n line-height: 1.2;\n outline: none;\n padding: 0 44px 0 14px;\n}\n\n.shared-checkout-v2-error {\n margin: 0;\n color: #c43737;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.35;\n}\n\n.shared-checkout-v2-card-submit {\n min-height: 60px;\n border: 0;\n border-radius: 11px;\n background: var(--shared-checkout-v2-button-color);\n color: #fff;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 14px;\n font-size: 22px;\n font-weight: 900;\n line-height: 1;\n letter-spacing: 0;\n cursor: pointer;\n}\n\n.shared-checkout-v2-card-submit:disabled {\n cursor: wait;\n opacity: 0.72;\n}\n\n.shared-checkout-v2-card-submit-icon {\n width: 26px;\n height: 26px;\n}\n\n.shared-checkout-v2-card-brands {\n min-height: 34px;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n opacity: 0.95;\n}\n\n.shared-checkout-v2-card-brand {\n width: 42px;\n height: 25px;\n}\n\n.shared-checkout-v2-secure-pill--card {\n position: absolute;\n left: 50%;\n bottom: 16px;\n transform: translateX(-50%);\n white-space: nowrap;\n}\n\n.shared-checkout-v2-sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n.shared-checkout-special-offer-overlay {\n position: fixed;\n inset: 0;\n z-index: 120;\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n padding: 22px;\n background: rgb(0 0 0 / 48%);\n}\n\n.shared-checkout-special-offer-dialog {\n width: min(100%, 386px);\n box-sizing: border-box;\n border-radius: 14px;\n background: #fff;\n padding: 55px 26px 28px;\n color: #303136;\n text-align: center;\n font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n}\n\n.shared-checkout-special-offer-gift {\n display: block;\n width: min(100%, 260px);\n height: auto;\n margin: 0 auto 22px;\n}\n\n.shared-checkout-special-offer-gift--image {\n position: relative;\n width: min(100%, 250px);\n aspect-ratio: 1;\n}\n\n.shared-checkout-special-offer-gift-image {\n display: block;\n width: 100%;\n height: 100%;\n background-position: center;\n background-repeat: no-repeat;\n background-size: contain;\n user-select: none;\n}\n\n.shared-checkout-special-offer-image-discount {\n position: absolute;\n top: 69%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: #1f8f4d;\n font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n font-size: 34px;\n font-weight: 900;\n line-height: 1;\n letter-spacing: 0;\n pointer-events: none;\n white-space: nowrap;\n}\n\n.shared-checkout-special-offer-dialog h2 {\n margin: 0;\n color: #303136;\n font-size: 36px;\n font-weight: 900;\n line-height: 1.1;\n letter-spacing: 0;\n}\n\n.shared-checkout-special-offer-dialog p {\n margin: 26px 0 32px;\n color: #77787e;\n font-size: 24px;\n font-weight: 500;\n line-height: 1.18;\n letter-spacing: 0;\n}\n\n.shared-checkout-special-offer-button {\n width: 100%;\n min-height: 76px;\n border: 0;\n border-radius: 12px;\n background: var(--shared-checkout-v2-button-color);\n color: #fff;\n font-size: 23px;\n font-weight: 900;\n line-height: 1;\n letter-spacing: 0;\n cursor: pointer;\n}\n\n@media (max-width: 429px) {\n .shared-checkout-v2-header {\n min-height: 64px;\n }\n\n .shared-checkout-v2-header h2 {\n font-size: 24px;\n }\n\n .shared-checkout-v2-summary {\n padding-inline: 17px;\n }\n\n .shared-checkout-v2-satisfaction {\n font-size: 19px;\n }\n\n .shared-checkout-v2-methods {\n gap: 42px;\n }\n\n .shared-checkout-v2-method strong {\n font-size: 19px;\n }\n\n .shared-checkout-v2-card-panel {\n padding-inline: 32px;\n }\n}\n\n.shared-checkout-v2-inline { width: 100%; min-width: 0; }\n.shared-checkout-v2-inline .shared-checkout-v2-shell {\n height: auto; max-height: none; overflow: visible; background: #fff; color: #303136;\n}\n.shared-checkout-v2-inline .shared-checkout-v2-shell .shared-checkout-v2-payment {\n padding: 0; margin: 0; background: #fff;\n}\n.shared-checkout-v2-inline .shared-checkout-v2-card-submit {\n border-radius: 12px; background: var(--shared-checkout-v2-button-color); text-transform: none;\n}\n\n.shared-checkout-v2-inline .shared-checkout-v2-card-panel {\n margin: 0; padding: 0; background: #fff;\n}\n.shared-checkout-v2-inline .shared-checkout-v2-email-control { border-radius: 12px; }\n.shared-checkout-v2-inline .shared-checkout-v2-email-input { font-size: 14px; }\n.shared-checkout-v2-inline .shared-checkout-v2-secure-pill { font-size: 11px; min-height: 32px; position: static; transform: none; align-self: center; white-space: normal; }\n";
|
|
1
|
+
export declare const sharedStripeCheckoutV2Styles = "\n.shared-checkout-v2-overlay {\n position: fixed;\n inset: 0;\n z-index: 110;\n display: flex;\n align-items: stretch;\n justify-content: center;\n overflow: hidden;\n background: #fff;\n}\n\n.shared-checkout-v2-dialog {\n width: min(100%, 430px);\n height: 100svh;\n max-height: 100svh;\n overflow: hidden;\n background: #fff;\n}\n\n@media (min-width: 640px) {\n .shared-checkout-v2-overlay:has(.shared-checkout-v2-dialog[data-variant='compact']) {\n align-items: center;\n padding: 24px;\n background: rgb(0 0 0 / 48%);\n }\n\n .shared-checkout-v2-dialog[data-variant='compact'] {\n width: min(100%, 520px);\n height: auto;\n max-height: calc(100svh - 48px);\n border-radius: 24px;\n box-shadow: 0 24px 70px rgb(0 0 0 / 28%);\n }\n}\n\n.shared-checkout-v2-shell[data-variant='compact'] {\n height: auto;\n max-height: min(100svh, 760px);\n}\n\n.shared-checkout-v2-shell[data-variant='compact'] .shared-checkout-v2-header {\n min-height: 88px;\n padding: 0 72px;\n}\n\n.shared-checkout-v2-shell[data-variant='compact'] .shared-checkout-v2-header h2 {\n font-size: 27px;\n}\n\n.shared-checkout-v2-shell[data-variant='compact'] .shared-checkout-v2-close {\n right: 22px;\n left: auto;\n}\n\n.shared-checkout-v2-shell[data-variant='compact'] .shared-checkout-v2-summary {\n padding: 0 36px;\n}\n\n.shared-checkout-v2-shell[data-variant='compact'] .shared-checkout-v2-total {\n min-height: 82px;\n border: 1px solid #ececef;\n border-radius: 16px;\n padding: 0 22px;\n background: #fbfbfc;\n}\n\n.shared-checkout-v2-shell[data-variant='compact'] .shared-checkout-v2-total svg {\n display: none;\n}\n\n.shared-checkout-v2-shell[data-variant='compact'] .shared-checkout-v2-payment {\n padding-right: 36px;\n padding-left: 36px;\n}\n\n.shared-checkout-v2-shell[data-variant='compact'] .shared-checkout-v2-card-submit {\n border-radius: 14px;\n background: var(--shared-checkout-v2-button-color);\n text-transform: none;\n}\n\n.shared-checkout-v2-shell {\n height: 100%;\n max-height: 100svh;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n overflow-x: hidden;\n -webkit-overflow-scrolling: touch;\n background: #fff;\n color: #303136;\n font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n}\n\n.shared-checkout-v2-verifying,\n.shared-checkout-v2-confirmed {\n box-sizing: border-box;\n min-height: 100%;\n margin: 0;\n padding: 32px 24px;\n background: #fff;\n color: #303136;\n font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n font-size: 16px;\n line-height: 1.5;\n}\n\n.shared-checkout-v2-verifying p {\n margin: 0 0 24px;\n}\n\n.shared-checkout-v2-status-button {\n min-height: 48px;\n border: 0;\n border-radius: 11px;\n padding: 12px 18px;\n background: var(--shared-checkout-v2-button-color);\n color: #fff;\n font: inherit;\n font-weight: 700;\n cursor: pointer;\n}\n\n.shared-checkout-v2-status-button:disabled {\n cursor: wait;\n opacity: 0.72;\n}\n\n.shared-checkout-v2-header {\n position: relative;\n min-height: 70px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0 56px;\n background: #fff;\n}\n\n.shared-checkout-v2-header h2 {\n margin: 0;\n color: #020203;\n font-size: 24px;\n font-weight: 900;\n line-height: 1.15;\n letter-spacing: 0;\n text-align: center;\n}\n\n.shared-checkout-v2-close {\n position: absolute;\n left: 16px;\n top: 50%;\n width: 40px;\n height: 40px;\n border: 0;\n border-radius: 999px;\n background: #f2f2f3;\n color: #aaa;\n transform: translateY(-50%);\n cursor: pointer;\n}\n\n.shared-checkout-v2-close span,\n.shared-checkout-v2-close span::after {\n position: absolute;\n left: 50%;\n top: 50%;\n width: 28px;\n height: 4px;\n border-radius: 999px;\n background: currentColor;\n content: '';\n}\n\n.shared-checkout-v2-close span {\n transform: translate(-50%, -50%) rotate(45deg);\n}\n\n.shared-checkout-v2-close span::after {\n transform: translate(-50%, -50%) rotate(90deg);\n}\n\n.shared-checkout-v2-summary {\n padding: 0 17px 0;\n background: #fff;\n}\n\n.shared-checkout-v2-countdown {\n margin: 0;\n min-height: 37px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 12px;\n background: color-mix(in srgb, var(--shared-checkout-v2-theme-color) 10%, #fff);\n color: var(--shared-checkout-v2-theme-color);\n font-size: 16px;\n font-weight: 900;\n line-height: 1.2;\n letter-spacing: 0;\n text-align: center;\n}\n\n.shared-checkout-v2-satisfaction {\n margin: 17px 0 21px;\n color: #2f3034;\n font-size: 19px;\n font-weight: 800;\n line-height: 1.15;\n letter-spacing: 0;\n text-align: center;\n}\n\n.shared-checkout-v2-satisfaction strong {\n color: var(--shared-checkout-v2-theme-color);\n}\n\n.shared-checkout-v2-price-stack {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.shared-checkout-v2-price-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n font-size: 18px;\n line-height: 1.2;\n letter-spacing: 0;\n}\n\n.shared-checkout-v2-price-row.is-muted {\n color: #9d9d9f;\n font-size: 17px;\n font-weight: 500;\n}\n\n.shared-checkout-v2-strike {\n text-decoration-line: line-through;\n text-decoration-thickness: 2px;\n}\n\n.shared-checkout-v2-price-row.is-discount {\n color: #040508;\n font-size: 17px;\n font-weight: 900;\n}\n\n.shared-checkout-v2-price-row.is-discount strong:last-child {\n color: #ff2544;\n}\n\n.shared-checkout-v2-promo {\n min-height: 42px;\n box-sizing: border-box;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n border-radius: 13px;\n background: #f0f0f0;\n padding: 0 14px;\n color: #8d8d90;\n font-size: 16px;\n font-weight: 500;\n line-height: 1.1;\n}\n\n.shared-checkout-v2-promo strong {\n color: #050609;\n font-weight: 900;\n}\n\n.shared-checkout-v2-total {\n margin-top: 20px;\n min-height: 76px;\n border-top: 1px solid #dedede;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n color: #303136;\n}\n\n.shared-checkout-v2-total span {\n font-size: 23px;\n font-weight: 800;\n line-height: 1;\n}\n\n.shared-checkout-v2-total strong {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n color: #303136;\n font-size: 25px;\n font-weight: 900;\n line-height: 1;\n}\n\n.shared-checkout-v2-total svg {\n width: 25px;\n height: 25px;\n fill: none;\n stroke: currentColor;\n stroke-width: 2;\n stroke-linejoin: round;\n}\n\n.shared-checkout-v2-total.is-plain {\n margin-top: 0;\n}\n\n.shared-checkout-v2-saved {\n width: fit-content;\n min-height: 36px;\n box-sizing: border-box;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n margin: -8px 0 9px auto;\n border-radius: 12px;\n background: #ffdce3;\n padding: 0 14px;\n color: #ff2544;\n font-size: 14px;\n font-weight: 900;\n line-height: 1.1;\n letter-spacing: 0;\n}\n\n.shared-checkout-v2-payment {\n min-height: 0;\n display: flex;\n flex-direction: column;\n gap: 12px;\n background: #f2f2f3;\n padding: 9px 17px 32px;\n}\n\n.shared-checkout-v2-methods {\n display: grid;\n grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);\n gap: 42px;\n}\n\n.shared-checkout-v2-methods[data-paypal='true'] {\n grid-template-columns: none;\n grid-auto-columns: minmax(0, 1fr);\n grid-auto-flow: column;\n gap: 10px;\n}\n\n.shared-checkout-v2-methods[data-paypal='true'] .shared-checkout-v2-tab-wallets {\n max-width: 100%;\n flex-wrap: wrap;\n gap: 2px;\n}\n\n.shared-checkout-v2-method:disabled { cursor: default; opacity: 0.6; }\n\n.shared-checkout-v2-method {\n min-width: 0;\n min-height: 74px;\n box-sizing: border-box;\n border: 2px solid #e6e7eb;\n border-radius: 13px;\n background: #fff;\n color: #344376;\n padding: 9px 8px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 6px;\n cursor: pointer;\n}\n\n.shared-checkout-v2-method.is-selected {\n border: 3px solid #050505;\n}\n\n.shared-checkout-v2-tab-wallet {\n width: 58px;\n height: 36px;\n}\n\n.shared-checkout-v2-tab-wallet svg {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n.shared-checkout-v2-tab-wallets {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n}\n\n.shared-checkout-v2-method strong {\n color: #344376;\n font-size: 20px;\n font-weight: 900;\n line-height: 1;\n letter-spacing: 0;\n}\n\n.shared-checkout-v2-tab-brands {\n display: flex;\n align-items: center;\n justify-content: center;\n max-width: 100%;\n gap: 4px;\n}\n\n.shared-checkout-v2-tab-brand {\n min-width: 0;\n width: 24px;\n height: 15px;\n}\n\n.shared-checkout-v2-tab-brand svg {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n.shared-checkout-v2-wallet-panel,\n.shared-checkout-v2-card-panel {\n display: flex;\n flex-direction: column;\n height: 0;\n overflow: hidden;\n opacity: 0;\n pointer-events: none;\n}\n\n.shared-checkout-v2-wallet-panel[hidden],\n.shared-checkout-v2-card-panel[hidden] {\n display: none;\n}\n\n.shared-checkout-v2-wallet-panel {\n align-items: center;\n gap: 22px;\n padding-top: 5px;\n}\n\n.shared-checkout-v2-secure-pill {\n width: fit-content;\n min-height: 47px;\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n margin: 0 auto;\n border-radius: 12px;\n background: #dcffd0;\n padding: 0 22px;\n color: #207733;\n font-size: 18px;\n font-weight: 600;\n line-height: 1.1;\n letter-spacing: 0;\n}\n\n.shared-checkout-v2-secure-pill-icon {\n width: 24px;\n height: 24px;\n flex: none;\n color: #147232;\n}\n\n.shared-checkout-v2-wallet-shell {\n width: 100%;\n min-height: 124px;\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n\n.shared-checkout-v2-express-buttons {\n width: 100%;\n}\n\n.shared-checkout-v2-card-panel {\n position: relative;\n gap: 10px;\n margin-inline: -17px;\n background: #f2f2f3;\n padding: 16px 32px 92px;\n}\n\n.shared-checkout-v2-wallet-panel.is-visible,\n.shared-checkout-v2-card-panel.is-visible {\n height: auto;\n overflow: visible;\n opacity: 1;\n pointer-events: auto;\n}\n\n.shared-checkout-v2-wallet-panel:not(.is-visible),\n.shared-checkout-v2-card-panel:not(.is-visible) {\n padding-top: 0;\n padding-bottom: 0;\n}\n\n.shared-checkout-v2-email-field {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 7px;\n border: 0;\n border-radius: 0;\n background: transparent;\n margin-bottom: 14px;\n padding: 0;\n color: #303136;\n font-size: 14px;\n font-weight: 400;\n line-height: 1.2;\n}\n\n.shared-checkout-v2-email-field.is-disabled {\n cursor: default;\n}\n\n.shared-checkout-v2-email-label {\n padding: 0 14px;\n color: #303136;\n font-size: 14px;\n font-weight: 400;\n line-height: 1.2;\n}\n\n.shared-checkout-v2-email-control {\n min-height: 50px;\n box-sizing: border-box;\n display: flex;\n align-items: center;\n border: 1px solid #dedede;\n background: #fff;\n box-shadow: 0 1px 2px rgb(16 24 40 / 8%);\n padding: 0 14px;\n}\n\n.shared-checkout-v2-email-input {\n width: 100%;\n min-height: 48px;\n box-sizing: border-box;\n border: 0;\n background: transparent;\n padding: 0;\n color: #303136;\n font: inherit;\n font-size: 18px;\n font-weight: 400;\n line-height: 1.2;\n outline: none;\n}\n\n.shared-checkout-v2-email-input:disabled {\n color: #74767c;\n opacity: 1;\n -webkit-text-fill-color: #74767c;\n}\n\n.shared-checkout-v2-email-input::placeholder {\n color: #8c909a;\n}\n\n.shared-checkout-v2-card-box {\n overflow: visible;\n border: 0;\n border-radius: 6px;\n background: transparent;\n box-shadow: none;\n}\n\n.shared-checkout-v2-country-field {\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 7px;\n margin: 14px 0 0;\n color: #303136;\n font-size: 14px;\n font-weight: 400;\n line-height: 1.2;\n}\n\n.shared-checkout-v2-country-label {\n padding: 0 14px;\n}\n\n.shared-checkout-v2-country-control {\n position: relative;\n display: block;\n}\n\n.shared-checkout-v2-country-control::after {\n position: absolute;\n right: 17px;\n top: 50%;\n width: 8px;\n height: 8px;\n border-right: 2px solid #6f7380;\n border-bottom: 2px solid #6f7380;\n content: '';\n pointer-events: none;\n transform: translateY(-65%) rotate(45deg);\n}\n\n.shared-checkout-v2-country-select {\n width: 100%;\n min-height: 50px;\n box-sizing: border-box;\n appearance: none;\n border: 1px solid #dedede;\n border-radius: 0;\n background: #fff;\n box-shadow: 0 1px 2px rgb(16 24 40 / 8%);\n color: #303136;\n font: inherit;\n font-size: 18px;\n font-weight: 400;\n line-height: 1.2;\n outline: none;\n padding: 0 44px 0 14px;\n}\n\n.shared-checkout-v2-error {\n margin: 0;\n color: #c43737;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.35;\n}\n\n.shared-checkout-v2-card-submit {\n min-height: 60px;\n border: 0;\n border-radius: 11px;\n background: var(--shared-checkout-v2-button-color);\n color: #fff;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 14px;\n font-size: 22px;\n font-weight: 900;\n line-height: 1;\n letter-spacing: 0;\n cursor: pointer;\n}\n\n.shared-checkout-v2-card-submit:disabled {\n cursor: wait;\n opacity: 0.72;\n}\n\n.shared-checkout-v2-card-submit-icon {\n width: 26px;\n height: 26px;\n}\n\n.shared-checkout-v2-card-brands {\n min-height: 34px;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n opacity: 0.95;\n}\n\n.shared-checkout-v2-card-brand {\n width: 42px;\n height: 25px;\n}\n\n.shared-checkout-v2-secure-pill--card {\n position: absolute;\n left: 50%;\n bottom: 16px;\n transform: translateX(-50%);\n white-space: nowrap;\n}\n\n.shared-checkout-v2-sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n.shared-checkout-special-offer-overlay {\n position: fixed;\n inset: 0;\n z-index: 120;\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n padding: 22px;\n background: rgb(0 0 0 / 48%);\n}\n\n.shared-checkout-special-offer-dialog {\n width: min(100%, 386px);\n box-sizing: border-box;\n border-radius: 14px;\n background: #fff;\n padding: 55px 26px 28px;\n color: #303136;\n text-align: center;\n font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n}\n\n.shared-checkout-special-offer-gift {\n display: block;\n width: min(100%, 260px);\n height: auto;\n margin: 0 auto 22px;\n}\n\n.shared-checkout-special-offer-gift--image {\n position: relative;\n width: min(100%, 250px);\n aspect-ratio: 1;\n}\n\n.shared-checkout-special-offer-gift-image {\n display: block;\n width: 100%;\n height: 100%;\n background-position: center;\n background-repeat: no-repeat;\n background-size: contain;\n user-select: none;\n}\n\n.shared-checkout-special-offer-image-discount {\n position: absolute;\n top: 69%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: #1f8f4d;\n font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n font-size: 34px;\n font-weight: 900;\n line-height: 1;\n letter-spacing: 0;\n pointer-events: none;\n white-space: nowrap;\n}\n\n.shared-checkout-special-offer-dialog h2 {\n margin: 0;\n color: #303136;\n font-size: 36px;\n font-weight: 900;\n line-height: 1.1;\n letter-spacing: 0;\n}\n\n.shared-checkout-special-offer-dialog p {\n margin: 26px 0 32px;\n color: #77787e;\n font-size: 24px;\n font-weight: 500;\n line-height: 1.18;\n letter-spacing: 0;\n}\n\n.shared-checkout-special-offer-button {\n width: 100%;\n min-height: 76px;\n border: 0;\n border-radius: 12px;\n background: var(--shared-checkout-v2-button-color);\n color: #fff;\n font-size: 23px;\n font-weight: 900;\n line-height: 1;\n letter-spacing: 0;\n cursor: pointer;\n}\n\n@media (max-width: 429px) {\n .shared-checkout-v2-header {\n min-height: 64px;\n }\n\n .shared-checkout-v2-header h2 {\n font-size: 24px;\n }\n\n .shared-checkout-v2-summary {\n padding-inline: 17px;\n }\n\n .shared-checkout-v2-satisfaction {\n font-size: 19px;\n }\n\n .shared-checkout-v2-methods {\n gap: 42px;\n }\n\n .shared-checkout-v2-method strong {\n font-size: 19px;\n }\n\n .shared-checkout-v2-card-panel {\n padding-inline: 32px;\n }\n}\n\n.shared-checkout-v2-inline { width: 100%; min-width: 0; }\n.shared-checkout-v2-inline .shared-checkout-v2-shell {\n height: auto; max-height: none; overflow: visible; background: #fff; color: #303136;\n}\n.shared-checkout-v2-inline .shared-checkout-v2-shell .shared-checkout-v2-payment {\n padding: 0; margin: 0; background: #fff;\n}\n.shared-checkout-v2-inline .shared-checkout-v2-card-submit {\n border-radius: 12px; background: var(--shared-checkout-v2-button-color); text-transform: none;\n}\n\n.shared-checkout-v2-inline .shared-checkout-v2-card-panel {\n margin: 0; padding: 0; background: #fff;\n}\n.shared-checkout-v2-inline .shared-checkout-v2-email-control { border-radius: 12px; }\n.shared-checkout-v2-inline .shared-checkout-v2-email-input { font-size: 14px; }\n.shared-checkout-v2-inline .shared-checkout-v2-secure-pill { font-size: 11px; min-height: 32px; position: static; transform: none; align-self: center; white-space: normal; }\n";
|
|
@@ -349,6 +349,21 @@ export const sharedStripeCheckoutV2Styles = `
|
|
|
349
349
|
gap: 42px;
|
|
350
350
|
}
|
|
351
351
|
|
|
352
|
+
.shared-checkout-v2-methods[data-paypal='true'] {
|
|
353
|
+
grid-template-columns: none;
|
|
354
|
+
grid-auto-columns: minmax(0, 1fr);
|
|
355
|
+
grid-auto-flow: column;
|
|
356
|
+
gap: 10px;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.shared-checkout-v2-methods[data-paypal='true'] .shared-checkout-v2-tab-wallets {
|
|
360
|
+
max-width: 100%;
|
|
361
|
+
flex-wrap: wrap;
|
|
362
|
+
gap: 2px;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.shared-checkout-v2-method:disabled { cursor: default; opacity: 0.6; }
|
|
366
|
+
|
|
352
367
|
.shared-checkout-v2-method {
|
|
353
368
|
min-width: 0;
|
|
354
369
|
min-height: 74px;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CSSProperties } from 'react';
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
2
|
import type { Stripe } from '@stripe/stripe-js';
|
|
3
3
|
import { type StripeSubscriptionCheckoutAnalyticsContext } from '../services/checkoutCompletionAnalytics.service.js';
|
|
4
4
|
import type { SharedStripeCheckoutV2Copy } from './checkout-v2.content.js';
|
|
@@ -38,6 +38,9 @@ export type SharedStripeCheckoutV2DialogProps = {
|
|
|
38
38
|
onPaymentInfoSubmitted?: () => void | Promise<void>;
|
|
39
39
|
onSuccess?: () => void | Promise<void>;
|
|
40
40
|
paymentMethodLabels?: readonly string[];
|
|
41
|
+
/** Native PayPal checkout for the selected Offer Set binding, inside the method picker. */
|
|
42
|
+
paypalCheckout?: ReactNode;
|
|
43
|
+
paypalPending?: boolean;
|
|
41
44
|
paypalButtonLabel: string;
|
|
42
45
|
paypalLabel: string;
|
|
43
46
|
promoActive?: boolean;
|
|
@@ -3,5 +3,5 @@ export declare const getFixedSupportedBillingCountry: (supportedCountries: reado
|
|
|
3
3
|
type SharedStripeCheckoutV2FormProps = Omit<SharedStripeCheckoutV2DialogProps, 'stripePromise' | 'onClose'> & {
|
|
4
4
|
onClose: () => void;
|
|
5
5
|
};
|
|
6
|
-
export declare function SharedStripeCheckoutV2CheckoutSessionForm({ copy, amountCents, buttonColor, cardSubmitLabel, closeAriaLabel, countdownLabel, checkoutAnalytics, checkoutSessionId, customerEmail, customerEmailEditable: allowEmailEditing, customerEmailLabel, customerEmailPlaceholder, customerEmailInvalidMessage, customerName, discountAmountLabel, discountLabel, discountPercent, initialWalletAvailable, onClose, onWalletCancel, onCustomerEmailChange, onCustomerEmailCommit, onError, onInactiveCheckoutSession, onPaymentInfoSubmitted, onSuccess, paymentMethodLabels, promoActive, processingLabel, promoCode, promoCodeLabel, remainingSeconds, returnUrl, savedLabel, secureLabel, satisfactionLabel, satisfactionPercent, summaryLabel, supportedCountries, themeColor, title, totalLabel, totalValue, unsupportedCountryMessage, walletButtonLabel, originalPriceLabel, originalPriceValue, variant, presentation, }: SharedStripeCheckoutV2FormProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export declare function SharedStripeCheckoutV2CheckoutSessionForm({ copy, amountCents, buttonColor, cardSubmitLabel, closeAriaLabel, countdownLabel, checkoutAnalytics, checkoutSessionId, customerEmail, customerEmailEditable: allowEmailEditing, customerEmailLabel, customerEmailPlaceholder, customerEmailInvalidMessage, customerName, discountAmountLabel, discountLabel, discountPercent, initialWalletAvailable, onClose, onWalletCancel, onCustomerEmailChange, onCustomerEmailCommit, onError, onInactiveCheckoutSession, onPaymentInfoSubmitted, onSuccess, paymentMethodLabels, paypalCheckout, paypalPending, paypalLabel, promoActive, processingLabel, promoCode, promoCodeLabel, remainingSeconds, returnUrl, savedLabel, secureLabel, satisfactionLabel, satisfactionPercent, summaryLabel, supportedCountries, themeColor, title, totalLabel, totalValue, unsupportedCountryMessage, walletButtonLabel, originalPriceLabel, originalPriceValue, variant, presentation, }: SharedStripeCheckoutV2FormProps): import("react/jsx-runtime").JSX.Element;
|
|
7
7
|
export {};
|
|
@@ -11,6 +11,7 @@ import { isInactiveCheckoutSessionError } from '../hooks/useStripeSubscriptionCh
|
|
|
11
11
|
import { getStripeWalletPaymentMethodOrder, usePlatformWalletPaymentMethods } from './walletPlatform.js';
|
|
12
12
|
import { trackPaidStripeSubscriptionCheckoutCompleted, trackStripeSubscriptionPaymentInfoSubmitted } from '../services/checkoutCompletionAnalytics.service.js';
|
|
13
13
|
import { trackCheckoutFailureShown } from '../../../services/checkoutObservability.service.js';
|
|
14
|
+
import { SharedCheckoutPaymentMethods } from './SharedCheckoutPaymentMethods.internal.js';
|
|
14
15
|
import { SharedStripeCheckoutV2Summary } from './SharedStripeCheckoutV2Summary.internal.js';
|
|
15
16
|
import { useCheckoutEmail } from './useCheckoutEmail.internal.js';
|
|
16
17
|
import { defaultCheckoutV2Copy } from './checkout-v2.content.js';
|
|
@@ -22,15 +23,16 @@ function getFriendlyCardErrorMessage(error, fallback) {
|
|
|
22
23
|
? error.message
|
|
23
24
|
: fallback;
|
|
24
25
|
}
|
|
25
|
-
export function SharedStripeCheckoutV2CheckoutSessionForm({ copy = defaultCheckoutV2Copy, amountCents, buttonColor, cardSubmitLabel, closeAriaLabel, countdownLabel, checkoutAnalytics, checkoutSessionId, customerEmail, customerEmailEditable: allowEmailEditing = false, customerEmailLabel = 'Email', customerEmailPlaceholder = 'Enter your email', customerEmailInvalidMessage = 'Enter a valid email address.', customerName, discountAmountLabel, discountLabel, discountPercent, initialWalletAvailable, onClose, onWalletCancel, 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, variant = 'standard', presentation = 'dialog', }) {
|
|
26
|
+
export function SharedStripeCheckoutV2CheckoutSessionForm({ copy = defaultCheckoutV2Copy, amountCents, buttonColor, cardSubmitLabel, closeAriaLabel, countdownLabel, checkoutAnalytics, checkoutSessionId, customerEmail, customerEmailEditable: allowEmailEditing = false, customerEmailLabel = 'Email', customerEmailPlaceholder = 'Enter your email', customerEmailInvalidMessage = 'Enter a valid email address.', customerName, discountAmountLabel, discountLabel, discountPercent, initialWalletAvailable, onClose, onWalletCancel, onCustomerEmailChange, onCustomerEmailCommit, onError, onInactiveCheckoutSession, onPaymentInfoSubmitted, onSuccess, paymentMethodLabels = defaultPaymentMethodLabels, paypalCheckout, paypalPending = false, paypalLabel, promoActive, processingLabel, promoCode, promoCodeLabel, remainingSeconds, returnUrl, savedLabel, secureLabel, satisfactionLabel, satisfactionPercent, summaryLabel, supportedCountries, themeColor, title, totalLabel, totalValue, unsupportedCountryMessage, walletButtonLabel, originalPriceLabel, originalPriceValue, variant = 'standard', presentation = 'dialog', }) {
|
|
26
27
|
const checkoutState = useCheckout();
|
|
27
28
|
const walletPaymentMethods = usePlatformWalletPaymentMethods();
|
|
28
|
-
const [selectedMethod, setSelectedMethod] = useState('wallet');
|
|
29
|
+
const [selectedMethod, setSelectedMethod] = useState(paypalPending ? 'paypal' : 'wallet');
|
|
29
30
|
const [walletAvailable, setWalletAvailable] = useState(initialWalletAvailable !== null && initialWalletAvailable !== void 0 ? initialWalletAvailable : null);
|
|
30
31
|
const effectiveWalletAvailable = initialWalletAvailable === true && walletAvailable !== false
|
|
31
32
|
? true
|
|
32
33
|
: walletAvailable;
|
|
33
34
|
const [submitting, setSubmitting] = useState(false);
|
|
35
|
+
const [walletConfirming, setWalletConfirming] = useState(false);
|
|
34
36
|
const [completed, setCompleted] = useState(false);
|
|
35
37
|
const [providerConfirmed, setProviderConfirmed] = useState(false);
|
|
36
38
|
const submitInFlightRef = useRef(false);
|
|
@@ -68,7 +70,9 @@ export function SharedStripeCheckoutV2CheckoutSessionForm({ copy = defaultChecko
|
|
|
68
70
|
&& rejectedEmail === null && resolvedCustomerEmail === initialCustomerEmail;
|
|
69
71
|
const showWalletMethod = presentation !== 'inline' && walletEmailReady
|
|
70
72
|
&& effectiveWalletAvailable !== false && walletPaymentMethods.length > 0;
|
|
71
|
-
const
|
|
73
|
+
const showPayPalMethod = presentation === 'dialog' && Boolean(paypalCheckout);
|
|
74
|
+
const effectiveSelectedMethod = showPayPalMethod && (paypalPending || selectedMethod === 'paypal') ? 'paypal'
|
|
75
|
+
: showWalletMethod && selectedMethod === 'wallet' ? 'wallet' : 'card';
|
|
72
76
|
const expressCheckoutOptions = useMemo(() => {
|
|
73
77
|
return {
|
|
74
78
|
buttonHeight: 55,
|
|
@@ -145,11 +149,12 @@ export function SharedStripeCheckoutV2CheckoutSessionForm({ copy = defaultChecko
|
|
|
145
149
|
onError === null || onError === void 0 ? void 0 : onError(message);
|
|
146
150
|
};
|
|
147
151
|
const selectWalletMethod = () => {
|
|
148
|
-
if (showWalletMethod) {
|
|
152
|
+
if (showWalletMethod && !paypalPending && !submitting) {
|
|
149
153
|
setSelectedMethod('wallet');
|
|
150
154
|
}
|
|
151
155
|
};
|
|
152
|
-
const selectCardMethod = () =>
|
|
156
|
+
const selectCardMethod = () => { if (!paypalPending && !submitting)
|
|
157
|
+
setSelectedMethod('card'); };
|
|
153
158
|
const handleWalletAvailabilityChange = (available) => {
|
|
154
159
|
setWalletAvailable(available);
|
|
155
160
|
if (!available && selectedMethod === 'wallet') {
|
|
@@ -190,6 +195,8 @@ export function SharedStripeCheckoutV2CheckoutSessionForm({ copy = defaultChecko
|
|
|
190
195
|
}, [effectiveSelectedMethod]);
|
|
191
196
|
const handleSubmit = async (event) => {
|
|
192
197
|
event.preventDefault();
|
|
198
|
+
if (effectiveSelectedMethod === 'paypal')
|
|
199
|
+
return;
|
|
193
200
|
if (submitInFlightRef.current || (emailSessionChanged && !providerConfirmed)) {
|
|
194
201
|
return;
|
|
195
202
|
}
|
|
@@ -263,22 +270,18 @@ export function SharedStripeCheckoutV2CheckoutSessionForm({ copy = defaultChecko
|
|
|
263
270
|
return _jsxs("form", { onSubmit: handleSubmit, className: 'shared-checkout-v2-verifying', style: checkoutStyle, children: [_jsx("p", { role: error ? 'alert' : 'status', children: error || copy.verifyingPayment }), _jsx("button", { type: 'submit', disabled: submitting, className: 'shared-checkout-v2-status-button', children: copy.checkPaymentStatus })] });
|
|
264
271
|
if (completed)
|
|
265
272
|
return _jsx("div", { role: 'status', className: 'shared-checkout-v2-confirmed', style: checkoutStyle, children: copy.paymentConfirmed });
|
|
266
|
-
return (_jsxs("div", { className: 'shared-checkout-v2-shell', "data-variant": variant, style: checkoutStyle, children: [presentation === 'dialog' ? _jsxs(_Fragment, { children: [_jsxs("header", { className: 'shared-checkout-v2-header', children: [_jsx("button", { type: 'button', className: 'shared-checkout-v2-close', onClick: onClose, "aria-label": closeAriaLabel, children: _jsx("span", { "aria-hidden": 'true' }) }), _jsx("h2", { id: 'shared-checkout-v2-title', children: title })] }), _jsx(SharedStripeCheckoutV2Summary, { countdownUnit: copy.countdownUnit, discountPercent, countdownLabel, remainingSeconds, variant, satisfactionPercent, satisfactionLabel, originalPriceLabel, originalPriceValue, discountLabel, discountAmountLabel, promoCodeLabel, promoCode, totalLabel, totalValue, savedLabel, showPromo })] }) : null, _jsxs("form", { className: 'shared-checkout-v2-payment', onSubmit: (event) => void handleSubmit(event), children: [showWalletMethod ? (
|
|
267
|
-
'shared-checkout-v2-method',
|
|
268
|
-
effectiveSelectedMethod === 'wallet' ? 'is-selected' : '',
|
|
269
|
-
]
|
|
270
|
-
.filter(Boolean)
|
|
271
|
-
.join(' '), onClick: selectWalletMethod, role: 'tab', "aria-selected": effectiveSelectedMethod === 'wallet', children: [_jsxs("span", { className: 'shared-checkout-v2-tab-wallets', "aria-hidden": 'true', children: [walletPaymentMethods.includes('applePay') ? (_jsx(PaymentBrandMark, { label: 'Apple Pay', className: 'shared-checkout-v2-tab-wallet' })) : null, walletPaymentMethods.includes('googlePay') ? (_jsx(PaymentBrandMark, { label: 'Google Pay', className: 'shared-checkout-v2-tab-wallet' })) : null] }), _jsx("span", { className: 'shared-checkout-v2-sr-only', children: walletAriaLabel })] }), _jsxs("button", { type: 'button', className: [
|
|
272
|
-
'shared-checkout-v2-method',
|
|
273
|
-
effectiveSelectedMethod === 'card' ? 'is-selected' : '',
|
|
274
|
-
]
|
|
275
|
-
.filter(Boolean)
|
|
276
|
-
.join(' '), onClick: selectCardMethod, role: 'tab', "aria-selected": effectiveSelectedMethod === 'card', children: [_jsx("strong", { children: copy.cardLabel }), _jsx("span", { className: 'shared-checkout-v2-tab-brands', "aria-hidden": 'true', children: cardBrandLabels.slice(0, 4).map((label) => (_jsx(PaymentBrandMark, { label: label, className: 'shared-checkout-v2-tab-brand' }, label))) })] })] })) : null, presentation === 'dialog' ? _jsxs("div", { hidden: effectiveSelectedMethod !== 'wallet', className: [
|
|
273
|
+
return (_jsxs("div", { className: 'shared-checkout-v2-shell', "data-variant": variant, style: checkoutStyle, children: [presentation === 'dialog' ? _jsxs(_Fragment, { children: [_jsxs("header", { className: 'shared-checkout-v2-header', children: [_jsx("button", { type: 'button', className: 'shared-checkout-v2-close', onClick: onClose, "aria-label": closeAriaLabel, children: _jsx("span", { "aria-hidden": 'true' }) }), _jsx("h2", { id: 'shared-checkout-v2-title', children: title })] }), effectiveSelectedMethod !== 'paypal' ? _jsx(SharedStripeCheckoutV2Summary, { countdownUnit: copy.countdownUnit, discountPercent, countdownLabel, remainingSeconds, variant, satisfactionPercent, satisfactionLabel, originalPriceLabel, originalPriceValue, discountLabel, discountAmountLabel, promoCodeLabel, promoCode, totalLabel, totalValue, savedLabel, showPromo }) : null] }) : null, _jsxs("form", { className: 'shared-checkout-v2-payment', onSubmit: (event) => void handleSubmit(event), children: [showWalletMethod || showPayPalMethod ? (_jsx(SharedCheckoutPaymentMethods, { showWalletMethod: showWalletMethod, showPayPalMethod: showPayPalMethod, selectedMethod: effectiveSelectedMethod, disabled: paypalPending || submitting || walletConfirming, paymentMethodLabel: copy.paymentMethodLabel, cardLabel: copy.cardLabel, paypalLabel: paypalLabel, walletAriaLabel: walletAriaLabel, walletPaymentMethods: walletPaymentMethods, cardBrandLabels: cardBrandLabels, onWalletSelect: selectWalletMethod, onCardSelect: selectCardMethod, onPayPalSelect: () => setSelectedMethod('paypal') })) : null, effectiveSelectedMethod === 'paypal' ? _jsx("div", { role: 'tabpanel', "aria-label": paypalLabel, children: paypalCheckout }) : null, presentation === 'dialog' ? _jsxs("div", { hidden: effectiveSelectedMethod !== 'wallet', className: [
|
|
277
274
|
'shared-checkout-v2-wallet-panel',
|
|
278
275
|
effectiveSelectedMethod === 'wallet' ? 'is-visible' : '',
|
|
279
276
|
]
|
|
280
277
|
.filter(Boolean)
|
|
281
|
-
.join(' '), children: [_jsxs("p", { className: 'shared-checkout-v2-secure-pill', children: [_jsx(SecurityLockIcon, { className: 'shared-checkout-v2-secure-pill-icon' }), secureLabel] }), _jsxs("div", { className: 'shared-checkout-v2-wallet-shell', "aria-label": walletAriaLabel, children: [_jsx(StripeCheckoutExpressCheckoutButton, { amountCents: amountCents, availabilityPaymentMethods: walletPaymentMethods, beforeConfirm:
|
|
278
|
+
.join(' '), children: [_jsxs("p", { className: 'shared-checkout-v2-secure-pill', children: [_jsx(SecurityLockIcon, { className: 'shared-checkout-v2-secure-pill-icon' }), secureLabel] }), _jsxs("div", { className: 'shared-checkout-v2-wallet-shell', "aria-label": walletAriaLabel, children: [_jsx(StripeCheckoutExpressCheckoutButton, { amountCents: amountCents, availabilityPaymentMethods: walletPaymentMethods, beforeConfirm: async () => {
|
|
279
|
+
setWalletConfirming(true);
|
|
280
|
+
const email = await ensureCustomerEmail();
|
|
281
|
+
if (!email)
|
|
282
|
+
setWalletConfirming(false);
|
|
283
|
+
return email;
|
|
284
|
+
}, checkoutAnalytics: checkoutAnalytics, checkoutSessionId: checkoutSessionId, className: 'shared-checkout-v2-express-buttons', confirmEmail: false, customerEmail: resolvedCustomerEmail, customerName: customerName, initialAvailable: initialWalletAvailable, keepInitialAvailableOnReady: initialWalletAvailable === true, onAvailabilityChange: handleWalletAvailabilityChange, onCancel: () => { setWalletConfirming(false); onWalletCancel === null || onWalletCancel === void 0 ? void 0 : onWalletCancel(); }, onError: (message) => setSharedError(message, true), onPaymentInfoSubmitted: onPaymentInfoSubmitted, onPaymentRejected: () => { setWalletConfirming(false); setSelectedMethod('card'); }, onSuccess: () => trackCheckoutSuccess(), onEmbeddedComplete: () => setCompleted(true), onVerificationPending: () => setProviderConfirmed(true), options: expressCheckoutOptions, returnUrl: returnUrl, summaryLabel: expressCheckoutSummaryLabel, supportedCountries: normalizedSupportedCountries, unsupportedCountryMessage: unsupportedCountryMessage }), _jsx("span", { className: 'shared-checkout-v2-sr-only', children: walletAriaLabel })] })] }) : null, _jsxs("div", { hidden: effectiveSelectedMethod !== 'card', className: [
|
|
282
285
|
'shared-checkout-v2-card-panel',
|
|
283
286
|
effectiveSelectedMethod === 'card' ? 'is-visible' : '',
|
|
284
287
|
]
|
|
@@ -2,14 +2,23 @@ import { type FunnelExperimentDefinition, type FunnelManifestExperiment, type Of
|
|
|
2
2
|
import type { BillingDiscountCatalog, BillingDiscountList, BillingFallbackPlanConfig, BillingPlanCatalog } from '../config/billing.config.js';
|
|
3
3
|
import { type PaywallPlan } from './paywallPlans.service.js';
|
|
4
4
|
export type PublishedBillingPlan = BillingFallbackPlanConfig & OfferSetRuntimePlanOverrides;
|
|
5
|
+
export type PayPalBillingOption = {
|
|
6
|
+
paymentProfileId: string;
|
|
7
|
+
clientId: string;
|
|
8
|
+
providerPlanId: string;
|
|
9
|
+
amountMinor: number;
|
|
10
|
+
currency: string;
|
|
11
|
+
summary: string;
|
|
12
|
+
};
|
|
5
13
|
export type ResolvedPublishedBillingRuntime = {
|
|
14
|
+
paypalOptionsByPlanKey?: Readonly<Record<string, PayPalBillingOption>>;
|
|
6
15
|
revisionId: string | null;
|
|
7
16
|
sourceVersionId: string | null;
|
|
8
17
|
paymentsApiVersion: 'v2';
|
|
9
18
|
mode: RuntimeMode;
|
|
10
19
|
offerSetId: string;
|
|
11
20
|
paymentProfileId: string | null;
|
|
12
|
-
provider: 'stripe' | 'solidgate';
|
|
21
|
+
provider: 'stripe' | 'solidgate' | 'paypal';
|
|
13
22
|
stripePublishableKey: string | null;
|
|
14
23
|
experimentId: string | null;
|
|
15
24
|
variantKey: string | null;
|
|
@@ -10,7 +10,7 @@ export const EMPTY_PUBLISHED_BILLING_RUNTIME = Object.freeze({
|
|
|
10
10
|
unavailablePlanKeys: [],
|
|
11
11
|
});
|
|
12
12
|
export const resolvePublishedBillingRuntime = ({ config, featureFlags, mode, search = '', defaultOfferSetId, locale, baseCatalogsByMode, fallbackDiscounts, resolvedExperiments = resolvePublishedRuntimeExperiments(config), }) => {
|
|
13
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
13
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
|
|
14
14
|
const offerSets = (_a = config === null || config === void 0 ? void 0 : config.offerSets) !== null && _a !== void 0 ? _a : [];
|
|
15
15
|
const selection = resolvePublishedOfferSetSelection({
|
|
16
16
|
offerSets,
|
|
@@ -37,19 +37,36 @@ export const resolvePublishedBillingRuntime = ({ config, featureFlags, mode, sea
|
|
|
37
37
|
const discountList = runtime.getDiscountList(mode);
|
|
38
38
|
const provider = runtime.getPaymentProvider(mode);
|
|
39
39
|
const paymentProviderConfig = (_c = (_b = runtime.offerSet) === null || _b === void 0 ? void 0 : _b.paymentProviderConfigByMode) === null || _c === void 0 ? void 0 : _c[mode];
|
|
40
|
+
const paypalOptionsByPlanKey = {};
|
|
41
|
+
for (const item of ((_d = runtime.offerSet) === null || _d === void 0 ? void 0 : _d.items) || []) {
|
|
42
|
+
const binding = (_e = item.paymentProfileBindings) === null || _e === void 0 ? void 0 : _e.find(candidate => candidate.provider === 'paypal');
|
|
43
|
+
const profileId = (binding === null || binding === void 0 ? void 0 : binding.paymentProfileId) || (provider === 'paypal' ? (_f = runtime.offerSet) === null || _f === void 0 ? void 0 : _f.paymentProfileId : null);
|
|
44
|
+
if (!profileId)
|
|
45
|
+
continue;
|
|
46
|
+
const mapping = binding ? (_g = binding.providerMappingsByMode) === null || _g === void 0 ? void 0 : _g[mode] : (_h = item.providerMappingsByMode) === null || _h === void 0 ? void 0 : _h[mode];
|
|
47
|
+
const profileConfig = ((_l = (_k = (_j = runtime.offerSet) === null || _j === void 0 ? void 0 : _j.paymentProviderConfigByProfileId) === null || _k === void 0 ? void 0 : _k[profileId]) === null || _l === void 0 ? void 0 : _l[mode])
|
|
48
|
+
|| (provider === 'paypal' ? paymentProviderConfig : null);
|
|
49
|
+
const clientId = (profileConfig === null || profileConfig === void 0 ? void 0 : profileConfig.provider) === 'paypal' ? profileConfig.paypalClientId : null;
|
|
50
|
+
if (!(mapping === null || mapping === void 0 ? void 0 : mapping.providerPlanId) || !clientId || !mapping.checkoutSummaryLabel || mapping.amountCents == null || !mapping.currency)
|
|
51
|
+
continue;
|
|
52
|
+
paypalOptionsByPlanKey[item.funnelPlanKey] = { paymentProfileId: profileId, clientId,
|
|
53
|
+
providerPlanId: mapping.providerPlanId, summary: mapping.checkoutSummaryLabel,
|
|
54
|
+
amountMinor: mapping.amountCents, currency: mapping.currency };
|
|
55
|
+
}
|
|
40
56
|
const pinnedFeatureFlags = Object.assign(Object.assign({}, featureFlags), (selection.experimentId && selection.variantKey
|
|
41
57
|
? { [selection.experimentId]: selection.variantKey }
|
|
42
58
|
: {}));
|
|
43
59
|
return {
|
|
44
|
-
|
|
45
|
-
|
|
60
|
+
paypalOptionsByPlanKey,
|
|
61
|
+
revisionId: (_m = config === null || config === void 0 ? void 0 : config.revisionId) !== null && _m !== void 0 ? _m : null,
|
|
62
|
+
sourceVersionId: (_o = config === null || config === void 0 ? void 0 : config.sourceVersionId) !== null && _o !== void 0 ? _o : null,
|
|
46
63
|
paymentsApiVersion: 'v2',
|
|
47
64
|
mode,
|
|
48
65
|
offerSetId: selection.offerSetId,
|
|
49
|
-
paymentProfileId: ((
|
|
66
|
+
paymentProfileId: ((_q = (_p = runtime.offerSet) === null || _p === void 0 ? void 0 : _p.paymentProfileId) === null || _q === void 0 ? void 0 : _q.trim()) || null,
|
|
50
67
|
provider,
|
|
51
68
|
stripePublishableKey: provider === 'stripe' && (paymentProviderConfig === null || paymentProviderConfig === void 0 ? void 0 : paymentProviderConfig.provider) === 'stripe'
|
|
52
|
-
? ((
|
|
69
|
+
? ((_r = paymentProviderConfig === null || paymentProviderConfig === void 0 ? void 0 : paymentProviderConfig.stripePublishableKey) === null || _r === void 0 ? void 0 : _r.trim()) || null
|
|
53
70
|
: null,
|
|
54
71
|
experimentId: selection.experimentId,
|
|
55
72
|
variantKey: selection.variantKey,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@funnelsgrove/payments",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/The-Solid-Grove/funnelsgrove.git",
|
|
@@ -46,11 +46,11 @@
|
|
|
46
46
|
"jsdom": "20.0.3",
|
|
47
47
|
"typescript": "^5",
|
|
48
48
|
"vitest": "^3.2.4",
|
|
49
|
-
"@funnelsgrove/runtime": "0.
|
|
50
|
-
"@funnelsgrove/analytics": "0.
|
|
49
|
+
"@funnelsgrove/runtime": "0.28.0",
|
|
50
|
+
"@funnelsgrove/analytics": "0.10.0"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"@funnelsgrove/runtime": "^0.
|
|
54
|
-
"@funnelsgrove/analytics": "^0.
|
|
53
|
+
"@funnelsgrove/runtime": "^0.28.0",
|
|
54
|
+
"@funnelsgrove/analytics": "^0.10.0"
|
|
55
55
|
}
|
|
56
56
|
}
|