微信支付小微商户可以通过小程序《微信买单服务商助手》来进件,也可以通过API接口来进件(详情可查阅小微商户专属接口文档)。
通过API接口进件前需要先在商户资料中的身份证照片和门店照片通过图片上传接口上传到微信的服务器,并获取media_id。
图片上传接口文档是这样写的:
上传图片
服务商操作员调用 API 接口,上传证件照片。支持 jpeg、jpg、bmp、png 格式。通过 POST 表单来调用该接口,表单媒体文件 id 为 media,需有 filename、filelength、content-type 等信息,请求报文的内容需以 multipart/form-data 方式提交。 URL 地址:https://api.mch.weixin.qq.com/secapi/mch/uploadmedia
是否需要证书
是
请求参数
字段名 |
变量名 |
必填 |
类型 |
示例值 |
描述 |
|
---|---|---|---|---|---|---|
商户号 |
mch_id |
是 |
String(32) |
|
银行商户号或渠道号 |
|
媒体文件 |
media |
是 |
String(256) |
|
form-data 中媒体文件标识,有 filename、filelength、content-type 等信息。不参与签名计算 |
|
媒体文件内容 hash 值 |
media_hash |
是 |
String(32) |
|
根据媒体文件内容进行MD5 计算后的值 |
|
签名 |
sign |
是 |
String(32) |
D675A1E471DBF7C0 B1AC5883B5BDA9EF |
|
|
签名方式 |
sign_type |
否 |
String(10) |
HMAC-SHA256 |
HMAC-SHA256 加密方式,其他或者不填为MD5方式 |
返回参数
字段名 |
变量名 |
必填 |
类型 |
示例值 |
描述 |
|
---|---|---|---|---|---|---|
返回状态码 |
return_code |
是 |
String(16) |
SUCCESS |
|
|
返回信息 |
return_msg |
否 |
String(128) |
签名失败 |
|
以下字段在 return_code 为 SUCCESS 的时候有返回
变量名 |
必填 |
类型 |
示例值 |
||
---|---|---|---|---|---|
业务结果 |
result_code |
是 |
String(32) |
|
|
媒体标识 Id |
media_id |
是 |
String(256) |
9UYfXi-WMUhgWXvJtEw4_g Ble_EfrD_pIL0bS8GAsK8 |
|
签名 |
sign |
是 |
String(32) |
|
|
错误代码 |
err_code |
否 |
String(32) |
INVALID_REQUEST |
|
错误代码描述 |
err_code_des |
否 |
String(128) |
商户号信息有误,请检查修改后重试 |
请求示例
curl --cert ./apiclient_cert.pem --key ./apiclient_key.pem -F'mch_id=1000000000' -F 'media_hash=709d1e858d2ef9d0501cc053668b2872' -F 'sign=D675A1E471DBF7C0B1AC5883B5BDA9EF' -F '[email protected]' https://api.mch.weixin.qq.com/secapi/mch/uploadmedia
响应示例
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<result_code><![CDATA[SUCCESS]]></result_code>
<media_id><![CDATA[9UYfXi-WMUhgWXvJtEw4_gBle_EfrD_pIL0bS8GAsK8]]></media_id>
<sign><![CDATA[D675A1E471DBF7C0B1AC5883B5BDA9EF]]></sign>
</xml>
错误码
错误码 | 名称 | 描述 | 解决方案 |
---|---|---|---|
不支持 http get方法 |
请使用 post 方法,请检查后重新提交 |
检查输入参数 |
|
INVALID_REQUEST |
xml 格式错误 |
xml 参数格式错误,请检查后重新提交 |
|
SIGNERROR |
验签错误 |
签名校验失败,请检查后重试 |
|
INVALID_REQUEST |
需要证书 |
获取客户端证书序列号失败 获取客户端证书特征名称(DN)域失败 证书校验失败,请检查后重试 |
检查证书 |
PARAM_ERROR |
参数错误 |
参数填写有误,请检查后重试 |
人工检查过滤 |
PARAM_ERROR |
图片文件错误 |
图片格式不正确,请检查后重新提交 图片大小不能超过 2M,请检查后重新提交 图片 Hash 值有误,请检查后重新提交 |
检查图片 |
PARAM_ERROR |
商户号错误 |
商户号 mchid 有误,请检查后重试 |
检查商户号是否正确 |
INVALID_REQUEST |
调用权限 |
暂无权限,请检查后重试 |
检查权限 |
FREQUENCY_LIMITED |
频率限制 |
操作过快,请稍后重试 |
放慢上传频率 |
SYSTEMERROR |
上传图片失败 |
上传图片失败,请稍后重试 |
联系微信支付客服 |
代码如下,其中getXML为解析XML的函数,请自行完成:
var https = require("https");
var crypto = require('crypto');
var hmac_sha256 = require("crypto-js/hmac-sha256"); //请自行 npm install crypto-js
var uploadMedia = function(filePath, fname, fn){
//要上传的文件完整路径
var cfile = filePath + "\\" + fname;
var mch_id = "服务商商户号";
var wkey = "API密钥或APIv3密钥";
//读取图片内容
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('./cert/' + mch_id + '.pfx'),
passphrase:mch_id,
path: '/secapi/mch/uploadmedia',
method: 'POST'
},function(ress){
var str='';
ress.on('data',function(buf){
str+=buf;//用字符串拼接
});
ress.on('end',function(){
if(getXML(str, "return_code") != "SUCCESS"){
if(fn) fn('{"code":-1, "msg":"无法上传图片《' + fname + '》(' + getXML(str, "return_msg") + ')"}');
return;
}
if(getXML(str, "result_code") != "SUCCESS"){
if(fn) fn('{"code":-1, "msg":"无法上传图片《' + fname + '》(' + getXML(str, "err_code_des") + ')"}');
return;
}
if(fn) fn(getXML(str, "media_id"));
});
});
//写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();
}