☞:官方文檔
☞:參考文檔
場景介紹
調用獲取平台證書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); }); }); }); }); }); }); }); });