本文详细介绍如何在 Node.js 环境下实现微信公众号 H5 支付功能。内容涵盖 OAuth2 授权获取 openid、统一下单接口获取 prepay_id、前端 JSAPI 调起支付以及支付结果异步回调处理。针对 Node.js 框架中 XML 解析的特殊需求,提供了 body-parser-xml 中间件的配置方案,帮助开发者顺利完成支付闭环。
Node.js 微信公众号 H5 支付概述
在 Node.js 环境中开发微信公众号功能,特别是移动端 H5 页面调用微信支付接口,是许多开发者面临的实际需求。目前微信官方尚未提供 Node.js 版本的支付 SDK,因此需要手动实现相关逻辑。本文将详细梳理基于 Node.js 和 H5 页面完成微信支付需求的完整开发流程,旨在帮助开发者顺利解决支付功能集成问题。
获取用户授权 Code
一. 请求 CODE
获取 code 的核心目的是获取用户的 openid(用户在该公众号下的唯一标识)以及 access_token。请求使用的 API 地址为:https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect。调用此 API 时需重点关注以下参数配置:
1. appid:公众号的 AppID,可在公众号后台查看。 2. redirect_uri:自定义的微信回调地址。用户授权后,微信会携带 code 跳转至该地址。注意 redirect_uri 需要进行 URL 编码,PHP 环境下使用 urlencode,Node.js 环境下需使用 encodeURLComponent(url) 进行编码。 3. response_type=code:固定参数,具体说明可参考微信官网文档。 4. scope=snsapi_userinfo:固定参数,具体说明可参考微信官网文档。 5. state=STATE:固定参数,具体说明可参考微信官网文档。 6. #wechat_redirect:固定参数,具体说明可参考微信官网文档。 ps:官网链接:
通过 Code 获取 Access Token
二. 通过 code 获取 access_token 和 openid
在第一步获取到 code 后,接下来需通过该 code 获取 access_token 和 openid。请求的 API 地址为:https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code。该 API 的参数说明如下:
1. appid:微信公众号 ID,从公众号后台获取。 2. secret:微信公众号密钥,从公众号后台获取。 3. code:第一步中获取到的 code 值。 4. grant_type=authorization_code:固定参数。
三. 通过 access_token 调用接口
获取到 access_token 后,可用于后续功能调用。具体接口使用方法可参考官方示例:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316518&lang=zh_CN
前端调起微信支付接口
四. 网页端调起支付 API
虽然看起来只需在网页端调用微信支付功能即可,但实际还有细节需要注意。在微信浏览器内打开 H5 网页时,需通过 JS 调起支付接口,输入输出数据格式均为 JSON。请注意,WeixinJSBridge 内置对象在其他非微信浏览器中无效。示例代码如下:
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId" : "wx2421b1c4370ec43b", //公众号名称,由商户传入
"timeStamp":" 1395712654", //时间戳,自1970年以来的秒数
"nonceStr" : "e61463f8efa94090b1f366cccfbbb444", //随机串
"package" : "prepay_id=u802345jgfjsdfgsdg888",
"signType" : "MD5", //微信签名方式:
"paySign" : "70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ) {} // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
}
);
}
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
}else{
onBridgeReady();
}
观察上述代码可知,调用微信支付功能需要传递特定参数,
{
"appId" : "wx2421b1c4370ec43b", //公众号名称,由商户传入
"timeStamp":" 1395712654", //时间戳,自1970年以来的秒数
"nonceStr" : "e61463f8efa94090b1f366cccfbbb444", //随机串
"package" : "prepay_id=u802345jgfjsdfgsdg888",
"signType" : "MD5", //微信签名方式:
"paySign" : "70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名
}
参数说明如下:
1. appId:公众号名称,由商户传入。 2. timeStamp:时间戳,自 1970 年以来的秒数。此处需特别注意,时间戳必须为字符串格式,即需用引号包裹。 3. nonceStr:32 位随机串,后续将提供生成方法。 4. signType:微信签名方式,通常为 MD5。 5. paySign:微信签名,后续说明。 6. package:关键参数,其来源将在下文说明。 ps: 官网接口说明:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
统一下单获取 Prepay ID
五. 获取 package,从微信统一下单接口获取 prepay_id
官方 API 地址为:https://api.mch.weixin.qq.com/pay/unifiedorder
请求参数较多,但部分非必填,以下是必须参数:
{
appid : APPID,
attach : ATTACH,
body : BODY,
mch_id : MCH_ID,
nonce_str: NONCE_STR,
notify_url : NOTIFY_URL,// 微信付款后的回调地址
openid : OPENID,
out_trade_no : OUT_TRADE_NO ,//new Date().getTime(), //订单号
spbill_create_ip : SPBILL_CREATE_IP , //客户端的 ip
total_fee : TOTAL_FEE, //商品的价格, 此处需要注意的是这个价格是以分算的, 那么一般是元, 你需要转换为 RMB 的元
trade_type : 'JSAPI',
}
微信统一下单接口要求传递 XML 格式数据,且数据需进行签名。首先需对数据进行签名,签名规则可参考微信官方提供的签名规则(签名方法将在后文给出)。微信官方签名规则链接为:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3
生成签名后,需将数据组装为 XML 格式:
var body = '<xml> ' +
'<appid>'+config.wxappid+'</appid> ' +
'<attach>'+obj.attach+'</attach> ' +
'<body>'+obj.body+'</body> ' +
'<mch_id>'+config.mch_id+'</mch_id> ' +
'<nonce_str>'+obj.nonce_str+'</nonce_str> ' +
'<notify_url>'+obj.notify_url+'</notify_url>' +
'<openid>'+obj.openid+'</openid> ' +
'<out_trade_no>'+obj.out_trade_no+'</out_trade_no>'+
'<spbill_create_ip>'+obj.spbill_create_ip+'</spbill_create_ip> ' +
'<total_fee>'+obj.total_fee+'</total_fee> ' +
'<trade_type>'+obj.trade_type+'</trade_type> ' +
'<sign>'+obj.sign+'</sign> ' + // 此处必带签名, 否者微信在验证数据的时候是不通过的
'</xml>';
接下来请求 API 以获取 prepay_id 值。将上述生成的 XML 数据发送至以下 API 发送给微信,微信验证数据无误后将返回所需值。API 地址:https://api.mch.weixin.qq.com/pay/unifiedorder
六. 获取到 prepay_id 是否即可在 H5 端直接调用微信支付?答案是否定的。
获取到 prepay_id 后,H5 端调起微信支付所需的参数结构如下:
{
"appId" : "wx2421b1c4370ec43b", //公众号名称,由商户传入
"timeStamp":" 1395712654", //时间戳,自1970年以来的秒数
"nonceStr" : "e61463f8efa94090b1f366cccfbbb444", //随机串
"package" : "prepay_id=u802345jgfjsdfgsdg888",
"signType" : "MD5", //微信签名方式:
}
拥有此类参数后,还需对所有参与签名的参数进行签名。签名规则与上文一致。生成签名后,需将签名结果 paySign 赋值给 H5 调起支付参数的对应字段(注意:微信签名不参与此次签名生成)。最终参数结构如下:
{
"appId" : "wx2421b1c4370ec43b", //公众号名称,由商户传入
"timeStamp":" 1395712654", //时间戳,自1970年以来的秒数
"nonceStr" : "e61463f8efa94090b1f366cccfbbb444", //随机串
"package" : "prepay_id=u802345jgfjsdfgsdg888",
"signType" : "MD5", //微信签名方式:
"paySign" : "70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名
}
若各环节无误,得到上述参数后即可正常调起微信支付功能,其体验与原生应用无异(无需 App 即可使用 App 级支付功能,确实神奇)。
支付结果异步回调处理
七. 支付完成的回调
微信支付完成后,H5 页面的支付回调函数会返回结果。若 res.err_msg 等于 "get_brand_wcpay_request:ok",则表示前端支付成功。但这并不意味着流程结束,因为还需确认微信是否实际收到款项以及金额是否与传递值一致。此外,还需将支付结果写入数据库等处理。在统一下单接口中有一个必须参数 notify_url(微信付款后的回调地址),该地址由商户提供。用户付款后,微信会以 POST 方式请求此接口,并传递用户付款信息,数据格式为 XML。XML 格式大致如下:
<xml>
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
<attach><![CDATA[支付测试]]></attach>
<bank_type><![CDATA[CFT]]></bank_type>
<fee_type><![CDATA[CNY]]></fee_type>
<is_subscribe><![CDATA[Y]]></is_subscribe>
<mch_id><![CDATA[10000100]]></mch_id>
<nonce_str><![CDATA[5d2b6c2a8db53831f7eda20af46e531c]]></nonce_str>
<openid><![CDATA[oUpF8uMEb4qRXf22hE3X68TekukE]]></openid>
<out_trade_no><![CDATA[1409811653]]></out_trade_no>
<result_code><![CDATA[SUCCESS]]></result_code>
<return_code><![CDATA[SUCCESS]]></return_code>
<sign><![CDATA[B552ED6B279343CB493C5DD0D78AB241]]></sign>
<sub_mch_id><![CDATA[10000100]]></sub_mch_id>
<time_end><![CDATA[20140903131540]]></time_end>
<total_fee>1</total_fee>
<trade_type><![CDATA[JSAPI]]></trade_type>
<transaction_id><![CDATA[1004400740201409030005092168]]></transaction_id>
</xml>
可根据自身业务逻辑解析此 XML 数据。注意:获取数据后需向微信返回回应,若一直不回应,微信会多次重试请求,可能导致逻辑异常。因此,需返回 XML 格式的回应。
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>
小坑提示:在使用 Node.js 和 Express 框架开发时,若在微信支付成功后的回调中无法获取到 XML 值,需安装组件 body-parser-xml。可通过 npm install body-parser-xml --save 安装,并在 app.js 中使用 require('body-parser-xml')(bodyParser); 以中间件方式使用。
// 解决微信支付通知回调数据
app.use(bodyParser.xml({
limit: '2MB', // Reject payload bigger than 1 MB
xmlParseOptions: {
normalize: true, // Trim whitespace inside text nodes
normalizeTags: true, // Transform tags to lowercase
explicitArray: false // Only put nodes in array if >1
}
}));
配置后,即可正常获取微信的 XML 数据。
使用方法如下:
pay.getAccessToken({
notify_url : 'http://demo.com/', //微信支付完成后的回调
out_trade_no : new Date().getTime(), //订单号
attach : '名称',
body : '购买信息',
total_fee : '1', // 此处的额度为分
spbill_create_ip : req.connection.remoteAddress,
}, function (error, responseData) {
res.render('payment', {
title : '微信支付',
wxPayParams : JSON.stringify(responseData),
//userInfo : userInfo
});
});
至此,内容基本完整。如有不当之处,敬请指正。
附上我的代码仓库:https://git.oschina.net/anziguoer/wechatPay
本文已被整理至《JavaScript 微信开发技巧汇总》,欢迎学习阅读。
为大家推荐当前关注度较高的微信小程序教程:《微信小程序开发教程》,由小编精心整理,希望读者喜欢。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。