Files
web/src/components/composites/purchase/pay.tsx
2026-03-31 16:11:46 +08:00

196 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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'
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
} & ({
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,
}
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>
<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}
</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)}
</span>
</div>
</div>
{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>
)
}