> 技术文档 > 微信小程序对接微信支付_微信小程序接入微信支付

微信小程序对接微信支付_微信小程序接入微信支付


文章目录

  • 前言
  • 一、微信支付开发前的准备工作
  • 二、微信支付整体流程概览
  • 三、后端开发:统一下单接口实现
    • 3.1 调用微信统一下单 API(服务端)
    • 3.2 统一下单 Node.js示例
  • 四、小程序端开发:调起支付流程
  • 五、后端开发:处理微信支付回调
  • 六、小程序支付常见问题与排查指南
  • 七、工程化封装与最佳实践
    • /utils/sign.ts —— 签名工具模块
    • /services/pay/unifiedOrder.ts —— 统一下单接口封装
    • /services/pay/generateParams.ts —— 小程序支付参数生成
    • /services/pay/notifyHandler.ts —— 微信支付回调处理
    • 小程序端 /pages/pay/index.ts —— 发起支付示例
  • 总结

前言

本文详细讲解了微信小程序内接入微信支付的全过程,包括配置准备、后端统一下单、小程序调起支付、回调处理、常见问题排查与工程化封装。结合实际项目开发经验,避免踩坑,助力快速上线高可用微信支付功能。


一、微信支付开发前的准备工作

在动手开发之前,请确保以下准备工作完成:

