☞:官方文档
☞:参考文档
场景介绍
调用获取平台证书V2接口之前,请前往微信支付商户平台升级API证书,升级后才可成功调用本接口。
接口调用请求说明
| 请求Url | https://api.mch.weixin.qq.com/applyment/micro/submit |
|---|---|
| 是否需要证书 | 否 |
| 请求方式 | POST、XML |
| 签名方式 | HMAC-SHA256 |
开发前准备:
首先要升级API证书, 设置APIv3秘钥(设置这个秘钥对API商户秘钥没有影响)
微信工具类:
var fs = require(\'fs\') var express = require(\'express\'); var parseString = require(\'xml2js\').parseString; var app = express(); var crypto = require(\'crypto\'); var hmac_sha256 = require("crypto-js/hmac-sha256"); //请自行 npm install crypto-js var fs = require(\'fs\'); // 载入fs模块 var https = require("https"); // 敏感信息加密 var wpayFieldEncrypt = function (str) { /** * 注意!!! * 公钥必须是通过 获取获取平台证书接口返回的 否则会报加密错误 * 接口API: https://pay.weixin.qq.com/wiki/doc/api/xiaowei.php?chapter=19_11 * 获取到返回值解密报文 获取到的 */ var RSA_PUBLIC_KEY = "-----BEGIN CERTIFICATE-----\n" + "MIIEEDCCAvigAwIBAgIUTctgkYr/OYHOSmKbETG3OgymeWkwDQYJKoZIhvcNAQEL\n" + "BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT\n" + "FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg\n" + "Q0EwHhcNMTkwNTA1MDkxOTUzWhcNMjQwNTAzMDkxOTUzWjCBoTETMBEGA1UEAwwK\n" + "MTQ5OTM1MDg0MjEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMTkwNwYDVQQL\n" + "DDDnpo/lt57luILmmYvlronljLrltLTliJvmnoHlrqLkv6Hmga/mnInpmZDlhazl\n" + "j7gxCzAJBgNVBAYMAkNOMRIwEAYDVQQIDAlHdWFuZ0RvbmcxETAPBgNVBAcMCFNo\n" + "ZW5aaGVuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv+z09hViR8lZ\n" + "BollKE2ahihsofTxQUc/r9vmTAw+JG7PHIJYJ87fV+e6TJYZvSgQZvSKxyyOb7tJ\n" + "bRGIgQBVNWjjDllnbcOq1ntVq9DczvWg9Fg3mnekE2WKDw/f+rvuL5bhmrt5fuhC\n" + "nyC5UvxU55UkFq2AMvFgmvS3j0Zp2A0v5waznkg7RrxvusjIunx3JS6GJDmOYdrq\n" + "DMqwJGHtTwh57jLTQBlzupI/cUOZSrv7fUQO4V4r2gy605rIINUa6/LCC1Sokrf1\n" + "OolK8UjzVZgK+vy4XbtCQy0if/cHk+4QL0jdZM843XDSNoPoNFp8SFamXM6ArsPy\n" + "tFUtyLPaQwIDAQABo4GBMH8wCQYDVR0TBAIwADALBgNVHQ8EBAMCBPAwZQYDVR0f\n" + "BF4wXDBaoFigVoZUaHR0cDovL2V2Y2EuaXRydXMuY29tLmNuL3B1YmxpYy9pdHJ1\n" + "c2NybD9DQT0xQkQ0MjIwRTUwREJDMDRCMDZBRDM5NzU0OTg0NkMwMUMzRThFQkQy\n" + "MA0GCSqGSIb3DQEBCwUAA4IBAQCvhiA/4Mj7budLyE5tn50Bt/cDJwfP1ZA/FwM9\n" + "w8cdLUvImbXzXphsPt8yEPly/8Km0BeGr2CYvzyg1pdlc5gY4S73FvmDOwB8gkPd\n" + "4yUxta9EcPldUisO+aYktH9mhdEOB5P7Zc7JF8D1hNOQITglHGdcZQf1os5RdhBM\n" + "DJ5+H5FuedzyFB5DVjOBNCI3OPbDdtx+YN3RZsXtH9GMFVAlF5VIcQvZS5V+P533\n" + "9+/BRKTN0JXvZ0CzjwO6yVGKg6thDSohY4dZVuhx2kKgssTLD5VrEtusOm3iL7fA\n" + "KwVIFtF/I07On3hy/AEuFwCzNuTi01EtG5t9KcVTFtkN7zJl\n" + "-----END CERTIFICATE-----"; //RSA_PKCS1_OAEP_PADDING var encrypted = crypto.publicEncrypt({ key: RSA_PUBLIC_KEY, padding: crypto.constants.RSA_PKCS1_PADDING }, new Buffer(str)).toString(\'base64\'); return encrypted; //以下为解密,这个是微信支付后台要做的,放在这里仅供参考 var buffer2 = new Buffer(encryptedStr, \'base64\') var decrypted = crypto.privateDecrypt({ key: pcert, padding: crypto.constants.RSA_PKCS1_PADDING }, buffer2) //pcert 为密钥 res.send(decrypted.toString("utf8")) //ceshi return \'\'; } // 上传图片 var uploadMedia = function (filePath, fname, fn) { //要上传的文件完整路径 var cfile = filePath; var mch_id = "服务商商户号"; var wkey = "服务商商户秘钥"; //读取图片内容 var buffer = fs.readFileSync(cfile); var fsize = Buffer.byteLength(buffer); //获取图片hash值(MD5) var fsHash = crypto.createHash(\'md5\'); fsHash.update(buffer); var fmd5 = fsHash.digest(\'hex\'); //对参数进行hmac_sha256签名 var signData = \'mch_id=\' + mch_id + \'&media_hash=\' + fmd5 + \'&sign_type=HMAC-SHA256\' + "&key=" + wkey; var sign = hmac_sha256(signData, wkey) + \'\'; sign = sign.toUpperCase(); //开始构建包头内容(包头需要传入必须的参数,如:mch_id、media_hash、sign、sign_type) var boundaryKey = Math.random().toString(16); //随机数,目的是防止上传文件中出现分隔符导致服务器无法正确识别文件起始位置 var payload = \'--\' + boundaryKey + \'\r\n\' + \'Content-Disposition:form-data; name="mch_id"\r\n\r\n\' + \'\' + mch_id + \'\r\n\' + \'--\' + boundaryKey + \'\r\n\' + \'Content-Disposition:form-data; name="media_hash"\r\n\r\n\' + \'\' + fmd5 + \'\r\n\' + \'--\' + boundaryKey + \'\r\n\' + \'Content-Disposition:form-data; name="sign"\r\n\r\n\' + \'\' + sign + \'\r\n\' + \'--\' + boundaryKey + \'\r\n\' + \'Content-Disposition:form-data; name="sign_type"\r\n\r\n\' + \'HMAC-SHA256\r\n\' + \'--\' + boundaryKey + \'\r\n\' + \'Content-Disposition:form-data; name="media"; filename="\' + fname + \'"\r\n\' + \'Content-Type:image/png\r\n\' + \'Content-Transfer-Encoding:binary\r\n\r\n\'; //开始构建包尾内容 //注意前面必须且只能有一个换行符,否则会返回“图片 Hash 值有误,请检查后重新提交”的错误提示 //后面必须且只能有各个换行符,否则会返回“参数填写有误,请检查后重试”的错误提示 var enddata = \'\r\n--\' + boundaryKey + \'--\r\n\'; var rq = https.request({ host: \'api.mch.weixin.qq.com\', port: 443, pfx: fs.readFileSync(\'./pem/apiclient_cert.p12\'), passphrase: mch_id, path: \'/secapi/mch/uploadmedia\', method: \'POST\' }, function (ress) { var str = \'\'; ress.on(\'data\', function (buf) { str += buf;//用字符串拼接 }); ress.on(\'end\', async function () { if (fn) { var buffer = str; const parseObj = await new Promise(function (resolve) { parseString(buffer, { explicitArray: false, }, function (err, json) { resolve(json); }); }); fn(parseObj) }; return; }); }); //写header头,表示包识别符,和包大小 rq.setHeader(\'Content-Type\', \'multipart/form-data; boundary=\' + boundaryKey + \'\'); rq.setHeader(\'Content-Length\', Buffer.byteLength(new Buffer(payload, \'binary\')) + Buffer.byteLength(new Buffer(enddata, \'binary\')) + fsize); //发包 rq.write(Buffer.concat([new Buffer(payload, \'binary\'), buffer, new Buffer(enddata, \'binary\')])); rq.on(\'error\', function (err) { if (fn) { fn(\'{"code":-1, "msg":"无法上传图片《\' + fname + \'》(\' + err.message + \')"}\') } }); rq.end(); } // 订单号生成 var randomString = function (len) { len = len || 10; var $chars = \'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\'; var maxPos = $chars.length; var pwd = \'\'; for (let i = 0; i < len; i++) { //0~32的整数 pwd += $chars.charAt(Math.floor(Math.random() * (maxPos + 1))); } return (pwd + (new Date()).getTime()); } // 微信支付API接口的通用函数 var wxPost = function (postObject, parm, fn) { //postObject为要提交的微信支付服务器的参数列表,为json格式 //parm为额外参数列表,包括path(微信支付API子地址,如:/pay/micropay)和sign_typ(参数加密类型,可选“MD5”或“HMAC-SHA256”) //fn为调用结束时的回调(无论成功与否都回调,返回的参数是xml格式的) var mch_id = "服务商商户号"; var wkey = "APIv3密钥"; var keys = Object.keys(postObject).sort(); var signData = \'\'; for (var key of keys) { if (postObject[key]) { signData += "&" + key + "=" + postObject[key]; } } signData = signData.slice(1) + "&key=" + wkey; var sign_type = parm.sign_type || "MD5"; if (sign_type == "HMAC-SHA256") { var sign = hmac_sha256(signData, wkey) + \'\'; sign = sign.toUpperCase(); } else { var sign = md5(signData).toUpperCase(); } postObject.sign = sign; var postData = \'<xml>\'; for (var key in postObject) { if (postObject[key]) { postData += \'<\' + key + \'>\' + postObject[key] + \'</\' + key + \'>\'; } } postData += \'</xml>\'; var req = https.request({ host: \'api.mch.weixin.qq.com\', port: 443, path: parm.path, pfx: fs.readFileSync(\'./pem/1499350842.pfx\'), // 对应API证书中的.p12文件,改后缀名即可 passphrase: mch_id, method: \'POST\' }, function (res) { var content = \'\'; res.on(\'data\', function (chunk) { content += chunk; }); res.on(\'end\', function () { if (fn) { fn(content) } }); }); req.on(\'error\', function (err) { console.log(\'出错了: \' + err) if (fn) { fn("<return_msg>" + err.message + "</return_msg>") } }); req.write(postData); req.end(); console.log(req); }
获取平台序列号
// 获取平台证书序列号 app.get(\'/getWxCertificate\', function (req, res) { var str = randomString(); var postObject = { \'sign_type\': \'HMAC-SHA256\', \'mch_id\': \'服务商商户号\', \'nonce_str\': str }; wxPost(postObject, { sign_type: "HMAC-SHA256", path: \'/risk/getcertficates\' }, async function (dat) { console.log(\'返回数据\' + dat) const parseObj = await new Promise(function (resolve) { parseString(dat, { explicitArray: false, }, function (err, json) { resolve(json); }); }); console.log(\'成功返回 : \' + JSON.stringify(parseObj)) }); });
提交微信进件申请
// 上传微信进件资料 app.get(\'/uploadXwsh\', function (req, res) { //小微商户入驻接口(参数接收仅为了展示效果,请自行设计接收方式) var sfzzmTp = "./image/1.jpg"; //身份证正面图片地址 var sfzfmTp = "./image/2.jpg"; //身份证背面图片地址 var mddmTp = "./image/3.png"; //门店店面图片地址 var mddnTp = "./image/4.png";//门店店内图片地址 var id_card_copy, id_card_national, store_entrance_pic, indoor_pic; //上传身份证正面照片 uploadMedia(sfzzmTp, "1.jpg", function (data) { id_card_copy = data.xml.media_id; //上传身份证背面照片 uploadMedia(sfzfmTp, "2.jpg", function (data) { id_card_national = data.xml.media_id; //上传门店店面照片 uploadMedia(mddmTp, "3.jpg", function (data) { store_entrance_pic = data.xml.media_id; //上传门店店内照片 uploadMedia(mddnTp, "4.jpg", function (data) { indoor_pic = data.xml.media_id; var nonce_str = randomString(); var postObject = { \'version\': \'3.0\', \'cert_sn\': \'平台证书序列号\', // 自行调用接口获取, 注意: 不是商户平台上的序列号 \'mch_id\': \'服务商商户号\', \'nonce_str\': nonce_str, \'sign_type\': \'HMAC-SHA256\', \'business_code\': \'9845641321\', \'id_card_copy\': id_card_copy, \'id_card_national\': id_card_national, \'id_card_name\': wpayFieldEncrypt(\'\'), // 身份证姓名 \'id_card_number\': wpayFieldEncrypt(\'\'), // 身份证号码 \'id_card_valid_time\': \'["\' + "2016-11-30" + \'","\' + "2036-11-30" + \'"]\', // 身份证有效期限 \'account_name\': wpayFieldEncrypt(\'\'), // 开户名称 \'account_bank\': \'工商银行\', // 开户银行 \'bank_address_code\': \'\', // 开户银行省市编码 \'account_number\': wpayFieldEncrypt(\'\'), // 银行账号 \'store_name\': \'\', // 门店名称 \'store_address_code\': \'\', // 门店省市编码 \'store_street\': \'\', // 门店街道名称 \'store_entrance_pic\': indoor_pic, // 门店门口照片 \'indoor_pic\': store_entrance_pic, // 店内环境照片 \'merchant_shortname\': \'\', // 商户简称 \'service_phone\': \'\', // 客服电话 \'product_desc\': \'餐饮\', // 售卖商品/提供服务描述 \'rate\': \'0.38%\', // 费率 \'contact\': wpayFieldEncrypt(\'\'), // 联系人姓名 \'contact_phone\': wpayFieldEncrypt(\'\') }; wxPost(postObject, { sign_type: "HMAC-SHA256", path: \'/applyment/micro/submit\' }, async function (dat) { console.log(\'返回数据\' + dat) const parseObj = await new Promise(function (resolve) { parseString(dat, { explicitArray: false, }, function (err, json) { resolve(json); }); }); }); }); }); }); }); });