Java微信退款開發(fā)
一、下載證書并導(dǎo)入到系統(tǒng)
微信支付接口中,涉及資金回滾的接口會使用到商戶證書,包括退款、撤銷接口。商家在申請微信支付成功后,可以按照以下路徑下載:微信商戶平臺(pay.weixin.qq.com)-->賬戶設(shè)置-->API安全-->證書下載。


下載的時候需要手機(jī)驗證及登錄密碼。下載后找到apiclient_cert.p12這個證書,雙擊導(dǎo)入,導(dǎo)入的時候提示輸入密碼,這個密碼就是商戶ID,且必須是在自己的商戶平臺下載的證書。否則會出現(xiàn)密碼錯誤的提示:

導(dǎo)入正確的提示:

二、編寫代碼
首先初始化退款接口中的請求參數(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 {
//獲得當(dāng)前目錄
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)為每一筆支付交易分配的訂單號,通過這個訂單號可以標(biāo)識這筆交易,它由支付訂單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)為每一筆支付交易分配的訂單號,通過這個訂單號可以標(biāo)識這筆交易,它由支付訂單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);
//隨機(jī)字符串,不長于32 位
setNonce_str(StringUtil.generateRandomString(16));
//根據(jù)API給的簽名規(guī)則進(jìn)行簽名
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;
}
//擴(kuò)展xstream,使其支持CDATA塊
private static XStream xstream = new XStream(new XppDriver() {
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 對所有xml節(jié)點的轉(zhuǎn)換都增加CDATA標(biāo)記
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 {
//連接超時時間,默認(rèn)10秒
private int socketTimeout = 10000;
//傳輸超時時間,默認(rèn)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");
//加載本地的證書進(jìn)行https加密傳輸
FileInputStream instream = new FileInputStream(new File(path));
try {
keyStore.load(instream, WxConfigure.Mch_id.toCharArray()); //加載證書密碼,默認(rèn)為商戶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()) //加載證書密碼,默認(rèn)為商戶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ù)默認(rèn)超時限制初始化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 當(dāng)前目錄,用于加載證書
* @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)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
詳解Elasticsearch如何把一個索引變?yōu)橹蛔x
這篇文章主要為大家介紹了詳解Elasticsearch如何把一個索引變?yōu)橹蛔x示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
SpringBoot實現(xiàn)License認(rèn)證(只校驗有效期)的詳細(xì)過程
License也就是版權(quán)許可證書,一般用于收費軟件給付費用戶提供的訪問許可證明,這篇文章主要介紹了SpringBoot實現(xiàn)License認(rèn)證(只校驗有效期),需要的朋友可以參考下2024-04-04
Intellij 下 mybatis 插件 MyBatisCodeHelperPro破解步驟詳解
這篇文章主要介紹了Intellij 下 mybatis 插件 MyBatisCodeHelperPro破解步驟,本文分步驟給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09
在IntelliJ IDEA中.idea文件是什么可以刪除嗎
相信有很多小伙伴,在用idea寫java代碼的時候,創(chuàng)建工程總是會出現(xiàn).idea文件,該文件也從來沒去打開使用過,那么它在我們項目里面,扮演什么角色,到底能不能刪除它呢?這篇文章主要介紹了在IntelliJ IDEA中.idea文件是什么可以刪除嗎,需要的朋友可以參考下2024-01-01
SpringSessionRedis配置及發(fā)現(xiàn)的問題講解
今天小編就為大家分享一篇關(guān)于SpringSessionRedis配置及發(fā)現(xiàn)的問題講解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03
Java ConcurrentModificationException異常解決案例詳解
這篇文章主要介紹了Java ConcurrentModificationException異常解決案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09
Spring 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

