|
|
@@ -4,7 +4,7 @@
|
|
|
*/
|
|
|
|
|
|
import Taro, { useRouter } from '@tarojs/taro'
|
|
|
-import { useState } from 'react'
|
|
|
+import { useState, useEffect } from 'react'
|
|
|
import { View, Text } from '@tarojs/components'
|
|
|
import { useQuery } from '@tanstack/react-query'
|
|
|
import { Button } from '@/components/ui/button'
|
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
PaymentRateLimiter,
|
|
|
retryPayment
|
|
|
} from '@/utils/payment'
|
|
|
-import { paymentClient } from '@/api'
|
|
|
+import { paymentClient, creditBalanceClient } from '@/api'
|
|
|
|
|
|
interface PaymentData {
|
|
|
timeStamp: string
|
|
|
@@ -26,10 +26,25 @@ interface PaymentData {
|
|
|
paySign: string
|
|
|
}
|
|
|
|
|
|
+interface CreditBalanceData {
|
|
|
+ totalLimit: number
|
|
|
+ usedAmount: number
|
|
|
+ availableAmount: number
|
|
|
+ isEnabled: boolean
|
|
|
+}
|
|
|
+
|
|
|
+enum PaymentMethod {
|
|
|
+ WECHAT = 'wechat',
|
|
|
+ CREDIT = 'credit'
|
|
|
+}
|
|
|
+
|
|
|
const PaymentPage = () => {
|
|
|
const [paymentStatus, setPaymentStatus] = useState<PaymentStatus>(PaymentStatus.PENDING)
|
|
|
const [isProcessing, setIsProcessing] = useState(false)
|
|
|
const [errorMessage, setErrorMessage] = useState('')
|
|
|
+ const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<PaymentMethod>(PaymentMethod.WECHAT)
|
|
|
+ const [creditBalance, setCreditBalance] = useState<CreditBalanceData | null>(null)
|
|
|
+ const [isCreditBalanceLoading, setIsCreditBalanceLoading] = useState(false)
|
|
|
|
|
|
// 使用useRouter钩子获取路由参数
|
|
|
const router = useRouter()
|
|
|
@@ -38,6 +53,50 @@ const PaymentPage = () => {
|
|
|
const amount = routerParams?.amount ? parseFloat(routerParams.amount) : 0
|
|
|
const orderNo = routerParams?.orderNo
|
|
|
|
|
|
+ // 获取用户额度信息
|
|
|
+ const fetchCreditBalance = async () => {
|
|
|
+ setIsCreditBalanceLoading(true)
|
|
|
+ try {
|
|
|
+ const response = await creditBalanceClient.me.$get({})
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const balanceData = await response.json()
|
|
|
+ setCreditBalance(balanceData)
|
|
|
+ } else if (response.status === 404) {
|
|
|
+ // 用户没有额度记录,创建默认额度
|
|
|
+ setCreditBalance({
|
|
|
+ totalLimit: 0,
|
|
|
+ usedAmount: 0,
|
|
|
+ availableAmount: 0,
|
|
|
+ isEnabled: false
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ // 其他错误情况
|
|
|
+ setCreditBalance({
|
|
|
+ totalLimit: 0,
|
|
|
+ usedAmount: 0,
|
|
|
+ availableAmount: 0,
|
|
|
+ isEnabled: false
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取用户额度失败:', error)
|
|
|
+ setCreditBalance({
|
|
|
+ totalLimit: 0,
|
|
|
+ usedAmount: 0,
|
|
|
+ availableAmount: 0,
|
|
|
+ isEnabled: false
|
|
|
+ })
|
|
|
+ } finally {
|
|
|
+ setIsCreditBalanceLoading(false)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 组件加载时获取额度信息
|
|
|
+ useEffect(() => {
|
|
|
+ fetchCreditBalance()
|
|
|
+ }, [])
|
|
|
|
|
|
// 获取支付参数
|
|
|
const { data: paymentData, isLoading: paymentLoading } = useQuery({
|
|
|
@@ -78,8 +137,72 @@ const PaymentPage = () => {
|
|
|
const paymentStateManager = PaymentStateManager.getInstance()
|
|
|
const rateLimiter = PaymentRateLimiter.getInstance()
|
|
|
|
|
|
+ // 处理额度支付
|
|
|
+ const handleCreditPayment = async () => {
|
|
|
+ if (!orderId || !amount) {
|
|
|
+ setErrorMessage('订单信息不完整')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!creditBalance?.isEnabled) {
|
|
|
+ setErrorMessage('额度支付未启用')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (creditBalance.availableAmount < amount) {
|
|
|
+ setErrorMessage(`额度不足,可用额度: ¥${creditBalance.availableAmount.toFixed(2)}`)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ setIsProcessing(true)
|
|
|
+ setErrorMessage('')
|
|
|
+ setPaymentStatus(PaymentStatus.PROCESSING)
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await creditBalanceClient.payment.$post({
|
|
|
+ json: {
|
|
|
+ amount: amount,
|
|
|
+ referenceId: orderNo || `ORD${orderId}`,
|
|
|
+ remark: `订单支付 - ${orderNo || `ORD${orderId}`}`
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ // 额度支付成功
|
|
|
+ setPaymentStatus(PaymentStatus.SUCCESS)
|
|
|
+
|
|
|
+ // 更新本地额度信息
|
|
|
+ const updatedBalance = await response.json()
|
|
|
+ setCreditBalance(updatedBalance)
|
|
|
+
|
|
|
+ // 跳转到支付成功页面
|
|
|
+ setTimeout(() => {
|
|
|
+ Taro.redirectTo({
|
|
|
+ url: `/pages/payment-success/index?orderId=${orderId}&amount=${amount}&paymentMethod=credit`
|
|
|
+ })
|
|
|
+ }, 1500)
|
|
|
+ } else {
|
|
|
+ const errorData = await response.json()
|
|
|
+ setPaymentStatus(PaymentStatus.FAILED)
|
|
|
+ setErrorMessage(errorData.message || '额度支付失败')
|
|
|
+ }
|
|
|
+ } catch (error: any) {
|
|
|
+ console.error('额度支付处理异常:', error)
|
|
|
+ setPaymentStatus(PaymentStatus.FAILED)
|
|
|
+ setErrorMessage(error.message || '额度支付异常')
|
|
|
+ } finally {
|
|
|
+ setIsProcessing(false)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
// 处理支付
|
|
|
const handlePayment = async () => {
|
|
|
+ if (selectedPaymentMethod === PaymentMethod.CREDIT) {
|
|
|
+ await handleCreditPayment()
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 微信支付逻辑
|
|
|
if (!paymentData || !orderId) {
|
|
|
setErrorMessage('支付参数不完整')
|
|
|
return
|
|
|
@@ -141,6 +264,11 @@ const PaymentPage = () => {
|
|
|
|
|
|
// 重试支付
|
|
|
const handleRetryPayment = async () => {
|
|
|
+ if (selectedPaymentMethod === PaymentMethod.CREDIT) {
|
|
|
+ await handleCreditPayment()
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
if (!paymentData || !orderId) return
|
|
|
|
|
|
setIsProcessing(true)
|
|
|
@@ -261,6 +389,88 @@ const PaymentPage = () => {
|
|
|
</View>
|
|
|
</View>
|
|
|
|
|
|
+ {/* 支付方式选择 */}
|
|
|
+ <View className="bg-white rounded-2xl p-6 mb-5">
|
|
|
+ <Text className="text-sm font-bold text-gray-800 block mb-4">选择支付方式</Text>
|
|
|
+
|
|
|
+ {/* 微信支付选项 */}
|
|
|
+ <View
|
|
|
+ className={`flex items-center justify-between p-4 mb-3 rounded-xl border-2 ${
|
|
|
+ selectedPaymentMethod === PaymentMethod.WECHAT
|
|
|
+ ? 'border-blue-500 bg-blue-50'
|
|
|
+ : 'border-gray-200'
|
|
|
+ }`}
|
|
|
+ onClick={() => setSelectedPaymentMethod(PaymentMethod.WECHAT)}
|
|
|
+ >
|
|
|
+ <View className="flex items-center">
|
|
|
+ <View className="w-10 h-10 bg-green-100 rounded-full flex items-center justify-center mr-3">
|
|
|
+ <Text className="text-green-600 text-lg">💰</Text>
|
|
|
+ </View>
|
|
|
+ <View>
|
|
|
+ <Text className="text-sm font-bold text-gray-800">微信支付</Text>
|
|
|
+ <Text className="text-xs text-gray-500">使用微信支付完成付款</Text>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ {selectedPaymentMethod === PaymentMethod.WECHAT && (
|
|
|
+ <View className="w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center">
|
|
|
+ <Text className="text-white text-xs">✓</Text>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* 额度支付选项 */}
|
|
|
+ <View
|
|
|
+ className={`flex items-center justify-between p-4 rounded-xl border-2 ${
|
|
|
+ selectedPaymentMethod === PaymentMethod.CREDIT
|
|
|
+ ? 'border-blue-500 bg-blue-50'
|
|
|
+ : 'border-gray-200'
|
|
|
+ } ${(!creditBalance?.isEnabled || creditBalance?.availableAmount <= 0 || creditBalance?.availableAmount < amount) ? 'opacity-50' : ''}`}
|
|
|
+ onClick={() => {
|
|
|
+ if (creditBalance?.isEnabled && creditBalance?.availableAmount > 0 && creditBalance?.availableAmount >= amount) {
|
|
|
+ setSelectedPaymentMethod(PaymentMethod.CREDIT)
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <View className="flex items-center">
|
|
|
+ <View className="w-10 h-10 bg-purple-100 rounded-full flex items-center justify-center mr-3">
|
|
|
+ <Text className="text-purple-600 text-lg">💳</Text>
|
|
|
+ </View>
|
|
|
+ <View>
|
|
|
+ <Text className="text-sm font-bold text-gray-800">额度支付</Text>
|
|
|
+ {isCreditBalanceLoading ? (
|
|
|
+ <Text className="text-xs text-gray-500">加载中...</Text>
|
|
|
+ ) : creditBalance?.isEnabled ? (
|
|
|
+ <Text className="text-xs text-gray-500">
|
|
|
+ 可用额度: ¥{creditBalance?.availableAmount?.toFixed(2) || '0.00'}
|
|
|
+ {creditBalance?.availableAmount < amount && ` (不足)`}
|
|
|
+ </Text>
|
|
|
+ ) : (
|
|
|
+ <Text className="text-xs text-red-500">额度未启用</Text>
|
|
|
+ )}
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ {selectedPaymentMethod === PaymentMethod.CREDIT ? (
|
|
|
+ <View className="w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center">
|
|
|
+ <Text className="text-white text-xs">✓</Text>
|
|
|
+ </View>
|
|
|
+ ) : (!creditBalance?.isEnabled || creditBalance?.availableAmount <= 0 || creditBalance?.availableAmount < amount) && (
|
|
|
+ <Text className="text-xs text-gray-400">不可用</Text>
|
|
|
+ )}
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* 额度支付说明 */}
|
|
|
+ {selectedPaymentMethod === PaymentMethod.CREDIT && creditBalance && (
|
|
|
+ <View className="mt-4 p-3 bg-blue-50 rounded-lg">
|
|
|
+ <Text className="text-xs text-blue-700">
|
|
|
+ • 使用信用额度支付,无需立即付款{'\n'}
|
|
|
+ • 可用额度: ¥{creditBalance.availableAmount.toFixed(2)}{'\n'}
|
|
|
+ • 总额度: ¥{creditBalance.totalLimit.toFixed(2)}{'\n'}
|
|
|
+ • 已用额度: ¥{creditBalance.usedAmount.toFixed(2)}
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+ </View>
|
|
|
+
|
|
|
{/* 支付状态 */}
|
|
|
<View className="bg-white rounded-2xl p-8 mb-5 text-center">
|
|
|
{renderPaymentStatus()}
|
|
|
@@ -271,12 +481,20 @@ const PaymentPage = () => {
|
|
|
{paymentStatus === PaymentStatus.PENDING && (
|
|
|
<Button
|
|
|
onClick={handlePayment}
|
|
|
- disabled={isProcessing || paymentLoading}
|
|
|
- className={`w-full h-22 bg-gradient-to-r from-orange-500 to-orange-400 text-white rounded-full text-lg font-bold ${
|
|
|
+ disabled={isProcessing || paymentLoading || (selectedPaymentMethod === PaymentMethod.CREDIT && (!creditBalance?.isEnabled || creditBalance?.availableAmount < amount))}
|
|
|
+ className={`w-full h-22 ${
|
|
|
+ selectedPaymentMethod === PaymentMethod.CREDIT
|
|
|
+ ? 'bg-gradient-to-r from-purple-500 to-purple-400'
|
|
|
+ : 'bg-gradient-to-r from-orange-500 to-orange-400'
|
|
|
+ } text-white rounded-full text-lg font-bold ${
|
|
|
isProcessing ? 'bg-gray-400' : ''
|
|
|
}`}
|
|
|
>
|
|
|
- {isProcessing ? '支付中...' : `确认支付 ¥${amount.toFixed(2)}`}
|
|
|
+ {isProcessing ? '支付中...' :
|
|
|
+ selectedPaymentMethod === PaymentMethod.CREDIT
|
|
|
+ ? `额度支付 ¥${amount.toFixed(2)}`
|
|
|
+ : `微信支付 ¥${amount.toFixed(2)}`
|
|
|
+ }
|
|
|
</Button>
|
|
|
)}
|
|
|
|