企业付款到零钱微信API请求报错:error:0909006C:PEM routines:get_name:no start line

370 阅读3分钟

 背景

对接企业付款到零钱,微信api请求时需要用到证书(node环境使用pem格式)

微信API:【微信支付】付款开发者文档

已经用微信的\WXCertUtil工具在本地生成了证书文件

问题所在:

证书使用方式错误,原本我是使用:

    // 需要证书
     agentOptions: {
                    cert: '../cert.pem',
                    key:'../key.pem'
                },

正确写法:

                agentOptions: {
                    cert: '-----BEGIN CERTIFICATE-----\r\nMIID6TXXXXXXXUNN6Lt1v7o\r\n-----END CERTIFICATE-----',
                    key:'-----BEGIN PRIVATE KEY-----\r\nMIIEvgIxxxxxxxxxxxxxHZXsjwoJ0gf71bQLrrAMHXh0i73N+KmItDaxTerRhK1XpLehFgTFoPQfmz6pPheHkDvLf9tsOiWZ4uF71+I24G4jHixFDDEyNy4cduzBRJgQTJrBooHLpPDFj2m6uqv\r\n-----END PRIVATE KEY-----',
                },

请求中的agentOptions对象 中证书直接使用pem格式的内容即可

注意:用微信工具WXCertUtil 生成的pem格式的文件有换行,node 请求中识别不了,一定要压缩一下,在加入\r\n

压缩工具:CSS代码压缩/解压缩_CSS在线格式化_CSS还原和美化_55查询

以下是相关代码参考:node egg企业付款到微信零钱_今天有坑么的博客-CSDN博客

//helper.js

const Xml2js = require('xml2js')
const Crypto = require('crypto')
const EARTH_RADIUS = 6378.137
module.exports = {
  // 计算两坐标点之间的距离 1.
    async space(lat1, lng1, lat2, lng2) {
        var radLat1 = lat1 * Math.PI / 180.0;
        var radLat2 = lat2 * Math.PI / 180.0;
        var a = radLat1 - radLat2;
        var b = lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0;
        var s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
        s = s * 6378.137;
        s = Math.round(s * 10000) / 10000;
        return s  // 单位千米
    },
    // 计算两坐标点之间的距离 2.-1
    async rad(d) {
        return d * Math.PI / 180.0;
    },
    // 计算两坐标点之间的距离 2.-2
    async getDistance(lng1, lat1, lng2, lat2) {
        // * 谷歌地图计算两个坐标点的距离 
        // * @param lng1  经度1 
        // * @param lat1  纬度1 
        // * @param lng2  经度2 
        // * @param lat2  纬度2 
        // * @return 距离(千米)
        var radLat1 = await this.rad(lat1);
        var radLat2 = await this.rad(lat2);
        var a = radLat1 - radLat2;
        var b = await this.rad(lng1) - await this.rad(lng2);
        var s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)
            + Math.cos(radLat1) * Math.cos(radLat2)
            * Math.pow(Math.sin(b / 2), 2)));
        s = s * EARTH_RADIUS;
        s = Math.round(s * 10000) / 10000;
        return s;
    },
    // 计算坐标距离 3.
    async GetMapDistance(lat1, lng1, lat2, lng2) {
        let MathLeb = Math.PI / 180.0;//经纬度转换成三角函数中度分表形式。
        var radLat1 = Number(lat1 * MathLeb);
        var radLat2 = Number(lat2 * MathLeb);
        var a = radLat1 - radLat2;
        var b = Number(lng1 * MathLeb) - Number(lng2 * MathLeb);
        var s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +
            Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
        s = s * 6378.137;//地球半径;
        s = Math.round(s * 10000) / 10000;//以Km输出
        s = s.toFixed(2);
        return s;
    },
    // 返回MD5加密
    async md5(data) {
        return Crypto.createHash('md5').update(data).digest('hex')
    },
    // 返回一个32位字符串
    async GetStr32() {
        return Crypto.randomBytes(16).toString('hex')
    },
    
    // 返回sha1 加密
    async sha1(data) {
        return Crypto.createHash('sha1').update(data).digest('hex')
    },
    // 构成XML
    createXML(obj) {
        return new Promise((resolve) => {
            var builder = new Xml2js.Builder({
                rootName: 'xml',
                headless: true,
                cdata: true,
            });
            return resolve(builder.buildObject(obj))
        })
    },
    // 公众号  XML 转为json
    async parseXMLAsync(data) {
        let { xml } = await Xml2js.parseStringPromise(data, { explicitArray: false })
        return xml
    },
    // 生成订单编号
    async  createOrderNumber() {
        let str = ''
        for (let i = 0; i < 10; i++) {
            let num = Math.floor(Math.random() * 10)
            str += num
        }
        let time = new Date()
        let year = time.getFullYear().toString()
        let month = time.getMonth().toString() + 1
        let day = time.getDate().toString()
        let hours = time.getHours().toString()
        let minutes = time.getMinutes().toString()
        let seconds = time.getSeconds().toString()
        let mill = time.getMilliseconds().toString()
        str += year += month += day += hours += minutes += seconds += mill
        return str;
    },
    // 按照ascll码排序
    async raw(args) {
        var keys = Object.keys(args);
        keys = keys.sort()
        var newArgs = {};
        keys.forEach(function (key) {
            newArgs[key] = args[key];
        });
        var string = '';
        for (var k in newArgs) {
            string += '&' + k + '=' + newArgs[k];
        }
        string = string.substr(1);
        return string;
    },
    /**
     * 企业付款到零钱签名
     */
   async transfersSign  (map) {
        let ret = {
            mch_appid: map.mch_appid,
            mchid: map.mchid,
            nonce_str: map.nonce_str,
            partner_trade_no: map.partner_trade_no,
            openid: map.openid,
            check_name: map.check_name,
            amount: map.amount,
            desc: map.desc,
            spbill_create_ip: map.spbill_create_ip,
        }
        console.log(ret)
        var string =await this.raw(ret);
        var key = map.mchkey;
        string = string + '&key=' + key;
        return Crypto.createHash('md5').update(string, 'utf8').digest('hex').toUpperCase();
    }
}

