Java微信退款開發(fā)
一、下載證書并導入到系統(tǒng)
微信支付接口中,涉及資金回滾的接口會使用到商戶證書,包括退款、撤銷接口。商家在申請微信支付成功后,可以按照以下路徑下載:微信商戶平臺(pay.weixin.qq.com)-->賬戶設(shè)置-->API安全-->證書下載。
下載的時候需要手機驗證及登錄密碼。下載后找到apiclient_cert.p12這個證書,雙擊導入,導入的時候提示輸入密碼,這個密碼就是商戶ID,且必須是在自己的商戶平臺下載的證書。否則會出現(xiàn)密碼錯誤的提示:
導入正確的提示:
二、編寫代碼
首先初始化退款接口中的請求參數(shù),如微信訂單號transaction_id(和商戶訂單號只需要知道一個)、訂單金額total_fee等;其次調(diào)用MobiMessage中的RefundResData2xml方法解析成需要的類型;最后調(diào)用RefundRequest類的httpsRequest方法觸發(fā)請求。
/** * 處理退款請求 * @param request * @return * @throws Exception */ @RequestMapping("/refund") @ResponseBody public JsonApi refund(HttpServletRequest request) throws Exception { //獲得當前目錄 String path = request.getSession().getServletContext().getRealPath("/"); LogUtils.trace(path); Date now = new Date(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");//可以方便地修改日期格式 String outRefundNo = "NO" + dateFormat.format( now ); //獲得退款的傳入?yún)?shù) String transactionID = "4008202001201609012791655620"; String outTradeNo = "20160901141024"; Integer totalFee = 1; Integer refundFee = totalFee; RefundReqData refundReqData = new RefundReqData(transactionID,outTradeNo,outRefundNo,totalFee,refundFee); String info = MobiMessage.RefundReqData2xml(refundReqData).replaceAll("__", "_"); LogUtils.trace(info); try { RefundRequest refundRequest = new RefundRequest(); String result = refundRequest.httpsRequest(WxConfigure.REFUND_API, info, path); LogUtils.trace(result); Map<String, String> getMap = MobiMessage.parseXml(new String(result.toString().getBytes(), "utf-8")); if("SUCCESS".equals(getMap.get("return_code")) && "SUCCESS".equals(getMap.get("return_msg"))){ return new JsonApi(); }else{ //返回錯誤描述 return new JsonApi(getMap.get("err_code_des")); } }catch(Exception e){ e.printStackTrace(); return new JsonApi(); } }
初始化退款接口需要的數(shù)據(jù),隱藏了get和set方法。
public class RefundReqData { //每個字段具體的意思請查看API文檔 private String appid = ""; private String mch_id = ""; private String nonce_str = ""; private String sign = ""; private String transaction_id = ""; private String out_trade_no = ""; private String out_refund_no = ""; private int total_fee = 0; private int refund_fee = 0; private String op_user_id = ""; /** * 請求退款服務(wù) * @param transactionID 是微信系統(tǒng)為每一筆支付交易分配的訂單號,通過這個訂單號可以標識這筆交易,它由支付訂單API支付成功時返回的數(shù)據(jù)里面獲取到。建議優(yōu)先使用 * @param outTradeNo 商戶系統(tǒng)內(nèi)部的訂單號,transaction_id 、out_trade_no 二選一,如果同時存在優(yōu)先級:transaction_id>out_trade_no * @param outRefundNo 商戶系統(tǒng)內(nèi)部的退款單號,商戶系統(tǒng)內(nèi)部唯一,同一退款單號多次請求只退一筆 * @param totalFee 訂單總金額,單位為分 * @param refundFee 退款總金額,單位為分 */ public RefundReqData(String transactionID,String outTradeNo,String outRefundNo,int totalFee,int refundFee){ //微信分配的公眾號ID(開通公眾號之后可以獲取到) setAppid(WxConfigure.AppId); //微信支付分配的商戶號ID(開通公眾號的微信支付功能之后可以獲取到) setMch_id(WxConfigure.Mch_id); //transaction_id是微信系統(tǒng)為每一筆支付交易分配的訂單號,通過這個訂單號可以標識這筆交易,它由支付訂單API支付成功時返回的數(shù)據(jù)里面獲取到。 setTransaction_id(transactionID); //商戶系統(tǒng)自己生成的唯一的訂單號 setOut_trade_no(outTradeNo); setOut_refund_no(outRefundNo); setTotal_fee(totalFee); setRefund_fee(refundFee); setOp_user_id(WxConfigure.Mch_id); //隨機字符串,不長于32 位 setNonce_str(StringUtil.generateRandomString(16)); //根據(jù)API給的簽名規(guī)則進行簽名 SortedMap<Object, Object> parameters = new TreeMap<Object, Object>(); parameters.put("appid", appid); parameters.put("mch_id", mch_id); parameters.put("nonce_str", nonce_str); parameters.put("transaction_id", transaction_id); parameters.put("out_trade_no", out_trade_no); parameters.put("out_refund_no", out_refund_no); parameters.put("total_fee", total_fee); parameters.put("refund_fee", refund_fee); parameters.put("op_user_id", op_user_id); String sign = DictionarySort.createSign(parameters); setSign(sign); //把簽名數(shù)據(jù)設(shè)置到Sign這個屬性中 }
MobiMessage實現(xiàn)json數(shù)據(jù)類型和xml數(shù)據(jù)之間的轉(zhuǎn)換。
public class MobiMessage { public static Map<String,String> xml2map(HttpServletRequest request) throws IOException, DocumentException { Map<String,String> map = new HashMap<String, String>(); SAXReader reader = new SAXReader(); InputStream inputStream = request.getInputStream(); Document document = reader.read(inputStream); Element root = document.getRootElement(); List<Element> list = root.elements(); for(Element e:list){ map.put(e.getName(), e.getText()); } inputStream.close(); return map; } //訂單轉(zhuǎn)換成xml public static String JsApiReqData2xml(JsApiReqData jsApiReqData){ /*XStream xStream = new XStream(); xStream.alias("xml",productInfo.getClass()); return xStream.toXML(productInfo);*/ MobiMessage.xstream.alias("xml",jsApiReqData.getClass()); return MobiMessage.xstream.toXML(jsApiReqData); } public static String RefundReqData2xml(RefundReqData refundReqData){ /*XStream xStream = new XStream(); xStream.alias("xml",productInfo.getClass()); return xStream.toXML(productInfo);*/ MobiMessage.xstream.alias("xml",refundReqData.getClass()); return MobiMessage.xstream.toXML(refundReqData); } public static String class2xml(Object object){ return ""; } public static Map<String, String> parseXml(String xml) throws Exception { Map<String, String> map = new HashMap<String, String>(); Document document = DocumentHelper.parseText(xml); Element root = document.getRootElement(); List<Element> elementList = root.elements(); for (Element e : elementList) map.put(e.getName(), e.getText()); return map; } //擴展xstream,使其支持CDATA塊 private static XStream xstream = new XStream(new XppDriver() { public HierarchicalStreamWriter createWriter(Writer out) { return new PrettyPrintWriter(out) { // 對所有xml節(jié)點的轉(zhuǎn)換都增加CDATA標記 boolean cdata = true; //@SuppressWarnings("unchecked") public void startNode(String name, Class clazz) { super.startNode(name, clazz); } protected void writeText(QuickWriter writer, String text) { if (cdata) { writer.write("<![CDATA["); writer.write(text); writer.write("]]>"); } else { writer.write(text); } } }; } }); }
RefundRequest類中initCert方法加載證書到系統(tǒng)中,其中證書地址如下:
public static String certLocalPath = "/WEB-INF/cert/apiclient_cert.p12";
RefundRequest類中httpsRequest方法調(diào)用微信接口,觸發(fā)請求。
/** * User: rizenguo * Date: 2014/10/29 * Time: 14:36 */ public class RefundRequest { //連接超時時間,默認10秒 private int socketTimeout = 10000; //傳輸超時時間,默認30秒 private int connectTimeout = 30000; //請求器的配置 private RequestConfig requestConfig; //HTTP請求器 private CloseableHttpClient httpClient; /** * 加載證書 * @param path * @throws IOException * @throws KeyStoreException * @throws UnrecoverableKeyException * @throws NoSuchAlgorithmException * @throws KeyManagementException */ private void initCert(String path) throws IOException, KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyManagementException { //拼接證書的路徑 path = path + WxConfigure.certLocalPath; KeyStore keyStore = KeyStore.getInstance("PKCS12"); //加載本地的證書進行https加密傳輸 FileInputStream instream = new FileInputStream(new File(path)); try { keyStore.load(instream, WxConfigure.Mch_id.toCharArray()); //加載證書密碼,默認為商戶ID } catch (CertificateException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } finally { instream.close(); } // Trust own CA and all self-signed certs SSLContext sslcontext = SSLContexts.custom() .loadKeyMaterial(keyStore, WxConfigure.Mch_id.toCharArray()) //加載證書密碼,默認為商戶ID .build(); // Allow TLSv1 protocol only SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslcontext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); httpClient = HttpClients.custom() .setSSLSocketFactory(sslsf) .build(); //根據(jù)默認超時限制初始化requestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build(); } /** * 通過Https往API post xml數(shù)據(jù) * @param url API地址 * @param xmlObj 要提交的XML數(shù)據(jù)對象 * @param path 當前目錄,用于加載證書 * @return * @throws IOException * @throws KeyStoreException * @throws UnrecoverableKeyException * @throws NoSuchAlgorithmException * @throws KeyManagementException */ public String httpsRequest(String url, String xmlObj, String path) throws IOException, KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyManagementException { //加載證書 initCert(path); String result = null; HttpPost httpPost = new HttpPost(url); //得指明使用UTF-8編碼,否則到API服務(wù)器XML的中文不能被成功識別 StringEntity postEntity = new StringEntity(xmlObj, "UTF-8"); httpPost.addHeader("Content-Type", "text/xml"); httpPost.setEntity(postEntity); //設(shè)置請求器的配置 httpPost.setConfig(requestConfig); try { HttpResponse response = httpClient.execute(httpPost); HttpEntity entity = response.getEntity(); result = EntityUtils.toString(entity, "UTF-8"); } catch (ConnectionPoolTimeoutException e) { LogUtils.trace("http get throw ConnectionPoolTimeoutException(wait time out)"); } catch (ConnectTimeoutException e) { LogUtils.trace("http get throw ConnectTimeoutException"); } catch (SocketTimeoutException e) { LogUtils.trace("http get throw SocketTimeoutException"); } catch (Exception e) { LogUtils.trace("http get throw Exception"); } finally { httpPost.abort(); } return result; } }
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
詳解Elasticsearch如何把一個索引變?yōu)橹蛔x
這篇文章主要為大家介紹了詳解Elasticsearch如何把一個索引變?yōu)橹蛔x示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02SpringBoot實現(xiàn)License認證(只校驗有效期)的詳細過程
License也就是版權(quán)許可證書,一般用于收費軟件給付費用戶提供的訪問許可證明,這篇文章主要介紹了SpringBoot實現(xiàn)License認證(只校驗有效期),需要的朋友可以參考下2024-04-04Intellij 下 mybatis 插件 MyBatisCodeHelperPro破解步驟詳解
這篇文章主要介紹了Intellij 下 mybatis 插件 MyBatisCodeHelperPro破解步驟,本文分步驟給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09在IntelliJ IDEA中.idea文件是什么可以刪除嗎
相信有很多小伙伴,在用idea寫java代碼的時候,創(chuàng)建工程總是會出現(xiàn).idea文件,該文件也從來沒去打開使用過,那么它在我們項目里面,扮演什么角色,到底能不能刪除它呢?這篇文章主要介紹了在IntelliJ IDEA中.idea文件是什么可以刪除嗎,需要的朋友可以參考下2024-01-01SpringSessionRedis配置及發(fā)現(xiàn)的問題講解
今天小編就為大家分享一篇關(guān)于SpringSessionRedis配置及發(fā)現(xiàn)的問題講解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03Java ConcurrentModificationException異常解決案例詳解
這篇文章主要介紹了Java ConcurrentModificationException異常解決案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-09-09Spring AOP結(jié)合注解實現(xiàn)接口層操作日志記錄
在項目開發(fā)中我們需要記錄接口的操作日志:包含請求參數(shù)、響應(yīng)參數(shù)、接口所屬模塊、接口功能描述、請求地址、ip地址等信息;實現(xiàn)思路很簡單就是基于注解和aop的方式去記錄日志,主要的難點在于日志表結(jié)構(gòu)、注解的設(shè)計已經(jīng)aop實現(xiàn)的一些比較好的實現(xiàn)方式的借鑒2022-08-08