From 1e332d8d08bff35e9e91b92ac9d4e41ae2009387 Mon Sep 17 00:00:00 2001 From: DU Date: Tue, 5 May 2026 13:35:29 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=85=E5=80=BC=EF=BC=8C=E5=92=8C=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E9=87=8D=E7=BD=AE=E7=8A=B6=E6=80=81=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/recharge.ts | 56 +++++++++++++ src/pagesOrder/recharge/index.scss | 4 + src/pagesOrder/recharge/index.tsx | 127 +++++++++++++++++++++++------ src/types/recharge.ts | 61 ++++++++++++++ 4 files changed, 225 insertions(+), 23 deletions(-) create mode 100644 src/api/recharge.ts create mode 100644 src/types/recharge.ts diff --git a/src/api/recharge.ts b/src/api/recharge.ts new file mode 100644 index 0000000..74e5c29 --- /dev/null +++ b/src/api/recharge.ts @@ -0,0 +1,56 @@ +/** + * 充值相关 API + */ +import { get, post } from '../services/request' +import { + CreateRechargePaymentDto, + RechargePaymentResponseDto, + RechargeWechatPayParameters, + PaymentChannel, +} from '../types/recharge' + +/** 小程序 JSAPI 支付固定交易类型 */ +const MINI_PROGRAM_TRADE_TYPE = 1 + +/** + * 创建充值订单 + * @param dto 充值请求参数 + * @returns 充值支付响应(含微信支付参数) + */ +export const createRechargeOrder = (dto: CreateRechargePaymentDto) => { + return post('/Recharge/create', { + tradeType: MINI_PROGRAM_TRADE_TYPE, + ...dto, + }, { + showLoading: true, + loadingText: '创建订单中...', + }) +} + +/** + * 查询支付状态 + * @param outTradeNo 商户订单号(创建充值订单时返回) + * @param channel 支付渠道 + * @returns 支付状态 + */ +export const queryPaymentStatus = (outTradeNo: string, channel?: PaymentChannel) => { + return get(`/Recharge/query/${outTradeNo}`, channel ? { channel } : undefined) +} + +/** + * 调起微信支付(充值场景) + * @param payParams 微信支付参数 + */ +export const invokeWechatPayForRecharge = (payParams: RechargeWechatPayParameters): Promise => { + return new Promise((resolve, reject) => { + wx.requestPayment({ + timeStamp: payParams.timeStamp, + nonceStr: payParams.nonceStr, + package: payParams.package, + signType: payParams.signType as 'MD5' | 'HMAC-SHA256' | 'RSA', + paySign: payParams.paySign, + success: () => resolve(), + fail: (err) => reject(err), + }) + }) +} diff --git a/src/pagesOrder/recharge/index.scss b/src/pagesOrder/recharge/index.scss index e45d8eb..c0325ea 100644 --- a/src/pagesOrder/recharge/index.scss +++ b/src/pagesOrder/recharge/index.scss @@ -182,6 +182,10 @@ font-size: 33px; color: #844614; } + + &.disabled { + opacity: 0.6; + } } } } diff --git a/src/pagesOrder/recharge/index.tsx b/src/pagesOrder/recharge/index.tsx index 00612c2..a09d755 100644 --- a/src/pagesOrder/recharge/index.tsx +++ b/src/pagesOrder/recharge/index.tsx @@ -4,14 +4,17 @@ import { View, Text } from '@tarojs/components' import Taro, { useLoad } from '@tarojs/taro' import { useState } from 'react' -import './index.scss' +import { observer } from 'mobx-react' import { Arrow } from '@taroify/icons' +import { createRechargeOrder, invokeWechatPayForRecharge, queryPaymentStatus } from '@/api/recharge' +import { userStore } from '@/store' +import './index.scss' -// 充值套餐数据 +// 充值套餐数据(套餐内容需与后端确认是否动态配置) interface RechargePackage { id: number - amount: number // 充值金额 - bonus: number // 赠送金额 + amount: number + bonus: number } const RechargePackages: RechargePackage[] = [ @@ -23,14 +26,18 @@ const RechargePackages: RechargePackage[] = [ { id: 6, amount: 1000, bonus: 700 }, ] -const RechargePage = () => { +const RechargePage = observer(() => { const [selectedPackageId, setSelectedPackageId] = useState(null) - const [currentBalance] = useState(28.8) // 当前余额 + const [loading, setLoading] = useState(false) useLoad(() => { console.log('余额充值页面加载') }) + // 当前余额和门店名称从 userStore 读取 + const currentBalance = userStore.balance + const storeName = userStore.userInfo?.bpInfoName || '--' + // 选择套餐 const handleSelectPackage = (id: number) => { setSelectedPackageId(id) @@ -43,25 +50,100 @@ const RechargePage = () => { }) } + // 轮询查询支付状态,最多查 5 次,每次间隔 2 秒 + const pollPaymentStatus = async (outTradeNo: string): Promise => { + for (let i = 0; i < 5; i++) { + await new Promise(resolve => setTimeout(resolve, 2000)) + try { + const res = await queryPaymentStatus(outTradeNo, 'WechatPay') + if (res.data === true) { + return true + } + } catch (e) { + console.warn('查询支付状态异常:', e) + } + } + return false + } + // 立即充值 - const handleRecharge = () => { + const handleRecharge = async () => { + if (loading) return + if (!selectedPackageId) { - Taro.showToast({ - title: '请选择充值套餐', - icon: 'none', - duration: 2000 - }) + Taro.showToast({ title: '请选择充值套餐', icon: 'none', duration: 2000 }) return } const selectedPackage = RechargePackages.find(pkg => pkg.id === selectedPackageId) - if (selectedPackage) { - Taro.showToast({ - title: `充值 ¥${selectedPackage.amount}`, - icon: 'none', - duration: 2000 + if (!selectedPackage) return + + const openId = userStore.userInfo?.wxOpenId + if (!openId) { + Taro.showToast({ title: '请先登录', icon: 'none', duration: 2000 }) + return + } + + setLoading(true) + + try { + // 创建充值订单 + const result = await createRechargeOrder({ + userId: String(userStore.userInfo?.id || ''), + customerSn: userStore.userInfo?.bpSn, + amount: selectedPackage.amount, + giftAmount: selectedPackage.bonus, + bpSn: userStore.userInfo?.bpSn, + instSn: userStore.userInfo?.instSn, + openId, + subject: '账户余额充值', + body: `充值 ¥${selectedPackage.amount},赠送 ¥${selectedPackage.bonus}`, }) - // TODO: 调用充值接口 + + const payData = result.data + + if (!payData?.payParameters) { + Taro.showToast({ title: '获取支付参数失败', icon: 'none', duration: 2000 }) + return + } + + // 调起微信支付 + try { + await invokeWechatPayForRecharge(payData.payParameters) + + // 微信支付成功回调后,查询服务端支付状态确认 + Taro.showLoading({ title: '确认支付中...', mask: true }) + const paid = await pollPaymentStatus(payData.outTradeNo) + Taro.hideLoading() + + if (paid) { + // 更新本地余额(充值金额 + 赠送金额) + const newBalance = currentBalance + selectedPackage.amount + selectedPackage.bonus + userStore.setBalance(newBalance) + + Taro.showToast({ title: '充值成功', icon: 'success', duration: 2000 }) + setSelectedPackageId(null) + } else { + Taro.showModal({ + title: '支付确认', + content: '支付结果暂未到账,请稍后刷新余额查看。如有疑问请联系店长。', + showCancel: false, + confirmText: '知道了', + }) + } + } catch (payError: any) { + console.log('支付错误:', payError) + if (payError.errMsg?.includes('cancel')) { + Taro.showToast({ title: '已取消支付', icon: 'none', duration: 2000 }) + } else { + Taro.showToast({ title: payError.errMsg || '支付失败', icon: 'none', duration: 2000 }) + } + } + } catch (error: any) { + console.error('创建充值订单失败:', error) + // 错误提示由 request 拦截器统一处理 + } finally { + setLoading(false) } } @@ -69,7 +151,7 @@ const RechargePage = () => { {/* 上部分:余额卡片区域 */} - 北京球场风云店 + {storeName} @@ -100,7 +182,6 @@ const RechargePage = () => { {/* 下部分:套餐和提示区域 */} - {/* 余额充值标题 */} 余额充值 {/* 充值套餐列表 */} @@ -132,12 +213,12 @@ const RechargePage = () => { {/* 底部固定按钮 */} - - 立即充值 + + {loading ? '处理中...' : '立即充值'} ) -} +}) export default RechargePage diff --git a/src/types/recharge.ts b/src/types/recharge.ts new file mode 100644 index 0000000..72104b4 --- /dev/null +++ b/src/types/recharge.ts @@ -0,0 +1,61 @@ +/** + * 充值相关类型定义 + */ + +/** + * 创建充值订单请求参数 + */ +export interface CreateRechargePaymentDto { + /** 用户ID */ + userId?: string + /** 客户编码(充值对象) */ + customerSn?: string + /** 充值金额 */ + amount?: number + /** 赠送金额 */ + giftAmount?: number + /** 充值类型 */ + rcgType?: number + /** 交易类型:1-JSAPI/小程序 */ + tradeType?: number + /** 店铺编码 */ + bpSn?: string + /** 机构编码 */ + instSn?: string + /** OpenId(微信支付必传) */ + openId?: string + /** 支付标题 */ + subject?: string + /** 支付描述 */ + body?: string +} + +/** + * 创建充值订单响应(data 字段内容) + */ +export interface RechargePaymentResponseDto { + /** 商户订单号 */ + outTradeNo: string + /** 支付渠道:1-微信、2-支付宝等 */ + channel: number + /** 调起微信支付所需参数 */ + payParameters: RechargeWechatPayParameters | null + /** 支付链接(Native扫码支付用) */ + payUrl: string +} + +/** + * 微信支付参数(充值场景) + */ +export interface RechargeWechatPayParameters { + timeStamp: string + nonceStr: string + package: string + signType: string + paySign: string +} + +/** + * 查询支付状态的支付渠道枚举 + */ +export type PaymentChannel = 'WechatPay' | 'Alipay' | 'UnionPay' | 'Balance' | 'BankCard'