2025-04-16 18:51:17 +08:00
|
|
|
|
'use client'
|
2025-06-22 14:42:21 +08:00
|
|
|
|
import {Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle} from '@/components/ui/dialog'
|
2025-04-16 18:51:17 +08:00
|
|
|
|
import {Button} from '@/components/ui/button'
|
2025-05-22 14:59:22 +08:00
|
|
|
|
import balance from './_assets/balance.svg'
|
2025-04-16 18:51:17 +08:00
|
|
|
|
import Image from 'next/image'
|
2025-06-22 14:42:21 +08:00
|
|
|
|
import {useState} from 'react'
|
2025-06-22 10:58:41 +08:00
|
|
|
|
import {useProfileStore} from '@/components/stores-provider'
|
2025-06-16 11:27:34 +08:00
|
|
|
|
import {Alert, AlertTitle} from '@/components/ui/alert'
|
2025-04-16 18:51:17 +08:00
|
|
|
|
import {toast} from 'sonner'
|
|
|
|
|
|
import {useRouter} from 'next/navigation'
|
2025-05-22 14:59:22 +08:00
|
|
|
|
import {completeResource, createResource, prepareResource} from '@/actions/resource'
|
2025-06-18 19:05:38 +08:00
|
|
|
|
import {PaymentMethod, Platform, usePlatformType} from '@/lib/models/trade'
|
2025-06-22 14:42:21 +08:00
|
|
|
|
import {PaymentModal} from '@/components/composites/payment/payment-modal'
|
|
|
|
|
|
import type {Trade} from '@/components/composites/payment/types'
|
2025-04-16 18:51:17 +08:00
|
|
|
|
|
|
|
|
|
|
export type PayProps = {
|
|
|
|
|
|
method: 'alipay' | 'wechat' | 'balance'
|
2025-05-22 14:59:22 +08:00
|
|
|
|
amount: string
|
2025-06-22 14:42:21 +08:00
|
|
|
|
resource: Parameters<typeof createResource>[0]
|
2025-04-16 18:51:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default function Pay(props: PayProps) {
|
2025-05-19 11:04:40 +08:00
|
|
|
|
const profile = useProfileStore(store => store.profile)
|
|
|
|
|
|
const refreshProfile = useProfileStore(store => store.refreshProfile)
|
2025-04-16 18:51:17 +08:00
|
|
|
|
const [open, setOpen] = useState(false)
|
2025-06-22 14:42:21 +08:00
|
|
|
|
const [trade, setTrade] = useState<Trade | null>(null)
|
|
|
|
|
|
const router = useRouter()
|
|
|
|
|
|
const platform = usePlatformType()
|
2025-04-16 18:51:17 +08:00
|
|
|
|
|
|
|
|
|
|
const onOpen = async () => {
|
|
|
|
|
|
setOpen(true)
|
|
|
|
|
|
|
2025-06-22 14:42:21 +08:00
|
|
|
|
if (props.method === 'balance') return
|
2025-04-16 18:51:17 +08:00
|
|
|
|
|
2025-06-22 14:42:21 +08:00
|
|
|
|
// 准备支付信息
|
|
|
|
|
|
const paymentMethod = props.method === 'alipay'
|
|
|
|
|
|
? platform === Platform.Mobile
|
|
|
|
|
|
? PaymentMethod.SftAlipay // 4
|
|
|
|
|
|
: PaymentMethod.Alipay // 1
|
|
|
|
|
|
: platform === Platform.Mobile
|
|
|
|
|
|
? PaymentMethod.SftWeChat // 5
|
|
|
|
|
|
: PaymentMethod.WeChat // 2
|
2025-05-22 14:59:22 +08:00
|
|
|
|
|
2025-06-18 19:05:38 +08:00
|
|
|
|
const res = {
|
2025-05-22 14:59:22 +08:00
|
|
|
|
...props.resource,
|
2025-06-18 19:05:38 +08:00
|
|
|
|
payment_method: paymentMethod,
|
2025-06-22 14:42:21 +08:00
|
|
|
|
payment_platform: platform,
|
2025-06-18 19:05:38 +08:00
|
|
|
|
}
|
2025-06-22 14:42:21 +08:00
|
|
|
|
console.log(res, '请求参数')
|
2025-06-18 19:05:38 +08:00
|
|
|
|
|
2025-06-22 14:42:21 +08:00
|
|
|
|
const resp = await prepareResource(res)
|
2025-04-16 18:51:17 +08:00
|
|
|
|
if (!resp.success) {
|
|
|
|
|
|
toast.error(`创建订单失败: ${resp.message}`)
|
|
|
|
|
|
setOpen(false)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-22 14:42:21 +08:00
|
|
|
|
setTrade({
|
|
|
|
|
|
inner_no: resp.data.trade_no,
|
|
|
|
|
|
method: props.method === 'alipay' ? PaymentMethod.Alipay : PaymentMethod.WeChat,
|
|
|
|
|
|
pay_url: resp.data.pay_url,
|
|
|
|
|
|
amount: Number(props.amount),
|
|
|
|
|
|
})
|
2025-04-16 18:51:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const onSubmit = async () => {
|
|
|
|
|
|
try {
|
2025-06-22 14:42:21 +08:00
|
|
|
|
let resp: Awaited<ReturnType<typeof completeResource>> | Awaited<ReturnType<typeof createResource>>
|
2025-05-22 14:59:22 +08:00
|
|
|
|
|
2025-06-22 14:42:21 +08:00
|
|
|
|
if (props.method === 'balance') {
|
2025-05-22 14:59:22 +08:00
|
|
|
|
resp = await createResource(props.resource)
|
2025-04-16 18:51:17 +08:00
|
|
|
|
}
|
2025-06-22 14:42:21 +08:00
|
|
|
|
else if (trade) {
|
|
|
|
|
|
resp = await completeResource({trade_no: trade.inner_no})
|
2025-04-16 18:51:17 +08:00
|
|
|
|
}
|
2025-06-22 14:42:21 +08:00
|
|
|
|
else {
|
|
|
|
|
|
throw new Error('支付信息不存在')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!resp.success) throw new Error(resp.message)
|
2025-04-16 18:51:17 +08:00
|
|
|
|
|
|
|
|
|
|
toast.success('购买成功', {
|
|
|
|
|
|
action: {
|
2025-06-22 14:42:21 +08:00
|
|
|
|
label: '去提取',
|
2025-04-16 18:51:17 +08:00
|
|
|
|
onClick: () => router.push('/admin/extract'),
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
setOpen(false)
|
2025-04-23 19:00:53 +08:00
|
|
|
|
await refreshProfile()
|
2025-04-16 18:51:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (e) {
|
|
|
|
|
|
toast.error('购买失败', {
|
|
|
|
|
|
description: (e as Error).message,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-22 14:59:22 +08:00
|
|
|
|
const balanceEnough = profile && profile.balance >= Number(props.amount)
|
|
|
|
|
|
|
2025-04-16 18:51:17 +08:00
|
|
|
|
return (
|
2025-06-22 14:42:21 +08:00
|
|
|
|
<>
|
|
|
|
|
|
<Button className="mt-4 h-12" onClick={onOpen}>
|
|
|
|
|
|
立即支付
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 余额支付对话框 */}
|
|
|
|
|
|
{/* 余额支付对话框 */}
|
|
|
|
|
|
{props.method === 'balance' && (
|
|
|
|
|
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
|
|
|
|
<DialogContent>
|
|
|
|
|
|
<DialogHeader>
|
|
|
|
|
|
<DialogTitle className="flex gap-2 items-center">
|
2025-06-07 11:49:57 +08:00
|
|
|
|
<Image src={balance} alt="余额" width={20} height={20}/>
|
|
|
|
|
|
<span>余额支付</span>
|
2025-06-22 14:42:21 +08:00
|
|
|
|
</DialogTitle>
|
|
|
|
|
|
</DialogHeader>
|
|
|
|
|
|
|
|
|
|
|
|
{profile && (
|
|
|
|
|
|
<div className="flex flex-col gap-4">
|
|
|
|
|
|
<div className="flex flex-col gap-1">
|
|
|
|
|
|
<div className="flex justify-between items-center">
|
|
|
|
|
|
<span className="text-weak text-sm">账户余额</span>
|
|
|
|
|
|
<span className="text-lg">
|
|
|
|
|
|
{profile.balance}
|
|
|
|
|
|
元
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex justify-between items-center">
|
|
|
|
|
|
<span className="text-weak text-sm">支付金额</span>
|
|
|
|
|
|
<span className="text-lg text-accent">
|
|
|
|
|
|
-
|
|
|
|
|
|
{props.amount}
|
|
|
|
|
|
元
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<hr className="my-2"/>
|
|
|
|
|
|
<div className="flex justify-between items-center">
|
|
|
|
|
|
<span className="text-weak text-sm">支付后余额</span>
|
|
|
|
|
|
<span className={`text-lg ${balanceEnough ? 'text-done' : 'text-fail'}`}>
|
|
|
|
|
|
{(profile.balance - Number(props.amount)).toFixed(2)}
|
|
|
|
|
|
元
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
2025-04-16 18:51:17 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-06-22 14:42:21 +08:00
|
|
|
|
{balanceEnough ? (
|
|
|
|
|
|
<Alert variant="done">
|
|
|
|
|
|
<AlertTitle>
|
|
|
|
|
|
检查无误后,点击确认支付按钮完成支付
|
|
|
|
|
|
</AlertTitle>
|
|
|
|
|
|
</Alert>
|
2025-04-16 18:51:17 +08:00
|
|
|
|
) : (
|
2025-06-22 14:42:21 +08:00
|
|
|
|
<Alert variant="fail">
|
|
|
|
|
|
<AlertTitle>
|
|
|
|
|
|
余额不足,请先充值或选择其他支付方式
|
|
|
|
|
|
</AlertTitle>
|
|
|
|
|
|
</Alert>
|
2025-04-16 18:51:17 +08:00
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2025-06-22 14:42:21 +08:00
|
|
|
|
)}
|
2025-04-16 18:51:17 +08:00
|
|
|
|
|
2025-06-22 14:42:21 +08:00
|
|
|
|
<DialogFooter>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
disabled={!balanceEnough}
|
|
|
|
|
|
onClick={onSubmit}
|
|
|
|
|
|
>
|
|
|
|
|
|
确认支付
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button theme="outline" onClick={() => setOpen(false)}>
|
|
|
|
|
|
取消
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</DialogFooter>
|
|
|
|
|
|
</DialogContent>
|
|
|
|
|
|
</Dialog>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* 支付宝/微信支付使用公共组件 */}
|
|
|
|
|
|
{props.method !== 'balance' && trade && (
|
|
|
|
|
|
<PaymentModal
|
|
|
|
|
|
open={open}
|
|
|
|
|
|
onOpenChange={setOpen}
|
|
|
|
|
|
trade={trade}
|
|
|
|
|
|
platform={platform}
|
|
|
|
|
|
onSuccess={onSubmit}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</>
|
2025-04-16 18:51:17 +08:00
|
|
|
|
)
|
|
|
|
|
|
}
|