修复支付取消的调用和规定时间内更新订单支付状态

This commit is contained in:
Eamon-meng
2025-08-16 11:41:07 +08:00
parent 1baa7c94dc
commit 99c3b9914e
7 changed files with 105 additions and 98 deletions

View File

@@ -94,3 +94,10 @@ export async function completeResource(props: {
}) { }) {
return callByUser('/api/trade/complete', props) return callByUser('/api/trade/complete', props)
} }
export async function payClose(props: {
trade_no: string
method: number
}) {
return callByUser('/api/trade/cancel', props)
}

View File

@@ -1,5 +1,5 @@
'use client' 'use client'
import {DialogContent, DialogHeader, DialogTitle} from '@/components/ui/dialog' import {DialogClose, DialogContent, DialogHeader, DialogTitle} from '@/components/ui/dialog'
import {Button} from '@/components/ui/button' import {Button} from '@/components/ui/button'
import {Loader} from 'lucide-react' import {Loader} from 'lucide-react'
import {useState} from 'react' import {useState} from 'react'
@@ -11,7 +11,7 @@ export function DesktopPayment(props: PaymentModalProps) {
const onSubmit = async () => { const onSubmit = async () => {
setLoading(true) setLoading(true)
await props.onConfirm?.() await props.onConfirm(true)
setLoading(false) setLoading(false)
} }
@@ -67,9 +67,11 @@ export function DesktopPayment(props: PaymentModalProps) {
{loading && <Loader className="animate-spin mr-2"/>} {loading && <Loader className="animate-spin mr-2"/>}
</Button> </Button>
<Button theme="outline" onClick={() => props.onClose?.()}> <DialogClose asChild>
<Button theme="outline" onClick={() => props.onClose?.()}>
</Button>
</Button>
</DialogClose>
</div> </div>
</div> </div>
</DialogContent> </DialogContent>

View File

@@ -1,5 +1,5 @@
'use client' 'use client'
import {DialogContent} from '@/components/ui/dialog' import {DialogClose, DialogContent} from '@/components/ui/dialog'
import {Button} from '@/components/ui/button' import {Button} from '@/components/ui/button'
import {toast} from 'sonner' import {toast} from 'sonner'
import {CreditCard, Loader} from 'lucide-react' import {CreditCard, Loader} from 'lucide-react'
@@ -26,7 +26,7 @@ export function MobilePayment(props: PaymentModalProps) {
// 处理支付完成确认 // 处理支付完成确认
const handlePaymentComplete = async () => { const handlePaymentComplete = async () => {
setLoading(true) setLoading(true)
await props.onConfirm?.() // 调用父组件传入的确认方法 await props.onConfirm(true) // 调用父组件传入的确认方法
setLoading(false) setLoading(false)
} }
@@ -77,13 +77,15 @@ export function MobilePayment(props: PaymentModalProps) {
<div className="flex gap-3"> <div className="flex gap-3">
{!paymentInitiated ? ( // 未发起支付时显示 {!paymentInitiated ? ( // 未发起支付时显示
<> <>
<Button <DialogClose asChild>
theme="outline" <Button
className="flex-1 py-3 text-base" theme="outline"
onClick={props.onClose} className="flex-1 py-3 text-base"
> onClick={props.onClose}
>
</Button>
</Button>
</DialogClose>
<Button <Button
className="flex-1 py-3 text-base" className="flex-1 py-3 text-base"
onClick={handleConfirmPayment} onClick={handleConfirmPayment}

View File

@@ -1,46 +0,0 @@
'use client'
import {Button} from '@/components/ui/button'
import {useState} from 'react'
import {PaymentModal} from './payment-modal'
import {PaymentProps} from './type'
export function PaymentButton({
onClick,
disabled,
onSuccess,
}: {
onClick: () => Promise<PaymentProps>
disabled?: boolean
onSuccess?: () => void
}) {
const [loading, setLoading] = useState(false)
const [trade, setTrade] = useState<PaymentProps>()
const handleClick = async () => {
setLoading(true)
try {
setTrade(await onClick())
}
finally {
setLoading(false)
}
}
return (
<>
<Button
onClick={handleClick}
disabled={disabled || loading}
>
{loading ? '处理中...' : '立即支付'}
</Button>
{trade && (
<PaymentModal
{...trade}
onConfirm={onSuccess}
/>
)}
</>
)
}

View File

@@ -4,26 +4,75 @@ import {DesktopPayment} from './desktop-payment'
import {TradePlatform} from '@/lib/models/trade' import {TradePlatform} from '@/lib/models/trade'
import {Dialog} from '@/components/ui/dialog' import {Dialog} from '@/components/ui/dialog'
import {PaymentProps} from './type' import {PaymentProps} from './type'
import {payClose} from '@/actions/resource'
import {useEffect} from 'react'
import {useRouter} from 'next/navigation'
export type PaymentModalProps = { export type PaymentModalProps = {
onConfirm?: () => Promise<void> onConfirm: (showFail: boolean) => Promise<void>
onClose?: () => void onClose: () => void
} & PaymentProps } & PaymentProps
export function PaymentModal(props: PaymentModalProps) { export function PaymentModal(props: PaymentModalProps) {
// 手动关闭时的处理
const handleClose = async () => {
try {
const req = {
trade_no: props.inner_no,
method: props.method,
}
const res = await payClose(req)
}
catch (error) {
console.error('关闭订单失败:', error)
}
finally {
props.onClose?.()
}
}
// 轮询检查支付状态
useEffect(() => {
const pollInterval = 2000
const maxRetries = 30
let retries = 0
const interval = setInterval(async () => {
try {
await props.onConfirm(false)
return
}
catch (error) {
console.error('支付状态检查失败:', error)
}
finally {
retries++
if (retries >= maxRetries) {
clearInterval(interval)
}
}
}, pollInterval)
return () => {
clearInterval(interval)
}
}, [props])
return ( return (
<Dialog <Dialog
defaultOpen={true} defaultOpen={true}
onOpenChange={(open) => { onOpenChange={(open) => {
if (!open) props.onClose?.() if (!open) handleClose()
}}> }}>
{props.platform === TradePlatform.Mobile ? ( {props.platform === TradePlatform.Mobile ? (
<MobilePayment <MobilePayment
{...props} {...props}
onClose={handleClose}
/> />
) : ( ) : (
<DesktopPayment <DesktopPayment
{...props} {...props}
onClose={handleClose}
/> />
)} )}
</Dialog> </Dialog>

View File

@@ -1,5 +1,5 @@
'use client' 'use client'
import {Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle} from '@/components/ui/dialog' import {Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle} from '@/components/ui/dialog'
import {Button} from '@/components/ui/button' import {Button} from '@/components/ui/button'
import balance from './_assets/balance.svg' import balance from './_assets/balance.svg'
import Image from 'next/image' import Image from 'next/image'
@@ -39,13 +39,14 @@ export default function Pay(props: PayProps) {
const method = props.method === 'alipay' const method = props.method === 'alipay'
? TradeMethod.SftAlipay ? TradeMethod.SftAlipay
: TradeMethod.SftWechat : TradeMethod.SftWechat
const res = { const req = {
...props.resource, ...props.resource,
payment_method: method, payment_method: method,
payment_platform: platform, payment_platform: platform,
} }
const resp = await prepareResource(res) const resp = await prepareResource(req)
if (!resp.success) { if (!resp.success) {
toast.error(`创建订单失败: ${resp.message}`) toast.error(`创建订单失败: ${resp.message}`)
setOpen(false) setOpen(false)
@@ -62,10 +63,9 @@ export default function Pay(props: PayProps) {
}) })
} }
const onSubmit = async () => { const purchase = async (showFail: boolean) => {
try { try {
let resp: Awaited<ReturnType<typeof completeResource>> | Awaited<ReturnType<typeof createResource>> let resp: Awaited<ReturnType<typeof completeResource>> | Awaited<ReturnType<typeof createResource>>
if (props.method === 'balance') { if (props.method === 'balance') {
resp = await createResource(props.resource) resp = await createResource(props.resource)
} }
@@ -79,22 +79,26 @@ export default function Pay(props: PayProps) {
throw new Error('支付信息不存在') throw new Error('支付信息不存在')
} }
if (!resp.success) throw new Error(resp.message) if (!resp.success) {
throw new Error(resp.message)
}
setOpen(false)
setTrade(null)
toast.success('购买成功', { toast.success('购买成功', {
action: { action: {
label: '去提取', label: '去提取',
onClick: () => router.push('/admin/extract'), onClick: () => router.push('/admin/extract'),
}, },
}) })
refreshProfile()
setOpen(false)
await refreshProfile()
} }
catch (e) { catch (e) {
toast.error('购买失败', { if (showFail) {
description: (e as Error).message, toast.error('购买失败', {
}) description: (e as Error).message,
})
}
} }
} }
@@ -164,38 +168,25 @@ export default function Pay(props: PayProps) {
<DialogFooter> <DialogFooter>
<Button <Button
disabled={!balanceEnough} disabled={!balanceEnough}
onClick={onSubmit} onClick={() => purchase(true)}
> >
</Button> </Button>
<Button theme="outline" onClick={() => setOpen(false)}> <DialogClose asChild>
<Button theme="outline" onClick={() => setOpen(false)}>
</Button>
</Button>
</DialogClose>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
)} )}
{/* 支付宝/微信支付使用公共组件 */} {/* 支付宝/微信支付 */}
{props.method !== 'balance' && trade && ( {props.method !== 'balance' && trade && (
<PaymentModal <PaymentModal
{...trade} {...trade}
onConfirm={async () => { onConfirm={purchase}
try {
const resp = await completeResource({trade_no: trade.inner_no, method: trade.method})
if (!resp.success) {
throw new Error(resp.message)
}
toast.success('支付成功')
setTrade(null)
setOpen(false)
refreshProfile()
}
catch (e) {
toast.error('支付验证失败', {description: (e as Error).message})
}
}}
onClose={() => { onClose={() => {
setTrade(null) setTrade(null)
setOpen(false) setOpen(false)

View File

@@ -91,7 +91,7 @@ export default function RechargeModal(props: RechargeModelProps) {
} }
} }
const handlePaymentSuccess = async () => { const handlePaymentSuccess = async (showFail: boolean) => {
if (!trade) return if (!trade) return
try { try {
const resp = await RechargeComplete({ const resp = await RechargeComplete({
@@ -110,7 +110,9 @@ export default function RechargeModal(props: RechargeModelProps) {
await refreshProfile() await refreshProfile()
} }
catch (e) { catch (e) {
toast.error('支付验证失败', {description: (e as Error).message}) if (showFail) {
toast.error('支付验证失败', {description: (e as Error).message})
}
} }
} }