/* Copyright (C) 2023-2026 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import { useState, useEffect } from 'react' import { Crown, CalendarClock, Package } from 'lucide-react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import { Alert, AlertDescription } from '@/components/ui/alert' import { Button } from '@/components/ui/button' import { Dialog, DialogContent, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Separator } from '@/components/ui/separator' import { GroupBadge } from '@/components/group-badge' import { paySubscriptionStripe, paySubscriptionCreem, paySubscriptionEpay, paySubscriptionWaffoPancake, } from '../../api' import { formatDuration, formatResetPeriod } from '../../lib' import type { PlanRecord } from '../../types' interface PaymentMethod { type: string name?: string } interface Props { open: boolean onOpenChange: (open: boolean) => void plan: PlanRecord | null enableStripe?: boolean enableCreem?: boolean enableWaffoPancake?: boolean enableOnlineTopUp?: boolean epayMethods?: PaymentMethod[] purchaseLimit?: number purchaseCount?: number } export function SubscriptionPurchaseDialog(props: Props) { const { t } = useTranslation() const [paying, setPaying] = useState(false) const [selectedEpayMethod, setSelectedEpayMethod] = useState('') useEffect(() => { if (props.open && props.epayMethods && props.epayMethods.length > 0) { setSelectedEpayMethod(props.epayMethods[0].type) } else if (!props.open) { setSelectedEpayMethod('') } }, [props.open, props.epayMethods]) const plan = props.plan?.plan if (!plan) return null const hasStripe = props.enableStripe && !!plan.stripe_price_id const hasCreem = props.enableCreem && !!plan.creem_product_id const hasWaffoPancake = props.enableWaffoPancake && !!plan.waffo_pancake_product_id const hasEpay = props.enableOnlineTopUp && (props.epayMethods || []).length > 0 const hasAnyPayment = hasStripe || hasCreem || hasWaffoPancake || hasEpay const selectedEpayMethodLabel = (props.epayMethods || []).find((m) => m.type === selectedEpayMethod) ?.name || selectedEpayMethod || t('Select payment method') const totalAmount = Number(plan.total_amount || 0) const price = Number(plan.price_amount || 0).toFixed(2) const limitReached = (props.purchaseLimit || 0) > 0 && (props.purchaseCount || 0) >= (props.purchaseLimit || 0) const handlePayStripe = async () => { setPaying(true) try { const res = await paySubscriptionStripe({ plan_id: plan.id }) if (res.message === 'success' && res.data?.pay_link) { window.open(res.data.pay_link, '_blank') toast.success(t('Payment page opened')) props.onOpenChange(false) } else { toast.error( res.message && res.message !== 'success' ? res.message : t('Payment request failed') ) } } catch { toast.error(t('Payment request failed')) } finally { setPaying(false) } } const handlePayCreem = async () => { setPaying(true) try { const res = await paySubscriptionCreem({ plan_id: plan.id }) if (res.message === 'success' && res.data?.checkout_url) { window.open(res.data.checkout_url, '_blank') toast.success(t('Payment page opened')) props.onOpenChange(false) } else { toast.error( res.message && res.message !== 'success' ? res.message : t('Payment request failed') ) } } catch { toast.error(t('Payment request failed')) } finally { setPaying(false) } } // In-tab redirect (not window.open) — user-gesture context is lost // across the await, so a popup would be blocked. Same as the wallet hook. const handlePayWaffoPancake = async () => { setPaying(true) try { const res = await paySubscriptionWaffoPancake({ plan_id: plan.id }) if (res.message === 'success' && res.data?.checkout_url) { toast.success(t('Redirecting to payment page...')) window.location.href = res.data.checkout_url } else { toast.error( res.message && res.message !== 'success' ? res.message : t('Payment request failed') ) } } catch { toast.error(t('Payment request failed')) } finally { setPaying(false) } } const isSafari = typeof navigator !== 'undefined' && /^((?!chrome|android).)*safari/i.test(navigator.userAgent) const handlePayEpay = async () => { if (!selectedEpayMethod) { toast.error(t('Please select a payment method')) return } setPaying(true) try { const res = await paySubscriptionEpay({ plan_id: plan.id, payment_method: selectedEpayMethod, }) if (res.message === 'success' && res.url) { const form = document.createElement('form') form.action = res.url form.method = 'POST' if (!isSafari) { form.target = '_blank' } Object.entries(res.data || {}).forEach(([key, value]) => { const input = document.createElement('input') input.type = 'hidden' input.name = key input.value = String(value) form.appendChild(input) }) document.body.appendChild(form) form.submit() document.body.removeChild(form) toast.success(t('Payment initiated')) props.onOpenChange(false) } else { toast.error( res.message && res.message !== 'success' ? res.message : t('Payment request failed') ) } } catch { toast.error(t('Payment request failed')) } finally { setPaying(false) } } return ( {t('Purchase Subscription')}
{t('Plan Name')} {plan.title}
{t('Validity Period')} {formatDuration(plan, t)}
{formatResetPeriod(plan, t) !== t('No Reset') && (
{t('Reset Period')} {formatResetPeriod(plan, t)}
)}
{t('Total Quota')} {totalAmount > 0 ? totalAmount : t('Unlimited')}
{plan.upgrade_group && (
{t('Upgrade Group')}
)}
{t('Amount Due')} ${price}
{limitReached && ( {t('Purchase limit reached')} ({props.purchaseCount}/ {props.purchaseLimit}) )} {hasAnyPayment ? (

{t('Select payment method')}

{(hasStripe || hasCreem || hasWaffoPancake) && (
{hasStripe && ( )} {hasCreem && ( )} {hasWaffoPancake && ( )}
)} {hasEpay && (
)}
) : ( {t( 'Online payment is not enabled. Please contact the administrator.' )} )}
) }