微信小程序对接微信支付_微信小程序接入微信支付
文章目录
- 前言
- 一、微信支付开发前的准备工作
- 二、微信支付整体流程概览
- 三、后端开发:统一下单接口实现
-
- 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 —— 发起支付示例
- 总结
前言
本文详细讲解了微信小程序内接入微信支付的全过程,包括配置准备、后端统一下单、小程序调起支付、回调处理、常见问题排查与工程化封装。结合实际项目开发经验,避免踩坑,助力快速上线高可用微信支付功能。
一、微信支付开发前的准备工作
在动手开发之前,请确保以下准备工作完成:
注意事项:
•商户号必须开通【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(\'微信返回支付失败\'); }}
六、小程序支付常见问题与排查指南
七、工程化封装与最佳实践
为了保证工程规范性,推荐模块化封装支付功能。
目录结构示例
/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\' }); }}
总结
微信小程序接入微信支付,流程严谨,规范严格,每一步都必须细致处理。
本篇文章从准备工作到工程化封装,系统梳理了整个开发链路,确保你可以快速掌握并避免掉坑。
规范封装、错误处理、日志监控,是打造稳定支付系统的基础。
希望本文能帮助你真正掌握微信小程序支付开发技能!