为了在保证支付安全的前提下,带给商户简单、一致且易用的开发体验,我们推出了全新的微信支付APIv3接口。该版本API的具体规则请参考“APIv3接口规则”
文档展示了如何使用微信支付服务端 SDK 快速接入扫码支付产品,完成与微信支付对接的部分。
• 文档中的代码示例是用来阐述 API 基本使用方法,代码中的示例参数需替换成商户自己账号及请求参数才能跑通。
• 以下接入步骤仅提供参考,请商户结合自身业务需求进行评估、修改。
步骤说明:通过本接口提交微信支付H5支付订单,获取微信支付跳转链接mweb_url。
代码示例
@Test
//Call Unified Order API
public void unifiedOrderTest() throws IOException {
String unifiedOrderBody = String.join("\n" ,
"{" ,
"'sp_appid': 'wx2421b1c4370ec43b'," ,
"'sp_mchid': '10000100'," ,
"'sub_mchid': '20000100'," ,
"'out_trade_no': '20150806125346'," ,
"'merchant_category_code': '1011'," ,
"'notify_url': 'https://wxpay.wxutil.com/pub_v2/pay/notify.v2.php'," ,
"'trade_type': 'MWEB'," ,
"'amount': {" ,
"'total': 10000," ,
"'currency': 'HKD'" ,
"}," ,
"'attach': 'Payment test'," ,
"'description': 'In-APP Pay test'," ,
"'goods_tag': '," ,
"'detail': {" ,
"'cost_price': 10000," ,
"'receipt_id': '1234'," ,
"'goods_detail': [{" ,
"'goods_id': 'iphone6s_16G'," ,
"'wxpay_goods_id': '1001'," ,
"'goods_name': 'iPhone6s 16G'," ,
"'quantity': 1," ,
"'price': 528800" ,
"}]" ,
"}," ,
"'scene_info': {" ,
"'payer_client_ip': '14.23.150.211'," ,
"'device_ip': '59.37.125.32'," ,
"'device_id': '013467007045764'," ,
"'operator_id': 'P001'," ,
"'store_info': {" ,
"'id': 'SZTX001'" ,
"}" ,
"}" ,
"}").replace("'","\"");
HttpPost httpPost = new HttpPost("https://apihk.mch.weixin.qq.com/v3/global/transactions/mweb");
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
httpPost.setEntity(new StringEntity(unifiedOrderBody));
CloseableHttpResponse response = httpClient.execute(httpPost);
//Process the response
}
重要参数:
out_trade_no: 商户系统内部订单号
notify_url: 接受异步支付结果回调通知的地址,通知url必须为外网可访问的url,不能携带参数。请使用https协议链接
其他重要参数请前往H5支付下单API文档页面参考。
步骤说明:商户在用户下单一定时间后,需主动进行查单以确认订单状态。
代码示例
@Test
//Call query order API
public void queryOrderTest() throws IOException {
HttpGet httpGet = new HttpGet("https://apihk.mch.weixin.qq.com/v3/global/transactions/id/" +
"4200123456789000?sub_mchid=100012321&sp_mchid=1900000000");
httpGet.addHeader("Accept", "application/json");
httpGet.addHeader("Content-type", "application/json; charset=utf-8");
CloseableHttpResponse response = httpClient.execute(httpGet);
//Process the response
}
重要参数:
id: 微信支付订单号
out_trade_no:商户内部订单号
可以用上述任一订单号进行查询。其他重要参数请前往查询订单API文档页面参考。
步骤说明:支付完成支付状态为SUCCESS后,可以调用该接口提交退款请求。
代码示例
@Test
//Call Refund API
public void refundTest() throws IOException {
String refundBody = String.join("\n" ,
"{" ,
"'sp_appid': 'wx2421b1c4370ec43b', " ,
"'sp_mchid': '10000100'," ,
"'sub_mchid': '20000100'," ,
"'transaction_id': '1008450740201411110005820873'," ,
"'out_refund_no': 'R20150806125346'," ,
" 'amount' : {" ,
" 'refund': 5," ,
" 'total':10," ,
" 'currency':'HKD'" ,
" }," ,
" 'reason': 'The item has been sold out.'," ,
" 'source': 'REFUND_SOURCE_UNSETTLED_FUNDS'" ,
"}").replace("'","\"");
HttpPost httpPost = new HttpPost("https://apihk.mch.weixin.qq.com/v3/global/refunds");
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
httpPost.setEntity(new StringEntity(refundBody));
CloseableHttpResponse response = httpClient.execute(httpPost);
//Process the response
}
重要参数:
out_trade_no: 商户内部订单号
transaction_id: 微信订单号
out_refund_no: 商户内部退款单号,应与订单号一一对应
notify_url: 退款结果接收地址
其他重要参数请前往申请退款API文档页面参考。
步骤说明:提交退款申请后,通过调用该接口查询退款状态。
代码示例
@Test
//Call Query Refund API
public void querySingleRefundTest() throws IOException {
HttpGet httpGet = new HttpGet("https://apihk.mch.weixin.qq.com/v3/global/refunds/out-refund-no/RYX001?sub_mchid=100012321&sp_mchid=1900000000");
httpGet.addHeader("Accept", "application/json");
httpGet.addHeader("Content-type", "application/json; charset=utf-8");
CloseableHttpResponse response = httpClient.execute(httpGet);
//Process the response
}
重要参数:
out_trade_no: 商户内部订单号
refund_id:微信支付退款单号
其他重要参数请前往查询单笔退款API文档页面参考。
步骤说明:提交退款申请后,通过调用该接口查询一笔订单下对应的全部退款信息。
代码示例
@Test
//Call Query All Refund API
public void queryAllRefundTest() throws IOException {
HttpGet httpGet = new HttpGet("https://apihk.mch.weixin.qq.com/v3/global/refunds" +
"?out_trade_no=YX202111100020&count=10&offset=0&sp_mchid=1900000000&sub_mchid=100012321");
httpGet.addHeader("Accept", "application/json");
httpGet.addHeader("Content-type", "application/json; charset=utf-8");
CloseableHttpResponse response = httpClient.execute(httpGet);
//Process the response
}
重要参数:
out_trade_no: 商户内部订单号
transaction_id: 微信订单号
offset: 分页起始位置
count: 单页返回的记录数,最大20条记录.
其他重要参数请前往查询所有退款API文档页面参考。
步骤说明:商户每天可以通过调用该接口可以下载历史交易账单,通过对帐单核对后可以校对更正支付信息。
代码示例
@Test
//Downloading Reconciliation API
public void downloadReconTest() throws IOException {
HttpGet httpGet = new HttpGet("https://apihk.mch.weixin.qq.com/v3/global/statements?date=20220401&mchid=1900000000");
httpGet.addHeader("Accept", "application/json");
httpGet.addHeader("Content-type", "application/json; charset=utf-8");
CloseableHttpResponse response = httpClient.execute(httpGet);
//Process the response
}
重要参数:
date: 账单日期, 格式:20180103
其他重要参数请前往下载对账单API文档页面参考。
步骤说明:下单超过5分钟后,可以调用该接口对下单未支付的订单进行关单操作,以便商户更换订单号重新下单发起支付或商户系统退出不再受理后避免用户继续支付。
代码示例
@Test
//Call close order API
public void closeOrderTest() throws IOException {
String closeBody = String.join("\n" ,
"{" ,
" 'sp_mchid': '10000100'," ,
" 'sub_mchid': '20000100'" ,
"}").replace("'","\"");
HttpPost httpPost = new HttpPost("https://apihk.mch.weixin.qq.com/v3/global/transactions/out-trade-no/YX001/close");
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
httpPost.setEntity(new StringEntity(closeBody));
CloseableHttpResponse response = httpClient.execute(httpPost);
System.out.println(response.getStatusLine().getStatusCode());
}
重要参数:
out_trade_no: 商户内部订单号
transaction_id: 微信支付订单号
可以用上述任一订单号进行查询。其他重要参数请前往关闭订单API文档页面参考。
步骤说明:在调用其他接口之前,需要调用该接口下载平台证书用于对返回消息进行验签和返回消息中的加密字段进行解密。
代码示例
@Test
//Call certificate downloading API
public void certDownloadingTest() throws IOException {
HttpGet httpGet = new HttpGet("https://apihk.mch.weixin.qq.com/v3/global/certificates");
httpGet.addHeader("Accept", "application/json");
httpGet.addHeader("Content-type", "application/json; charset=utf-8");
CloseableHttpResponse response = httpClient.execute(httpGet);
//Process the response
//Get the response body: EntityUtils.toString(response.getEntity());
//Get the response status code: response.getStatusLine().getStatusCode();
//Instead of calling the API to download platform certificate,
//We also recommend use the certificateMenager to get the valid certificate
verifier.getValidCertificate();
}
重要参数:无
其他重要参数请前往下载平台证书API文档页面参考。
步骤说明:用户完成支付后,微信支付会向下单时传入的notify_url推送支付结果通知,商户需要在接收到通知后返回对应的信息。
• 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
• 如果在所有通知频率(4小时)后没有收到微信侧回调,商户应调用查询订单接口确认订单状态。
特别提醒:机构系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄露导致出现“假通知”,造成资金损失。
用户支付完成后,微信会把相关支付结果和用户信息发送给商户,商户需要接收处理后返回应答成功。只有支付成功才会通知。
对后台通知交互时,如果微信收到应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。 (通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m)
支付结果通知是以“POST”方法访问商户设置的通知url,通知的数据以“JSON”格式通过请求主体(BODY)传输。通知的数据包括了加密的支付结果详情。
下面详细描述证书解密的流程
1、从商户平台上获取商户的密钥,记为“key”。
2、针对“algorithm”中描述的算法(目前为AEAD_AES_256_GCM),取得对应的参数“nonce”和“associated_data”。
3、使用“key”、“nonce”和“associated_data”对数据密文“ciphertext”进行解密(需要先对ciphertext做base64解码,然后再解密),得到证书内容
加密不能保证通知请求来自微信。微信会对发送给商户的通知进行签名,并将签名值放在通知的HTTP头Wechatpay-Signature。商户应当验证签名,以确认请求来自微信,而不是其他的第三方。签名验证的算法请参考《微信支付API V3签名验证》。
支付成功结果通知
{
"id":"EV-2018022511223320873",
"create_time":"20180225112233",
"resource_type":"encrypt-resource",
"event_type":"TRANSACTION.SUCCESS",
"resource" : {
"algorithm":"AEAD_AES_256_GCM",
"ciphertext": "...",
"nonce": "...",
"associated_data": ""
}
}
商户对resource对象进行解密后,得到的资源对象示例
{
"id": "1008450740201411110005820873",
"sp_appid": "wx2421b1c4370ec43b",
"sp_mchid": "10000100",
"sub_mchid": "20000100",
"out_trade_no": "20150806125346",
"payer": {
"sp_openid": "oUpF8uN95-Ptaags6E_roPHg7AG0"
},
"amount" : {
"total": 528800,
"currency": "HKD",
"payer_total": 518799,
"payer_currency": "CNY",
"exchange_rate" : {
"type": "SETTLEMENT_RATE",
"rate": 8000000
}
},
"trade_type": "MICROPAY",
"trade_state": "SUCCESS",
"trade_state_desc": "支付成功",
"bank_type": "CCB_DEBIT",
"attach": "支付测试",
"success_time": "2018-06-08T10:34:56+08:00",
"promotion_detail":[
{
"promotion_id":"109519",
"name":"单品惠-6",
"scope":"SINGLE",
"type":"DISCOUNT",
"amount":1,
"currency":"HKD",
"activity_id":"931386",
"wechatpay_contribute_amount":1,
"merchant_contribute_amount":0,
"other_contribute_amount":0,
"goods_detail":[
{
"goods_id":"iphone6s_16G",
"goods_remark":"商品备注",
"quantity":1,
"price":528800
}
]
}
]
}
支付通知http应答码为200或204才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx。
其他重要参数请前往支付结果通知API文档页面参考。
步骤说明:退款状态改变后,微信会把相关退款结果发送给商户。
• 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
• 如果在所有通知频率(4小时)后没有收到微信侧回调,商户应调用查询订单接口确认订单状态。
特别提醒:机构系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄露导致出现“假通知”,造成资金损失。
退款状态改变后,微信会把相关退款结果发送给商户。
对后台通知交互时,如果微信收到应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。 (通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m)
退款结果通知是以“POST”方法访问商户设置的通知url,通知的数据以“JSON”格式通过请求主体(BODY)传输。通知的数据包括了加密的退款结果详情。
下面详细描述证书解密的流程
1、从商户平台上获取商户的密钥,记为“key”。
2、针对“algorithm”中描述的算法(目前为AEAD_AES_256_GCM),取得对应的参数“nonce”和“associated_data”。
3、使用“key”、“nonce”和“associated_data”对数据密文“ciphertext”进行解密(需要先对ciphertext做base64解码,然后再解密),得到证书内容
加密不能保证通知请求来自微信。微信会对发送给商户的通知进行签名,并将签名值放在通知的HTTP头Wechatpay-Signature。商户应当验证签名,以确认请求来自微信,而不是其他的第三方。签名验证的算法请参考《微信支付API V3签名验证》。
退款通知
{
"id": "EV-2018022511223320873",
"create_time": "2018-06-08T10:34:56+08:00",
"resource_type": "encrypt-resource",
"event_type": "REFUND.SUCCESS",
"summary": "退款成功",
"resource": {
"original_type": "refund",
"algorithm": "AEAD_AES_256_GCM",
"ciphertext": "...",
"associated_data": "",
"nonce": "..."
}
}
商户对resource对象进行解密后,得到的资源对象示例
{
"sp_mchid": "1900000100",
"sub_mchid": "1900000109",
"transaction_id": "1008450740201411110005820873",
"out_trade_no": "20150806125346",
"refund_id": "50200207182018070300011301001",
"out_refund_no": "7752501201407033233368018",
"refund_status": "SUCCESS",
"success_time": "2018-06-08T10:34:56+08:00",
"recv_account": "招商银行信用卡0403",
"fund_source": "REFUND_SOURCE_UNSETTLED_FUNDS",
"amount" : {
"total": 528800,
"currency": "HKD",
"refund": 528800,
"payer_total": 528800,
"payer_refund": 528800,
"payer_currency": "HKD",
"exchange_rate" : {
"type": "SETTLEMENT_RATE",
"rate": 100000000
}
}
}
退款通知http应答码为200或204才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx。
其他重要参数请前往退款通知API文档页面参考。
步骤说明:商户在交易完结之后,可按结算日期查询已结算资金明细(sette_state为SETTLED),也可以查询未结算资金明细(sette_state为UNSETTLE)。
代码示例
@Test
//Query Settlement API
public void querySettlementTest() throws IOException {
HttpGet httpGet = new HttpGet("https://apihk.mch.weixin.qq.com/v3/global/settle/settlements" +
"?sub_mchid=100012321&settle_state=SETTLED&settle_start_date=20221225" +
"&settle_end_date=20221226&offset=10&limit=5");
httpGet.addHeader("Accept", "application/json");
httpGet.addHeader("Content-type", "application/json; charset=utf-8");
CloseableHttpResponse response = httpClient.execute(httpGet);
//Process the response
}
重要参数:
settle_state: 资金结算状态,枚举值:
SETTLED:已结算
UNSETTLE:未结算
其他重要参数请前往查询资金结算明细API文档页面参考。
H5支付要求商户在统一下单接口中上传用户真实ip地址“spbill_create_ip”,为保证微信端获取的用户ip地址与商户端获取的一致,提供了以下获取用户ip的指引,希望对大家有所帮助。
在商户的前端接入层没有做代理的情况下获取ip的方式比较简单,直接获取'REMOTE_ADDR '即可。
在有代理的情况下,因为要代替客户端去访问服务器,所以,当请求包经过反向代理后,在代理服务器这里这个IP数据包的IP包头做了修改,最终后端WEB服务器得到的数据包的头部源IP地址是代理服务器的IP地址。这样一来,后端服务器的程序就无法获取用户的真实ip。
nginx有代理的情况:
在nginx中配置中加入
vi /usr/local/apache/conf/httpd.conf
Include conf/extra/httpd-remoteip.conf
vi /usr/local/apache/conf/extra/httpd-remoteip.conf
LoadModule remoteip_module modules/mod_remoteip.so
RemoteIPHeader X-Forwarded-For
RemoteIPinternalProxy 127.0.0.1
Apache有代理的情况:
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Real-Port $remote_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
代码示例
string GetClientIp(CgiInput * poInput) {
string client_ip = "";
string strClientIPList;
GetHttpHeader("X-Forwarded-For", strClientIPList);
if (strClientIPList.empty()) {
GetHttpHeader("X-Real-IP", strClientIPList);
}
if (!strClientIPList.empty()) {
size_t iPos = strClientIPList.find(",");
if (iPos != std::string::npos) {
client_ip = strClientIPList.substr(iPos);
} else {
client_ip = strClientIPList;
}
}
if (client_ip.empty()) {
GetHttpHeader("PROXY_FORWARDED_FOR", strClientIPList);
// 做下兼容
if (strClientIPList.empty()) {
client_ip = getRemoteAddr();
} else {
size_t iPos = strClientIPList.find(",");
if (iPos != std::string::npos) {
client_ip = strClientIPList.substr(iPos);
} else {
client_ip = strClientIPList;
}
}
}
if (!MMPayCommFunc::IsIp(client_ip))
client_ip = getRemoteAddr();
return client_ip;
}
正常流程用户支付完成后会返回至发起支付的页面,如需返回至指定页面,则可以在MWEB_URL后拼接上redirect_url参数,来指定回调页面。
如您希望用户支付完成后跳转至https://www.wechatpay.com.cn,则可以做如下处理:
假设您通过统一下单接口获到的MWEB_URL= https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx20161110163838f231619da20804912345&package=1037687096
则拼接后的地址为MWEB_URL= https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx20161110163838f231619da20804912345&package=1037687096&redirect_url=https%3A%2F%2Fwww.wechatpay.com.cn
1、需对redirect_url进行urlencode处理;
2、由于设置redirect_url后,回跳指定页面的操作可能发生在:1,微信支付中间页调起微信收银台后超过5秒 2,用户点击“取消支付“或支付完成后点“完成”按钮。因此无法保证页面回跳时,支付流程已结束,所以商户设置的redirect_url地址不能自动执行查单操作,应让用户去点击按钮触发查单操作。回跳页面展示效果可参考下图。
问题 | 问题说明 | 解决方案 |
---|---|---|
网络环境未能通过安全验证,请稍后再试 | 1. 商户侧统一下单传的终端IP(spbill_create_ip)与用户实际调起支付时微信侧检测到的终端IP不一致导致的,这个问题一般是商户在统一下单时没有传递正确的终端IP到spbill_create_ip导致,详细可参见客户端ip获取指引 2. 统一下单与调起支付时的网络有变动,如统一下单时是WIFI网络,下单成功后切换成4G网络再调起支付,这样可能会引发我们的正常拦截,请保持网络环境一致的情况下重新发起支付流程 |
|
商家参数格式有误,请联系商家解决 | 1. 当前调起H5支付的referer为空导致,一般是因为直接访问页面调起H5支付,请按正常流程进行页面跳转后发起支付,或自行抓包确认referer值是否为空
2. 如果是APP里调起H5支付,需要在webview中手动设置referer,如(
Map extraHeaders = new HashMap(); |
|
商家存在未配置的参数,请联系商家解决 | 1,当前调起H5支付的域名(微信侧从referer中获取)与申请H5支付时提交的授权域名不一致,如需添加或修改授权域名,请登录商户号对应的商户平台,进入Development Configuration菜单,在Payment Authorization Config区域对Authorized Domain进行配置。 2,如果设置了回跳地址redirect_url,请确认设置的回跳地址的域名与申请H5支付时提交的授权域名是否一致 |
|
支付请求已失效,请重新发起支付 | 统一下单返回的MWEB_URL生成后,有效期为5分钟,如超时请重新生成MWEB_URL后再发起支付 | |
请在微信外打开订单,进行支付 | H5支付不能直接在微信客户端内调起,请在外部浏览器调起 | |
IOS:签名验证失败 安卓:系统繁忙,请稍后再试 |
1,请确认同一个MWEB_URL只被一个微信号调起,如果不同微信号调起请重新下单生成新的MWEB_URL
2,如MWEB_URL有添加redirect_url,请确认参数拼接格式是否有误,是否有对redirect_url的值做urlencode,可对比以下例子格式: https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx20161110163838f231619da20804912345&package=1037687096& |
Customer Service Tel
Business Development
9:00-18:00
Monday-Friday GMT+8
Technical Support
WeChat Pay Global
ICP证