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