摘要:注意交易時(shí)間超過(guò)一年的訂單無(wú)法提交退款微信支付退款支持單筆交易分多次退款,多次退款需要提交原支付訂單的商戶訂單號(hào)和設(shè)置不同的退款單號(hào)。
本文是【淺析微信支付】系列文章的第八篇,主要講解商戶如何處理微信申請(qǐng)退款、退款回調(diào)、查詢退款接口,其中有一些坑的地方,會(huì)著重強(qiáng)調(diào)。
淺析微信支付系列已經(jīng)更新七篇了喲~,沒(méi)有看過(guò)的朋友們可以看一下哦。
淺析微信支付:查詢訂單和關(guān)閉訂單
淺析微信支付:支付結(jié)果通知
淺析微信支付:統(tǒng)一下單接口
在實(shí)際場(chǎng)景中,申請(qǐng)退款和退款回調(diào)接口是比較常用到的微信支付接口,這里我們會(huì)講原路返回方式的退款,還有的是使用直接為用戶付款到零錢(qián)、現(xiàn)金紅包等方式來(lái)退款,此種情況主要會(huì)出現(xiàn)在客服退款時(shí),不是全部退款的情況,也有的會(huì)出現(xiàn)在使用了微信代金券-單品券的時(shí)候,因?yàn)閱纹啡荒懿糠滞丝?,所以只能走企業(yè)付款用戶的方式,以下我們主要講原路返回退款。
PS:原路返回的意思就是,從你支付時(shí)的關(guān)聯(lián)支付單中扣款,微信會(huì)記錄相關(guān)數(shù)據(jù),可以在客戶端通知中展示。
1、申請(qǐng)退款接口以下為微信官方的申請(qǐng)退款文檔:
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_41.1. 應(yīng)用場(chǎng)景
當(dāng)交易發(fā)生之后一段時(shí)間內(nèi),由于買(mǎi)家或者賣(mài)家的原因需要退款時(shí),賣(mài)家可以通過(guò)退款接口將支付款退還給買(mǎi)家,微信支付將在收到退款請(qǐng)求并且驗(yàn)證成功之后,按照退款規(guī)則將支付款按原路退到買(mǎi)家?guī)ぬ?hào)上。
注意: 1、交易時(shí)間超過(guò)一年的訂單無(wú)法提交退款 2、微信支付退款支持單筆交易分多次退款,多次退款需要提交原支付訂單的商戶訂單號(hào)和設(shè)置不同的退款單號(hào)。申請(qǐng)退款總金額不能超過(guò)訂單金額。 一筆退款失敗后重新提交,請(qǐng)不要更換退款單號(hào),請(qǐng)使用原商戶退款單號(hào) 3、請(qǐng)求頻率限制:150qps,即每秒鐘正常的申請(qǐng)退款請(qǐng)求次數(shù)不超過(guò)150次 錯(cuò)誤或無(wú)效請(qǐng)求頻率限制:6qps,即每秒鐘異常或錯(cuò)誤的退款申請(qǐng)請(qǐng)求不超過(guò)6次 4、每個(gè)支付訂單的部分退款次數(shù)不能超過(guò)50次
PS:以上限制一般情況下不會(huì)出現(xiàn),但我們也必須寫(xiě)入系統(tǒng)異常場(chǎng)景處理中,請(qǐng)求頻率可以使用隊(duì)列或增加延遲等方式來(lái)處理,部分退款此時(shí)不要超過(guò)微信的限制。
1.2. 接口鏈接https://api.mch.weixin.qq.com/secapi/pay/refund1.3. 是否需要證書(shū)
請(qǐng)求需要雙向證書(shū)。
PS:關(guān)于微信證書(shū),可以在 [商戶平臺(tái)-賬戶中心-API安全] 去下載,此證書(shū)很多支付接口均需要使用,請(qǐng)將證書(shū)地址配置為常量,具體實(shí)現(xiàn)可以參考作者github源碼。
1.4. 調(diào)用接口先看源碼,如下:
/** * [微信退款接口] - 保存調(diào)用的相關(guān)記錄 * @param refundPayment 退款訂單的支付記錄 * @param tradePayment 歷史付款單 * @return map * @throws Exception e * * @author yclimb * @date 2018/6/21 */ public MapsaveWxPayRefund(Payment refundPayment, Payment tradePayment) throws Exception { if (refundPayment == null || tradePayment == null) { return null; } // 微信訂單號(hào)/商戶訂單號(hào),必須傳入其中一個(gè),此處默認(rèn)傳入商戶訂單號(hào) // 微信訂單號(hào),微信生成的訂單號(hào),在支付通知中有返回 // String transaction_id = null; // 商戶訂單號(hào),商戶系統(tǒng)內(nèi)部訂單號(hào),要求32個(gè)字符內(nèi),只能是數(shù)字、大小寫(xiě)字母_-|*@ ,且在同一個(gè)商戶號(hào)下唯一。 String out_trade_no = tradePayment.getFlowNumer(); // 商戶退款單號(hào),商戶系統(tǒng)內(nèi)部的退款單號(hào),商戶系統(tǒng)內(nèi)部唯一,只能是數(shù)字、大小寫(xiě)字母_-|*@ ,同一退款單號(hào)多次請(qǐng)求只退一筆。 String out_refund_no = refundPayment.getFlowNumer(); // 訂單總金額,傳入?yún)?shù)單位為:元 String total_fee = String.valueOf(tradePayment.getAmount()); // 退款總金額,訂單總金額,傳入?yún)?shù)單位為:元 String refund_fee = String.valueOf(refundPayment.getAmount()); // 退款原因,若商戶傳入,會(huì)在下發(fā)給用戶的退款消息中體現(xiàn)退款原因 String refund_desc = refundPayment.getBody(); // 微信支付對(duì)象 WXPay wxPay = new WXPay(WXPayConfigImpl.getInstance()); // 微信退款接口 Map resultMap = wxPay.refund(refundUrl, null, out_trade_no, out_refund_no, total_fee, refund_fee, refund_desc); logger.info("saveWxPayRefund:resultMap:" + resultMap.toString()); // 記錄付款流水 // 下單失敗,進(jìn)行處理 if (WXPayConstants.FAIL.equals(resultMap.get(WXPayConstants.RETURN_CODE)) || WXPayConstants.FAIL.equals(resultMap.get(WXPayConstants.RESULT_CODE))) { // 處理結(jié)果返回,無(wú)需繼續(xù)執(zhí)行 resultMap.put(WXPayConstants.RESULT_CODE, WXPayConstants.FAIL); resultMap.put(WXPayConstants.ERR_CODE_DES, resultMap.get(WXPayConstants.RETURN_MSG)); return resultMap; } return resultMap; }
以上為sdk退款調(diào)用示例代碼,有幾個(gè)參數(shù)需要我們注意:
字段名 | 變量名 | 必填 | 類(lèi)型 | 描述 | |
---|---|---|---|---|---|
微信訂單號(hào) | transaction_id | 是 | String(32) | 微信生成的訂單號(hào),在支付通知中有返回 | |
商戶訂單號(hào) | out_trade_no | 是 | String(32) | 商戶系統(tǒng)內(nèi)部訂單號(hào),要求32個(gè)字符內(nèi),只能是數(shù)字、大小寫(xiě)字母_- | *@ ,且在同一個(gè)商戶號(hào)下唯一。 |
商戶退款單號(hào) | out_refund_no | 是 | String(64) | 商戶系統(tǒng)內(nèi)部的退款單號(hào),商戶系統(tǒng)內(nèi)部唯一,只能是數(shù)字、大小寫(xiě)字母_- | *@ ,同一退款單號(hào)多次請(qǐng)求只退一筆。 |
退款金額 | refund_fee | 是 | Int | 退款總金額,訂單總金額,單位為分,只能為整數(shù) | |
退款結(jié)果通知url | notify_url | 否 | String(256) | 異步接收微信支付退款結(jié)果通知的回調(diào)地址,通知URL必須為外網(wǎng)可訪問(wèn)的url,不允許帶參數(shù),如果參數(shù)中傳了notify_url,則商戶平臺(tái)上配置的回調(diào)地址將不會(huì)生效。 |
PS:推薦以上的參數(shù)都必填,notify_url參數(shù)可配置為環(huán)境常量,根據(jù)環(huán)境的不同配置調(diào)用不會(huì)的回調(diào)地址。
下面為具體的實(shí)際sdkwxPay.refund調(diào)用代碼:
/** * 作用:申請(qǐng)退款
* 場(chǎng)景:當(dāng)交易發(fā)生之后一段時(shí)間內(nèi),由于買(mǎi)家或者賣(mài)家的原因需要退款時(shí),賣(mài)家可以通過(guò)退款接口將支付款退還給買(mǎi)家, * 微信支付將在收到退款請(qǐng)求并且驗(yàn)證成功之后,按照退款規(guī)則將支付款按原路退到買(mǎi)家?guī)ぬ?hào)上。 * 接口文檔地址:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_4 * * @param notify_url 回調(diào)地址 * @param transaction_id 微信生成的訂單號(hào),在支付通知中有返回 * @param out_trade_no 商戶系統(tǒng)內(nèi)部訂單號(hào),要求32個(gè)字符內(nèi),只能是數(shù)字、大小寫(xiě)字母_-|*@ ,且在同一個(gè)商戶號(hào)下唯一。 * @param out_refund_no 商戶系統(tǒng)內(nèi)部的退款單號(hào),商戶系統(tǒng)內(nèi)部唯一,只能是數(shù)字、大小寫(xiě)字母_-|*@ ,同一退款單號(hào)多次請(qǐng)求只退一筆。 * @param total_fee 訂單總金額,傳入?yún)?shù)單位為:元 * @param refund_fee 退款總金額,訂單總金額,傳入?yún)?shù)單位為:元 * @param refund_desc 退款原因,若商戶傳入,會(huì)在下發(fā)給用戶的退款消息中體現(xiàn)退款原因 * @return API返回?cái)?shù)據(jù) * @throws Exception e */ public Maprefund(String notify_url, String transaction_id, String out_trade_no, String out_refund_no, String total_fee, String refund_fee, String refund_desc) throws Exception { /** 構(gòu)造請(qǐng)求參數(shù)數(shù)據(jù) **/ Map data = new HashMap<>(); // 變量名 字段名 必填 類(lèi)型 示例值 描述 // 微信訂單號(hào) 二選一 String(32) 1.21775E+27 微信生成的訂單號(hào),在支付通知中有返回 if (transaction_id != null) { data.put("transaction_id", transaction_id); } // 商戶訂單號(hào) String(32) 1.21775E+27 商戶系統(tǒng)內(nèi)部訂單號(hào),要求32個(gè)字符內(nèi),只能是數(shù)字、大小寫(xiě)字母_-|*@ ,且在同一個(gè)商戶號(hào)下唯一。 data.put("out_trade_no", out_trade_no); // 商戶退款單號(hào) 是 String(64) 1.21775E+27 商戶系統(tǒng)內(nèi)部的退款單號(hào),商戶系統(tǒng)內(nèi)部唯一,只能是數(shù)字、大小寫(xiě)字母_-|*@ ,同一退款單號(hào)多次請(qǐng)求只退一筆。 data.put("out_refund_no", out_refund_no); // 訂單金額 是 Int 100 訂單總金額,單位為分,只能為整數(shù),詳見(jiàn)支付金額 data.put("total_fee", String.valueOf(new BigDecimal(total_fee).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue())); // 退款金額 是 Int 100 退款總金額,訂單總金額,單位為分,只能為整數(shù),詳見(jiàn)支付金額 // 默認(rèn)單位為分,系統(tǒng)是元,所以需要*100 data.put("refund_fee", String.valueOf(new BigDecimal(refund_fee).multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue())); // 退款原因 否 String(80) 商品已售完 若商戶傳入,會(huì)在下發(fā)給用戶的退款消息中體現(xiàn)退款原因 data.put("refund_desc", refund_desc); // 貨幣種類(lèi) 否 String(8) CNY 貨幣類(lèi)型,符合ISO?4217標(biāo)準(zhǔn)的三位字母代碼,默認(rèn)人民幣:CNY,其他值列表詳見(jiàn)貨幣類(lèi)型 data.put("refund_fee_type", WXPayConstants.FEE_TYPE_CNY); // 退款結(jié)果通知url 否 String(256) https://weixin.qq.com/notify/ 異步接收微信支付退款結(jié)果通知的回調(diào)地址,通知URL必須為外網(wǎng)可訪問(wèn)的url,不允許帶參數(shù),如果參數(shù)中傳了notify_url,則商戶平臺(tái)上配置的回調(diào)地址將不會(huì)生效。 data.put("notify_url", notify_url); /** 以下參數(shù)為非必填參數(shù) **/ // 退款資金來(lái)源 否 String(30) REFUND_SOURCE_RECHARGE_FUNDS 僅針對(duì)老資金流商戶使用;REFUND_SOURCE_UNSETTLED_FUNDS---未結(jié)算資金退款(默認(rèn)使用未結(jié)算資金退款);REFUND_SOURCE_RECHARGE_FUNDS---可用余額退款 // data.put("refund_account", null); /** 以下五個(gè)參數(shù),在 this.fillRequestData 方法中會(huì)自動(dòng)賦值 **/ /*// 小程序ID appid 是 String(32) wxd678efh567hg6787 微信分配的小程序ID data.put("appid", WXPayConstants.APP_ID); // 商戶號(hào) mch_id 是 String(32) 1230000109 微信支付分配的商戶號(hào) data.put("mch_id", WXPayConstants.MCH_ID); // 隨機(jī)字符串 nonce_str 是 String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 隨機(jī)字符串,長(zhǎng)度要求在32位以內(nèi)。推薦隨機(jī)數(shù)生成算法 data.put("nonce_str", nonce_str); // 簽名類(lèi)型 sign_type 否 String(32) MD5 簽名類(lèi)型,默認(rèn)為MD5,支持HMAC-SHA256和MD5。 data.put("sign_type", WXPayConstants.MD5); // 簽名 sign 是 String(32) C380BEC2BFD727A4B6845133519F3AD6 通過(guò)簽名算法計(jì)算得出的簽名值,詳見(jiàn)簽名生成算法 data.put("sign", sign);*/ // 微信退款接口 Map resultMap = this.refund(data); WXPayUtil.getLogger().info("wxPay.refund:" + resultMap); return resultMap; }
以上已經(jīng)詳細(xì)說(shuō)明的具體的字段含義,有不明白的同學(xué)可以查看微信的官方文檔,具體的源碼可以查看作者的github。
這里有一個(gè)比較需要注意的點(diǎn),在我們調(diào)用退款之后,會(huì)返回一些異常處理情況,官方文檔中收錄了一系列錯(cuò)誤碼code,我們可以在系統(tǒng)中對(duì)其進(jìn)行處理,這里就不細(xì)說(shuō)了。
2、退款回調(diào)接口以下為微信官方的退款結(jié)果通知文檔:
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_16&index=102.1. 應(yīng)用場(chǎng)景
當(dāng)商戶申請(qǐng)的退款有結(jié)果后,微信會(huì)把相關(guān)結(jié)果發(fā)送給商戶,商戶需要接收處理,并返回應(yīng)答。
對(duì)后臺(tái)通知交互時(shí),如果微信收到商戶的應(yīng)答不是成功或超時(shí),微信認(rèn)為通知失敗,微信會(huì)通過(guò)一定的策略定期重新發(fā)起通知,盡可能提高通知的成功率,但微信不保證通知最終能成功。
(通知頻率為15/15/30/180/1800/1800/1800/1800/3600,單位:秒)
注意:同樣的通知可能會(huì)多次發(fā)送給商戶系統(tǒng)。商戶系統(tǒng)必須能夠正確處理重復(fù)的通知。
推薦的做法是,當(dāng)收到通知進(jìn)行處理時(shí),首先檢查對(duì)應(yīng)業(yè)務(wù)數(shù)據(jù)的狀態(tài),判斷該通知是否已經(jīng)處理過(guò),如果沒(méi)有處理過(guò)再進(jìn)行處理,如果處理過(guò)直接返回結(jié)果成功。在對(duì)業(yè)務(wù)數(shù)據(jù)進(jìn)行狀態(tài)檢查和處理之前,要采用數(shù)據(jù)鎖進(jìn)行并發(fā)控制,以避免函數(shù)重入造成的數(shù)據(jù)混亂。
特別說(shuō)明:退款結(jié)果對(duì)重要的數(shù)據(jù)進(jìn)行了加密,商戶需要用商戶秘鑰進(jìn)行解密后才能獲得結(jié)果通知的內(nèi)容
2.2. 接口鏈接在申請(qǐng)退款接口中上傳參數(shù)“notify_url”以開(kāi)通該功能
如果鏈接無(wú)法訪問(wèn),商戶將無(wú)法接收到微信通知。
通知url必須為直接可訪問(wèn)的url,不能攜帶參數(shù)。
示例:notify_url:“https://pay.weixin.qq.com/wxpay/pay.action”2.3. 解密方式
解密步驟如下: (1)對(duì)加密串A做base64解碼,得到加密串B (2)對(duì)商戶key做md5,得到32位小寫(xiě)key* ( key設(shè)置路徑:微信商戶平臺(tái)-->賬戶設(shè)置-->API安全-->密鑰設(shè)置 ) (3)用key*對(duì)加密串B做AES-256-ECB解密(PKCS7Padding)
PS:特別注意,如果要進(jìn)行微信AES解密,因?yàn)镚J的進(jìn)口管制限制,Java發(fā)布的運(yùn)行環(huán)境包中的加解密有一定的限制。默認(rèn)不允許256位密鑰的AES加解密,解決方法就是修改策略文件,我們需要從官方網(wǎng)站下載無(wú)限制權(quán)限策略文件,注意自己JDK的版本別下錯(cuò)了。
jdk8的jce下載地址:https://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
將local_policy.jar和US_export_policy.jar這兩個(gè)文件替換%JRE_HOME%libsecurity和%JDK_HOME%jrelibsecurity下原來(lái)的文件,注意先備份原文件。
如果是jdk8,可能會(huì)遇到安全目錄下有policy文件夾的情況,拿作者的電腦舉例,jdk路徑為/opt/jdk1.8.0_152/jre/lib/security/policy,此目錄下有兩個(gè)子文件夾limited、limited,需要替換limited文件夾下的文件 local_policy.jar、US_export_policy.jar這兩個(gè),最好先備份哦~?。?!替換后重啟項(xiàng)目即可。
2.4. 調(diào)用接口因?yàn)橥丝罨卣{(diào)接口是咋們系統(tǒng)被動(dòng)接收微信的消息,所以此處和支付回調(diào)接口一致,也是使用了流的方式,格式為xml,下面我們來(lái)看代碼:
/** * 退款結(jié)果通知 ** 在申請(qǐng)退款接口中上傳參數(shù)“notify_url”以開(kāi)通該功能 * 如果鏈接無(wú)法訪問(wèn),商戶將無(wú)法接收到微信通知。 * 通知url必須為直接可訪問(wèn)的url,不能攜帶參數(shù)。示例:notify_url:“https://pay.weixin.qq.com/wxpay/pay.action” *
* 當(dāng)商戶申請(qǐng)的退款有結(jié)果后,微信會(huì)把相關(guān)結(jié)果發(fā)送給商戶,商戶需要接收處理,并返回應(yīng)答。 * 對(duì)后臺(tái)通知交互時(shí),如果微信收到商戶的應(yīng)答不是成功或超時(shí),微信認(rèn)為通知失敗,微信會(huì)通過(guò)一定的策略定期重新發(fā)起通知,盡可能提高通知的成功率,但微信不保證通知最終能成功。 * (通知頻率為15/15/30/180/1800/1800/1800/1800/3600,單位:秒) * 注意:同樣的通知可能會(huì)多次發(fā)送給商戶系統(tǒng)。商戶系統(tǒng)必須能夠正確處理重復(fù)的通知。 * 推薦的做法是,當(dāng)收到通知進(jìn)行處理時(shí),首先檢查對(duì)應(yīng)業(yè)務(wù)數(shù)據(jù)的狀態(tài),判斷該通知是否已經(jīng)處理過(guò),如果沒(méi)有處理過(guò)再進(jìn)行處理,如果處理過(guò)直接返回結(jié)果成功。在對(duì)業(yè)務(wù)數(shù)據(jù)進(jìn)行狀態(tài)檢查和處理之前,要采用數(shù)據(jù)鎖進(jìn)行并發(fā)控制,以避免函數(shù)重入造成的數(shù)據(jù)混亂。 * 特別說(shuō)明:退款結(jié)果對(duì)重要的數(shù)據(jù)進(jìn)行了加密,商戶需要用商戶秘鑰進(jìn)行解密后才能獲得結(jié)果通知的內(nèi)容 * @param request req * @param response resp * @return res xml * * @author yclimb * @date 2018/6/21 */ @ApiOperation(value = "微信支付|微信退款回調(diào)接口", httpMethod = "POST", notes = "該鏈接是通過(guò)【微信退款A(yù)PI】中提交的參數(shù)notify_url設(shè)置,如果參數(shù)中傳了notify_url,則商戶平臺(tái)上配置的回調(diào)地址將不會(huì)生效。") @RequestMapping("/refund") public void refund(HttpServletRequest request, HttpServletResponse response) { String resXml = ""; InputStream inStream; try { inStream = request.getInputStream(); ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while ((len = inStream.read(buffer)) != -1) { outSteam.write(buffer, 0, len); } WXPayUtil.getLogger().info("refund:微信退款----start----"); // 獲取微信調(diào)用我們notify_url的返回信息 String result = new String(outSteam.toByteArray(), "utf-8"); WXPayUtil.getLogger().info("refund:微信退款----result----=" + result); // 關(guān)閉流 outSteam.close(); inStream.close(); // xml轉(zhuǎn)換為map Map
map = WXPayUtil.xmlToMap(result); if (WXPayConstants.SUCCESS.equalsIgnoreCase(map.get(WXPayConstants.RETURN_CODE))) { WXPayUtil.getLogger().info("refund:微信退款----返回成功"); /** 以下字段在return_code為SUCCESS的時(shí)候有返回: **/ // 加密信息:加密信息請(qǐng)用商戶秘鑰進(jìn)行解密,詳見(jiàn)解密方式 String req_info = map.get("req_info"); /** * 解密方式 * 解密步驟如下: * (1)對(duì)加密串A做base64解碼,得到加密串B * (2)對(duì)商戶key做md5,得到32位小寫(xiě)key* ( key設(shè)置路徑:微信商戶平臺(tái)(pay.weixin.qq.com)-->賬戶設(shè)置-->API安全-->密鑰設(shè)置 ) * (3)用key*對(duì)加密串B做AES-256-ECB解密(PKCS7Padding) */ String resultStr = AESUtil.decryptData(req_info); // WXPayUtil.getLogger().info("refund:解密后的字符串:" + resultStr); Map aesMap = WXPayUtil.xmlToMap(resultStr); /** 以下為返回的加密字段: **/ // 商戶退款單號(hào) 是 String(64) 1.21775E+27 商戶退款單號(hào) String out_refund_no = aesMap.get("out_refund_no"); // 退款狀態(tài) 是 String(16) SUCCESS SUCCESS-退款成功、CHANGE-退款異常、REFUNDCLOSE—退款關(guān)閉 String refund_status = aesMap.get("refund_status"); // 商戶訂單號(hào) 是 String(32) 1.21775E+27 商戶系統(tǒng)內(nèi)部的訂單號(hào) String out_trade_no = aesMap.get("out_trade_no"); /*// 微信訂單號(hào) 是 String(32) 1.21775E+27 微信訂單號(hào) String transaction_id = null; // 微信退款單號(hào) 是 String(32) 1.21775E+27 微信退款單號(hào) String refund_id = null; // 訂單金額 是 Int 100 訂單總金額,單位為分,只能為整數(shù),詳見(jiàn)支付金額 String total_fee = null; // 應(yīng)結(jié)訂單金額 否 Int 100 當(dāng)該訂單有使用非充值券時(shí),返回此字段。應(yīng)結(jié)訂單金額=訂單金額-非充值代金券金額,應(yīng)結(jié)訂單金額<=訂單金額。 String settlement_total_fee = null; // 申請(qǐng)退款金額 是 Int 100 退款總金額,單位為分 String refund_fee = null; // 退款金額 是 Int 100 退款金額=申請(qǐng)退款金額-非充值代金券退款金額,退款金額<=申請(qǐng)退款金額 String settlement_refund_fee = null;*/ // 退款是否成功 if (!WXPayConstants.SUCCESS.equals(refund_status)) { resXml = resFailXml; } else { // 通知微信.異步確認(rèn)成功.必寫(xiě).不然會(huì)一直通知后臺(tái).八次之后就認(rèn)為交易失敗了. resXml = resSuccessXml; isSuccess = true; } // 根據(jù)付款單號(hào)查詢付款記錄 out_refund_no // 付款記錄修改 & 記錄付款日志 if (payment != null) { WXPayUtil.getLogger().error("refund:微信支付回調(diào):修改支付單"); } else { WXPayUtil.getLogger().error("refund:微信支付回調(diào):找不到對(duì)應(yīng)的支付單"); } } else { WXPayUtil.getLogger().error("refund:支付失敗,錯(cuò)誤信息:" + map.get(WXPayConstants.RETURN_MSG)); resXml = resFailXml; } } catch (Exception e) { WXPayUtil.getLogger().error("refund:微信退款回調(diào)發(fā)布異常:", e); } finally { try { // 處理業(yè)務(wù)完畢 BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream()); out.write(resXml.getBytes()); out.flush(); out.close(); } catch (IOException e) { WXPayUtil.getLogger().error("refund:微信退款回調(diào)發(fā)布異常:out:", e); } } }
以上代碼詳細(xì)解釋了如何接收微信回調(diào)數(shù)據(jù)和解碼數(shù)據(jù),具體的AESUtil.decryptData(req_info)請(qǐng)參考作者源碼,文末有地址,這里就不細(xì)講了。
具體的退款接收參數(shù)請(qǐng)參考微信官方文檔,需要注意的是商戶退款單號(hào)和微信退款單號(hào),此兩個(gè)參數(shù)是修改和記錄退款的必要憑證。
3、查詢退款以下為微信官方的查詢退款文檔:
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_53.1. 應(yīng)用場(chǎng)景
提交退款申請(qǐng)后,通過(guò)調(diào)用該接口查詢退款狀態(tài)。退款有一定延時(shí),用零錢(qián)支付的退款20分鐘內(nèi)到賬,銀行卡支付的退款3個(gè)工作日后重新查詢退款狀態(tài)。
注意:如果單個(gè)支付訂單部分退款次數(shù)超過(guò)20次請(qǐng)使用退款單號(hào)查詢
3.2. 接口鏈接https://api.mch.weixin.qq.com/pay/refundquery3.3. 是否需要證書(shū)
不需要
3.4. 調(diào)用接口注意:當(dāng)一個(gè)訂單部分退款超過(guò)10筆后,商戶用微信訂單號(hào)或商戶訂單號(hào)調(diào)退款查詢API查詢退款時(shí),默認(rèn)返回前10筆和total_refund_count(訂單總退款次數(shù))。商戶需要查詢同一訂單下超過(guò)10筆的退款單時(shí),可傳入訂單號(hào)及offset來(lái)查詢,微信支付會(huì)返回offset及后面的10筆,以此類(lèi)推。當(dāng)商戶傳入的offset超過(guò)total_refund_count,則系統(tǒng)會(huì)返回報(bào)錯(cuò)PARAM_ERROR。
舉例:
一筆訂單下的退款單有36筆,當(dāng)商戶想查詢第25筆時(shí),可傳入訂單號(hào)及offset=24,微信支付平臺(tái)會(huì)返回第25筆到第35筆的退款單信息,或商戶可直接傳入退款單號(hào)查詢退款
以下為調(diào)用方式:
private void doRefundQuery() { // 四選一,微信訂單號(hào)查詢的優(yōu)先級(jí)是: refund_id > out_refund_no > transaction_id > out_trade_no HashMapdata = new HashMap (); // 商戶訂單號(hào) data.put("out_trade_no", out_trade_no); // 微信訂單號(hào) data.put("transaction_id", out_trade_no); // 商戶退款單號(hào) data.put("out_refund_no", out_trade_no); // 微信退款單號(hào) data.put("refund_id", out_trade_no); try { Map r = wxpay.refundQuery(data); System.out.println(r); } catch (Exception e) { e.printStackTrace(); } }
PS:微信訂單號(hào)查詢的優(yōu)先級(jí)是: refund_id > out_refund_no > transaction_id > out_trade_no
需要注意的是,查詢退款時(shí),需要注意退款返回的錯(cuò)誤碼,如果出現(xiàn)錯(cuò)誤,需要及時(shí)同步商戶系統(tǒng)中的退款數(shù)據(jù)。
結(jié)語(yǔ)以上為申請(qǐng)退款、退款回調(diào)接口、查詢退款相關(guān)的解釋和源碼,特別需要注意的是接收退款時(shí)的解密方式和替換安全文件,小伙伴們一定要注意哦,具體的源碼可以看作者的github,里面對(duì)每個(gè)方法有詳細(xì)的注釋。
預(yù)告:下一篇文章 下載對(duì)賬單和資金賬單,敬請(qǐng)期待?。。?/p>
?如果想要提前一覽源碼的小伙伴,可以先看看我的 github,地址如下:
?
?`
?https://github.com/YClimb/wxp...
?`
加作者私人微信,作者微信號(hào)如下 yclimb,標(biāo)明 微信支付 可拉入微信支付討論群與小伙伴一起探討哦,一定要標(biāo)明 微信支付 哦~
到此本文就結(jié)束了,關(guān)注公眾號(hào)查看更多推送?。。?/p>
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/72089.html
摘要:本文是淺析微信支付系列文章的第九篇,主要講解商戶下載對(duì)賬單接口和資金賬單接口的實(shí)現(xiàn)和一些注意事項(xiàng)。注意微信側(cè)未成功下單的交易不會(huì)出現(xiàn)在對(duì)賬單中。 本文是【淺析微信支付】系列文章的第九篇,主要講解商戶下載對(duì)賬單接口和資金賬單接口的實(shí)現(xiàn)和一些注意事項(xiàng)。 淺析微信支付系列已經(jīng)更新九篇了喲~,沒(méi)有看過(guò)的朋友們可以看一下哦。 淺析微信支付:申請(qǐng)退款、退款回調(diào)接口、查詢退款 淺析微信支付:查詢訂...
摘要:本文是淺析微信支付系列文章的第十一篇,主要講解支付驗(yàn)收示例和驗(yàn)收指引。為保證商戶接入質(zhì)量,提升交易安全及用戶體驗(yàn),微信支付的合作服務(wù)商在正式上線交易前,必須先根據(jù)本文指引完成驗(yàn)收。 本文是【淺析微信支付】系列文章的第十一篇,主要講解支付驗(yàn)收示例和驗(yàn)收指引。 淺析微信支付系列已經(jīng)更新十一篇了喲~,沒(méi)有看過(guò)的朋友們可以看一下。 淺析微信支付:如何使用沙箱環(huán)境測(cè)試 淺析微信支付:下載對(duì)賬單...
摘要:本文是淺析微信支付系列文章的第十篇,主要講解如何使用沙箱環(huán)境來(lái)測(cè)試微信支付。圖為微信支付仿真測(cè)試系統(tǒng)后簡(jiǎn)稱仿真系統(tǒng)的簡(jiǎn)化原理圖。沙箱說(shuō)明微信支付沙箱環(huán)境,是提供給微信支付商戶的開(kāi)發(fā)者,用于模擬支付及回調(diào)通知。 本文是【淺析微信支付】系列文章的第十篇,主要講解如何使用沙箱環(huán)境來(lái)測(cè)試微信支付。 淺析微信支付系列已經(jīng)更新十篇了喲~,沒(méi)有看過(guò)的朋友們可以看一下。 淺析微信支付:下載對(duì)賬單和資...
摘要:本文是淺析微信支付系列文章的第十五篇,主要講解如何開(kāi)通免充值產(chǎn)品功能流程和其中的注意事項(xiàng),對(duì)于接口升級(jí)會(huì)重要講解,避免爬坑。 本文是【淺析微信支付】系列文章的第十五篇,主要講解如何開(kāi)通免充值產(chǎn)品功能流程和其中的注意事項(xiàng),對(duì)于接口升級(jí)會(huì)重要講解,避免爬坑。 淺析微信支付系列已經(jīng)更新十五篇了喲~,沒(méi)有看過(guò)的朋友們可以看一下哦。 淺析微信支付:商戶平臺(tái)代金券或立減優(yōu)惠開(kāi)通、指定用戶代金券發(fā)...
摘要:本文是淺析微信支付系列文章的第十二篇,主要講解在商戶存在的提現(xiàn)商戶付款到微信用戶零錢(qián)或者銀行卡需求。應(yīng)用場(chǎng)景微信支付已上線企業(yè)付款至銀行卡功能。企業(yè)付款到銀行卡發(fā)票與交易手續(xù)費(fèi)發(fā)票為拆分單獨(dú)開(kāi)具。 本文是【淺析微信支付】系列文章的第十二篇,主要講解在商戶存在的提現(xiàn)、商戶付款到微信用戶零錢(qián)或者銀行卡需求。 淺析微信支付系列已經(jīng)更新十二篇了喲~,沒(méi)有看過(guò)的朋友們可以看一下哦。 淺析微信支...
閱讀 1965·2023-04-25 19:51
閱讀 1266·2021-11-15 11:43
閱讀 4679·2021-11-02 14:40
閱讀 2096·2021-10-11 10:59
閱讀 1428·2021-09-22 15:05
閱讀 1142·2021-09-09 09:32
閱讀 750·2019-08-30 15:56
閱讀 639·2019-08-30 15:52