Files
web/src/components/composites/purchase/pay.tsx

196 lines
5.8 KiB
TypeScript
Raw Normal View History

'use client'
import {Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle} from '@/components/ui/dialog'
import {Button} from '@/components/ui/button'
import Image from 'next/image'
import imgBalance from './_assets/balance.svg'
import {useState} from 'react'
2025-12-11 14:10:52 +08:00
import {useProfileStore} from '@/components/stores/profile'
import {Alert, AlertTitle} from '@/components/ui/alert'
import {toast} from 'sonner'
import {useRouter} from 'next/navigation'
import {completeResource, createResource, CreateResourceReq, prepareResource} from '@/actions/resource'
import {
TradeMethod,
TradePlatform,
} from '@/lib/models/trade'
import {PaymentModal} from '@/components/composites/payment/payment-modal'
import {PaymentProps} from '@/components/composites/payment/type'
import {usePlatformType} from '@/lib/hooks'
export type PayProps = {
amount: string
resource: CreateResourceReq
2025-12-11 14:10:52 +08:00
} & ({
method: 'alipay' | 'wechat'
} | {
method: 'balance'
balance: number
})
export default function Pay(props: PayProps) {
const refreshProfile = useProfileStore(store => store.refreshProfile)
const [open, setOpen] = useState(false)
const [trade, setTrade] = useState<PaymentProps | null>(null)
const router = useRouter()
// const platform = usePlatformType()
const onOpen = async () => {
setOpen(true)
if (props.method === 'balance') return
const method = props.method === 'alipay'
? TradeMethod.SftAlipay
: TradeMethod.SftWechat
const req = {
...props.resource,
payment_method: method,
payment_platform: TradePlatform.Desktop,
}
2026-03-30 13:11:40 +08:00
console.log(req, 'req')
const resp = await prepareResource(req)
if (!resp.success) {
toast.error(`创建订单失败: ${resp.message}`)
setOpen(false)
return
}
setTrade({
inner_no: resp.data.trade_no,
pay_url: resp.data.pay_url,
amount: Number(props.amount),
platform: TradePlatform.Desktop,
method: method,
})
}
const purchase = async (showFail: boolean) => {
try {
let resp: Awaited<ReturnType<typeof completeResource>> | Awaited<ReturnType<typeof createResource>>
if (props.method === 'balance') {
resp = await createResource(props.resource)
}
else if (trade) {
resp = await completeResource({
trade_no: trade.inner_no,
method: trade.method,
})
}
else {
throw new Error('支付信息不存在')
}
if (!resp.success) {
throw new Error(resp.message)
}
setOpen(false)
setTrade(null)
toast.success('购买成功', {
action: {
label: '去提取',
onClick: () => router.push('/admin/extract'),
},
})
refreshProfile()
}
catch (e) {
if (showFail) {
toast.error('购买失败', {
description: (e as Error).message,
})
}
}
}
const balanceEnough = props.method == 'balance' && props.balance >= Number(props.amount)
return (
<div className="mt-4">
<Button className="w-full h-12" onClick={onOpen}>
</Button>
{/* 余额支付对话框 */}
{props.method === 'balance' && (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle className="flex gap-2 items-center">
<Image src={imgBalance} alt="余额" width={20} height={20}/>
<span></span>
</DialogTitle>
</DialogHeader>
2025-12-11 14:10:52 +08:00
<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">
{props.balance}
2025-12-11 14:10:52 +08:00
</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'}`}>
{(props.balance - Number(props.amount)).toFixed(2)}
2025-12-11 14:10:52 +08:00
</span>
</div>
</div>
2025-12-11 14:10:52 +08:00
{balanceEnough ? (
<Alert variant="done">
<AlertTitle>
</AlertTitle>
</Alert>
) : (
<Alert variant="fail">
<AlertTitle>
</AlertTitle>
</Alert>
)}
</div>
<DialogFooter>
<Button
disabled={!balanceEnough}
onClick={() => purchase(true)}
>
</Button>
<DialogClose asChild>
<Button theme="outline" onClick={() => setOpen(false)}>
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
)}
{/* 支付宝/微信支付 */}
{props.method !== 'balance' && trade && (
<PaymentModal
{...trade}
onConfirm={purchase}
onClose={() => {
setTrade(null)
setOpen(false)
}}
/>
)}
</div>
)
}