@@ -129,6 +129,13 @@ export async function paySubscriptionWaffoPancake(
|
||||
return res.data
|
||||
}
|
||||
|
||||
export async function paySubscriptionBalance(
|
||||
data: SubscriptionPayRequest
|
||||
): Promise<SubscriptionPayResponse> {
|
||||
const res = await api.post('/api/subscription/balance/pay', data)
|
||||
return res.data
|
||||
}
|
||||
|
||||
// Mints a Pancake OnetimeProduct (see controller for the OnetimeProduct vs
|
||||
// SubscriptionProduct rationale) using persisted creds + StoreID.
|
||||
export async function createWaffoPancakeSubscriptionProduct(data: {
|
||||
|
||||
+62
-9
@@ -20,7 +20,9 @@ import { useState, useEffect } from 'react'
|
||||
import { Crown, CalendarClock, Package } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import { DEFAULT_CURRENCY_CONFIG } from '@/stores/system-config-store'
|
||||
import { formatQuota } from '@/lib/format'
|
||||
import { useSystemConfig } from '@/hooks/use-system-config'
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
@@ -44,6 +46,7 @@ import {
|
||||
paySubscriptionCreem,
|
||||
paySubscriptionEpay,
|
||||
paySubscriptionWaffoPancake,
|
||||
paySubscriptionBalance,
|
||||
} from '../../api'
|
||||
import { formatDuration, formatResetPeriod } from '../../lib'
|
||||
import type { PlanRecord } from '../../types'
|
||||
@@ -64,10 +67,13 @@ interface Props {
|
||||
epayMethods?: PaymentMethod[]
|
||||
purchaseLimit?: number
|
||||
purchaseCount?: number
|
||||
userQuota?: number
|
||||
onPurchaseSuccess?: () => void | Promise<void>
|
||||
}
|
||||
|
||||
export function SubscriptionPurchaseDialog(props: Props) {
|
||||
const { t } = useTranslation()
|
||||
const { currency } = useSystemConfig()
|
||||
const [paying, setPaying] = useState(false)
|
||||
const [selectedEpayMethod, setSelectedEpayMethod] = useState('')
|
||||
|
||||
@@ -96,6 +102,16 @@ export function SubscriptionPurchaseDialog(props: Props) {
|
||||
t('Select payment method')
|
||||
const totalAmount = Number(plan.total_amount || 0)
|
||||
const price = Number(plan.price_amount || 0).toFixed(2)
|
||||
const quotaPerUnit =
|
||||
currency?.quotaPerUnit && currency.quotaPerUnit > 0
|
||||
? currency.quotaPerUnit
|
||||
: DEFAULT_CURRENCY_CONFIG.quotaPerUnit
|
||||
const balanceCost = Math.max(
|
||||
0,
|
||||
Math.ceil(Number(plan.price_amount || 0) * quotaPerUnit)
|
||||
)
|
||||
const userQuota = Math.max(0, Number(props.userQuota || 0))
|
||||
const insufficientBalance = userQuota < balanceCost
|
||||
const limitReached =
|
||||
(props.purchaseLimit || 0) > 0 &&
|
||||
(props.purchaseCount || 0) >= (props.purchaseLimit || 0)
|
||||
@@ -215,6 +231,28 @@ export function SubscriptionPurchaseDialog(props: Props) {
|
||||
}
|
||||
}
|
||||
|
||||
const handlePayBalance = async () => {
|
||||
setPaying(true)
|
||||
try {
|
||||
const res = await paySubscriptionBalance({ plan_id: plan.id })
|
||||
if (res.success) {
|
||||
toast.success(t('Subscription purchased successfully'))
|
||||
void props.onPurchaseSuccess?.()
|
||||
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 (
|
||||
<Dialog open={props.open} onOpenChange={props.onOpenChange}>
|
||||
<DialogContent className='max-sm:w-[calc(100vw-1.5rem)] sm:max-w-md'>
|
||||
@@ -285,7 +323,30 @@ export function SubscriptionPurchaseDialog(props: Props) {
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{hasAnyPayment ? (
|
||||
<div className='flex flex-col gap-2 rounded-md border p-3'>
|
||||
<div className='flex items-center justify-between gap-2 text-xs'>
|
||||
<span className='text-muted-foreground'>{t('Required')}</span>
|
||||
<span>{formatQuota(balanceCost)}</span>
|
||||
</div>
|
||||
<div className='flex items-center justify-between gap-2 text-xs'>
|
||||
<span className='text-muted-foreground'>{t('Available')}</span>
|
||||
<span>{formatQuota(userQuota)}</span>
|
||||
</div>
|
||||
{insufficientBalance && (
|
||||
<Alert variant='destructive'>
|
||||
<AlertDescription>{t('Insufficient balance')}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
<Button
|
||||
variant='outline'
|
||||
onClick={handlePayBalance}
|
||||
disabled={paying || limitReached || insufficientBalance}
|
||||
>
|
||||
{t('Pay with Balance')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{hasAnyPayment && (
|
||||
<div className='space-y-3'>
|
||||
<p className='text-muted-foreground text-xs'>
|
||||
{t('Select payment method')}
|
||||
@@ -361,14 +422,6 @@ export function SubscriptionPurchaseDialog(props: Props) {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
{t(
|
||||
'Online payment is not enabled. Please contact the administrator.'
|
||||
)}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
@@ -62,6 +62,8 @@ import type { PaymentMethod, TopupInfo } from '../types'
|
||||
interface SubscriptionPlansCardProps {
|
||||
topupInfo: TopupInfo | null
|
||||
onAvailabilityChange?: (available: boolean) => void
|
||||
userQuota?: number
|
||||
onPurchaseSuccess?: () => void | Promise<void>
|
||||
}
|
||||
|
||||
function getEpayMethods(payMethods: PaymentMethod[] = []): PaymentMethod[] {
|
||||
@@ -91,6 +93,8 @@ function getBillingPreferenceLabel(
|
||||
export function SubscriptionPlansCard({
|
||||
topupInfo,
|
||||
onAvailabilityChange,
|
||||
userQuota,
|
||||
onPurchaseSuccess,
|
||||
}: SubscriptionPlansCardProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -633,6 +637,8 @@ export function SubscriptionPlansCard({
|
||||
enableWaffoPancake={enableWaffoPancake}
|
||||
enableOnlineTopUp={enableOnlineTopUp}
|
||||
epayMethods={epayMethods}
|
||||
userQuota={userQuota}
|
||||
onPurchaseSuccess={onPurchaseSuccess}
|
||||
purchaseLimit={
|
||||
selectedPlan?.plan?.max_purchase_per_user
|
||||
? Number(selectedPlan.plan.max_purchase_per_user)
|
||||
|
||||
+2
@@ -309,6 +309,8 @@ export function Wallet(props: WalletProps) {
|
||||
<SubscriptionPlansCard
|
||||
topupInfo={topupInfo}
|
||||
onAvailabilityChange={handleSubscriptionAvailabilityChange}
|
||||
userQuota={user?.quota}
|
||||
onPurchaseSuccess={fetchUser}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user