支付流程重新设计枚举值更新传参方式
This commit is contained in:
@@ -14,7 +14,7 @@ export function PaymentDialog({trade, open, onOpenChange}: {
|
||||
trade: {
|
||||
inner_no: string
|
||||
method: number
|
||||
pay_url: string
|
||||
pay_url?: string
|
||||
amount?: number
|
||||
}
|
||||
open: boolean
|
||||
@@ -36,7 +36,7 @@ export function PaymentDialog({trade, open, onOpenChange}: {
|
||||
const canvas = useRef<HTMLCanvasElement>(null)
|
||||
// 生成微信二维码
|
||||
useEffect(() => {
|
||||
if (!open || !canvas.current || trade.method === 1) return
|
||||
if (!open || !canvas.current || trade.method === 1 || !trade.pay_url) return
|
||||
qrcode.toCanvas(canvas.current, trade.pay_url, {
|
||||
width: 200,
|
||||
margin: 0,
|
||||
@@ -112,7 +112,11 @@ export function PaymentDialog({trade, open, onOpenChange}: {
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<div className="bg-gray-100 size-50 flex items-center justify-center">
|
||||
{trade.method === 1 ? (
|
||||
<iframe src={trade.pay_url} className="w-full h-full"/>
|
||||
trade.pay_url ? (
|
||||
<iframe src={trade.pay_url} className="w-full h-full"/>
|
||||
) : (
|
||||
<div className="text-center text-gray-500">支付链接无效</div>
|
||||
)
|
||||
) : (
|
||||
<canvas ref={canvas} className="w-full h-full"/>
|
||||
)}
|
||||
|
||||
@@ -8,8 +8,9 @@ import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
import wechat from '@/components/composites/purchase/_assets/wechat.svg'
|
||||
import alipay from '@/components/composites/purchase/_assets/alipay.svg'
|
||||
import {usePlatformType, Platform} from '@/lib/models/trade'
|
||||
import {usePlatformType, TradePlatform} from '@/lib/models/trade'
|
||||
import {PaymentDialog} from './payment-dialog'
|
||||
import {PaymentProps} from '@/components/composites/payment/type'
|
||||
|
||||
export function PaymentStatusCell({trade}: {
|
||||
trade?: {
|
||||
@@ -29,7 +30,7 @@ export function PaymentStatusCell({trade}: {
|
||||
e.preventDefault()
|
||||
if (!trade?.pay_url) return
|
||||
|
||||
if (platform === Platform.Desktop) {
|
||||
if (platform === TradePlatform.Desktop) {
|
||||
setOpen(true)
|
||||
}
|
||||
else {
|
||||
@@ -69,82 +70,6 @@ export function PaymentStatusCell({trade}: {
|
||||
)
|
||||
}
|
||||
|
||||
if (paymentFailed) {
|
||||
return (
|
||||
<Dialog open={paymentFailed} onOpenChange={setPaymentFailed}>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<div className="text-center mb-6">
|
||||
<div className="inline-flex items-center justify-center h-16 w-16 rounded-full bg-primary/10 text-primary mb-4">
|
||||
<i className="fa fa-credit-card text-2xl"></i>
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-neutral-700 mb-2">支付失败</h3>
|
||||
<p className="text-neutral-500">未能成功唤起支付App,请检查是否已安装相关应用或选择其他支付方式。</p>
|
||||
</div>
|
||||
<div className="space-y-3 py-4 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-500">订单号</span>
|
||||
<span>{trade.inner_no}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-500">支付方式</span>
|
||||
<span>
|
||||
{trade.method === 1 ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Image
|
||||
src={alipay}
|
||||
alt="支付宝logo"
|
||||
width={16}
|
||||
height={16}
|
||||
className="rounded-md"
|
||||
/>
|
||||
<span>支付宝</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<Image
|
||||
src={wechat}
|
||||
alt="微信支付logo"
|
||||
width={16}
|
||||
height={16}
|
||||
className="rounded-md"
|
||||
/>
|
||||
<span>微信支付</span>
|
||||
</div>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-500">支付金额</span>
|
||||
<span className="font-medium text-red-500">
|
||||
¥
|
||||
{typeof trade.amount === 'string'
|
||||
? parseFloat(trade.amount).toFixed(2)
|
||||
: (trade.amount || 0).toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-500">支付时间</span>
|
||||
<span>{format(new Date(), 'yyyy-MM-dd HH:mm:ss')}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center mt-4">
|
||||
<Button className="w-full max-w-[200px]" onClick={() => setPaymentFailed(false)}>完成</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
if (!trade.inner_no || !trade.method || !trade.pay_url) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<ClockIcon size={16} className="text-warn"/>
|
||||
<span>订单异常</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<ClockIcon size={16} className="text-warn"/>
|
||||
|
||||
@@ -7,44 +7,32 @@ import {CheckCircle, Loader} from 'lucide-react'
|
||||
import {useState, useEffect, useRef} from 'react'
|
||||
import * as qrcode from 'qrcode'
|
||||
import Image from 'next/image'
|
||||
import wechat from '@/components/composites/purchase/_assets/wechat.svg'
|
||||
import alipay from '@/components/composites/purchase/_assets/alipay.svg'
|
||||
import {PaymentMethod} from './types'
|
||||
import {TradeMethod} from '@/lib/models/trade'
|
||||
import {PaymentModalProps} from './payment-modal'
|
||||
|
||||
interface Trade {
|
||||
inner_no: string
|
||||
method: number
|
||||
pay_url: string
|
||||
amount?: number
|
||||
status?: number
|
||||
}
|
||||
export function DesktopPayment({trade, onClose}: {trade: Trade, onClose: () => void}) {
|
||||
export function DesktopPayment(props: PaymentModalProps) {
|
||||
console.log(props, 'props')
|
||||
const [paymentVerified, setPaymentVerified] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
|
||||
const paymentInfo = {
|
||||
icon: trade.method === PaymentMethod.Alipay ? alipay : wechat,
|
||||
name: trade.method === PaymentMethod.Alipay ? '支付宝' : '微信支付',
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!canvasRef.current || trade.method === 1) return
|
||||
qrcode.toCanvas(canvasRef.current, trade.pay_url, {width: 200})
|
||||
if (!canvasRef.current || props.method === TradeMethod.Alipay) return
|
||||
qrcode.toCanvas(canvasRef.current, props.pay_url, {width: 200})
|
||||
.catch((err) => {
|
||||
console.error('生成二维码失败:', err)
|
||||
toast.error('生成支付二维码失败')
|
||||
})
|
||||
}, [trade.method, trade.pay_url])
|
||||
}, [props.method, props.pay_url])
|
||||
|
||||
const handleComplete = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const resp = await completeResource({trade_no: trade.inner_no})
|
||||
const resp = await completeResource({trade_no: props.inner_no})
|
||||
if (!resp.success) throw new Error(resp.message)
|
||||
toast.success('支付成功')
|
||||
setPaymentVerified(true)
|
||||
setTimeout(onClose, 2000)
|
||||
setTimeout(() => props.onSuccess?.(), 2000)
|
||||
}
|
||||
catch (e) {
|
||||
toast.error('支付验证失败', {description: (e as Error).message})
|
||||
@@ -53,13 +41,24 @@ export function DesktopPayment({trade, onClose}: {trade: Trade, onClose: () => v
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
console.log(props.decoration.text, 'props.decoration.text')
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex gap-2 items-center">
|
||||
<Image src={paymentInfo.icon} alt={paymentInfo.name} width={24} height={24}/>
|
||||
<span>{paymentInfo.name}</span>
|
||||
{props.decoration.icon ? (
|
||||
<Image
|
||||
src={props.decoration.icon}
|
||||
alt={props.decoration.text}
|
||||
width={24}
|
||||
height={24}
|
||||
className="rounded-md"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-6 h-6 bg-gray-200 rounded-full"/>
|
||||
)}
|
||||
<span>{props.decoration.text}</span>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -71,15 +70,15 @@ export function DesktopPayment({trade, onClose}: {trade: Trade, onClose: () => v
|
||||
) : (
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div className="bg-gray-100 w-52 h-52 flex items-center justify-center">
|
||||
{trade.method === 1 ? (
|
||||
<iframe src={trade.pay_url} className="w-full h-full"/>
|
||||
{props.method === TradeMethod.Alipay ? (
|
||||
<iframe src={props.pay_url} className="w-full h-full"/>
|
||||
) : (
|
||||
<canvas ref={canvasRef} className="w-full h-full"/>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-gray-600">
|
||||
请使用
|
||||
{paymentInfo.name}
|
||||
{props.decoration.text}
|
||||
扫码支付
|
||||
</p>
|
||||
|
||||
@@ -89,12 +88,12 @@ export function DesktopPayment({trade, onClose}: {trade: Trade, onClose: () => v
|
||||
{' '}
|
||||
<span className="text-accent">
|
||||
¥
|
||||
{trade.amount?.toFixed(2) || '0.00'}
|
||||
{props.amount?.toFixed(2) || '0.00'}
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">
|
||||
订单号:
|
||||
{trade.inner_no}
|
||||
{props.inner_no}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -103,7 +102,7 @@ export function DesktopPayment({trade, onClose}: {trade: Trade, onClose: () => v
|
||||
{loading && <Loader className="animate-spin mr-2"/>}
|
||||
已完成支付
|
||||
</Button>
|
||||
<Button theme="outline" onClick={onClose}>
|
||||
<Button theme="outline" onClick={() => props.onClose?.()}>
|
||||
关闭
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,6 @@ export * from './payment-button'
|
||||
export * from './payment-modal'
|
||||
export * from './mobile-payment'
|
||||
export * from './desktop-payment'
|
||||
export type {Trade, PaymentResponse, PaymentMethod} from './types'
|
||||
|
||||
// components/
|
||||
// composites/
|
||||
|
||||
@@ -1,46 +1,27 @@
|
||||
'use client'
|
||||
import {DialogContent, DialogHeader, DialogTitle} from '@/components/ui/dialog'
|
||||
import {DialogContent} from '@/components/ui/dialog'
|
||||
import {Button} from '@/components/ui/button'
|
||||
import {completeResource} from '@/actions/resource'
|
||||
import {toast} from 'sonner'
|
||||
import {CheckCircle, CreditCard} from 'lucide-react'
|
||||
import {useState} from 'react'
|
||||
import Image from 'next/image'
|
||||
import wechat from '@/components/composites/purchase/_assets/wechat.svg'
|
||||
import alipay from '@/components/composites/purchase/_assets/alipay.svg'
|
||||
import {Trade, PaymentMethod} from './types'
|
||||
import {PaymentModalProps} from './payment-modal'
|
||||
|
||||
export function MobilePayment(props: PaymentModalProps) {
|
||||
console.log(props, 'props')
|
||||
|
||||
export function MobilePayment({
|
||||
trade,
|
||||
onClose,
|
||||
}: {
|
||||
trade: Trade
|
||||
onClose: () => void
|
||||
}) {
|
||||
const [paymentVerified, setPaymentVerified] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const paymentInfo = (() => {
|
||||
switch (trade.method) {
|
||||
case PaymentMethod.Alipay:
|
||||
case PaymentMethod.SftAlipay:
|
||||
return {icon: alipay, name: '支付宝'}
|
||||
case PaymentMethod.WeChat:
|
||||
case PaymentMethod.SftWeChat:
|
||||
return {icon: wechat, name: '微信支付'}
|
||||
default:
|
||||
console.warn('Unknown payment method:', trade.method)
|
||||
return {icon: wechat, name: '微信支付'} // 默认返回微信支付
|
||||
}
|
||||
})()
|
||||
|
||||
const handleComplete = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const resp = await completeResource({trade_no: trade.inner_no})
|
||||
const resp = await completeResource({trade_no: props.inner_no})
|
||||
if (!resp.success) throw new Error(resp.message)
|
||||
toast.success('支付成功')
|
||||
setPaymentVerified(true)
|
||||
setTimeout(onClose, 2000)
|
||||
props.onClose?.()
|
||||
}
|
||||
catch (e) {
|
||||
toast.error('支付验证失败', {description: (e as Error).message})
|
||||
@@ -72,28 +53,29 @@ export function MobilePayment({
|
||||
<div className="space-y-4 rounded-lg bg-gray-50 p-4">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">订单号</span>
|
||||
<span className="font-medium">{trade.inner_no}</span>
|
||||
<span className="font-medium">{props.inner_no}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">支付方式</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<Image
|
||||
src={paymentInfo.icon}
|
||||
alt={paymentInfo.name}
|
||||
width={28}
|
||||
height={28}
|
||||
className="rounded-md"
|
||||
/>
|
||||
<span>{paymentInfo.name}</span>
|
||||
{props.decoration.icon && (
|
||||
<Image
|
||||
src={props.decoration.icon}
|
||||
alt={props.decoration.text}
|
||||
width={28}
|
||||
height={28}
|
||||
className="rounded-md"
|
||||
/>
|
||||
)}
|
||||
<span>{props.decoration.text}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">支付金额</span>
|
||||
<span className="text-lg font-bold text-blue-600">
|
||||
¥
|
||||
{typeof trade.amount === 'number'
|
||||
? trade.amount.toFixed(2)
|
||||
: '0.00'}
|
||||
{' '}
|
||||
{props.amount.toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -103,10 +85,10 @@ export function MobilePayment({
|
||||
<Button
|
||||
theme="outline"
|
||||
className="flex-1 py-3 text-base"
|
||||
onClick={onClose} >
|
||||
onClick={props.onClose} >
|
||||
取消
|
||||
</Button>
|
||||
<Button className="flex-1 py-3 text-base" onClick={() => window.location.href = trade.pay_url} >
|
||||
<Button className="flex-1 py-3 text-base" onClick={() => window.location.href = props.pay_url} >
|
||||
确认支付
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -2,31 +2,24 @@
|
||||
import {Button} from '@/components/ui/button'
|
||||
import {useState} from 'react'
|
||||
import {PaymentModal} from './payment-modal'
|
||||
import type {Trade, PaymentResponse} from './types'
|
||||
import {usePlatformType, Platform} from '@/lib/models/trade'
|
||||
import {PaymentProps} from './type'
|
||||
|
||||
export function PaymentButton({
|
||||
onClick,
|
||||
trade,
|
||||
disabled,
|
||||
onSuccess,
|
||||
}: {
|
||||
onClick: () => Promise<PaymentResponse>
|
||||
trade?: Trade
|
||||
onClick: () => Promise<PaymentProps>
|
||||
disabled?: boolean
|
||||
onSuccess?: () => void
|
||||
}) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const platform = usePlatformType() // 获取平台信息
|
||||
const [trade, setTrade] = useState<PaymentProps>()
|
||||
|
||||
const handleClick = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const result = await onClick()
|
||||
if (result?.success && result.data) {
|
||||
setOpen(true)
|
||||
}
|
||||
setTrade(await onClick())
|
||||
}
|
||||
finally {
|
||||
setLoading(false)
|
||||
@@ -44,10 +37,7 @@ export function PaymentButton({
|
||||
|
||||
{trade && (
|
||||
<PaymentModal
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
trade={trade}
|
||||
platform={platform} // 传递平台信息
|
||||
{...trade}
|
||||
onSuccess={onSuccess}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,41 +1,35 @@
|
||||
'use client'
|
||||
import {Dialog} from '@/components/ui/dialog'
|
||||
import {MobilePayment} from './mobile-payment'
|
||||
import {DesktopPayment} from './desktop-payment'
|
||||
import {Platform} from '@/lib/models/trade'
|
||||
import {Trade} from './types'
|
||||
import {TradePlatform} from '@/lib/models/trade'
|
||||
import {Dialog} from '@/components/ui/dialog'
|
||||
import {PaymentProps} from './type'
|
||||
|
||||
export function PaymentModal({
|
||||
open,
|
||||
onOpenChange,
|
||||
trade,
|
||||
platform,
|
||||
onSuccess,
|
||||
}: {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
trade: Trade
|
||||
platform: Platform
|
||||
export type PaymentModalProps = {
|
||||
onSuccess?: () => void
|
||||
}) {
|
||||
onClose?: () => void
|
||||
} & PaymentProps
|
||||
|
||||
export function PaymentModal(props: PaymentModalProps) {
|
||||
const handleClose = (success: boolean) => {
|
||||
onOpenChange(false)
|
||||
if (success && onSuccess) {
|
||||
onSuccess()
|
||||
if (success && props.onSuccess) {
|
||||
props.onSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
{platform === Platform.Mobile ? (
|
||||
<Dialog
|
||||
defaultOpen={true}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) props.onClose?.()
|
||||
}}>
|
||||
{props.platform === TradePlatform.Mobile ? (
|
||||
<MobilePayment
|
||||
trade={trade}
|
||||
onClose={() => handleClose(true)}
|
||||
{...props}
|
||||
/>
|
||||
) : (
|
||||
<DesktopPayment
|
||||
trade={trade}
|
||||
onClose={() => handleClose(true)}
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
'use client'
|
||||
import {CheckCircle, AlertCircle, ClockIcon} from 'lucide-react'
|
||||
import {Trade, PaymentStatus} from './types'
|
||||
|
||||
export function PaymentStatus({trade}: {trade?: Trade}) {
|
||||
if (!trade) return null
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
{trade.status === PaymentStatus.Completed ? (
|
||||
<CheckCircle size={16} className="text-done"/>
|
||||
) : trade.status === PaymentStatus.Cancelled ? (
|
||||
<AlertCircle size={16} className="text-weak"/>
|
||||
) : trade.status === PaymentStatus.Refunded ? (
|
||||
<AlertCircle size={16} className="text-fail"/>
|
||||
) : (
|
||||
<ClockIcon size={16} className="text-warn"/>
|
||||
)}
|
||||
<span>
|
||||
{trade.status === PaymentStatus.Completed ? '已完成'
|
||||
: trade.status === PaymentStatus.Cancelled ? '已取消'
|
||||
: trade.status === PaymentStatus.Refunded ? '已退款' : '待支付'}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
15
src/components/composites/payment/type.ts
Normal file
15
src/components/composites/payment/type.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import {TradeMethod, PaymentMethodConfig, TradePlatform} from '@/lib/models/trade'
|
||||
import {StaticImageData} from 'next/image'
|
||||
|
||||
// 交易信息类型
|
||||
export type PaymentProps = {
|
||||
inner_no: string
|
||||
pay_url: string
|
||||
amount: number
|
||||
platform: TradePlatform
|
||||
method: TradeMethod
|
||||
decoration: {
|
||||
icon: StaticImageData
|
||||
text: string
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
export enum PaymentMethod {
|
||||
Alipay = 1,
|
||||
WeChat = 2,
|
||||
Sft = 3,
|
||||
SftAlipay = 4,
|
||||
SftWeChat = 5,
|
||||
}
|
||||
|
||||
export interface Trade {
|
||||
inner_no: string
|
||||
method: PaymentMethod
|
||||
pay_url: string
|
||||
amount?: number
|
||||
status?: PaymentStatus
|
||||
}
|
||||
|
||||
export interface PaymentResponse {
|
||||
success: boolean
|
||||
data?: {
|
||||
trade_no: string
|
||||
pay_url: string
|
||||
}
|
||||
message?: string
|
||||
}
|
||||
export enum PaymentStatus {
|
||||
Pending = 0,
|
||||
Completed = 1,
|
||||
Cancelled = 2,
|
||||
Refunded = 3,
|
||||
}
|
||||
export type PaymentPlatform = 'mobile' | 'desktop'
|
||||
@@ -9,9 +9,14 @@ import {Alert, AlertTitle} from '@/components/ui/alert'
|
||||
import {toast} from 'sonner'
|
||||
import {useRouter} from 'next/navigation'
|
||||
import {completeResource, createResource, prepareResource} from '@/actions/resource'
|
||||
import {PaymentMethod, Platform, usePlatformType} from '@/lib/models/trade'
|
||||
import {
|
||||
TradePlatform,
|
||||
usePlatformType,
|
||||
TradeMethod,
|
||||
TradeMethodDecoration,
|
||||
} from '@/lib/models/trade'
|
||||
import {PaymentModal} from '@/components/composites/payment/payment-modal'
|
||||
import type {Trade} from '@/components/composites/payment/types'
|
||||
import {PaymentProps} from '@/components/composites/payment/type'
|
||||
|
||||
export type PayProps = {
|
||||
method: 'alipay' | 'wechat' | 'balance'
|
||||
@@ -20,10 +25,12 @@ export type PayProps = {
|
||||
}
|
||||
|
||||
export default function Pay(props: PayProps) {
|
||||
console.log(props, 'props')
|
||||
|
||||
const profile = useProfileStore(store => store.profile)
|
||||
const refreshProfile = useProfileStore(store => store.refreshProfile)
|
||||
const [open, setOpen] = useState(false)
|
||||
const [trade, setTrade] = useState<Trade | null>(null)
|
||||
const [trade, setTrade] = useState<PaymentProps | null>(null)
|
||||
const router = useRouter()
|
||||
const platform = usePlatformType()
|
||||
|
||||
@@ -32,18 +39,16 @@ export default function Pay(props: PayProps) {
|
||||
|
||||
if (props.method === 'balance') return
|
||||
|
||||
// 准备支付信息
|
||||
const paymentMethod = props.method === 'alipay'
|
||||
? platform === Platform.Mobile
|
||||
? PaymentMethod.SftAlipay // 4
|
||||
: PaymentMethod.Alipay // 1
|
||||
: platform === Platform.Mobile
|
||||
? PaymentMethod.SftWeChat // 5
|
||||
: PaymentMethod.WeChat // 2
|
||||
const method = platform === TradePlatform.Desktop
|
||||
? TradeMethod.Sft
|
||||
: props.method === 'alipay'
|
||||
? TradeMethod.SftAlipay
|
||||
: TradeMethod.SftWechat
|
||||
console.log(method, 'methodConfig')
|
||||
|
||||
const res = {
|
||||
...props.resource,
|
||||
payment_method: paymentMethod,
|
||||
payment_method: method,
|
||||
payment_platform: platform,
|
||||
}
|
||||
console.log(res, '请求参数')
|
||||
@@ -57,9 +62,11 @@ export default function Pay(props: PayProps) {
|
||||
|
||||
setTrade({
|
||||
inner_no: resp.data.trade_no,
|
||||
method: props.method === 'alipay' ? PaymentMethod.Alipay : PaymentMethod.WeChat,
|
||||
pay_url: resp.data.pay_url,
|
||||
amount: Number(props.amount),
|
||||
platform: platform,
|
||||
method: method,
|
||||
decoration: TradeMethodDecoration[props.method],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -178,11 +185,17 @@ export default function Pay(props: PayProps) {
|
||||
{/* 支付宝/微信支付使用公共组件 */}
|
||||
{props.method !== 'balance' && trade && (
|
||||
<PaymentModal
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
trade={trade}
|
||||
platform={platform}
|
||||
onSuccess={onSubmit}
|
||||
{...trade}
|
||||
onSuccess={() => {
|
||||
toast.success('支付成功')
|
||||
setTrade(null)
|
||||
setOpen(false)
|
||||
refreshProfile()
|
||||
}}
|
||||
onClose={() => {
|
||||
setTrade(null)
|
||||
setOpen(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -19,16 +19,19 @@ import {RechargePrepare} from '@/actions/user'
|
||||
import {useProfileStore} from '@/components/stores-provider'
|
||||
import {merge} from '@/lib/utils'
|
||||
import {
|
||||
Platform,
|
||||
PAYMENT_METHODS,
|
||||
TradePlatform,
|
||||
usePlatformType,
|
||||
PaymentMethod,
|
||||
TradeMethod,
|
||||
TradeMethodDecoration,
|
||||
} from '@/lib/models/trade'
|
||||
import {PaymentModal} from '@/components/composites/payment/payment-modal'
|
||||
import type {Trade} from '@/components/composites/payment/types'
|
||||
import Image from 'next/image'
|
||||
import wechat from '@/components/composites/purchase/_assets/wechat.svg'
|
||||
import alipay from '@/components/composites/purchase/_assets/alipay.svg'
|
||||
import {PaymentProps} from '@/components/composites/payment/type'
|
||||
|
||||
const schema = zod.object({
|
||||
method: zod.enum(['alipay', 'wechat', 'sft', 'sftAlipay', 'sftWeChat']),
|
||||
method: zod.enum(['alipay', 'wechat']),
|
||||
amount: zod.number().min(1, '充值金额必须大于 0'),
|
||||
})
|
||||
|
||||
@@ -51,54 +54,36 @@ export default function RechargeModal(props: RechargeModelProps) {
|
||||
},
|
||||
})
|
||||
|
||||
const method = form.watch('method')
|
||||
const amount = form.watch('amount')
|
||||
const [trade, setTrade] = useState<Trade | null>(null)
|
||||
const [trade, setTrade] = useState<PaymentProps>()
|
||||
const refreshProfile = useProfileStore(store => store.refreshProfile)
|
||||
|
||||
// 获取当前平台可用的支付方法
|
||||
const availableMethods = useMemo(() => {
|
||||
return Object.values(PAYMENT_METHODS)
|
||||
.filter(method => method.availablePlatforms.includes(platform))
|
||||
.map(method => ({
|
||||
value: method.formValue,
|
||||
name: method.name,
|
||||
icon: method.icon,
|
||||
}))
|
||||
}, [platform])
|
||||
|
||||
const createRecharge = async (data: Schema) => {
|
||||
try {
|
||||
const paymentMethod = Object.entries(PAYMENT_METHODS).find(
|
||||
([_, config]) => config.formValue === data.method,
|
||||
)
|
||||
console.log(data, 'data')
|
||||
|
||||
if (!paymentMethod) {
|
||||
throw new Error('无效的支付方式')
|
||||
}
|
||||
const actualMethod = paymentMethod[1].getActualMethod(platform)
|
||||
console.log('转换后的支付方式:', {
|
||||
formValue: data.method,
|
||||
platform: platform === Platform.Mobile ? 'Mobile' : 'Desktop',
|
||||
actualMethod,
|
||||
methodName: actualMethod === PaymentMethod.Alipay ? 'Alipay'
|
||||
: actualMethod === PaymentMethod.SftAlipay ? 'SftAlipay'
|
||||
: actualMethod === PaymentMethod.WeChat ? 'WeChat'
|
||||
: actualMethod === PaymentMethod.SftWeChat ? 'SftWeChat' : 'Unknown',
|
||||
})
|
||||
const resp = {
|
||||
try {
|
||||
const method = platform === TradePlatform.Desktop
|
||||
? TradeMethod.Sft
|
||||
: data.method === 'alipay'
|
||||
? TradeMethod.SftAlipay
|
||||
: TradeMethod.SftWechat
|
||||
|
||||
const req = {
|
||||
amount: data.amount.toString(),
|
||||
platform: platform,
|
||||
method: actualMethod,
|
||||
method: method,
|
||||
}
|
||||
console.log(req, '请求参数')
|
||||
|
||||
const result = await RechargePrepare(req)
|
||||
|
||||
const result = await RechargePrepare(resp)
|
||||
if (result.success) {
|
||||
setTrade({
|
||||
inner_no: result.data.trade_no,
|
||||
method: actualMethod,
|
||||
pay_url: result.data.pay_url,
|
||||
amount: data.amount,
|
||||
platform: platform,
|
||||
method: method,
|
||||
decoration: TradeMethodDecoration[data.method],
|
||||
})
|
||||
}
|
||||
else {
|
||||
@@ -116,18 +101,31 @@ export default function RechargeModal(props: RechargeModelProps) {
|
||||
try {
|
||||
await refreshProfile()
|
||||
toast.success('充值成功')
|
||||
setOpen(false)
|
||||
form.reset()
|
||||
setTrade(undefined) // 清除交易状态
|
||||
setOpen(false) // 关闭弹窗
|
||||
form.reset() // 重置表单
|
||||
}
|
||||
catch (e) {
|
||||
toast.error('刷新账户信息失败', {
|
||||
description: (e as Error).message,
|
||||
})
|
||||
toast.error('刷新账户信息失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setTrade(undefined)
|
||||
setOpen(false)
|
||||
form.reset()
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(isOpen) => {
|
||||
if (!isOpen) {
|
||||
setTrade(undefined)
|
||||
form.reset()
|
||||
}
|
||||
setOpen(isOpen)
|
||||
}}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
theme="accent"
|
||||
@@ -138,7 +136,7 @@ export default function RechargeModal(props: RechargeModelProps) {
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent className={platform === Platform.Mobile ? 'max-w-[95vw]' : 'max-w-md'}>
|
||||
<DialogContent className={platform === TradePlatform.Mobile ? 'max-w-[95vw]' : 'max-w-md'}>
|
||||
{!trade ? (
|
||||
<>
|
||||
<DialogTitle className="flex flex-col gap-2">充值中心</DialogTitle>
|
||||
@@ -185,22 +183,28 @@ export default function RechargeModal(props: RechargeModelProps) {
|
||||
{({id, field}) => (
|
||||
<RadioGroup
|
||||
id={id}
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={field.onChange}
|
||||
className="flex gap-2"
|
||||
>
|
||||
{availableMethods.map(({value, name, icon}) => (
|
||||
<FormOption
|
||||
key={value}
|
||||
id={`${id}-${value}`}
|
||||
value={value}
|
||||
compare={field.value}
|
||||
className="flex-1 flex-row justify-center items-center"
|
||||
>
|
||||
{icon && <img src={icon.src} alt={`${name} logo`} className="w-6 h-6 mr-2"/>}
|
||||
<span>{name}</span>
|
||||
</FormOption>
|
||||
))}
|
||||
<FormOption
|
||||
id="alipay"
|
||||
value="alipay"
|
||||
compare={field.value}
|
||||
className="flex-1 flex-row justify-center items-center"
|
||||
>
|
||||
<Image src={alipay} alt="logo" aria-hidden className="w-6 h-6 mr-2"/>
|
||||
<span>支付宝</span>
|
||||
</FormOption>
|
||||
<FormOption
|
||||
id="wechat"
|
||||
value="wechat"
|
||||
compare={field.value}
|
||||
className="flex-1 flex-row justify-center items-center"
|
||||
>
|
||||
<Image src={wechat} alt="logo" aria-hidden className="w-6 h-6 mr-2"/>
|
||||
<span>微信</span>
|
||||
</FormOption>
|
||||
</RadioGroup>
|
||||
)}
|
||||
</FormField>
|
||||
@@ -212,16 +216,9 @@ export default function RechargeModal(props: RechargeModelProps) {
|
||||
</>
|
||||
) : (
|
||||
<PaymentModal
|
||||
open={open}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setTrade(null)
|
||||
setOpen(false)
|
||||
}
|
||||
}}
|
||||
trade={trade}
|
||||
platform={platform}
|
||||
{...trade}
|
||||
onSuccess={handlePaymentSuccess}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
)}
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,26 +1,36 @@
|
||||
import {StaticImageData} from 'next/image'
|
||||
import {useEffect, useState} from 'react'
|
||||
import zod from 'zod'
|
||||
import wechat from '@/components/composites/purchase/_assets/wechat.svg'
|
||||
import alipay from '@/components/composites/purchase/_assets/alipay.svg'
|
||||
|
||||
export const TradeMethodDecoration = {
|
||||
alipay: {
|
||||
text: '支付宝',
|
||||
icon: alipay,
|
||||
},
|
||||
wechat: {
|
||||
text: '微信支付',
|
||||
icon: wechat,
|
||||
},
|
||||
}
|
||||
|
||||
// 支付方法枚举
|
||||
export const PaymentMethod = {
|
||||
export const TradeMethod = {
|
||||
Alipay: 1,
|
||||
WeChat: 2,
|
||||
Wechat: 2,
|
||||
Sft: 3,
|
||||
SftAlipay: 4,
|
||||
SftWeChat: 5,
|
||||
SftWechat: 5,
|
||||
} as const
|
||||
|
||||
// 平台枚举
|
||||
export const Platform = {
|
||||
export const TradePlatform = {
|
||||
Desktop: 1,
|
||||
Mobile: 2,
|
||||
} as const
|
||||
|
||||
// 支付状态枚举
|
||||
export const PaymentStatus = {
|
||||
export const TradeStatus = {
|
||||
Pending: 0,
|
||||
Completed: 1,
|
||||
Cancelled: 2, // 同步修改
|
||||
@@ -28,82 +38,27 @@ export const PaymentStatus = {
|
||||
} as const
|
||||
|
||||
// 定义类型
|
||||
export type PaymentMethod = typeof PaymentMethod[keyof typeof PaymentMethod]
|
||||
export type Platform = typeof Platform[keyof typeof Platform]
|
||||
export type PaymentStatus = typeof PaymentStatus[keyof typeof PaymentStatus]
|
||||
export type TradeMethod = typeof TradeMethod[keyof typeof TradeMethod]
|
||||
export type TradePlatform = typeof TradePlatform[keyof typeof TradePlatform]
|
||||
export type TradeStatus = typeof TradeStatus[keyof typeof TradeStatus]
|
||||
|
||||
// 支付方法配置类型
|
||||
export type PaymentMethodConfig = {
|
||||
name: string
|
||||
icon: StaticImageData | null
|
||||
formValue: string
|
||||
availablePlatforms: Platform[]
|
||||
getActualMethod: (platform: Platform) => PaymentMethod
|
||||
}
|
||||
|
||||
// 支付方法配置
|
||||
export const PAYMENT_METHODS: Record<string, PaymentMethodConfig> = {
|
||||
alipay: {
|
||||
name: '支付宝',
|
||||
icon: alipay,
|
||||
formValue: 'alipay',
|
||||
availablePlatforms: [Platform.Desktop, Platform.Mobile],
|
||||
getActualMethod: platform =>
|
||||
platform === Platform.Desktop ? PaymentMethod.Sft : PaymentMethod.SftAlipay,
|
||||
},
|
||||
wechat: {
|
||||
name: '微信支付',
|
||||
icon: wechat,
|
||||
formValue: 'wechat',
|
||||
availablePlatforms: [Platform.Desktop, Platform.Mobile],
|
||||
getActualMethod: platform =>
|
||||
platform === Platform.Desktop ? PaymentMethod.Sft : PaymentMethod.SftWeChat,
|
||||
},
|
||||
sft: {
|
||||
name: '商福通',
|
||||
icon: null,
|
||||
formValue: 'sft',
|
||||
availablePlatforms: [],
|
||||
getActualMethod: () => PaymentMethod.Sft,
|
||||
},
|
||||
} as const
|
||||
|
||||
// 交易信息类型
|
||||
export interface Trade {
|
||||
inner_no: string
|
||||
method: PaymentMethod
|
||||
pay_url: string
|
||||
status?: PaymentStatus
|
||||
amount?: number
|
||||
created_at?: string
|
||||
expired_at?: string
|
||||
}
|
||||
|
||||
// 支付请求参数类型
|
||||
export type PaymentRequest = {
|
||||
amount: string
|
||||
platform: Platform
|
||||
method: PaymentMethod
|
||||
product_id?: string
|
||||
product_name?: string
|
||||
}
|
||||
|
||||
// 支付结果类型
|
||||
export type PaymentResult = {
|
||||
success: boolean
|
||||
trade_no: string
|
||||
message?: string
|
||||
payment_time?: string
|
||||
value: TradeMethod
|
||||
decoration: {
|
||||
icon: StaticImageData
|
||||
text: string
|
||||
}
|
||||
}
|
||||
|
||||
// 设备检测Hook
|
||||
export const usePlatformType = (): Platform => {
|
||||
export const usePlatformType = (): TradePlatform => {
|
||||
// 在SSR环境下返回默认值
|
||||
const [platform, setPlatform] = useState<Platform>(() => {
|
||||
if (typeof window === 'undefined') return Platform.Desktop
|
||||
const [platform, setPlatform] = useState<TradePlatform>(() => {
|
||||
if (typeof window === 'undefined') return TradePlatform.Desktop
|
||||
return window.matchMedia('(max-width: 768px)').matches
|
||||
? Platform.Mobile
|
||||
: Platform.Desktop
|
||||
? TradePlatform.Mobile
|
||||
: TradePlatform.Desktop
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
@@ -112,7 +67,7 @@ export const usePlatformType = (): Platform => {
|
||||
|
||||
const checkPlatform = () => {
|
||||
const isMobile = window.matchMedia('(max-width: 768px)').matches
|
||||
setPlatform(isMobile ? Platform.Mobile : Platform.Desktop)
|
||||
setPlatform(isMobile ? TradePlatform.Mobile : TradePlatform.Desktop)
|
||||
}
|
||||
|
||||
const mediaQuery = window.matchMedia('(max-width: 768px)')
|
||||
@@ -125,21 +80,3 @@ export const usePlatformType = (): Platform => {
|
||||
|
||||
return platform
|
||||
}
|
||||
|
||||
// 支付表单验证schema
|
||||
export const paymentSchema = zod.object({
|
||||
method: zod.enum(['alipay', 'wechat', 'sft', 'sftAlipay', 'sftWeChat']),
|
||||
amount: zod.number().min(1, '充值金额必须大于 0'),
|
||||
})
|
||||
|
||||
export type PaymentSchema = zod.infer<typeof paymentSchema>
|
||||
// 新增函数:根据PaymentMethod获取展示信息
|
||||
export const getPaymentMethodInfo = (method: PaymentMethod) => {
|
||||
const found = Object.values(PAYMENT_METHODS).find(
|
||||
config => config.getActualMethod(Platform.Mobile) === method,
|
||||
)
|
||||
|
||||
return found
|
||||
? {icon: found.icon, name: found.name}
|
||||
: {icon: null, name: '未知支付方式'}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user