230 lines
7.1 KiB
TypeScript
230 lines
7.1 KiB
TypeScript
'use client'
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogFooter,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from '@/components/ui/dialog'
|
|
import {Button} from '@/components/ui/button'
|
|
import {Form, FormField} from '@/components/ui/form'
|
|
import {useForm} from 'react-hook-form'
|
|
import zod from 'zod'
|
|
import FormOption from '@/components/composites/purchase/option'
|
|
import {RadioGroup} from '@/components/ui/radio-group'
|
|
import {zodResolver} from '@hookform/resolvers/zod'
|
|
import {toast} from 'sonner'
|
|
import {useState} from 'react'
|
|
import {RechargeComplete, RechargePrepare} from '@/actions/user'
|
|
import {useProfileStore} from '@/components/stores/profile'
|
|
import {merge} from '@/lib/utils'
|
|
import {
|
|
TradePlatform,
|
|
TradeMethod,
|
|
} from '@/lib/models/trade'
|
|
import {PaymentModal} from '@/components/composites/payment/payment-modal'
|
|
import Image from 'next/image'
|
|
import wechat from '@/components/composites/purchase/_assets/wechat.svg'
|
|
import alipay from '@/components/composites/purchase/_assets/alipay.svg'
|
|
import {PaymentProps} from '@/components/composites/payment/type'
|
|
import {usePlatformType} from '@/lib/hooks'
|
|
|
|
const schema = zod.object({
|
|
method: zod.enum(['alipay', 'wechat']),
|
|
amount: zod.number().min(1, '充值金额必须大于 0'),
|
|
})
|
|
|
|
type Schema = zod.infer<typeof schema>
|
|
|
|
export type RechargeModelProps = {
|
|
classNames?: {
|
|
trigger?: string
|
|
}
|
|
}
|
|
|
|
export default function RechargeModal(props: RechargeModelProps) {
|
|
const [open, setOpen] = useState(false)
|
|
const platform = usePlatformType()
|
|
const form = useForm<Schema>({
|
|
resolver: zodResolver(schema),
|
|
defaultValues: {
|
|
method: 'alipay',
|
|
amount: 50,
|
|
},
|
|
})
|
|
|
|
const [trade, setTrade] = useState<PaymentProps>()
|
|
const refreshProfile = useProfileStore(store => store.refreshProfile)
|
|
|
|
const createRecharge = async (data: Schema) => {
|
|
try {
|
|
const method = data.method === 'alipay'
|
|
? TradeMethod.SftAlipay
|
|
: TradeMethod.SftWechat
|
|
const req = {
|
|
platform: platform,
|
|
method: method,
|
|
amount: Number(data.amount) * 100,
|
|
}
|
|
|
|
const result = await RechargePrepare(req)
|
|
|
|
if (result.success) {
|
|
setTrade({
|
|
inner_no: result.data.trade_no,
|
|
pay_url: result.data.pay_url,
|
|
amount: data.amount,
|
|
platform: platform,
|
|
method: method,
|
|
})
|
|
}
|
|
else {
|
|
throw new Error(result.message)
|
|
}
|
|
}
|
|
catch (error) {
|
|
toast.error('创建订单失败', {
|
|
description: error instanceof Error ? error.message : '未知错误',
|
|
})
|
|
}
|
|
}
|
|
|
|
const handlePaymentSuccess = async (showFail: boolean) => {
|
|
if (!trade) return
|
|
try {
|
|
const resp = await RechargeComplete({
|
|
trade_no: trade.inner_no,
|
|
method: trade.method,
|
|
})
|
|
if (!resp.success) {
|
|
throw new Error(resp.message)
|
|
}
|
|
|
|
toast.success('充值成功')
|
|
setTrade(undefined) // 清除交易状态
|
|
setOpen(false) // 关闭弹窗
|
|
form.reset() // 重置表单
|
|
|
|
await refreshProfile()
|
|
}
|
|
catch (e) {
|
|
if (showFail) {
|
|
toast.error('支付验证失败', {description: (e as Error).message})
|
|
}
|
|
}
|
|
}
|
|
|
|
const handleClose = () => {
|
|
setTrade(undefined)
|
|
}
|
|
|
|
return (
|
|
<Dialog
|
|
open={open}
|
|
onOpenChange={(isOpen) => {
|
|
if (!isOpen) {
|
|
setTrade(undefined)
|
|
form.reset()
|
|
}
|
|
setOpen(isOpen)
|
|
}}>
|
|
<DialogTrigger asChild>
|
|
<Button
|
|
theme="accent"
|
|
type="button"
|
|
className={merge(`px-4 h-8`, props.classNames?.trigger)}
|
|
>
|
|
去充值
|
|
</Button>
|
|
</DialogTrigger>
|
|
|
|
<DialogContent className={platform === TradePlatform.Mobile ? 'max-w-[95vw]' : 'max-w-md'}>
|
|
{!trade ? (
|
|
<>
|
|
<DialogTitle className="flex flex-col gap-2">充值中心</DialogTitle>
|
|
<Form form={form} handler={form.handleSubmit(createRecharge)} className="flex flex-col gap-8">
|
|
{/* 充值额度 */}
|
|
<FormField<Schema> name="amount" label="充值额度" className="flex flex-col gap-4">
|
|
{({id, field}) => (
|
|
<RadioGroup
|
|
id={id}
|
|
defaultValue={String(field.value)}
|
|
onValueChange={v => field.onChange(Number(v))}
|
|
className="flex flex-col gap-2"
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
{[20, 50, 100].map(value => (
|
|
<FormOption
|
|
key={value}
|
|
id={`${id}-${value}`}
|
|
value={String(value)}
|
|
label={`${value}元`}
|
|
compare={String(field.value)}
|
|
className="flex-1 max-sm:text-sm max-sm:px-0"
|
|
/>
|
|
))}
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
{[200, 500, 1000].map(value => (
|
|
<FormOption
|
|
key={value}
|
|
id={`${id}-${value}`}
|
|
value={String(value)}
|
|
label={`${value}元`}
|
|
compare={String(field.value)}
|
|
className="flex-1 max-sm:text-sm max-sm:px-0"
|
|
/>
|
|
))}
|
|
</div>
|
|
</RadioGroup>
|
|
)}
|
|
</FormField>
|
|
|
|
{/* 支付方式 */}
|
|
<FormField name="method" label="支付方式" className="flex flex-col gap-4">
|
|
{({id, field}) => (
|
|
<RadioGroup
|
|
id={id}
|
|
{...field}
|
|
onValueChange={field.onChange}
|
|
className="flex gap-2"
|
|
>
|
|
<FormOption
|
|
id="alipay"
|
|
value="alipay"
|
|
compare={field.value}
|
|
className="flex-1 flex-row justify-center items-center"
|
|
>
|
|
<Image src={alipay} alt="logo" aria-hidden className="w-6 h-6 mr-2"/>
|
|
<span>支付宝</span>
|
|
</FormOption>
|
|
<FormOption
|
|
id="wechat"
|
|
value="wechat"
|
|
compare={field.value}
|
|
className="flex-1 flex-row justify-center items-center"
|
|
>
|
|
<Image src={wechat} alt="logo" aria-hidden className="w-6 h-6 mr-2"/>
|
|
<span>微信</span>
|
|
</FormOption>
|
|
</RadioGroup>
|
|
)}
|
|
</FormField>
|
|
|
|
<DialogFooter>
|
|
<Button>立即支付</Button>
|
|
</DialogFooter>
|
|
</Form>
|
|
</>
|
|
) : (
|
|
<PaymentModal
|
|
{...trade}
|
|
onConfirm={handlePaymentSuccess}
|
|
onClose={handleClose}
|
|
/>
|
|
)}
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|