微信JSAPI支付(境外)踩坑记录

日期: 2019-10-28         浏览量: 10527

这两周在对接微信支付(境外版),感慨万千,生活不容易,且行且珍惜。 (默默的诅咒下微信,别问我为啥)


一. 准备工作


    1. 申请境外商户 (具体怎么申请,请问微信客服)


    2. 注册微信公众号 (怎么注册,自行百度)


    3. 商户绑定公众号 (⚠️注意:如果你的商户主体和微信公众号主体不是一个,自己是办法绑定的,请联系微信客服,或者你的Bd经理 )


    4. 商户绑定公众号成功后,请在商户平台生成我们需要用到的API证书及商户keys(APIv3 secret)如下图:


    


    在生成API证书时,我们要使用微信生成工具,在生成后,会包含三个在证书文件(.pem),请妥善保管。 apiclient_key.pem 是密钥文件我们下面会用到。


二. 微信公众号授权(获取openid


    1. 微信公众号授权请看官方文档(这里没有坑)微信传送门 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html


三. 支付下单


    1. 文档说明 (境外支付文档需要看v3版本,下载地址链接: https://pan.baidu.com/s/17flGOD0CYizsU0T3EoBV4g 提取码: 366a)自测只有这个版本是对的,官网上的都是坑。


    2. 统一下单(详情请看文档,这里直接上代码 node.js)

    

    

let unifiedUrl = 'https://apihk.mch.weixin.qq.com/hk/v3/transactions/jsapi';   // 微信统一下单接口

let productName = '小棉袄一件';  //商品名称

// 请求参数
let pay_parameter = {
  'mchid': '1052***49', //商户号
  'appid': 'wx459f9b30****b725', // appid
  'description': productName,  // 描述
  'notify_url': 'https://***.fangzhenqi.xin/api**/wx/pay-callback',  //回调通知地址
  'out_trade_no': '123*****32f', //订单号  
  'trade_type': 'JSAPI',  // 支付方式固定的
  'merchant_category_code': '5977',  // 商业编号 具体看文档
  'payer': {
    openid: 'openid'     // 用户的openid
  },
  'amount': {
    total: 10,       // 支付金额 整数
    currency: 'HKD'  // 币种
  }
};

// 时间
let time = Math.round(new Date().getTime() / 1000).toString();
// 签名字符串
let randomStr = Math.random().toString(36).substr(2, 15);
// 组成签名字符串
let str = `POST\n/hk/v3/transactions/jsapi\n${time}\n${randomStr}\n${JSON.stringify(pay_parameter)}\n`;
// 密钥是在生成api证书时下载的密钥文件
let PrivateKey = String(fs.readFileSync('私钥证书路径/apiclient_key.pem', 'utf-8'));

// rsa签名 安装依赖包 node-rsa
let key = new NodeRsa(PrivateKey, { signingScheme: 'sha256' });
let sign = key.sign(str, "base64", "utf8");
// 请求头信息 serial_no 证书编号 在商户平台 - 证书 可以查看
let authorization = `WECHATPAY2-SHA256-RSA2048 mchid="105****49",nonce_str="${randomStr}",signature="${sign}",timestamp="${time}",serial_no="25C835B74B3C********5341E7"`;
const result = await new Promise((reslove, reject) => {
  request({
    url: unifiedUrl,
    method: "POST",
    json: true,
    headers: {
      "authorization": authorization,
      "User-Agent": '127.0.0.1',
      'Content-Type': 'application/json',
    },
    body: pay_parameter
  }, function (error, response, body) {
    if (!error) {
      reslove(body);
    } else {
      reject(error);
    }
  });
})

// 下单成功后  前端唤醒支付 数据做签名处理
if (!result.prepay_id) { return false };

let timeStamp = Math.round(new Date().getTime() / 1000).toString();
let nonceStr = Math.random().toString(36).substr(2, 15);
//做数据签名
str = `wx459f9*****5b725\n${timeStamp}\n${nonceStr}\nprepay_id=${result.prepay_id}\n`;
let paySign = key.sign(str, "base64", "utf8");
//返回数据 前端拿此数据直接可唤醒微信支付
return {
  appId: 'wx45*******d5b725',
  timeStamp: timeStamp,
  nonceStr: nonceStr,
  package: `prepay_id=${result.prepay_id}`,
  signType: 'RSA',
  paySign: paySign
}


    3. 在微信内部前端唤醒支付方法


WeixinJSBridge.invoke('getBrandWCPayRequest', res.data, function(res){     
    if(res.err_msg == "get_brand_wcpay_request:ok" ) {
        alert('好像成功了');
    }      
}); 


    4. 回调解密


let key = `fqnp5********6166lbkw88`;  // 解密key 上面提到的商户keys(APIv3 secret)
let nonce = param.resource.nonce;  // 加密使用的随机串
let associated_data = param.resource.associated_data;  // 加密用的附加数据
let ciphertext = param.resource.ciphertext;  // 加密体 base64

// 解密 ciphertext字符  AEAD_AES_256_GCM算法
ciphertext = Buffer.from(ciphertext, 'base64');
let authTag = ciphertext.slice(ciphertext.length - 16);
let data = ciphertext.slice(0, ciphertext.length - 16);
let decipher = crypto.createDecipheriv('aes-256-gcm', key, nonce);
decipher.setAuthTag(authTag);
decipher.setAAD(Buffer.from(param.resource.associated_data));
let decoded = decipher.update(data, null, 'utf8');
decipher.final();
let payData = JSON.parse(decoded); //解密后的数据

// 订单状态更新操作