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

222 lines
7.1 KiB
TypeScript
Raw Normal View History

'use client'
import {Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger} from '@/components/ui/dialog'
import {Button} from '@/components/ui/button'
import alipay from '../_assets/alipay.svg'
import wechat from '../_assets/wechat.svg'
import balance from '../_assets/balance.svg'
import Image from 'next/image'
import {useContext, useRef, useState} from 'react'
import {StoreContext, useProfileStore} from '@/components/providers/StoreProvider'
import {Alert, AlertDescription} from '@/components/ui/alert'
import {
prepareResourceByAlipay,
prepareResourceByWechat,
CreateResourceReq,
CreateResourceResp,
createResourceByBalance,
createResourceByAlipay,
createResourceByWechat,
} from '@/actions/resource'
import {ApiResponse} from '@/lib/api'
import {toast} from 'sonner'
import {Loader} from 'lucide-react'
import {useRouter} from 'next/navigation'
import * as qrcode from 'qrcode'
export type PayProps = {
method: 'alipay' | 'wechat' | 'balance'
amount: number
resource: CreateResourceReq
}
export default function Pay(props: PayProps) {
const profile = useProfileStore(store=>store.profile)
const refreshProfile = useProfileStore(store=>store.refreshProfile)
const [open, setOpen] = useState(false)
const [payInfo, setPayInfo] = useState<CreateResourceResp | undefined>()
const canvas = useRef<HTMLCanvasElement>(null)
const onOpen = async () => {
setOpen(true)
if (props.method === 'balance') {
return
}
let resp: ApiResponse<CreateResourceResp>
switch (props.method) {
case 'alipay':
resp = await prepareResourceByAlipay(props.resource)
break
case 'wechat':
resp = await prepareResourceByWechat(props.resource)
break
}
if (!resp.success) {
toast.error(`创建订单失败: ${resp.message}`)
setOpen(false)
return
}
setPayInfo(resp.data)
await qrcode.toCanvas(canvas.current, resp.data.pay_url)
}
const router = useRouter()
const onSubmit = async () => {
let resp: ApiResponse
try {
switch (props.method) {
case 'alipay':
resp = await createResourceByAlipay({
trade_no: payInfo!.trade_no,
})
break
case 'wechat':
resp = await createResourceByWechat({
trade_no: payInfo!.trade_no,
})
break
case 'balance':
resp = await createResourceByBalance(props.resource)
break
}
if (!resp.success) {
throw new Error(resp.message)
}
toast.success('购买成功', {
duration: 10 * 1000,
closeButton: true,
action: {
label: `去提取`,
onClick: () => router.push('/admin/extract'),
},
})
setOpen(false)
await refreshProfile()
}
catch (e) {
console.log(e)
toast.error('购买失败', {
description: (e as Error).message,
})
}
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button className={`mt-4 h-12`} onClick={onOpen}>
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle className={`flex gap-2 items-center`}>
{props.method === 'alipay' && (<>
<Image src={alipay} alt={`支付宝`} width={20} height={20}/>
<span></span>
</>)}
{props.method === 'wechat' && (<>
<Image src={wechat} alt={`微信`} width={20} height={20}/>
<span></span>
</>)}
{props.method === 'balance' && (<>
<Image src={balance} alt={`余额`} width={20} height={20}/>
<span></span>
</>)}
</DialogTitle>
</DialogHeader>
{props.method === 'balance' ? (
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 ${profile.balance > props.amount ? 'text-done' : `text-fail`}`}>
{profile.balance - props.amount}
</span>
</div>
</div>
{profile.balance < props.amount && (
<Alert variant="fail">
<AlertDescription>
</AlertDescription>
</Alert>
)}
{profile.balance >= props.amount && (
<Alert>
<AlertDescription>
</AlertDescription>
</Alert>
)}
</div>
)
) : (
<div className="flex flex-col items-center gap-4">
<div className="flex flex-col items-center gap-3">
<div className="bg-gray-100 w-52 h-52 flex items-center justify-center">
{payInfo ? (
props.method === 'alipay'
? <iframe
src={payInfo.pay_url}
className="w-full h-full"
title="支付二维码"
/>
: <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">
使{props.method === 'alipay' ? '支付宝' : '微信'}
</p>
</div>
<div className="text-center space-y-1">
<p className="font-medium">: <span className="text-accent">{props.amount}</span></p>
<p className="text-xs text-gray-500">: {payInfo?.trade_no || '创建订单中...'}</p>
</div>
</div>
)}
<DialogFooter>
<Button
type="button"
disabled={props.method === 'balance' && !!profile && profile.balance < props.amount}
onClick={onSubmit}
>
{props.method === 'balance' ? '确认支付' : '已完成支付'}
</Button>
<Button
type="button"
theme="outline"
onClick={() => setOpen(false)}
>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}