196 lines
5.8 KiB
TypeScript
196 lines
5.8 KiB
TypeScript
'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>
|
||
)
|
||
}
|