# 微信小程序支付集成文档 ## 概述 本文档总结了套餐订购模块中的微信支付完整实现流程,可复用到其他项目中。 ## 一、核心文件结构 ``` miniprogram/ ├── utils/ │ └── payment.ts # 微信支付工具类 ├── api/ │ └── order.ts # 订单API接口 ├── types/ │ └── api.ts # API类型定义 └── pages/ └── package/ └── package.ts # 套餐订购页面(业务实现) ``` ## 二、支付流程 ### 2.1 标准套餐支付流程(三步走) ``` 1. 创建订单 → 2. 获取支付参数 → 3. 调起微信支付 ``` #### 第一步:创建订单 **接口:** `POST /api/c/v1/orders/create` **请求参数:** ```typescript { app_type: 'miniapp', // 应用类型:miniapp(小程序) | official_account(公众号) identifier: string, // 资产标识符(设备ID/ICCID等) package_ids: number[] // 套餐ID列表 } ``` **响应数据:** ```typescript { order_type: 'package' | 'recharge', // 订单类型 order: { order_id: number, // 订单ID(用于后续支付) order_no: string, payment_status: 1, // 1:待支付 total_amount: number, // 总金额(分) created_at: string }, pay_config?: {...} // 强充订单才返回(见2.2) } ``` #### 第二步:获取支付参数 **接口:** `POST /api/c/v1/orders/:id/pay` **请求参数:** ```typescript { payment_method: 'wechat', // 支付方式:wechat | wallet app_type: 'miniapp' // 应用类型(微信支付必传) } ``` **响应数据:** ```typescript { payment_method: 'wechat', pay_config: { app_id: string, // 微信AppID timestamp: string, // 时间戳 nonce_str: string, // 随机字符串 package: string, // 预支付ID,格式:prepay_id=wx... sign_type: string, // 签名类型(RSA/MD5/HMAC-SHA256) pay_sign: string // 支付签名 } } ``` #### 第三步:调起微信支付 使用 `wx.requestPayment()` 调起支付: ```typescript wx.requestPayment({ timeStamp: pay_config.timestamp, nonceStr: pay_config.nonce_str, package: pay_config.package, signType: pay_config.sign_type, paySign: pay_config.pay_sign, success: (res) => { // 支付成功 }, fail: (err) => { // 支付失败或取消 } }) ``` ### 2.2 强充订单支付流程(一步走) 当订单返回 `order_type === 'recharge'` 且有 `pay_config` 时,表示强充订单: ``` 创建订单(直接返回支付参数) → 调起微信支付 ``` **特点:** - 创建订单时已返回完整 `pay_config` - 无需再调用 `/orders/:id/pay` 接口 - 直接使用返回的 `pay_config` 调起支付 **判断逻辑:** ```typescript if (orderResult.order_type === 'recharge' && orderResult.pay_config) { // 强充订单,直接支付 await WxPayment.payWithCallback(orderResult.pay_config, callbacks); } else { // 普通订单,需要获取支付参数 const payResult = await OrderApi.payOrder(orderId, {...}); await WxPayment.payWithCallback(payResult.pay_config, callbacks); } ``` ## 三、核心代码实现 ### 3.1 微信支付工具类(payment.ts) ```typescript /** * 微信支付工具类 */ import { Logger } from './logger'; /** 微信支付配置 */ export interface WxPayConfig { timeStamp: string; nonceStr: string; package: string; signType: 'RSA' | 'MD5' | 'HMAC-SHA256'; paySign: string; } /** 支付结果 */ export interface PaymentResult { success: boolean; errMsg?: string; userCancelled?: boolean; } export class WxPayment { /** * 发起微信支付 * @param payConfig 后端返回的支付配置 * @returns 支付结果 */ static async pay(payConfig: { app_id: string; timestamp: string; nonce_str: string; package: string; sign_type: string; pay_sign: string; }): Promise { return new Promise((resolve) => { // 1. 验证 package 参数格式 if (!payConfig.package || !payConfig.package.startsWith('prepay_id=')) { Logger.error('微信支付参数错误:package 格式不正确', { package: payConfig.package, expected: 'prepay_id=wx...' }); resolve({ success: false, errMsg: '支付参数获取失败,请稍后重试' }); return; } Logger.info('发起微信支付', { ...payConfig, pay_sign: payConfig.pay_sign.substring(0, 10) + '...' // 脱敏 }); // 2. 调用微信支付API wx.requestPayment({ timeStamp: payConfig.timestamp, nonceStr: payConfig.nonce_str, package: payConfig.package, signType: payConfig.sign_type as any, paySign: payConfig.pay_sign, success: (res) => { Logger.info('微信支付成功', res); resolve({ success: true }); }, fail: (err) => { Logger.error('微信支付失败', err); // 判断是否为用户取消 const userCancelled = err.errMsg?.includes('cancel') || err.errMsg?.includes('用户取消'); resolve({ success: false, errMsg: err.errMsg, userCancelled }); } }); }); } /** * 支付并使用回调处理结果 * @param payConfig 支付配置 * @param callbacks 回调函数 */ static async payWithCallback( payConfig: { app_id: string; timestamp: string; nonce_str: string; package: string; sign_type: string; pay_sign: string; }, callbacks?: { onSuccess?: () => void; onFail?: (errMsg: string) => void; onCancel?: () => void; } ): Promise { const result = await this.pay(payConfig); // 根据结果调用相应回调 if (result.success) { callbacks?.onSuccess?.(); } else if (result.userCancelled) { callbacks?.onCancel?.(); } else { callbacks?.onFail?.(result.errMsg || '支付失败'); } return result; } } ``` ### 3.2 订单API封装(order.ts) ```typescript import { Request } from '../utils/request'; import type { CreateOrderRequest, CreateOrderResponse, PayOrderRequest, PayOrderResponse } from '../types/api'; export class OrderApi { /** * 创建订单 * POST /api/c/v1/orders/create */ static async createOrder(data: CreateOrderRequest): Promise { return Request.post( '/api/c/v1/orders/create', data ); } /** * 订单支付 * POST /api/c/v1/orders/:id/pay */ static async payOrder(orderId: number, data: PayOrderRequest): Promise { return Request.post( `/api/c/v1/orders/${orderId}/pay`, data ); } } ``` ### 3.3 业务层实现示例(package.ts) ```typescript import { OrderApi } from '../../api/order'; import { WxPayment } from '../../utils/payment'; import { Logger } from '../../utils/logger'; /** * 创建订单并支付(微信支付) */ async createOrderAndPay() { try { const selectedPackage = this.data.packages[this.data.selectedIdx]; // 第一步:创建订单 wx.showLoading({ title: '创建订单...', mask: true }); const orderResult = await OrderApi.createOrder({ app_type: 'miniapp', identifier: this.getIdentifier(), package_ids: [selectedPackage.package_id] }); // 判断是否为强充订单 if (orderResult.order_type === 'recharge' && orderResult.pay_config) { // 强充订单:直接使用返回的 pay_config 支付 // 验证 pay_config 有效性 if (!orderResult.pay_config.package) { Logger.error('强充订单支付配置无效', { recharge_id: orderResult.recharge?.recharge_id }); wx.hideLoading(); wx.showToast({ title: '支付参数获取失败', icon: 'none' }); return; } wx.hideLoading(); this.closeModal(); // 调起支付 await WxPayment.payWithCallback( orderResult.pay_config, { onSuccess: () => { this.handlePaySuccess(); }, onFail: (errMsg) => { wx.showToast({ title: '支付失败', icon: 'none' }); }, onCancel: () => { wx.showToast({ title: '已取消支付', icon: 'none' }); } } ); return; } // 普通套餐订单:需要获取支付参数 const orderId = orderResult.order.order_id; // 第二步:获取支付参数 wx.showLoading({ title: '准备支付...', mask: true }); const payResult = await OrderApi.payOrder(orderId, { payment_method: 'wechat', app_type: 'miniapp' }); Logger.info('订单支付接口返回:', { order_id: orderId, has_pay_config: !!payResult.pay_config }); wx.hideLoading(); this.closeModal(); // 验证 pay_config if (!payResult.pay_config || !payResult.pay_config.package) { Logger.error('订单支付接口返回配置无效', { order_id: orderId }); wx.showToast({ title: '支付参数获取失败', icon: 'none' }); return; } // 第三步:调起微信支付 await WxPayment.payWithCallback( payResult.pay_config, { onSuccess: () => { this.handlePaySuccess(); }, onFail: (errMsg) => { wx.showToast({ title: '支付失败', icon: 'none' }); }, onCancel: () => { wx.showToast({ title: '已取消支付', icon: 'none' }); } } ); } catch (error: any) { wx.hideLoading(); this.closeModal(); Logger.error('创建订单或支付失败:', error); // 特殊错误处理:订单正在创建中 if (error.code === 1008) { wx.showModal({ title: '提示', content: '订单正在创建中,是否跳转到我的订单页面进行支付?', confirmText: '去支付', success: (res) => { if (res.confirm) { wx.navigateTo({ url: '/pages/orders/orders' }); } } }); return; } // 其他错误提示 const errorMsg = error.message || error.msg || '操作失败,请稍后重试'; wx.showToast({ title: errorMsg, icon: 'none' }); } } /** * 支付成功处理 */ handlePaySuccess() { const pkg = this.data.packages[this.data.selectedIdx]; this.setData({ selectedIdx: -1 }); wx.showToast({ title: `「${pkg.name}」支付成功`, icon: 'success' }); // 可选:刷新数据、跳转页面等 } ``` ## 四、类型定义(api.ts) ```typescript /** 创建订单 - 请求 */ export interface CreateOrderRequest { app_type?: 'official_account' | 'miniapp'; // 应用类型(强充必传) identifier: string; // 资产标识符 package_ids: number[]; // 套餐ID列表 } /** 创建订单 - 响应 */ export interface CreateOrderResponse { order_type: 'package' | 'recharge'; // 订单类型 order: { order_id: number; order_no: string; payment_status: 1 | 2 | 3 | 4; // 1:待支付 2:已支付 3:已取消 4:已退款 total_amount: number; // 总金额(分) created_at: string; }; pay_config?: { // 强充订单才返回 app_id: string; timestamp: string; nonce_str: string; package: string; // prepay_id=wx... sign_type: string; pay_sign: string; } | null; recharge?: { // 充值订单信息 recharge_id: number; recharge_no: string; amount: number; status: 0 | 1 | 2; // 0:待支付 1:已支付 2:已关闭 auto_purchase_status: string; }; linked_package_info?: { // 强充关联套餐信息 package_names: string[]; total_package_amount: number; wallet_credit: number; force_recharge_amount: number; }; } /** 订单支付 - 请求 */ export interface PayOrderRequest { payment_method: 'wallet' | 'wechat'; // 支付方式 app_type?: 'official_account' | 'miniapp'; // 微信支付必传 password?: string; // 钱包支付必传 } /** 订单支付 - 响应 */ export interface PayOrderResponse { payment_method: 'wallet' | 'wechat'; pay_config?: { // 微信支付时返回 app_id: string; timestamp: string; nonce_str: string; package: string; sign_type: string; pay_sign: string; }; } ``` ## 五、关键要点 ### 5.1 支付参数验证 **必须验证 `package` 参数格式:** ```typescript if (!payConfig.package || !payConfig.package.startsWith('prepay_id=')) { // 参数无效,停止支付 return; } ``` ### 5.2 支付结果判断 ```typescript // 成功 success: (res) => { /* 支付成功 */ } // 失败/取消 fail: (err) => { // 判断是否用户取消 const userCancelled = err.errMsg?.includes('cancel') || err.errMsg?.includes('用户取消'); if (userCancelled) { // 用户主动取消 } else { // 支付失败 } } ``` ### 5.3 错误处理 1. **参数验证失败** - 提示用户并停止支付 2. **用户取消支付** - 友好提示,不作为错误 3. **支付失败** - 提示错误信息 4. **订单创建中(1008)** - 引导用户到订单页 ### 5.4 日志记录 关键节点必须记录日志: - 支付参数(脱敏) - 支付成功/失败 - 参数验证失败 - 异常情况 ```typescript Logger.info('发起微信支付', { ...payConfig, pay_sign: payConfig.pay_sign.substring(0, 10) + '...' // 脱敏 }); ``` ### 5.5 Loading状态管理 ```typescript // 创建订单前 wx.showLoading({ title: '创建订单...', mask: true }); // 获取支付参数前 wx.showLoading({ title: '准备支付...', mask: true }); // 调起支付前 wx.hideLoading(); // 必须关闭,否则遮挡支付界面 ``` ## 六、常见问题 ### Q1: 为什么有两种支付流程? **A:** - **标准流程(三步)**:普通套餐订购 - **快捷流程(一步)**:强充订单,后端已预创建支付参数 ### Q2: package 参数为什么要验证? **A:** 后端可能返回空值或格式错误,直接传给微信会导致支付崩溃。必须验证格式为 `prepay_id=wx...` ### Q3: 如何区分用户取消和支付失败? **A:** 检查 `errMsg` 是否包含 `'cancel'` 或 `'用户取消'` 关键字。 ### Q4: 为什么调起支付前要 hideLoading? **A:** Loading 遮罩会挡住微信支付界面,导致用户无法操作。 ## 七、快速集成清单 要在新项目中集成微信支付,需要: - [ ] 复制 `utils/payment.ts` 工具类 - [ ] 创建订单API接口(`createOrder`、`payOrder`) - [ ] 定义类型文件(`api.ts`) - [ ] 实现业务逻辑(参考 `createOrderAndPay`) - [ ] 添加 Logger 日志工具(可选) - [ ] 处理支付回调(成功、失败、取消) - [ ] 添加错误处理逻辑 ## 八、注意事项 1. **安全性** - 日志中敏感信息(pay_sign)要脱敏 - 永远不要在前端生成支付签名 - 所有支付参数由后端返回 2. **用户体验** - 支付过程中显示 Loading 提示 - 调起支付前必须关闭 Loading - 区分取消和失败,给出不同提示 - 支付失败时保留订单,允许用户重试 3. **异常处理** - 参数验证失败时友好提示 - 网络错误时允许重试 - 订单创建中(1008)引导到订单页 - 所有异常都要记录日志 4. **测试要点** - 正常支付流程 - 用户取消支付 - 网络异常 - 参数异常 - 强充订单流程 - 普通订单流程 --- **文档版本:** v1.0 **更新日期:** 2026-04-14 **适用范围:** 微信小程序支付