Add PayPal checkout provider

This commit is contained in:
Codex Agent
2026-02-04 12:18:14 +01:00
parent 56a39d0535
commit fc5dfb272c
33 changed files with 1586 additions and 571 deletions

View File

@@ -19,27 +19,19 @@ import { useAnalytics } from '@/hooks/useAnalytics';
type PaymentStatus = 'idle' | 'processing' | 'ready' | 'error';
type LemonSqueezyEvent = {
event?: string;
data?: Record<string, unknown>;
};
declare global {
interface Window {
createLemonSqueezy?: () => void;
LemonSqueezy?: {
Setup: (options: { eventHandler?: (event: LemonSqueezyEvent) => void }) => void;
Refresh?: () => void;
Url: {
Open: (url: string) => void;
Close?: () => void;
paypal?: {
Buttons: (options: Record<string, unknown>) => {
render: (selector: HTMLElement | string) => Promise<void>;
close?: () => void;
};
};
}
}
const LEMON_SCRIPT_URL = 'https://app.lemonsqueezy.com/js/lemon.js';
const PRIMARY_CTA_STYLES = 'min-w-[200px] disabled:bg-muted disabled:text-muted-foreground disabled:hover:bg-muted disabled:hover:text-muted-foreground';
const PAYPAL_SDK_BASE = 'https://www.paypal.com/sdk/js';
const getCookieValue = (name: string): string | null => {
if (typeof document === 'undefined') {
@@ -116,54 +108,59 @@ export function resolveCheckoutLocale(rawLocale?: string | null): string {
return short || 'en';
}
let lemonLoaderPromise: Promise<typeof window.LemonSqueezy | null> | null = null;
type PayPalSdkOptions = {
clientId: string;
currency: string;
intent: string;
locale?: string | null;
};
async function loadLemonSqueezy(): Promise<typeof window.LemonSqueezy | null> {
let paypalLoaderPromise: Promise<typeof window.paypal | null> | null = null;
let paypalLoaderKey: string | null = null;
async function loadPayPalSdk(options: PayPalSdkOptions): Promise<typeof window.paypal | null> {
if (typeof window === 'undefined') {
return null;
}
if (window.LemonSqueezy) {
return window.LemonSqueezy;
if (window.paypal) {
return window.paypal;
}
if (!lemonLoaderPromise) {
lemonLoaderPromise = new Promise<typeof window.LemonSqueezy | null>((resolve, reject) => {
const script = document.createElement('script');
script.src = LEMON_SCRIPT_URL;
script.defer = true;
script.onload = () => {
window.createLemonSqueezy?.();
resolve(window.LemonSqueezy ?? null);
};
script.onerror = (error) => reject(error);
document.head.appendChild(script);
}).catch((error) => {
console.error('Failed to load Lemon.js', error);
lemonLoaderPromise = null;
return null;
});
const params = new URLSearchParams({
'client-id': options.clientId,
currency: options.currency,
intent: options.intent,
components: 'buttons',
});
if (options.locale) {
params.set('locale', options.locale);
}
return lemonLoaderPromise;
const src = `${PAYPAL_SDK_BASE}?${params.toString()}`;
if (paypalLoaderPromise && paypalLoaderKey === src) {
return paypalLoaderPromise;
}
paypalLoaderKey = src;
paypalLoaderPromise = new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.async = true;
script.onload = () => resolve(window.paypal ?? null);
script.onerror = (error) => reject(error);
document.head.appendChild(script);
}).catch((error) => {
console.error('Failed to load PayPal SDK', error);
paypalLoaderPromise = null;
return null;
});
return paypalLoaderPromise;
}
const LemonSqueezyCta: React.FC<{ onCheckout: () => Promise<void>; disabled: boolean; isProcessing: boolean; className?: string }> = ({ onCheckout, disabled, isProcessing, className }) => {
const { t } = useTranslation('marketing');
return (
<Button
size="lg"
className={cn('w-full sm:w-auto', className)}
disabled={disabled}
onClick={onCheckout}
>
{isProcessing && <LoaderCircle className="mr-2 h-4 w-4 animate-spin" />}
{t('checkout.payment_step.pay_with_lemonsqueezy')}
</Button>
);
};
export const PaymentStep: React.FC = () => {
const { t, i18n } = useTranslation('marketing');
const { trackEvent } = useAnalytics();
@@ -176,10 +173,10 @@ export const PaymentStep: React.FC = () => {
setPaymentCompleted,
checkoutSessionId,
setCheckoutSessionId,
paypalConfig,
} = useCheckoutWizard();
const [status, setStatus] = useState<PaymentStatus>('idle');
const [message, setMessage] = useState<string>('');
const [inlineActive, setInlineActive] = useState(false);
const [acceptedTerms, setAcceptedTerms] = useState(false);
const [consentError, setConsentError] = useState<string | null>(null);
const [couponCode, setCouponCode] = useState<string>(() => {
@@ -199,10 +196,6 @@ export const PaymentStep: React.FC = () => {
const [couponError, setCouponError] = useState<string | null>(null);
const [couponNotice, setCouponNotice] = useState<string | null>(null);
const [couponLoading, setCouponLoading] = useState(false);
const lemonRef = useRef<typeof window.LemonSqueezy | null>(null);
const eventHandlerRef = useRef<(event: LemonSqueezyEvent) => void>();
const lastCheckoutIdRef = useRef<string | null>(null);
const hasAutoAppliedCoupon = useRef(false);
const [showWithdrawalModal, setShowWithdrawalModal] = useState(false);
const [withdrawalHtml, setWithdrawalHtml] = useState<string | null>(null);
const [withdrawalTitle, setWithdrawalTitle] = useState<string | null>(null);
@@ -212,10 +205,25 @@ export const PaymentStep: React.FC = () => {
const [voucherExpiry, setVoucherExpiry] = useState<string | null>(null);
const [isGiftVoucher, setIsGiftVoucher] = useState(false);
const [freeActivationBusy, setFreeActivationBusy] = useState(false);
const [pendingConfirmation, setPendingConfirmation] = useState<{
orderId: string | null;
checkoutId: string | null;
} | null>(null);
const paypalContainerRef = useRef<HTMLDivElement | null>(null);
const paypalButtonsRef = useRef<{ close?: () => void } | null>(null);
const paypalActionsRef = useRef<{ enable: () => void; disable: () => void } | null>(null);
const checkoutSessionRef = useRef<string | null>(checkoutSessionId);
const acceptedTermsRef = useRef<boolean>(acceptedTerms);
const couponCodeRef = useRef<string | null>(null);
useEffect(() => {
checkoutSessionRef.current = checkoutSessionId;
}, [checkoutSessionId]);
useEffect(() => {
acceptedTermsRef.current = acceptedTerms;
}, [acceptedTerms]);
useEffect(() => {
couponCodeRef.current = couponPreview?.coupon.code ?? null;
}, [couponPreview]);
const checkoutLocale = useMemo(() => {
const sourceLocale = i18n.language || (typeof document !== 'undefined' ? document.documentElement.lang : null);
@@ -224,31 +232,6 @@ export const PaymentStep: React.FC = () => {
const isFree = useMemo(() => (selectedPackage ? Number(selectedPackage.price) <= 0 : false), [selectedPackage]);
const confirmCheckoutSession = useCallback(async (payload: { orderId: string | null; checkoutId: string | null }) => {
if (!checkoutSessionId) {
return;
}
if (!payload.orderId && !payload.checkoutId) {
return;
}
try {
await refreshCheckoutCsrfToken();
await fetch(`/checkout/session/${checkoutSessionId}/confirm`, {
method: 'POST',
headers: buildCheckoutHeaders(),
credentials: 'same-origin',
body: JSON.stringify({
order_id: payload.orderId,
checkout_id: payload.checkoutId,
}),
});
} catch (error) {
console.warn('Failed to confirm Lemon Squeezy session', error);
}
}, [checkoutSessionId]);
const applyCoupon = useCallback(async (code: string) => {
if (!selectedPackage) {
return;
@@ -310,12 +293,7 @@ export const PaymentStep: React.FC = () => {
}, [RateLimitHelper, selectedPackage, t, trackEvent]);
useEffect(() => {
if (hasAutoAppliedCoupon.current) {
return;
}
if (couponCode && selectedPackage) {
hasAutoAppliedCoupon.current = true;
applyCoupon(couponCode);
}
}, [applyCoupon, couponCode, selectedPackage]);
@@ -324,7 +302,6 @@ export const PaymentStep: React.FC = () => {
setCouponPreview(null);
setCouponNotice(null);
setCouponError(null);
hasAutoAppliedCoupon.current = false;
}, [selectedPackage?.id]);
useEffect(() => {
@@ -383,7 +360,7 @@ export const PaymentStep: React.FC = () => {
const payload = await response.json().catch(() => ({}));
if (!response.ok) {
const errorMessage = payload?.errors?.accepted_waiver?.[0] || payload?.message || t('checkout.payment_step.lemonsqueezy_error');
const errorMessage = payload?.errors?.accepted_waiver?.[0] || payload?.message || t('checkout.payment_step.paypal_error');
setConsentError(errorMessage);
toast.error(errorMessage);
return;
@@ -394,7 +371,7 @@ export const PaymentStep: React.FC = () => {
nextStep();
} catch (error) {
console.error('Failed to activate free package', error);
const fallbackMessage = t('checkout.payment_step.lemonsqueezy_error');
const fallbackMessage = t('checkout.payment_step.paypal_error');
setConsentError(fallbackMessage);
toast.error(fallbackMessage);
} finally {
@@ -402,216 +379,192 @@ export const PaymentStep: React.FC = () => {
}
};
const startLemonSqueezyCheckout = async () => {
if (!selectedPackage) {
return;
const handlePayPalCapture = useCallback(async (orderId: string) => {
const sessionId = checkoutSessionRef.current;
if (!sessionId) {
throw new Error('Missing checkout session');
}
if (!isAuthenticated || !authUser) {
const message = t('checkout.payment_step.auth_required');
setStatus('error');
setMessage(message);
toast.error(message);
goToStep('auth');
return;
await refreshCheckoutCsrfToken();
const response = await fetch('/paypal/capture-order', {
method: 'POST',
headers: buildCheckoutHeaders(),
credentials: 'same-origin',
body: JSON.stringify({
checkout_session_id: sessionId,
order_id: orderId,
}),
});
const payload = await response.json().catch(() => ({}));
if (!response.ok) {
throw new Error(payload?.message || t('checkout.payment_step.paypal_error'));
}
if (!acceptedTerms) {
setConsentError(t('checkout.legal.checkbox_terms_error'));
return;
}
if (!selectedPackage.lemonsqueezy_variant_id) {
setStatus('error');
setMessage(t('checkout.payment_step.lemonsqueezy_not_configured'));
return;
}
setPaymentCompleted(false);
setStatus('processing');
setMessage(t('checkout.payment_step.lemonsqueezy_preparing'));
setInlineActive(false);
setCheckoutSessionId(null);
try {
await refreshCheckoutCsrfToken();
const response = await fetch('/lemonsqueezy/create-checkout', {
method: 'POST',
headers: buildCheckoutHeaders(),
credentials: 'same-origin',
body: JSON.stringify({
package_id: selectedPackage.id,
locale: checkoutLocale,
coupon_code: couponPreview?.coupon.code ?? undefined,
accepted_terms: acceptedTerms,
}),
});
const rawBody = await response.text();
if (
response.status === 401 ||
response.status === 419 ||
(response.redirected && response.url.includes('/login'))
) {
const message = t('checkout.payment_step.auth_required');
setStatus('error');
setMessage(message);
toast.error(message);
goToStep('auth');
return;
}
if (typeof window !== 'undefined') {
console.info('[Checkout] Lemon Squeezy checkout response', { status: response.status, rawBody });
}
let data: { checkout_url?: string; message?: string; simulated?: boolean; order_id?: string; checkout_id?: string; id?: string; checkout_session_id?: string } | null = null;
try {
data = rawBody && rawBody.trim().startsWith('{') ? JSON.parse(rawBody) : null;
} catch (parseError) {
console.warn('Failed to parse Lemon Squeezy checkout payload as JSON', parseError);
data = null;
}
const checkoutSession = data?.checkout_session_id ?? null;
if (checkoutSession && typeof checkoutSession === 'string') {
setCheckoutSessionId(checkoutSession);
}
if (data?.simulated) {
const orderId = typeof data.order_id === 'string' ? data.order_id : null;
const checkoutId = typeof data.checkout_id === 'string'
? data.checkout_id
: (typeof data.id === 'string' ? data.id : null);
setStatus('processing');
setMessage(t('checkout.payment_step.processing_confirmation'));
setInlineActive(false);
setPendingConfirmation({ orderId, checkoutId });
setPaymentCompleted(true);
nextStep();
return;
}
let checkoutUrl: string | null = typeof data?.checkout_url === 'string' ? data.checkout_url : null;
if (!checkoutUrl) {
const trimmed = rawBody.trim();
if (/^https?:\/\//i.test(trimmed)) {
checkoutUrl = trimmed;
} else if (trimmed.startsWith('<')) {
const match = trimmed.match(/https?:\/\/["'a-zA-Z0-9._~:/?#@!$&'()*+,;=%-]+/);
if (match) {
checkoutUrl = match[0];
}
}
}
if (!response.ok || !checkoutUrl) {
const message = data?.message || rawBody || 'Unable to create Lemon Squeezy checkout.';
throw new Error(message);
}
if (data && typeof (data as { id?: string }).id === 'string') {
lastCheckoutIdRef.current = (data as { id?: string }).id ?? null;
}
const lemon = await loadLemonSqueezy();
if (lemon?.Url?.Open) {
lemon.Url.Open(checkoutUrl);
setInlineActive(true);
setStatus('ready');
setMessage(t('checkout.payment_step.lemonsqueezy_overlay_ready'));
return;
}
window.open(checkoutUrl, '_blank', 'noopener');
setInlineActive(false);
setStatus('ready');
setMessage(t('checkout.payment_step.lemonsqueezy_ready'));
} catch (error) {
console.error('Failed to start Lemon Squeezy checkout', error);
setStatus('error');
setMessage(t('checkout.payment_step.lemonsqueezy_error'));
setInlineActive(false);
setPaymentCompleted(false);
}
};
return payload;
}, [t]);
useEffect(() => {
if (!selectedPackage || isFree) {
return;
}
const clientId = paypalConfig?.client_id ?? null;
if (!clientId) {
setStatus('error');
setMessage(t('checkout.payment_step.paypal_not_configured'));
return;
}
let cancelled = false;
(async () => {
const lemon = await loadLemonSqueezy();
const initButtons = async () => {
const paypal = await loadPayPalSdk({
clientId,
currency: paypalConfig?.currency ?? 'EUR',
intent: paypalConfig?.intent ?? 'capture',
locale: paypalConfig?.locale ?? checkoutLocale,
});
if (cancelled || !lemon) {
if (cancelled || !paypal || !paypalContainerRef.current) {
return;
}
try {
eventHandlerRef.current = (event) => {
if (!event?.event) {
return;
}
if (typeof window !== 'undefined') {
console.debug('[Checkout] Lemon Squeezy event', event);
}
if (event.event === 'Checkout.Success') {
const data = event.data as { id?: string; identifier?: string; attributes?: { checkout_id?: string } } | undefined;
const orderId = typeof data?.id === 'string' ? data.id : (typeof data?.identifier === 'string' ? data.identifier : null);
const checkoutId = typeof data?.attributes?.checkout_id === 'string'
? data?.attributes?.checkout_id
: lastCheckoutIdRef.current;
setStatus('processing');
setMessage(t('checkout.payment_step.processing_confirmation'));
setInlineActive(false);
setPaymentCompleted(false);
setPendingConfirmation({ orderId, checkoutId });
toast.success(t('checkout.payment_step.toast_success'));
setPaymentCompleted(true);
nextStep();
}
};
lemon.Setup({
eventHandler: (event) => eventHandlerRef.current?.(event),
});
lemonRef.current = lemon;
} catch (error) {
console.error('Failed to initialize Lemon.js', error);
setStatus('error');
setMessage(t('checkout.payment_step.lemonsqueezy_error'));
setPaymentCompleted(false);
if (paypalButtonsRef.current?.close) {
paypalButtonsRef.current.close();
}
})();
paypalContainerRef.current.innerHTML = '';
paypalButtonsRef.current = paypal.Buttons({
onInit: (_data: unknown, actions: { enable: () => void; disable: () => void }) => {
paypalActionsRef.current = actions;
if (!acceptedTermsRef.current) {
actions.disable();
}
},
createOrder: async () => {
if (!selectedPackage) {
throw new Error('Missing package');
}
if (!isAuthenticated || !authUser) {
const authMessage = t('checkout.payment_step.auth_required');
setStatus('error');
setMessage(authMessage);
toast.error(authMessage);
goToStep('auth');
throw new Error(authMessage);
}
if (!acceptedTermsRef.current) {
const consentMessage = t('checkout.legal.checkbox_terms_error');
setConsentError(consentMessage);
throw new Error(consentMessage);
}
setConsentError(null);
setStatus('processing');
setMessage(t('checkout.payment_step.paypal_preparing'));
setPaymentCompleted(false);
setCheckoutSessionId(null);
await refreshCheckoutCsrfToken();
const response = await fetch('/paypal/create-order', {
method: 'POST',
headers: buildCheckoutHeaders(),
credentials: 'same-origin',
body: JSON.stringify({
package_id: selectedPackage.id,
locale: checkoutLocale,
coupon_code: couponCodeRef.current ?? undefined,
accepted_terms: acceptedTermsRef.current,
}),
});
const payload = await response.json().catch(() => ({}));
if (!response.ok) {
const errorMessage = payload?.message || t('checkout.payment_step.paypal_error');
setStatus('error');
setMessage(errorMessage);
throw new Error(errorMessage);
}
if (payload?.checkout_session_id) {
setCheckoutSessionId(payload.checkout_session_id);
checkoutSessionRef.current = payload.checkout_session_id;
}
const orderId = payload?.order_id;
if (!orderId) {
throw new Error('PayPal order ID missing.');
}
setStatus('ready');
setMessage(t('checkout.payment_step.paypal_ready'));
return orderId;
},
onApprove: async (data: { orderID?: string }) => {
if (!data?.orderID) {
throw new Error('Missing PayPal order ID.');
}
setStatus('processing');
setMessage(t('checkout.payment_step.processing_confirmation'));
try {
const payload = await handlePayPalCapture(data.orderID);
if (payload?.status === 'completed') {
setPaymentCompleted(true);
toast.success(t('checkout.payment_step.toast_success'));
nextStep();
return;
}
setStatus('error');
setMessage(t('checkout.payment_step.paypal_error'));
} catch (error) {
console.error('Failed to capture PayPal order', error);
setStatus('error');
setMessage(t('checkout.payment_step.paypal_error'));
}
},
onCancel: () => {
setStatus('idle');
setMessage(t('checkout.payment_step.paypal_cancelled'));
},
onError: (error: unknown) => {
console.error('PayPal button error', error);
setStatus('error');
setMessage(t('checkout.payment_step.paypal_error'));
},
}) as { render: (selector: HTMLElement | string) => Promise<void>; close?: () => void };
await paypalButtonsRef.current.render(paypalContainerRef.current);
};
void initButtons();
return () => {
cancelled = true;
if (paypalButtonsRef.current?.close) {
paypalButtonsRef.current.close();
}
};
}, [nextStep, setPaymentCompleted, t]);
}, [authUser, checkoutLocale, goToStep, isAuthenticated, isFree, paypalConfig?.client_id, paypalConfig?.currency, paypalConfig?.intent, paypalConfig?.locale, selectedPackage, setCheckoutSessionId, setPaymentCompleted, t, handlePayPalCapture, nextStep]);
useEffect(() => {
setPaymentCompleted(false);
setCheckoutSessionId(null);
setStatus('idle');
setMessage('');
setInlineActive(false);
setPendingConfirmation(null);
}, [selectedPackage?.id, setPaymentCompleted]);
useEffect(() => {
if (!pendingConfirmation || !checkoutSessionId) {
return;
if (paypalActionsRef.current) {
if (acceptedTerms) {
paypalActionsRef.current.enable();
} else {
paypalActionsRef.current.disable();
}
}
void confirmCheckoutSession(pendingConfirmation);
setPendingConfirmation(null);
}, [checkoutSessionId, confirmCheckoutSession, pendingConfirmation]);
}, [acceptedTerms]);
const handleCouponSubmit = useCallback((event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
@@ -665,7 +618,6 @@ export const PaymentStep: React.FC = () => {
}
}, [checkoutLocale, t, withdrawalHtml, withdrawalLoading]);
if (!selectedPackage) {
return (
<Alert variant="destructive">
@@ -738,7 +690,6 @@ export const PaymentStep: React.FC = () => {
);
}
const TrustPill = ({ icon: Icon, label }: { icon: React.ElementType; label: string }) => (
<div className="flex items-center gap-2 rounded-full bg-white/10 px-3 py-1 text-xs font-medium text-white backdrop-blur-sm">
<Icon className="h-4 w-4 text-white/80" />
@@ -746,10 +697,10 @@ export const PaymentStep: React.FC = () => {
</div>
);
const LemonSqueezyLogo = () => (
const PayPalBadge = () => (
<div className="flex items-center gap-3 rounded-full bg-white/15 px-4 py-2 text-white shadow-inner backdrop-blur-sm">
<span className="text-sm font-semibold tracking-wide">Lemon Squeezy</span>
<span className="text-xs font-semibold">{t('checkout.payment_step.lemonsqueezy_partner')}</span>
<span className="text-sm font-semibold tracking-wide">PayPal</span>
<span className="text-xs font-semibold">{t('checkout.payment_step.paypal_partner')}</span>
</div>
);
@@ -757,11 +708,10 @@ export const PaymentStep: React.FC = () => {
<div className="space-y-4">
<div className="rounded-2xl border bg-card p-6 shadow-sm">
<div className="space-y-6">
{!inlineActive && (
<div className="overflow-hidden rounded-2xl border bg-gradient-to-br from-[#001835] via-[#002b55] to-[#00407c] p-6 text-white shadow-md">
<div className="overflow-hidden rounded-2xl border bg-gradient-to-br from-[#0b1f4b] via-[#003087] to-[#009cde] p-6 text-white shadow-md">
<div className="flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between">
<div className="space-y-4">
<LemonSqueezyLogo />
<PayPalBadge />
<div className="space-y-2">
<h3 className="text-2xl font-semibold">{t('checkout.payment_step.guided_title')}</h3>
<p className="text-sm text-white/80">{t('checkout.payment_step.guided_body')}</p>
@@ -785,7 +735,7 @@ export const PaymentStep: React.FC = () => {
setConsentError(null);
}
}}
className="border-white/60 data-[state=checked]:bg-white data-[state=checked]:text-[#001835]"
className="border-white/60 data-[state=checked]:bg-white data-[state=checked]:text-[#003087]"
/>
<div className="space-y-1 text-sm">
<Label htmlFor="checkout-terms-hero" className="cursor-pointer text-white">
@@ -817,12 +767,7 @@ export const PaymentStep: React.FC = () => {
</div>
<div className="space-y-2">
<LemonSqueezyCta
onCheckout={startLemonSqueezyCheckout}
disabled={status === 'processing' || !acceptedTerms}
isProcessing={status === 'processing'}
className={cn('bg-white text-[#001835] hover:bg-white/90', PRIMARY_CTA_STYLES)}
/>
<div ref={paypalContainerRef} className={cn('min-h-[44px]', PRIMARY_CTA_STYLES)} />
<p className="text-xs text-white/70 text-center">
{t('checkout.payment_step.guided_cta_hint')}
</p>
@@ -831,7 +776,6 @@ export const PaymentStep: React.FC = () => {
</div>
</div>
</div>
)}
<div className="space-y-3">
<form className="flex flex-col gap-2 sm:flex-row" onSubmit={handleCouponSubmit}>
@@ -907,20 +851,6 @@ export const PaymentStep: React.FC = () => {
)}
</div>
{!inlineActive && (
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<p className="text-sm text-muted-foreground">
{t('checkout.payment_step.lemonsqueezy_intro')}
</p>
<LemonSqueezyCta
onCheckout={startLemonSqueezyCheckout}
disabled={status === 'processing' || !acceptedTerms}
isProcessing={status === 'processing'}
className={PRIMARY_CTA_STYLES}
/>
</div>
)}
{status !== 'idle' && (
<Alert variant={status === 'error' ? 'destructive' : 'default'}>
<AlertTitle>
@@ -939,8 +869,8 @@ export const PaymentStep: React.FC = () => {
</Alert>
)}
<p className={`text-xs text-muted-foreground ${inlineActive ? 'text-center' : 'sm:text-right'}`}>
{t('checkout.payment_step.lemonsqueezy_disclaimer')}
<p className="text-xs text-muted-foreground sm:text-right">
{t('checkout.payment_step.paypal_disclaimer')}
</p>
</div>
</div>