微信商家券H5开发全过程总结
最近遇到一个需要在第三方页面领取微信商家券的需求,其中涉及的创建->发券->领取->核销的整个闭环,简单记录一下这个过程。 1.创建商家券 参考网页:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_1.shtml 创建商家券只要传递相应的参数即可,其中一个重要的参数stock_id,是发券批次的唯一标识,在后续过程中也会用到。请求的参数中涉及数量的参数可能回存在依赖关系,比如max_coupons,因此,创建的时候需要满足特定的条件才能创建成功。需要注意“券code模式”,因为需求背景是在第三方页面中调取接口,每张券的code是由第三方系统自行生成的卡号code进行管理的,所以这里选择了MERCHANT_API。第一个选项WECHATPAY_MODE是无法自主指定、而是由微信卡券系统自动随机分配code码的;第三个选项MERCHANT_UPLOAD是需要创建者预先上传code,然后系统再在这些code里面随机分配的,因此,根据需求选择了MERCHANT_API,在后续的发券addCard的时传递对应的code字段即可。2.发券 参考网页:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_4_1.shtml 商家券创建成功后就到了发券流程,参考网页是h5发券的接口网页,其中,前端h5页面需要先注入JSSDK才能使用wx.addCard接口,详细参考JSSDK:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html。3.领取 参考网页:https://docs.qq.com/doc/DVlRacmRIWWhBUFVE?_t=1611714639674 这一步的关键在于addCard要传的参数:sign,这个参数是对除了sign以外其他几个参数进行签名之后得到的结果。参考网页中可以看到,加入签名的部分参数是有下标的,例如out_request_no这个字段,在加入签名的时候是以out_request_no0、out_request_no1、out_request_no2的形式加入到签名sign当中的,但是在传入addCard的cardExt中时,仍然是out_request_no字段。如下:// 1.单张卡券
// 参与签名的参数
const params = {
stock_id0:'';
out_request_no0:'';
coupon_code0:'';
customize_send_time0:'';
send_coupon_merchant:'';
openid:'';
};
// 生成签名,需要使用HMAC-SHA256算法
sign = generateSign(params);
// 构造cardExt参数
const addCardParams = [{
cardId: stock_id, // 批次号
cardExt = JSON.stringfy({ // JSON字符串
stock_id:'';
out_request_no:'';
coupon_code:'';
customize_send_time:'';
send_coupon_merchant:'';
openid:'';
sign
})
}]
wx.addCard({
cardList: addCardParams, // 需要添加的商家券列表
success: function (res) {
var cardList = res.cardList; // 添加的商家券列表信息
}
});
// 2.多张卡券(以两张为例)
// 参与签名的参数
const params = {
// 第一张券的(0)
stock_id0:'';
out_request_no0:'';
coupon_code0:'';
customize_send_time0:'';
// 第二张券的(1)
stock_id1:'';
out_request_no1:'';
coupon_code1:'';
customize_send_time1:'';
// 这两个参数则不需要下标0、1、2
send_coupon_merchant:'';
openid:'';
};
// 生成签名,需要使用HMAC-SHA256算法
sign = generateSign(params);
// 构造addCard参数
const addCardParams = [{ // 第一张券
cardId: stock_id, // 第一张券批次号
cardExt = JSON.stringfy({ // 第一张JSON字符串
stock_id:'';
out_request_no:'';
coupon_code:'';
customize_send_time:'';
send_coupon_merchant:'';
openid:'';
sign
})
}, { // 第二张券
cardId: stock_id, // 第二张券批次号
cardExt = JSON.stringfy({ // 第二张券JSON字符串(不需要再传openid和sign)
stock_id:'';
out_request_no:'';
coupon_code:'';
customize_send_time:'';
})
}
]
wx.addCard({
cardList: addCardParams, // 需要添加的商家券列表
success: function (res) {
var cardList = res.cardList; // 添加的商家券列表信息
}
});
可以使用签名工具进行检验:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=20_1。最后,领取卡券需要真机的微信环境下才能成功,PC端微信开发者工具环境下领取会显示无权限。4.核销 参考网页:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_2_3.shtml 用户领取完以后核销,传递相应的参数即可。5.补充 开发过程中经常会出现addCard签名错误的问题,可能的原因是:1.算法出错(要用HMAC-SHA256),2.加入签名的参数字段和参数值的问题,排查错误方法:https://mp.weixin.qq.com/s/WhYpWmfuhUBw2wseTXdt2A。这里卡券的核销方式(coupon_use_rule.use_method)是线下核销OFF_LINE,如果需要使用MINI_PROGRAMS的核销方式,则还需要对小程序跳转的参数使用AEAD_AES_256_GCM算法进行解密,解密的demo在这:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_2.shtml。附上一份node.js的示例供参考:const crypto = require('crypto');
// 1、用商户平台上设置的APIv3密钥【微信商户平台—>账户设置—>API安全—>设置APIv3密钥】,记为key。
const key = '*******';
/**
* 使用指定的 API V3 密钥对给定的ciphertext进行解密,返回解密后的明文数据coupon_code
*
* @param {Object} params - 解密参数
* @param {string} params.associate - 加密参数 - 类型
* @param {Buffer} params.nonce - 加密参数 - 随机数
* @param {string} params.ciphertext - 加密密文
* @returns {string} 明文数据
*/
const decrypt = ({ associate, nonce, ciphertext }) => {
// 2、从跳转路径中取得参数nonce、associate和密文ciphertext;
// 3、使用urldecode对ciphertext进行解码,得到strUrlDecodeText
const strUrlDecodeText = decodeURIComponent(ciphertext);
// 4、使用base64对strUrlDecodeText进行解码,得到strBase64DecodeText;
const strBase64DecodeText = Buffer.from(strUrlDecodeText, 'base64');
// 提取authTag和数据
const authTag = strBase64DecodeText.slice(strBase64DecodeText.length - 16);
const data = strBase64DecodeText.slice(0, strBase64DecodeText.length - 16);
// 5、使用key、nonce和associate,对数据密文strBase64DecodeText进行解密,得到的字符串即为coupon_code。
const decipher = crypto.createDecipheriv('aes-256-gcm', key, nonce);
decipher.setAuthTag(authTag);
decipher.setAAD(Buffer.from(associate));
let coupon_code = decipher.update(data, null, 'utf8');
coupon_code += decipher.final();
return coupon_code;
};
文章有错误的地方还望指出修正,感谢!