331 lines
10 KiB
TypeScript
331 lines
10 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 Image from 'next/image'
|
|
import {zodResolver} from '@hookform/resolvers/zod'
|
|
import {toast} from 'sonner'
|
|
import wechat from '@/components/composites/purchase/_assets/wechat.svg'
|
|
import alipay from '@/components/composites/purchase/_assets/alipay.svg'
|
|
import {useEffect, useMemo, useRef, useState} from 'react'
|
|
import {Loader} from 'lucide-react'
|
|
import {RechargeByPay, RechargeByAlipayConfirm, RechargeByWechat, RechargeByWechatConfirm} from '@/actions/user'
|
|
import * as qrcode from 'qrcode'
|
|
import {useProfileStore} from '@/components/providers/StoreProvider'
|
|
import {merge} from '@/lib/utils'
|
|
import {
|
|
Platform,
|
|
PAYMENT_METHODS,
|
|
usePlatformType,
|
|
} from '@/lib/models/trade'
|
|
|
|
const schema = zod.object({
|
|
method: zod.enum(['alipay', 'wechat', 'sft', 'sftAlipay', 'sftWeChat']),
|
|
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 method = form.watch('method')
|
|
const amount = form.watch('amount')
|
|
|
|
const [step, setStep] = useState(0)
|
|
const [payInfo, setPayInfo] = useState<{
|
|
trade_no: string
|
|
pay_url: string
|
|
}>()
|
|
|
|
// 获取当前平台可用的支付方法
|
|
const availableMethods = useMemo(() => {
|
|
console.log(PAYMENT_METHODS, 'PAYMENT_METHODS')
|
|
|
|
return Object.values(PAYMENT_METHODS)
|
|
.filter(method => method.availablePlatforms.includes(platform))
|
|
.map(method => ({
|
|
value: method.formValue,
|
|
name: method.name,
|
|
icon: method.icon,
|
|
}))
|
|
}, [platform])
|
|
const canvas = useRef<HTMLCanvasElement>(null)
|
|
useEffect(() => {
|
|
console.log('Canvas ref:', canvas.current)
|
|
if (!payInfo || !canvas.current || method.includes('alipay')) return
|
|
qrcode.toCanvas(canvas.current, payInfo.pay_url, {
|
|
width: 200,
|
|
margin: 0,
|
|
})
|
|
}, [payInfo, method])
|
|
|
|
const refreshProfile = useProfileStore(store => store.refreshProfile)
|
|
|
|
const createRecharge = async (data: Schema) => {
|
|
try {
|
|
const paymentMethod = Object.entries(PAYMENT_METHODS).find(
|
|
([_, config]) => config.formValue === data.method,
|
|
)
|
|
console.log(paymentMethod, 'paymentMethod')
|
|
|
|
if (!paymentMethod) {
|
|
throw new Error('无效的支付方式')
|
|
}
|
|
const resp = {
|
|
amount: data.amount.toString(),
|
|
platform: platform,
|
|
method: paymentMethod[1].actualMethod,
|
|
}
|
|
console.log(resp, 'resp')
|
|
|
|
// const result = await RechargeByPay({
|
|
// amount: data.amount.toString(),
|
|
// platform: platform,
|
|
// method: parseInt(paymentMethod[0]) as PaymentMethod,
|
|
// })
|
|
const result = await RechargeByPay(resp)
|
|
console.log(result, 'result')
|
|
|
|
if (result.success) {
|
|
setStep(1)
|
|
setPayInfo(result.data)
|
|
}
|
|
else {
|
|
throw new Error(result.message)
|
|
}
|
|
}
|
|
catch (error) {
|
|
toast.error('创建订单失败', {
|
|
description: error instanceof Error ? error.message : '未知错误',
|
|
})
|
|
}
|
|
}
|
|
|
|
const confirmRecharge = async () => {
|
|
if (!payInfo) {
|
|
toast.error(`充值失败`, {
|
|
description: `订单信息不存在`,
|
|
})
|
|
return
|
|
}
|
|
try {
|
|
switch (method) {
|
|
case 'alipay':
|
|
const aliRes = await RechargeByAlipayConfirm({
|
|
trade_no: payInfo.trade_no,
|
|
})
|
|
if (!aliRes.success) {
|
|
throw new Error(aliRes.message)
|
|
}
|
|
break
|
|
case 'wechat':
|
|
const weRes = await RechargeByWechatConfirm({
|
|
trade_no: payInfo.trade_no,
|
|
})
|
|
if (!weRes.success) {
|
|
throw new Error(weRes.message)
|
|
}
|
|
break
|
|
}
|
|
toast.success(`充值成功`)
|
|
closeDialog()
|
|
await refreshProfile()
|
|
}
|
|
catch (e) {
|
|
toast.error(`充值失败`, {
|
|
description: (e as Error).message,
|
|
})
|
|
}
|
|
}
|
|
|
|
const closeDialog = () => {
|
|
setOpen(false)
|
|
setPayInfo(undefined)
|
|
setStep(0)
|
|
form.reset()
|
|
}
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
<DialogTrigger asChild>
|
|
<Button theme="accent" type="button" className={merge(`px-4 h-8`, props.classNames?.trigger)}>去充值</Button>
|
|
</DialogTrigger>
|
|
|
|
<DialogContent className={platform === Platform.Mobile ? 'max-w-[95vw]' : 'max-w-md'}>
|
|
<DialogTitle className="flex flex-col gap-2">
|
|
充值中心
|
|
</DialogTitle>
|
|
|
|
{step === 0 && (
|
|
<Form form={form} onSubmit={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">
|
|
<FormOption
|
|
id={`${id}-20`}
|
|
value="20"
|
|
label="20元"
|
|
compare={String(field.value)}
|
|
className="flex-1 max-sm:text-sm max-sm:px-0"
|
|
/>
|
|
<FormOption
|
|
id={`${id}-50`}
|
|
value="50"
|
|
label="50元"
|
|
compare={String(field.value)}
|
|
className="flex-1 max-sm:text-sm max-sm:px-0"
|
|
/>
|
|
<FormOption
|
|
id={`${id}-100`}
|
|
value="100"
|
|
label="100元"
|
|
compare={String(field.value)}
|
|
className="flex-1 max-sm:text-sm max-sm:px-0"
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2">
|
|
<FormOption
|
|
id={`${id}-200`}
|
|
value="200"
|
|
label="200元"
|
|
compare={String(field.value)}
|
|
className="flex-1 max-sm:text-sm max-sm:px-0"
|
|
/>
|
|
<FormOption
|
|
id={`${id}-500`}
|
|
value="500"
|
|
label="500元"
|
|
compare={String(field.value)}
|
|
className="flex-1 max-sm:text-sm max-sm:px-0"
|
|
/>
|
|
<FormOption
|
|
id={`${id}-1000`}
|
|
value="1000"
|
|
label="1000元"
|
|
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}
|
|
defaultValue={field.value}
|
|
onValueChange={field.onChange}
|
|
className="flex gap-2">
|
|
{availableMethods.map(({value, name, icon}) => (
|
|
<FormOption
|
|
key={value}
|
|
id={`${id}-${value}`}
|
|
value={value}
|
|
compare={field.value}
|
|
className="flex-1 flex-row justify-center items-center"
|
|
>
|
|
{icon && <Image src={icon} alt={`${name} logo`} className="w-6 h-6"/>}
|
|
<span>{name}</span>
|
|
</FormOption>
|
|
))}
|
|
</RadioGroup>
|
|
)}
|
|
</FormField>
|
|
|
|
<DialogFooter className="!flex !flex-row !justify-center">
|
|
<Button className="px-8 h-12 text-lg">立即支付</Button>
|
|
</DialogFooter>
|
|
</Form>
|
|
)}
|
|
{step == 1 && (
|
|
<>
|
|
<div className="flex flex-col items-center gap-4">
|
|
<div className="flex flex-col items-center gap-3">
|
|
<div className="bg-gray-100 size-50 flex items-center justify-center">
|
|
{payInfo
|
|
? method === 'alipay'
|
|
? <iframe src={payInfo.pay_url} className="w-full h-full"/>
|
|
: <canvas ref={canvas} className="w-full h-full"/>
|
|
: (
|
|
<Loader size={40} className="animate-spin text-weak"/>
|
|
)}
|
|
</div>
|
|
<p className="text-sm text-gray-600 text-center">
|
|
请使用
|
|
{method === 'alipay' ? '支付宝' : '微信'}
|
|
扫码支付
|
|
</p>
|
|
</div>
|
|
<div className="text-center space-y-1">
|
|
<p className="font-medium">
|
|
支付金额:
|
|
<span className="text-accent">
|
|
{amount}
|
|
元
|
|
</span>
|
|
</p>
|
|
<p className="text-xs text-gray-500">
|
|
订单号:
|
|
{payInfo?.trade_no || '创建订单中...'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<DialogFooter className="!flex !flex-row !justify-center">
|
|
<Button
|
|
className="px-8 text-lg"
|
|
onClick={confirmRecharge}
|
|
>
|
|
已完成支付
|
|
</Button>
|
|
<Button
|
|
theme="outline"
|
|
className="px-8 text-lg"
|
|
onClick={closeDialog}
|
|
>
|
|
关闭
|
|
</Button>
|
|
</DialogFooter>
|
|
</>
|
|
)}
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|