项目 说明 小程序 AppID 微信公众平台获取 商户号 MchID 注册微信商户平台(https://pay.weixin.qq.com) API密钥 商户平台账户中心 ➔ API安全配置 小程序与商户号绑定 商户平台 ➔ 产品中心 ➔ APPID绑定 HTTPS 服务端 后端必须部署 HTTPS(必需) OpenID 小程序用户的唯一标识,用于支付关联

注意事项:

•商户号必须开通【JSAPI支付】产品;•小程序与商户号必须绑定关系并审核通过;•后端服务器必须支持 HTTPS。

二、微信支付整体流程概览

理解整体流程至关重要:

用户点击支付 ↓小程序请求后端生成订单(统一下单) ↓后端向微信服务器请求 prepay_id ↓后端返回支付参数 ↓小程序调起 wx.requestPayment ↓支付成功或失败 ↓微信服务器异步通知后端(回调) ↓后端确认并更新订单状态

小程序端只负责调起支付,统一下单、生成签名、支付回调,必须由后端完成!

三、后端开发:统一下单接口实现

3.1 调用微信统一下单 API(服务端)

微信官方接口文档:JSAPI下单API

接口地址(v2版):https://api.mch.weixin.qq.com/pay/unifiedorder请求方式:POST数据格式:XML

3.2 统一下单 Node.js示例

// pay.ts —— 微信统一下单模块import axios from \'axios\';import * as crypto from \'crypto\';import * as xml2js from \'xml2js\';import { createSign } from \'@/utils/sign\';const config = { appId: \'你的AppID\', mchId: \'你的商户号\', apiKey: \'你的API密钥\', notifyUrl: \'你的支付回调地址\',};export async function unifiedOrder(params) { const nonceStr = crypto.randomBytes(16).toString(\'hex\'); const requestParams = { appid: config.appId, mch_id: config.mchId, nonce_str: nonceStr, body: params.body, out_trade_no: params.outTradeNo, total_fee: params.totalFee, spbill_create_ip: params.spbillCreateIp, notify_url: config.notifyUrl, trade_type: \'JSAPI\', openid: params.openid, }; requestParams[\'sign\'] = createSign(requestParams, config.apiKey); const builder = new xml2js.Builder({ rootName: \'xml\', headless: true }); const xmlData = builder.buildObject(requestParams); const res = await axios.post(\'https://api.mch.weixin.qq.com/pay/unifiedorder\', xmlData, { headers: { \'Content-Type\': \'text/xml\' }, }); const parsed = await xml2js.parseStringPromise(res.data, { explicitArray: false }); if (parsed.xml.return_code !== \'SUCCESS\' || parsed.xml.result_code !== \'SUCCESS\') { throw new Error(parsed.xml.return_msg || parsed.xml.err_code_des); } return parsed.xml.prepay_id;}

四、小程序端开发:调起支付流程

小程序端代码示例:

// pages/pay/index.tsasync function requestPay(payData) { try { await wx.requestPayment({ timeStamp: payData.timeStamp, nonceStr: payData.nonceStr, package: payData.package, signType: payData.signType, paySign: payData.paySign, success(res) { console.log(\'支付成功\', res); wx.showToast({ title: \'支付成功\', icon: \'success\' }); }, fail(err) { console.error(\'支付失败\', err); wx.showToast({ title: \'支付失败\', icon: \'none\' }); }, }); } catch (error) { console.error(\'调起支付异常\', error); }}

注意:必须由用户点击行为触发 wx.requestPayment,否则调用失败!

五、后端开发:处理微信支付回调

// notify.tsimport * as Koa from \'koa\';import * as xml2js from \'xml2js\';import { createSign } from \'@/utils/sign\';export async function handlePaymentNotify(xmlString: string, apiKey: string) { const parsed = await xml2js.parseStringPromise(xmlString, { explicitArray: false }); const data = parsed.xml; const receivedSign = data.sign; const { sign, ...dataWithoutSign } = data; const validSign = createSign(dataWithoutSign, apiKey); if (receivedSign !== validSign) { throw new Error(\'签名校验失败\'); } if (data.return_code === \'SUCCESS\' && data.result_code === \'SUCCESS\') { return { orderNo: data.out_trade_no, transactionId: data.transaction_id, totalFee: parseInt(data.total_fee, 10), openid: data.openid, }; } else { throw new Error(\'微信返回支付失败\'); }}

六、小程序支付常见问题与排查指南

问题 原因 排查建议 wx.requestPayment失败 非点击触发 保证是按钮点击触发 prepay_id错误 商户号与小程序未绑定 确认商户后台关联 签名校验失败 参数顺序错误或缺失字段 使用统一签名生成方法 回调收不到 notify_url 错误或服务器异常 检查 HTTPS,服务器日志

七、工程化封装与最佳实践

为了保证工程规范性,推荐模块化封装支付功能。

目录结构示例

/services/pay/ ├── unifiedOrder.ts // 后端统一下单,生成 prepay_id ├── generateParams.ts // 后端生成小程序支付参数 ├── notifyHandler.ts // 后端处理微信回调通知/utils/ ├── sign.ts // 签名生成与验证工具/pages/pay/index.ts // 小程序端调起支付页面示例

/utils/sign.ts —— 签名工具模块

封装创建签名的方法,供统一下单、回调校验复用。

// utils/sign.tsimport * as crypto from \'crypto\';/** * 创建签名 * @param params 参与签名的参数对象 * @param apiKey 商户平台设置的 API 密钥 */export function createSign(params: Record<string, any>, apiKey: string): string { const sortedString = Object.keys(params) .filter(k => params[k] !== undefined && params[k] !== \'\') .sort() .map(k => `${k}=${params[k]}`) .join(\'&\') + `&key=${apiKey}`; return crypto.createHash(\'md5\').update(sortedString, \'utf8\').digest(\'hex\').toUpperCase();}

/services/pay/unifiedOrder.ts —— 统一下单接口封装

// services/pay/unifiedOrder.tsimport axios from \'axios\';import * as xml2js from \'xml2js\';import { createSign } from \'@/utils/sign\';const config = { appId: \'你的AppID\', mchId: \'你的商户号\', apiKey: \'你的API密钥\', notifyUrl: \'你的回调地址\',};/** * 微信统一下单 */export async function unifiedOrder({ openid, body, outTradeNo, totalFee, spbillCreateIp,}: { openid: string; body: string; outTradeNo: string; totalFee: number; spbillCreateIp: string;}): Promise<string> { const nonceStr = crypto.randomBytes(16).toString(\'hex\'); const params = { appid: config.appId, mch_id: config.mchId, nonce_str: nonceStr, body, out_trade_no: outTradeNo, total_fee: totalFee, spbill_create_ip: spbillCreateIp, notify_url: config.notifyUrl, trade_type: \'JSAPI\', openid, }; params[\'sign\'] = createSign(params, config.apiKey); const builder = new xml2js.Builder({ rootName: \'xml\', headless: true }); const xmlData = builder.buildObject(params); const res = await axios.post(\'https://api.mch.weixin.qq.com/pay/unifiedorder\', xmlData, { headers: { \'Content-Type\': \'text/xml\' }, }); const result = await xml2js.parseStringPromise(res.data, { explicitArray: false }); const resData = result.xml; if (resData.return_code !== \'SUCCESS\' || resData.result_code !== \'SUCCESS\') { throw new Error(resData.return_msg || resData.err_code_des); } return resData.prepay_id;}

/services/pay/generateParams.ts —— 小程序支付参数生成

// services/pay/generateParams.tsimport { createSign } from \'@/utils/sign\';import * as crypto from \'crypto\';const config = { appId: \'你的AppID\', apiKey: \'你的API密钥\',};/** * 根据 prepay_id 生成小程序端支付参数 */export function generateMiniPayParams(prepayId: string) { const timeStamp = Math.floor(Date.now() / 1000).toString(); const nonceStr = crypto.randomBytes(16).toString(\'hex\'); const packageStr = `prepay_id=${prepayId}`; const signType = \'MD5\'; const paySign = createSign({ appId: config.appId, timeStamp, nonceStr, package: packageStr, signType, }, config.apiKey); return { timeStamp, nonceStr, package: packageStr, signType, paySign, };}

/services/pay/notifyHandler.ts —— 微信支付回调处理

// services/pay/notifyHandler.tsimport * as xml2js from \'xml2js\';import { createSign } from \'@/utils/sign\';import * as crypto from \'crypto\';/** * 处理微信支付回调 */export async function handlePaymentNotify(xmlString: string, apiKey: string) { const parsed = await xml2js.parseStringPromise(xmlString, { explicitArray: false }); const notifyData = parsed.xml; // 校验签名 const receivedSign = notifyData.sign; const { sign, ...dataWithoutSign } = notifyData; const validSign = createSign(dataWithoutSign, apiKey); if (receivedSign !== validSign) { throw new Error(\'支付回调签名验证失败\'); } if (notifyData.return_code !== \'SUCCESS\' || notifyData.result_code !== \'SUCCESS\') { throw new Error(\'支付回调返回失败\'); } // 支付成功,处理订单 return { outTradeNo: notifyData.out_trade_no, transactionId: notifyData.transaction_id, totalFee: Number(notifyData.total_fee), openid: notifyData.openid, };}

小程序端 /pages/pay/index.ts —— 发起支付示例

// pages/pay/index.tsasync function startPayment() { try { const res = await wx.request({ url: \'你的后端统一下单接口地址\', method: \'POST\', data: { amount: 1, // 单位分 body: \'测试商品\', }, }); const payData = res.data; await wx.requestPayment({ timeStamp: payData.timeStamp, nonceStr: payData.nonceStr, package: payData.package, signType: payData.signType, paySign: payData.paySign, success: () => { wx.showToast({ title: \'支付成功\' }); }, fail: (err) => { wx.showToast({ title: \'支付失败\', icon: \'none\' }); console.error(\'支付失败:\', err); }, }); } catch (err) { console.error(\'发起支付异常:\', err); wx.showToast({ title: \'发起支付异常\', icon: \'none\' }); }}

总结

微信小程序接入微信支付,流程严谨,规范严格,每一步都必须细致处理。

本篇文章从准备工作到工程化封装,系统梳理了整个开发链路,确保你可以快速掌握并避免掉坑。

规范封装、错误处理、日志监控,是打造稳定支付系统的基础。

希望本文能帮助你真正掌握微信小程序支付开发技能!

ChatGPT中文网