//egg.js  serive

'use strict';

const Service = require('egg').Service;
const fs = require('fs')
const path = require('path')
const xml2js = require('xml2js')
const request = require('request')

class QiyezzService extends Service {
      async transfers(map)  {
        let mapInfo = {}
        // 商户appid
        // 这里的商户appid是公众号appid
        mapInfo.mch_appid = ''
        // 商户id
        mapInfo.mchid = ''
        // 商户密钥
        mapInfo.mchkey =''
        // 随机字符串
        mapInfo.nonce_str = await this.ctx.helper.GetStr32()
        // 商户内部流水号
        mapInfo.partner_trade_no = map.recordId
        // 用户的openid
        mapInfo.openid = map.openid
        // 是否需要校验名字
        mapInfo.check_name = 'NO_CHECK'
        // 金额,单为是分
        mapInfo.amount = map.amount * 100
        // 描述
        mapInfo.desc = '企业付款到零钱'
        // ip,该IP同在商户平台设置的IP白名单中的IP没有关联,该IP可传用户端或者服务端的IP。
        // mapInfo.spbill_create_ip = ''
        // 签名
        let sign = await this.ctx.helper.transfersSign(mapInfo)
    
        // 拼接xm字符串
        let formData = "<xml>";
        formData += "<mch_appid>" + mapInfo.mch_appid + "</mch_appid>";
        formData += "<mchid>" + mapInfo.mchid + "</mchid>";
        formData += "<nonce_str>" + mapInfo.nonce_str + "</nonce_str>";
        formData += "<partner_trade_no>" + mapInfo.partner_trade_no + "</partner_trade_no>";
        formData += "<openid>" + mapInfo.openid + "</openid>";
        formData += "<check_name>" + mapInfo.check_name + "</check_name>";
        formData += "<amount>" + mapInfo.amount + "</amount>";
        formData += "<desc>" + mapInfo.desc + "</desc>";
        formData += "<spbill_create_ip>" + mapInfo.spbill_create_ip + "</spbill_create_ip>";
        formData += "<sign>" + sign + "</sign>";
        formData += "</xml>";
        console.log(formData)
    
      // 请求路径
        let url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers'
        return new Promise((resolve, reject) => {
            request({
                url: url,
           // 需要证书
                agentOptions: {
                    cert: this.config.wxpay_cert_pem,
                    key:this.config.wxpay_cert_keypem
                },
                method: 'post',
                body: formData,
            }, function (err, response, body) {
                if (!err && response.statusCode == 200) {
    
                    // 创建一个解析xml的对象
                    let parser = new xml2js.Parser({
                        trim: true,
                        explicitArray: false,
                        explicitRoot: false
                    }); 
    
                    //解析签名结果xml转json
                    parser.parseString(body, (err, res) => {
                        console.log(res)
                        let result = {}
    
                        // 判断return_code 为fail的时候,的错误信息
                        if (res.return_code == 'FAIL') {
                            result.msg = res.return_msg
                            reject(result)
                        }
                        
                        // return_code是success 的话, 只代表业务已受理, 并不代表已成功
                        // result_code是success 的话, 才算是付款成功, fail的话,返回错误信息
                        if (res.return_code == 'SUCCESS' && res.result_code == 'FAIL') {
                            result.msg = res.err_code_des
                            reject(result)
                        } else {
                            result.msg = ''
                            resolve(result)
                        }
                    })
                }
                reject(err)
            })
        })
    }
}

module.exports = QiyezzService;