为了在保证支付安全的前提下,带给商户简单、一致且易用的开发体验,我们推出了全新的微信支付APIv3接口。该版本API的具体规则请参考“APIv3接口规则”
备注:当前接口用于微信国内钱包
为了帮助开发者调用开放接口,我们提供了JAVA、PHP、GO三种语言版本的开发库,封装了签名生成、签名验证、敏感信息加/解密、媒体文件上传等基础功能(更多语言版本的开发库将在近期陆续提供)
测试步骤:
1、根据自身开发语言,选择对应的开发库并构建项目,具体配置请参考下面链接的详细说明:
• wechatpay-java(推荐)wechatpay-apache-httpclient,适用于Java开发者。
• wechatpay-php(推荐)、wechatpay-guzzle-middleware,适用于PHP开发者
注:当前开发指引接口PHP示例代码采用wechatpay-guzzle-middleware版本
• wechatpay-go,适用于Go开发者
更多资源可前往微信支付开发者社区搜索查看
2、创建加载商户私钥、加载平台证书、初始化httpClient的通用方法
@Before
public void setup() throws IOException {
// 加载商户私钥(privateKey:私钥字符串)
PrivateKey merchantPrivateKey = PemUtil
.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),apiV3Key.getBytes("utf-8"));
// 初始化httpClient
httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier)).build();
}
@After
public void after() throws IOException {
httpClient.close();
}
3、基于接口的示例代码,替换请求参数后可发起测试
说明:
• 上面的开发库为微信支付官方开发库,其它没有审核或者控制下的第三方工具和库,微信支付不保证它们的安全性和可靠性
通过包管理工具引入SDK后,可根据下面每个接口的示例代码替换相关参数后进行快速测试
• 开发者如果想详细了解签名生成、签名验证、敏感信息加/解密、媒体文件上传等常用方法的具体代码实现,可阅读下面的详细说明:
1.签名生成
2.签名验证
3.敏感信息加解密
• 如想更详细的了解我们的接口规则,可查看我们的接口规则指引文档
• 支付授权目录说明:
a、商户最后请求拉起微信支付收银台的页面地址我们称之为“支付目录”,例如:https://www.weixin.com/pay.php。
b、商户实际的支付目录必须和在微信支付商户平台设置的一致,否则会报错“当前页面的URL未注册:”
• 支付授权目录设置说明:
登录微信支付【商户平台->产品中心->开发配置】,设置后一般5分钟内生效。
图1 支付授权目录设置说明
• 支付授权目录校验规则说明:
a、如果支付授权目录设置为顶级域名(例如:https://www.weixin.com/ ),那么只校验顶级域名,不校验后缀;
b、如果支付授权目录设置为多级目录,就会进行全匹配,例如设置支付授权目录为https://www.weixin.com/abc/123/,则实际请求页面目录不能为https://www.weixin.com/abc/,也不能为https://www.weixin.com/abc/123/pay/,必须为https://www.weixin.com/abc/123/
图2 支付授权目录校验规则说明
• 授权域名说明:开发JSAPI支付时,在JSAPI下单接口中要求必传用户openid,而获取openid则需要您在公众平台设置获取openid的域名,只有被设置过的域名才是一个有效的获取openid的域名,否则将获取失败。具体配置页如图2所示
• 授权域名设置说明:登录【微信公众平台->公众号设置->功能设置】
图2
微信网页授权域名设置
重点步骤说明:
步骤3 用户下单发起支付,商户可通过JSAPI下单创建支付订单。
步骤8 商户可在微信浏览器内通过JSAPI调起支付API调起微信支付,发起支付请求。
步骤15 用户支付成功后,商户可接收到微信支付支付结果通知支付结果通知API。
步骤20 商户在没有接收到微信支付结果通知的情况下需要主动调用查询订单API查询支付结果。
文档展示了如何使用微信支付服务端 SDK 快速接入JSAPI支付产品,完成与微信支付对接的部分。
注意:
步骤说明:用户通过商户下发的模板消息或扫描二维码在微信内进入商户网页,当用户选择相关商户购买时,商户系统先调用该接口在微信支付服务后台生成预支付交易单。
示例代码
public void CreateOrder() throws Exception{
//请求URL
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
// 请求body参数
String reqdata = "{"
+ "\"amount\": {"
+ "\"total\": 100,"
+ "\"currency\": \"CNY\""
+ "},"
+ "\"mchid\": \"1900006891\","
+ "\"description\": \"Image形象店-深圳腾大-QQ公仔\","
+ "\"notify_url\": \"https://www.weixin.qq.com/wxpay/pay.php\","
+ "\"payer\": {"
+ "\"openid\": \"o4GgauE1lgaPsLabrYvqhVg7O8yA\"" + "},"
+ "\"out_trade_no\": \"1217752501201407033233388881\","
+ "\"goods_tag\": \"WXG\","
+ "\"appid\": \"wxdace645e0bc2c424\"" + "}";
StringEntity entity = new StringEntity(reqdata,"utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
CloseableHttpResponse response = httpClient.execute(httpPost);
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
} else if (statusCode == 204) {
System.out.println("success");
} else {
System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
throw new IOException("request failed");
}
} finally {
response.close();
httpClient.close();
}
}
重要入参说明:
• out_trade_no:商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一
• description:商品描述
• notify_url:支付回调通知URL,该地址必须为直接可访问的URL,不允许携带查询串
• total:订单总金额,单位为分
• openid:openid是微信用户在appid下的唯一用户标识(appid不同,则获取到的openid就不同),可用于永久标记一个用户。openid获取方式请参考以下文档小程序获取openid、公众号获取openid、APP获取openid
更多参数、响应详情及错误码请参见JSAPI下单接口文档
步骤说明:通过JSAPI下单API成功获取预支付交易会话标识(prepay_id)后,需要通过JSAPI调起支付API来调起微信支付收银台
注意:
• WeixinJSBridge内置对象在其他浏览器中无效
• 此API需要将请求参数进行签名(参与签名的参数为:appId、timeStamp、nonceStr、package,参数区分大小写)
示例代码:
function onBridgeReady() {
WeixinJSBridge.invoke('getBrandWCPayRequest', {
"appId": "wx2421b1c4370ecxxx", //公众号ID,由商户传入
"timeStamp": "1395712654", //时间戳,自1970年以来的秒数
"nonceStr": "e61463f8efa94090b1f366cccfbbb444", //随机串
"package": "prepay_id=wx21201855730335ac86f8c43d1889123400",
"signType": "RSA", //微信签名方式:
"paySign": "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ\/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq\/xDg==" //微信签名
},
function(res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
}
});
}
if (typeof WeixinJSBridge == "undefined") {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
} else {
onBridgeReady();
}
重要入参说明:
• package:JSAPI下单接口返回的prepay_id参数值,提交格式如:prepay_id=***
• signType:该接口V3版本仅支持RSA
• paySign:签名
paySign生成规则、响应详情及错误码请参见 JSAPI调起支付接口文档
步骤说明:当用户完成支付,微信会把相关支付结果将通过异步回调的方式通知商户,商户需要接收处理,并按文档规范返回应答
注意:
步骤说明:当商户后台、网络、服务器等出现异常,商户系统最终未接收到支付通知时,商户可通过查询订单接口核实订单支付状态
示例代码(通过微信订单号查询):
public void QueryOrder() throws Exception {
//请求URL
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/id/4200000745202011093730578574");
uriBuilder.setParameter("mchid", mchId);
//完成签名并执行请求
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = httpClient.execute(httpGet);
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
} else if (statusCode == 204) {
System.out.println("success");
} else {
System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
throw new IOException("request failed");
}
} finally {
response.close();
}
}
注意:
步骤说明:当商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付;系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口
示例代码:
public void CloseOrder() throws Exception {
//请求URL
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/sdkphp12345678920201028112429/close");
//请求body参数
String reqdata ="{\"mchid\": \""+mchId+"\"}";
StringEntity entity = new StringEntity(reqdata,"utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
CloseableHttpResponse response = httpClient.execute(httpPost);
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
} else if (statusCode == 204) {
System.out.println("success");
} else {
System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
throw new IOException("request failed");
}
} finally {
response.close();
}
}
注意:
步骤说明:微信支付按天提供交易账单文件,商户可以通过该接口获取账单文件的下载地址
示例代码:
public void TradeBill() throws Exception {
//请求URL
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/bill/tradebill");
uriBuilder.setParameter("bill_date", "2020-11-09");
uriBuilder.setParameter("bill_type", "ALL");
//完成签名并执行请求
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = httpClient.execute(httpGet);
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
} else if (statusCode == 204) {
System.out.println("success");
} else {
System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
throw new IOException("request failed");
}
} finally {
response.close();
}
}
注意:
步骤说明:通过申请交易账单接口获取到账单下载地址(download_url)后,再通过该接口获取到对应的账单文件,文件内包含交易相关的金额、时间、营销等信息,供商户核对订单、退款、银行到账等情况
示例代码:
public void DownloadUrl(String download_url) throws Exception{
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
//初始化httpClient
//该接口无需进行签名验证、通过withValidator((response) -> true)实现
httpClient = WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, merchantPrivateKey).withValidator((response) -> true).build();
//请求URL
//账单文件的下载地址的有效时间为30s
URIBuilder uriBuilder = new URIBuilder(download_url);
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
//执行请求
CloseableHttpResponse response = httpClient.execute(httpGet);
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
} else if (statusCode == 204) {
System.out.println("success");
} else {
System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
throw new IOException("request failed");
}
} finally {
response.close();
}
}
注意:
• 账单文件的下载地址的有效时间为30s
• 强烈建议商户将实际账单文件的哈希值和之前从接口获取到的哈希值进行比对,以确认数据的完整性
A:请按以下步骤进行排查:
1. 建议检查一下公众号的功能。比如是不是在订阅号/未认证的公众号里面尝试调用认证服务号的功能
2. 确认APPID是否认证过期或者APPID填写错误
3. 请尝试使用snsapi_userinfo的授权登录方式
A:请按以下步骤进行排查:
1. 请检查你的下单接口是否指定了支付用户的身份,该功能需单独开通指定身份支付权限方可使用
2. 请确认你使用的商户号是否有jsapi支付的权限,可登录商户平台-产品中心查看
A:请检查下单接口中使用的商户号是否在商户平台配置了对应的支付目录,可参考“设置支付目录”章节说明
A:请按以下步骤进行排查:
1. 检查下单接口传的appid与获取openid接口的appid是否同一个(需一致)
2. 检查appid对应的公众号后台,是否配置的授权域名和获取openid的域名一致。授权域名配置路径:【公众平台-> 设置-> 公众号设置-> 功能设置–> 网页授权域名】
A:JSAPI支付只能从微信浏览器内发起支付请求