关于微信公众号支付开发,我想我可以帮到你们一些
文档真的很重要,不管是写还是看的人都应该认真点
本文主要写给公众平台h5支付开发者
一、技术栈
- 语言:Node
- web框架 Express
- 开发过程中依赖的模块
- express-xml-bodyparser 用于解析客户端post过来的xml格式的数据
- jstoxml 用于将js对象转换成xml
- xml2js 用户将xml转换js对象
- md5 在有需要md5加密的地方可以用到
二、微信开发之旅
痛并快乐着
1. 注册商户平台
这一步很重要,注册后可以拿到后面【统一下单】需要的商户号,以及 参与签名需要的key,不懂的话就谷歌一下。
2. 支付目录设置
微信公众平台-> 微信支付-> 开发配置,见微信官方图,具体细节见图片
3. 统一下单需要注意的地方
接口地址 :https://api.mch.weixin.qq.com/pay/unifiedorder
必须提交的参数
appid 公众平台的appid
attach 自定义消息
body 商品类别【商品信息都可以】
mch_id 商户平台的账号
nonce_str 随机字符串,不超过32位
notify_url 微信生成prepay_id后,会发送请求到所设置的回调地址
out_trade_no 商户自定义生成的订单号,要保证不能重复
spbill_create_ip 客户端的ip地址
total_fee 支付的金额,以分为单位
trade_type 交易类型 取值有JSAPI,NATIVE,APP ,因为是在h5页面触发支付,我选择了 JSAPI,要根据实际需求选择
根据项目,需要提交的参数
openid 标示用户的id 【下面会贴相关链接】,如果是h5支付开发就一定要传
以上提交参数要注意区分大小写【如果有大写的话】,针对几个参数说明一下
openid :用户授权后,微信会在你设置的授权回调域名后拼接code参数,通过这个code的值,然后通过code换取网页授权access_token,返回的数据中就包含openid。请注意,这里用的是网页授权access_token
mch_id :注册商户平台后,微信会把商户号和密码发到指定的邮箱,这里就只用到商户账号而已
4.代码
看代码啦,展示了主要部分,【用es6写】
1)后端代码
为了展示,我把内容整合成在一起,实际开发并没有写在一起
// config 里面的值实际开发请填写正确的,展示用随便写的
let config = {
appid : 'wx2323423432342', // 这里是公众号的appid
mch_id : '1230000109', // 这里是商户号,
notify_url : 'http://mimeay.cc/pay/receive', // 哈哈,这里随便贴了个地址
key : 'ABc1234567890987654321234567890',//在商户平台设置的,32位的字符串,包括大小写祖母,数字
}
let util = {
createNonceStr () {
return Math.random().toString(36).substr(2, 15);
},
createTimestamp () {
return parseInt(new Date().getTime() / 1000) + '';
},
raw (args) {
let keys = Object.keys(args);
keys = keys.sort()
let newArgs = {};
keys.forEach(function (key) {
newArgs[key] = args[key];
});
let string = '';
for (let k in newArgs) {
string += '&' + k + '=' + newArgs[k];
}
string = string.substr(1);
return string;
},
paySign (data,key) {
let stringA,stringSignTemp,sign;
stringA = this.raw(data);
stringSignTemp = stringA +'&key=' + key;
// md5函数来自依赖的模块,这里不展示
sign = md5(stringSignTemp).toUpperCase();
return sign;
},
post_request_xml(url,data){
// 这里返回响应的内容
}
}
生成 prepay_id
unified_order ({openid,ip,out_trade_no,total_fee,category = '纪念品',trade_type = 'JSAPI',device_info = 'WEB',attach = '网上商城'}){
let baseUrl = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
let nonce_str = util.createNonceStr();
// ip 可以从请求对象取
// openid 通过客户端传递
// out_trade_no 请求统一下单前先生成【不管用户最终是否完成支付】
// total_fee 支付金额,单位为分,通过商品的id从数据库获取价格并转化成分【前端传递金额不可靠】
// 其他参数不传则默认
let data = {
appid : config.appid,
attach : attach,
body : '公司名-' + category,
mch_id : config.mch_id,
nonce_str: nonce_str,
notify_url : config.notify_url,
openid : openid,
out_trade_no : out_trade_no,
spbill_create_ip : ip,
total_fee:total_fee,
trade_type : trade_type,
device_info : device_info
};
let sign = util.paySign(data,config.key);
data['sign'] = sign;
let xmlData = {
xml : data
}
//jstoxml.toXML 是把js转换成xml的一个方法来的
let xml = jstoxml.toXML(xmlData);
// post_request_xml
return util.post_request_xml(baseUrl,xml).then((result)=>{
const {sign,prepay_id,return_code,return_msg} = result;
if(return_code == 'FAIL'){
return {
return_code,
return_msg
}
}else{
return {
return_code,
return_msg,
prepay_id : prepay_id
}
}
})
}
结合prepay_id生成客户端支付请求的参数,这里的 appId, timeStamp, nonceStr, package, signType严格区分大小写,我一开始没注意,把参数都小写了,结果导致客户端签名失败
wx_pay_sign (prepay_id){
let baseUrl = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
let nonce_str,data,sign,timestamp,xmlData,xml,_package;
nonce_str = util.createNonceStr();
timestamp = util.createTimestamp();
_package = 'prepay_id=' + prepay_id;
//这里的参数要区分大小写
data = {
appId : config.appId,
timeStamp : timestamp,
nonceStr : nonce_str,
package : _package,
signType : 'MD5'
}
// appId, timeStamp, nonceStr, package, signType
sign = util.paySign(data,config.key);
data['paySign'] = sign;
return data;
}
到此,后端【统一下单】做的事情告一段落,接下来说下前端的
2)前端代码
还记得wx_pay_sign函数做的事情吗,假设我们通过接口请求,拿到了wx_pay_sign返回的值
$.ajax({
url : 'xxxxxxx',
type : 'post',
data : {
openid : '通过授权拿到的openid'
},
success (json){
const {code,data,msg} = json;
if(code == 0){
wx.chooseWXPay({
timestamp: data.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
nonceStr: data.nonceStr, // 支付签名随机串,不长于 32 位
package: data.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***)
signType: data.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
paySign: data.paySign, // 支付签名
success: function (res) {
// 支付成功后的回调函数
}
});
}
}
})
5. 服务端调试时可能出现的问题
不管是大神还是菜鸟,你们一定要注意,在签名的时候,一定要主要看微信要求签名的是大写还是小写
- XML格式错误 :如果出现错误,可以在发送请求钱打印在本地看下 ,格式一般如下(只展示格式,不代表这就是请求发送的参数):
<xml>
<appid>xxxxx</appid>
</xml>
- 签名错误: 一般会出现这个问题,可能是参数值为空,如下情况【appid为空】就会导致签名失败
<xml>
<appid></appid>
<mch_id>23123123</mch_id>
</xml>
6. 客户端调试可能出现的问题
- 支付签名失败 :正常来说是 appId, timeStamp, nonceStr, package, signType 签名的时候出现问题,一有可能是没有区分大小写,要么就是参数的值设置错误,参与签名的时候key一定要正确,如果没有的话,可以在商户平台设置