Commit 5def62fe authored by jmdhappy's avatar jmdhappy
Browse files

微信支付SDK更换为weixin-java-pay,增加微信H5支付,优化一些异常情况的处理

parent dd9d81f9
......@@ -16,6 +16,7 @@ public class PayConstant {
public final static String PAY_CHANNEL_WX_JSAPI = "WX_JSAPI"; // 微信公众号支付
public final static String PAY_CHANNEL_WX_NATIVE = "WX_NATIVE"; // 微信原生扫码支付
public final static String PAY_CHANNEL_WX_APP = "WX_APP"; // 微信APP支付
public final static String PAY_CHANNEL_WX_MWEB = "WX_MWEB"; // 微信H5支付
public final static String PAY_CHANNEL_IAP = "IAP"; // 苹果应用内支付
public final static String PAY_CHANNEL_ALIPAY_MOBILE = "ALIPAY_MOBILE"; // 支付宝移动支付
public final static String PAY_CHANNEL_ALIPAY_PC = "ALIPAY_PC"; // 支付宝PC支付
......@@ -61,8 +62,10 @@ public class PayConstant {
public static class WxConstant {
public final static String TRADE_TYPE_APP = "APP"; // APP支付
public final static String TRADE_TYPE_JSPAI = "JSAPI"; // 公众号支付
public final static String TRADE_TYPE_JSPAI = "JSAPI"; // 公众号支付或小程序支付
public final static String TRADE_TYPE_NATIVE = "NATIVE"; // 原生扫码支付
public final static String TRADE_TYPE_MWEB = "MWEB"; // H5支付
}
public static class IapConstant {
......
......@@ -19,8 +19,8 @@ public class PayOrderDemo {
// 验签key
static final String repKey = "Hpcl522AV6q613KIi46u6g6XuW8vM1N8bFgyv769770MdYe9u37M4y7rIpl8";
static final String baseUrl = "http://api.xxpay.org/api";
//static final String baseUrl = "http://localhost:3020/api";
//static final String baseUrl = "http://api.xxpay.org/api";
static final String baseUrl = "http://localhost:3020/api";
static final String notifyUrl = "http://www.baidu.com"; // 本地环境测试,可到ngrok.cc网站注册
public static void main(String[] args) {
......@@ -32,17 +32,31 @@ public class PayOrderDemo {
JSONObject paramMap = new JSONObject();
paramMap.put("mchId", mchId); // 商户ID
paramMap.put("mchOrderNo", System.currentTimeMillis()); // 商户订单号
paramMap.put("channelId", "ALIPAY_PC"); // 支付渠道ID, WX_NATIVE,ALIPAY_WAP,ALIPAY_PC,ALIPAY_MOBILE
// 支付渠道ID, WX_NATIVE(微信扫码),WX_JSAPI(微信公众号或微信小程序),WX_APP(微信APP),WX_MWEB(微信H5),ALIPAY_WAP(支付宝手机支付),ALIPAY_PC(支付宝网站支付),ALIPAY_MOBILE(支付宝移动支付)
paramMap.put("channelId", "WX_MWEB");
paramMap.put("amount", 1); // 支付金额,单位分
paramMap.put("currency", "cny"); // 币种, cny-人民币
paramMap.put("clientIp", "114.112.124.236"); // 用户地址
paramMap.put("clientIp", "211.94.116.218"); // 用户地址,微信H5支付时要真实的
paramMap.put("device", "WEB"); // 设备
paramMap.put("subject", "XXPAY支付测试");
paramMap.put("body", "XXPAY支付测试");
paramMap.put("notifyUrl", notifyUrl); // 回调URL
paramMap.put("param1", ""); // 扩展参数1
paramMap.put("param2", ""); // 扩展参数2
paramMap.put("extra", "{\"productId\":\"120989823\",\"openId\":\"o2RvowBf7sOVJf8kJksUEMceaDqo\"}"); // 附加参数
paramMap.put("extra", "{\n" +
" \"productId\": \"120989823\",\n" +
" \"openId\": \"o2RvowBf7sOVJf8kJksUEMceaDqo\",\n" +
" \"sceneInfo\": {\n" +
" \"h5_info\": {\n" +
" \"type\": \"Wap\",\n" +
" \"wap_url\": \"http://shop.xxpay.org\",\n" +
" \"wap_name\": \"xxpay充值\"\n" +
" }\n" +
" }\n" +
"}"); // 附加参数
//{"h5_info": {"type":"Wap","wap_url": "https://pay.qq.com","wap_name": "腾讯充值"}}
String reqSign = PayDigestUtil.getSign(paramMap, reqKey);
paramMap.put("sign", reqSign); // 签名
String reqData = "params=" + paramMap.toJSONString();
......
# \u652F\u4ED8\u5B9D\u670D\u52A1\u5668\u5F02\u6B65\u901A\u77E5\u9875\u9762\u8DEF\u5F84
ali.notify_url=http://api.xxpay.org/notify/pay/aliPayNotifyRes.htm
ali.notify_url=http://xxpay.ngrok.cc/notify/pay/aliPayNotifyRes.htm
# \u652F\u4ED8\u5B9D\u9875\u9762\u8DF3\u8F6C\u540C\u6B65\u901A\u77E5\u9875\u9762\u8DEF\u5F84(\u81EA\u884C\u914D\u7F6E)
ali.return_url=http://www.xxpay.org
# \u5FAE\u4FE1\u652F\u4ED8\u56DE\u8C03\u5730\u5740
wx.notify_url=http://api.xxpay.org/notify/pay/wxPayNotifyRes.htm
wx.notify_url=http://xxpay.ngrok.cc/notify/pay/wxPayNotifyRes.htm
# \u652F\u4ED8\u8BC1\u4E66\u6839\u8DEF\u5F84
cert.root.path=/home/xxpay/service/cert
\ No newline at end of file
cert.root.path=/Users/dingzhiwei/java/tmp/cert
\ No newline at end of file
......@@ -24,22 +24,6 @@
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/webapp/WEB-INF/lib/</directory>
<targetPath>BOOT-INF/lib/</targetPath>
<includes>
<include>**/*.jar</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<targetPath>BOOT-INF/classes/</targetPath>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
......
......@@ -53,6 +53,11 @@
<artifactId>xstream</artifactId>
<version>1.4.7</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>2.7.7</version>
</dependency>
<!--ali_pay-->
<dependency>
<groupId>com.alipay</groupId>
......
package org.xxpay.service.channel.tencent;
public class Main {
/*public static void main(String[] args) {
try {
//--------------------------------------------------------------------
//温馨提示,第一次使用该SDK时请到com.tencent.common.Configure类里面进行配置
//--------------------------------------------------------------------
//--------------------------------------------------------------------
//PART One:基础组件测试
//--------------------------------------------------------------------
//1)https请求可用性测试
//HTTPSPostRquestWithCert.test();
//2)测试项目用到的XStream组件,本项目利用这个组件将Java对象转换成XML数据Post给API
//XStreamTest.test();
//--------------------------------------------------------------------
//PART Two:基础服务测试
//--------------------------------------------------------------------
//1)测试被扫支付API
//PayServiceTest.test();
//2)测试被扫订单查询API
//PayQueryServiceTest.test();
//3)测试撤销API
//温馨提示,测试支付API成功扣到钱之后,可以通过调用PayQueryServiceTest.test(),将支付成功返回的transaction_id和out_trade_no数据贴进去,完成撤销工作,把钱退回来 ^_^v
//ReverseServiceTest.test();
//4)测试退款申请API
//RefundServiceTest.test();
//5)测试退款查询API
//RefundQueryServiceTest.test();
//6)测试对账单API
//DownloadBillServiceTest.test();
//本地通过xml进行API数据模拟的时候,先按需手动修改xml各个节点的值,然后通过以下方法对这个新的xml数据进行签名得到一串合法的签名,最后把这串签名放到这个xml里面的sign字段里,这样进行模拟的时候就可以通过签名验证了
// Util.log(Signature.getSignFromResponseString(Util.getLocalXMLString("/test/com/tencent/business/refundqueryserviceresponsedata/refundquerysuccess2.xml")));
//Util.log(new Date().getTime());
//Util.log(System.currentTimeMillis());
} catch (Exception e){
Util.log(e.getMessage());
}
}*/
}
package org.xxpay.service.channel.tencent;
import org.xxpay.service.channel.tencent.protocol.downloadbill_protocol.DownloadBillReqData;
import org.xxpay.service.channel.tencent.protocol.pay_protocol.ScanPayReqData;
import org.xxpay.service.channel.tencent.protocol.pay_query_protocol.ScanPayQueryReqData;
import org.xxpay.service.channel.tencent.protocol.refund_protocol.RefundReqData;
import org.xxpay.service.channel.tencent.protocol.refund_query_protocol.RefundQueryReqData;
import org.xxpay.service.channel.tencent.protocol.reverse_protocol.ReverseReqData;
import org.xxpay.service.channel.tencent.service.*;
/**
* SDK总入口
*/
public class WXPay {
/**
* 初始化SDK依赖的几个关键配置
* @param key 签名算法需要用到的秘钥
* @param appID 公众账号ID
* @param mchID 商户ID
* @param sdbMchID 子商户ID,受理模式必填
* @param certLocalPath HTTP证书在服务器中的路径,用来加载证书用
* @param certPassword HTTP证书的密码,默认等于MCHID
*/
public static void initSDKConfiguration(String key,String appID,String mchID,String sdbMchID,String certLocalPath,String certPassword){
/* Configure.setKey(key);
Configure.setAppID(appID);
Configure.setMchID(mchID);
Configure.setSubMchID(sdbMchID);
Configure.setCertLocalPath(certLocalPath);
Configure.setCertPassword(certPassword);*/
}
/**
* 请求支付服务
* @param scanPayReqData 这个数据对象里面包含了API要求提交的各种数据字段
* @return API返回的数据
* @throws Exception
*/
public static String requestScanPayService(ScanPayReqData scanPayReqData) throws Exception{
return new ScanPayService().request(scanPayReqData);
}
/**
* 请求支付查询服务
* @param scanPayQueryReqData 这个数据对象里面包含了API要求提交的各种数据字段
* @return API返回的XML数据
* @throws Exception
*/
public static String requestScanPayQueryService(ScanPayQueryReqData scanPayQueryReqData) throws Exception{
return new ScanPayQueryService().request(scanPayQueryReqData);
}
/**
* 请求退款服务
* @param refundReqData 这个数据对象里面包含了API要求提交的各种数据字段
* @return API返回的XML数据
* @throws Exception
*/
public static String requestRefundService(RefundReqData refundReqData) throws Exception{
return new RefundService().request(refundReqData);
}
/**
* 请求退款查询服务
* @param refundQueryReqData 这个数据对象里面包含了API要求提交的各种数据字段
* @return API返回的XML数据
* @throws Exception
*/
public static String requestRefundQueryService(RefundQueryReqData refundQueryReqData) throws Exception{
return new RefundQueryService().request(refundQueryReqData);
}
/**
* 请求撤销服务
* @param reverseReqData 这个数据对象里面包含了API要求提交的各种数据字段
* @return API返回的XML数据
* @throws Exception
*/
public static String requestReverseService(ReverseReqData reverseReqData) throws Exception{
return new ReverseService().request(reverseReqData);
}
/**
* 请求对账单下载服务
* @param downloadBillReqData 这个数据对象里面包含了API要求提交的各种数据字段
* @return API返回的XML数据
* @throws Exception
*/
public static String requestDownloadBillService(DownloadBillReqData downloadBillReqData) throws Exception{
return new DownloadBillService().request(downloadBillReqData);
}
/*
*/
/**
* 直接执行被扫支付业务逻辑(包含最佳实践流程)
* @param scanPayReqData 这个数据对象里面包含了API要求提交的各种数据字段
* @param resultListener 商户需要自己监听被扫支付业务逻辑可能触发的各种分支事件,并做好合理的响应处理
* @throws Exception
*//*
public static void doScanPayBusiness(ScanPayReqData scanPayReqData, ScanPayBusiness.ResultListener resultListener) throws Exception {
//new ScanPayBusiness().run(scanPayReqData, resultListener);
}
*/
/**
* 调用退款业务逻辑
* @param refundReqData 这个数据对象里面包含了API要求提交的各种数据字段
* @param resultListener 业务逻辑可能走到的结果分支,需要商户处理
* @throws Exception
*//*
public static void doRefundBusiness(RefundReqData refundReqData, RefundBusiness.ResultListener resultListener) throws Exception {
//new RefundBusiness().run(refundReqData,resultListener);
}
*/
/**
* 运行退款查询的业务逻辑
* @param refundQueryReqData 这个数据对象里面包含了API要求提交的各种数据字段
* @param resultListener 商户需要自己监听被扫支付业务逻辑可能触发的各种分支事件,并做好合理的响应处理
* @throws Exception
*//*
public static void doRefundQueryBusiness(RefundQueryReqData refundQueryReqData, RefundQueryBusiness.ResultListener resultListener) throws Exception {
//new RefundQueryBusiness().run(refundQueryReqData,resultListener);
}
*/
/**
* 请求对账单下载服务
* @param downloadBillReqData 这个数据对象里面包含了API要求提交的各种数据字段
* @param resultListener 商户需要自己监听被扫支付业务逻辑可能触发的各种分支事件,并做好合理的响应处理
* @return API返回的XML数据
* @throws Exception
*//*
public static void doDownloadBillBusiness(DownloadBillReqData downloadBillReqData, DownloadBillBusiness.ResultListener resultListener) throws Exception {
//new DownloadBillBusiness().run(downloadBillReqData,resultListener);
}
*/
}
package org.xxpay.service.channel.tencent.bridge;
/**
* User: rizenguo
* Date: 2014/12/1
* Time: 17:11
*/
public interface IBridge {
/**
* 获取auth_code,这个是扫码终端设备从用户手机上扫取到的支付授权号,这个号是跟用户用来支付的银行卡绑定的,有效期是1分钟
* @return 授权码
*/
public String getAuthCode();
/**
* 获取out_trade_no,这个是商户系统内自己可以用来唯一标识该笔订单的字符串,可以包含字母和数字,不超过32位
* @return 订单号
*/
public String getOutTradeNo();
/**
* 获取body:要支付的商品的描述信息,用户会在支付成功页面里看到这个信息
* @return 描述信息
*/
public String getBody();
/**
* 获取attach:支付订单里面可以填的附加数据,API会将提交的这个附加数据原样返回,有助于商户自己可以注明该笔消费的具体内容,方便后续的运营和记录
* @return 附加数据
*/
public String getAttach();
/**
* 获取订单总额
* @return 订单总额
*/
public int getTotalFee();
/**
* 获取device_info:商户自己定义的扫码支付终端设备号,方便追溯这笔交易发生在哪台终端设备上
* @return 支付终端设备号
*/
public String getDeviceInfo();
/**
* 获取机器的ip地址
* @return 机器设备的ip地址
*/
public String getUserIp();
/**
* 获取spBillCreateIP:订单生成的机器IP
* @return 订单生成的机器IP
*/
public String getSpBillCreateIP();
/**
* 获取time_start:订单生成时间
* @return 订单生成时间
*/
public String getTimeStart();
/**
* 获取time_end:订单生成时间
* @return 订单失效时间
*/
public String getTimeExpire();
/**
* 获取goods_tag:商品标记,微信平台配置的商品标记,用于优惠券或者满减使用
* @return 商品标记
*/
public String getGoodsTag();
/**
* 获取transaction_id:微信平台支付成功时给分配的唯一交易号,一般只要有这个tracnsacion_id,后续的查询、撤销、退款都建议优先用这个,而不是商户自己的那个out_trade_no
* @return 微信平台官方分配的交易号
*/
public String getTransactionID();
/**
* 获取out_refund_no:商户系统内部的退款单号,商户系统内部唯一,同一退款单号多次请求只退一笔
* @return 商户系统内部的退款单号
*/
public String getOutRefundNo();
/**
* 获取refund_fee:获取本次退款请求所要退的具体金额,这个金额不能比这个订单的total_fee(总金额)还大
* @return 本次退款请求所要退的具体金额
*/
public int getRefundFee();
/**
* 获取refund_id:微信平台退款成功时给分配的唯一退款号,一般只要有这个refund_id,后续的查询建议优先用这个
* @return 微信平台官方分配的退款号
*/
public String getRefundID();
/**
* 获取bill_date:获取对账单API需要的日期,格式是yyyyMMdd
* @return 要查询对账单的日期
*/
public String getBillDate();
/**
* 获取bill_type:获取对账单API需要的数据类型,这些类型在DownloadBillService里面有定义
* @return 要查询对账单的类型
*/
public String getBillType();
/**
* 获取操作员的ID,默认等于商户号
* @return 返回操作员的ID
*/
public String getOpUserID();
/**
* 获取退款货币类型,符合ISO 4217标准的三位字母代码,默认为CNY(人民币)
* @return 获取退款货币类型
*/
public String getRefundFeeType();
}
package org.xxpay.service.channel.tencent.common;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import org.xxpay.common.util.IPUtility;
import org.xxpay.common.util.MyLog;
import java.io.File;
import java.util.Date;
/**
* User: rizenguo
* Date: 2014/10/29
* Time: 14:40
* 这里放置各种配置数据
*/
@RefreshScope
@Service
public class Configure {
private static final MyLog _log = MyLog.getLog(Configure.class);
public Configure init(String configParam) {
JSONObject paramObj = JSON.parseObject(configParam);
this.setMchID(paramObj.getString("mchId"));
this.setAppID(paramObj.getString("appId"));
this.setCertLocalPath(certRootPath + File.separator + paramObj.getString("certLocalPath"));
this.setCertPassword(paramObj.getString("certPassword"));
this.setKey(paramObj.getString("key"));
this.setIp(IPUtility.getLocalIP());
return this;
}
// 这个就是自己要保管好的私有Key了(切记只能放在自己的后台代码里,不能放在任何可能被看到源代码的客户端程序中)
// 每次自己Post数据给API的时候都要用这个key来对所有字段进行签名,生成的签名会放在Sign这个字段,API收到Post数据的时候也会用同样的签名算法对Post过来的数据进行签名和验证
// 收到API的返回的时候也要用这个key来对返回的数据算下签名,跟API的Sign数据进行比较,如果值不一致,有可能数据被第三方给篡改
private String key;
//微信分配的公众号ID(开通公众号之后可以获取到)
private String appID;
private String mchID;
//HTTPS证书的本地路径
// private static String certLocalPath = "/Users/dingzhiwei/java/tmp/wx.crt.p12";
private String certLocalPath;
//HTTPS证书密码,默认密码等于商户号MCHID
private String certPassword;
//是否使用异步线程的方式来上报API测速,默认为异步模式
private static boolean useThreadToDoReport = true;
//配置描述
private String desc;
//机器IP
private String ip;
@Value("${cert.root.path}")
private String certRootPath;
//以下是几个API的路径:
//1)被扫支付API
public static String PAY_API = "https://api.mch.weixin.qq.com/pay/micropay";
//2)被扫支付查询API
public static String ORDER_QUERY_API = "https://api.mch.weixin.qq.com/pay/orderquery";
//3)退款API
public static String REFUND_API = "https://api.mch.weixin.qq.com/secapi/pay/refund";
//4)退款查询API
public static String REFUND_QUERY_API = "https://api.mch.weixin.qq.com/pay/refundquery";
//5)撤销API
public static String REVERSE_API = "https://api.mch.weixin.qq.com/secapi/pay/reverse";
//6)下载对账单API
public static String DOWNLOAD_BILL_API = "https://api.mch.weixin.qq.com/pay/downloadbill";
//7) 统计上报API
public static String REPORT_API = "https://api.mch.weixin.qq.com/payitil/report";
//8) 统一下单API
public static String UNIFIED_ORDER_API = "https://api.mch.weixin.qq.com/pay/unifiedorder";
//9) 发送现金红包API
public static String SEND_REDPACK_API = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";
//10) 查询现金红包API
public static String QUERY_REDPACK_API = "https://api.mch.weixin.qq.com/mmpaymkttransfers/gethbinfo";
//9) 企业付款API
public static String TRANSFERS_API = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
//10) 查询企业付款API
public static String GET_TRANSFERS_API = "https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo";
@Value("${wx.notify_url}")
private String notify_url;
// 配置加载时间
private Long loadTime = new Date().getTime();
public static boolean isUseThreadToDoReport() {
return useThreadToDoReport;
}
public static void setUseThreadToDoReport(boolean useThreadToDoReport) {
Configure.useThreadToDoReport = useThreadToDoReport;
}
public static String HttpsRequestClassName = "org.xxpay.service.channel.tencent.common.HttpsRequest";
public static void setHttpsRequestClassName(String name){
HttpsRequestClassName = name;
}
public String getAppID() {
return appID;
}
public void setAppID(String appID) {
this.appID = appID;
}
public String getCertLocalPath() {
return certLocalPath;
}
public void setCertLocalPath(String certLocalPath) {
this.certLocalPath = certLocalPath;
}
public String getCertPassword() {
return certPassword;
}
public void setCertPassword(String certPassword) {
this.certPassword = certPassword;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getMchID() {
return mchID;
}
public void setMchID(String mchID) {
this.mchID = mchID;
}
public String getDesc() {
return desc;
}
public String getNotify_url() {
return notify_url;
}
public void setNotify_url(String notify_url) {
this.notify_url = notify_url;
}
public void setDesc(String desc) {
this.desc = desc;
}
public Long getLoadTime() {
return loadTime;
}
public void setLoadTime(Long loadTime) {
this.loadTime = loadTime;
}
@Override
public String toString() {
return "Configure{" +
"key='" + key + '\'' +
", appID='" + appID + '\'' +
", certLocalPath='" + certLocalPath + '\'' +
", certPassword='" + certPassword + '\'' +
", desc='" + desc + '\'' +
", ip='" + ip + '\'' +
", loadTime=" + loadTime +
'}';
}
}
package org.xxpay.service.channel.tencent.common;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
import org.xxpay.service.channel.tencent.service.IServiceRequest;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ConnectionPoolTimeoutException;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.LoggerFactory;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.security.*;
import java.security.cert.CertificateException;
/**
* User: rizenguo
* Date: 2014/10/29
* Time: 14:36
*/
public class HttpsRequest implements IServiceRequest {
public interface ResultListener {
public void onConnectionPoolTimeoutError();
}
private static Log log = new Log(LoggerFactory.getLogger(HttpsRequest.class));
//表示请求器是否已经做了初始化工作
private boolean hasInit = false;
//连接超时时间,默认10秒
private int socketTimeout = 10000;
//传输超时时间,默认30秒
private int connectTimeout = 30000;
//请求器的配置
private RequestConfig requestConfig;
//HTTP请求器
private CloseableHttpClient httpClient;
private String crtPath;
private String crtPwd;
public HttpsRequest() throws UnrecoverableKeyException, KeyManagementException, NoSuchAlgorithmException, KeyStoreException, IOException {
//init();
}
public void init() throws IOException, KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyManagementException {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
FileInputStream instream = new FileInputStream(new File(crtPath));//加载本地的证书进行https加密传输
try {
keyStore.load(instream, crtPwd.toCharArray());//设置证书密码
} 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, crtPwd.toCharArray())
.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();
//根据默认超时限制初始化requestConfig
requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
hasInit = true;
}
/**
* 通过Https往API post xml数据
*
* @param url API地址
* @param xmlObj 要提交的XML数据对象
* @return API回包的实际数据
* @throws IOException
* @throws KeyStoreException
* @throws UnrecoverableKeyException
* @throws NoSuchAlgorithmException
* @throws KeyManagementException
*/
public String sendPost(String url, Object xmlObj) throws IOException, KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyManagementException {
if (!hasInit) {
init();
}
String result = null;
HttpPost httpPost = new HttpPost(url);
//解决XStream对出现双下划线的bug
XStream xStreamForRequestPostData = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")));
//将要提交给API的数据对象转换成XML格式数据Post给API
String postDataXML = xStreamForRequestPostData.toXML(xmlObj);
Util.log("API,POST过去的数据是:");
Util.log(postDataXML);
//得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
StringEntity postEntity = new StringEntity(postDataXML, "UTF-8");
httpPost.addHeader("Content-Type", "text/xml");
httpPost.setEntity(postEntity);
//设置请求器的配置
httpPost.setConfig(requestConfig);
Util.log("executing request" + httpPost.getRequestLine());
try {
HttpResponse response = httpClient.execute(httpPost);
HttpEntity entity = response.getEntity();
result = EntityUtils.toString(entity, "UTF-8");
} catch (ConnectionPoolTimeoutException e) {
log.e("http get throw ConnectionPoolTimeoutException(wait time out)");
} catch (ConnectTimeoutException e) {
log.e("http get throw ConnectTimeoutException");
} catch (SocketTimeoutException e) {
log.e("http get throw SocketTimeoutException");
} catch (Exception e) {
log.e("http get throw Exception");
} finally {
httpPost.abort();
}
return result;
}
/**
* 设置连接超时时间
*
* @param socketTimeout 连接时长,默认10秒
*/
public void setSocketTimeout(int socketTimeout) {
socketTimeout = socketTimeout;
resetRequestConfig();
}
/**
* 设置传输超时时间
*
* @param connectTimeout 传输时长,默认30秒
*/
public void setConnectTimeout(int connectTimeout) {
connectTimeout = connectTimeout;
resetRequestConfig();
}
private void resetRequestConfig(){
requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
}
/**
* 允许商户自己做更高级更复杂的请求器配置
*
* @param requestConfig 设置HttpsRequest的请求器配置
*/
public void setRequestConfig(RequestConfig requestConfig) {
requestConfig = requestConfig;
}
public void setCrt(Configure configure) {
this.crtPath = configure.getCertLocalPath();
this.crtPwd = configure.getCertPassword();
}
}
package org.xxpay.service.channel.tencent.common;
import org.slf4j.Logger;
/**
* User: rizenguo
* Date: 2014/11/12
* Time: 14:32
*/
public class Log {
public static final String LOG_TYPE_TRACE = "logTypeTrace";
public static final String LOG_TYPE_DEBUG = "logTypeDebug";
public static final String LOG_TYPE_INFO = "logTypeInfo";
public static final String LOG_TYPE_WARN = "logTypeWarn";
public static final String LOG_TYPE_ERROR = "logTypeError";
//打印日志
private Logger logger;
public Log(Logger log){
logger = log;
}
public void t(String s){
logger.trace(s);
}
public void d(String s){
logger.debug(s);
}
public void i(String s){
logger.info(s);
}
public void w(String s){
logger.warn(s);
}
public void e(String s){
logger.error(s);
}
public void log(String type,String s){
if(type.equals(Log.LOG_TYPE_TRACE)){
t(s);
}else if(type.equals(Log.LOG_TYPE_DEBUG)){
d(s);
}else if(type.equals(Log.LOG_TYPE_INFO)){
i(s);
}else if(type.equals(Log.LOG_TYPE_WARN)){
w(s);
}else if(type.equals(Log.LOG_TYPE_ERROR)){
e(s);
}
}
}
package org.xxpay.service.channel.tencent.common;
import java.security.MessageDigest;
/**
* User: rizenguo
* Date: 2014/10/23
* Time: 15:43
*/
public class MD5 {
private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7",
"8", "9", "a", "b", "c", "d", "e", "f"};
/**
* 转换字节数组为16进制字串
* @param b 字节数组
* @return 16进制字串
*/
public static String byteArrayToHexString(byte[] b) {
StringBuilder resultSb = new StringBuilder();
for (byte aB : b) {
resultSb.append(byteToHexString(aB));
}
return resultSb.toString();
}
/**
* 转换byte到16进制
* @param b 要转换的byte
* @return 16进制格式
*/
private static String byteToHexString(byte b) {
int n = b;
if (n < 0) {
n = 256 + n;
}
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
/**
* MD5编码
* @param origin 原始字符串
* @return 经过MD5加密之后的结果
*/
public static String MD5Encode(String origin) {
String resultString = null;
try {
resultString = origin;
MessageDigest md = MessageDigest.getInstance("MD5");
resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
} catch (Exception e) {
e.printStackTrace();
}
return resultString;
}
}
package org.xxpay.service.channel.tencent.common;
import java.util.Random;
/**
* User: rizenguo
* Date: 2014/10/29
* Time: 14:18
*/
public class RandomStringGenerator {
/**
* 获取一定长度的随机字符串
* @param length 指定字符串长度
* @return 一定长度的字符串
*/
public static String getRandomStringByLength(int length) {
String base = "abcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
}
package org.xxpay.service.channel.tencent.common;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
/**
* User: rizenguo
* Date: 2014/10/29
* Time: 15:23
*/
public class Signature {
/**
* 签名算法
* @param o 要参与签名的数据对象
* @return 签名
* @throws IllegalAccessException
*/
public static String getSign(Object o, String key) throws IllegalAccessException {
ArrayList<String> list = new ArrayList<String>();
Class cls = o.getClass();
Field[] fields = cls.getDeclaredFields();
for (Field f : fields) {
f.setAccessible(true);
if (f.get(o) != null && f.get(o) != "") {
list.add(f.getName() + "=" + f.get(o) + "&");
}
}
int size = list.size();
String [] arrayToSort = list.toArray(new String[size]);
Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
StringBuilder sb = new StringBuilder();
for(int i = 0; i < size; i ++) {
sb.append(arrayToSort[i]);
}
String result = sb.toString();
result += "key=" + key;
Util.log("Sign Before MD5:" + result);
result = MD5.MD5Encode(result).toUpperCase();
Util.log("Sign Result:" + result);
return result;
}
public static String getSign(Map<String,Object> map, String key){
ArrayList<String> list = new ArrayList<String>();
for(Map.Entry<String,Object> entry:map.entrySet()){
if(!"".equals(entry.getValue())){
list.add(entry.getKey() + "=" + entry.getValue() + "&");
}
}
int size = list.size();
String [] arrayToSort = list.toArray(new String[size]);
Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
StringBuilder sb = new StringBuilder();
for(int i = 0; i < size; i ++) {
sb.append(arrayToSort[i]);
}
String result = sb.toString();
result += "key=" + key;
//Util.log("Sign Before MD5:" + result);
result = MD5.MD5Encode(result).toUpperCase();
//Util.log("Sign Result:" + result);
return result;
}
/**
* 从API返回的XML数据里面重新计算一次签名
* @param responseString API返回的XML数据
* @return 新鲜出炉的签名
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
*/
public static String getSignFromResponseString(String responseString, String key) throws IOException, SAXException, ParserConfigurationException {
Map<String,Object> map = XMLParser.getMapFromXML(responseString);
//清掉返回数据对象里面的Sign数据(不能把这个数据也加进去进行签名),然后用签名算法进行签名
map.put("sign","");
//将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较
return Signature.getSign(map, key);
}
/**
* 检验API返回的数据里面的签名是否合法,避免数据在传输的过程中被第三方篡改
* @param responseString API返回的XML数据字符串
* @return API签名是否合法
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
*/
public static boolean checkIsSignValidFromResponseString(String responseString, String key) throws ParserConfigurationException, IOException, SAXException {
Map<String,Object> map = XMLParser.getMapFromXML(responseString);
Util.log(map.toString());
String signFromAPIResponse = map.get("sign").toString();
if(signFromAPIResponse=="" || signFromAPIResponse == null){
Util.log("API返回的数据签名数据不存在,有可能被第三方篡改!!!");
return false;
}
Util.log("服务器回包里面的签名是:" + signFromAPIResponse);
//清掉返回数据对象里面的Sign数据(不能把这个数据也加进去进行签名),然后用签名算法进行签名
map.put("sign","");
//将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较
String signForAPIResponse = Signature.getSign(map, key);
if(!signForAPIResponse.equals(signFromAPIResponse)){
//签名验不过,表示这个API返回的数据有可能已经被篡改了
Util.log("API返回的数据签名验证不通过,有可能被第三方篡改!!!");
return false;
}
Util.log("恭喜,API返回的数据签名验证通过!!!");
return true;
}
}
package org.xxpay.service.channel.tencent.common;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Map;
/**
* User: rizenguo
* Date: 2014/10/23
* Time: 14:59
*/
public class Util {
//打log用
private static Log logger = new Log(LoggerFactory.getLogger(Util.class));
/**
* 通过反射的方式遍历对象的属性和属性值,方便调试
*
* @param o 要遍历的对象
* @throws Exception
*/
public static void reflect(Object o) throws Exception {
Class cls = o.getClass();
Field[] fields = cls.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field f = fields[i];
f.setAccessible(true);
Util.log(f.getName() + " -> " + f.get(o));
}
}
public static byte[] readInput(InputStream in) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
int len = 0;
byte[] buffer = new byte[1024];
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
out.close();
in.close();
return out.toByteArray();
}
public static String inputStreamToString(InputStream is) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int i;
while ((i = is.read()) != -1) {
baos.write(i);
}
return baos.toString();
}
public static InputStream getStringStream(String sInputString) {
ByteArrayInputStream tInputStringStream = null;
if (sInputString != null && !sInputString.trim().equals("")) {
tInputStringStream = new ByteArrayInputStream(sInputString.getBytes());
}
return tInputStringStream;
}
public static Object getObjectFromXML(String xml, Class tClass) {
//将从API返回的XML数据映射到Java对象
XStream xStreamForResponseData = new XStream();
xStreamForResponseData.alias("xml", tClass);
xStreamForResponseData.ignoreUnknownElements();//暂时忽略掉一些新增的字段
return xStreamForResponseData.fromXML(xml);
}
public static String getStringFromMap(Map<String, Object> map, String key, String defaultValue) {
if (key == "" || key == null) {
return defaultValue;
}
String result = (String) map.get(key);
if (result == null) {
return defaultValue;
} else {
return result;
}
}
public static int getIntFromMap(Map<String, Object> map, String key) {
if (key == "" || key == null) {
return 0;
}
if (map.get(key) == null) {
return 0;
}
return Integer.parseInt((String) map.get(key));
}
/**
* 打log接口
* @param log 要打印的log字符串
* @return 返回log
*/
public static String log(Object log){
logger.i(log.toString());
//System.out.println(log);
return log.toString();
}
/**
* 读取本地的xml数据,一般用来自测用
* @param localPath 本地xml文件路径
* @return 读到的xml字符串
*/
public static String getLocalXMLString(String localPath) throws IOException {
return Util.inputStreamToString(Util.class.getResourceAsStream(localPath));
}
public static String objectToXML(Object o){
//解决XStream对出现双下划线的bug
XStream xStreamForRequestPostData = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")));
//将要提交给API的数据对象转换成XML格式数据Post给API
return xStreamForRequestPostData.toXML(o);
}
}
package org.xxpay.service.channel.tencent.common;
import org.xxpay.service.channel.tencent.protocol.refund_query_protocol.RefundOrderData;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* User: rizenguo
* Date: 2014/11/1
* Time: 14:06
*/
public class XMLParser {
/**
* 从RefunQueryResponseString里面解析出退款订单数据
* @param refundQueryResponseString RefundQuery API返回的数据
* @return 因为订单数据有可能是多个,所以返回一个列表
*/
public static List<RefundOrderData> getRefundOrderList(String refundQueryResponseString) throws IOException, SAXException, ParserConfigurationException {
List list = new ArrayList();
Map<String,Object> map = XMLParser.getMapFromXML(refundQueryResponseString);
int count = Integer.parseInt((String) map.get("refund_count"));
Util.log("count:" + count);
if(count<1){
return list;
}
RefundOrderData refundOrderData;
for(int i=0;i<count;i++){
refundOrderData = new RefundOrderData();
refundOrderData.setOutRefundNo(Util.getStringFromMap(map,"out_refund_no_" + i,""));
refundOrderData.setRefundID(Util.getStringFromMap(map,"refund_id_" + i,""));
refundOrderData.setRefundChannel(Util.getStringFromMap(map,"refund_channel_" + i,""));
refundOrderData.setRefundFee(Util.getIntFromMap(map,"refund_fee_" + i));
refundOrderData.setCouponRefundFee(Util.getIntFromMap(map,"coupon_refund_fee_" + i));
refundOrderData.setRefundStatus(Util.getStringFromMap(map,"refund_status_" + i,""));
list.add(refundOrderData);
}
return list;
}
public static Map<String,Object> getMapFromXML(String xmlString) throws ParserConfigurationException, IOException, SAXException {
//这里用Dom的方式解析回包的最主要目的是防止API新增回包字段
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
InputStream is = Util.getStringStream(xmlString);
Document document = builder.parse(is);
//获取到document里面的全部结点
NodeList allNodes = document.getFirstChild().getChildNodes();
Node node;
Map<String, Object> map = new HashMap<String, Object>();
int i=0;
while (i < allNodes.getLength()) {
node = allNodes.item(i);
if(node instanceof Element){
map.put(node.getNodeName(),node.getTextContent());
}
i++;
}
return map;
}
}
package org.xxpay.service.channel.tencent.common.report;
import org.xxpay.service.channel.tencent.common.report.service.ReportService;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
/**
* User: rizenguo
* Date: 2014/12/3
* Time: 16:34
*/
public class ReportRunable implements Runnable {
private ReportService reportService ;
ReportRunable(ReportService rs){
reportService = rs;
}
@Override
public void run() {
try {
reportService.request();
} catch (UnrecoverableKeyException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package org.xxpay.service.channel.tencent.common.report;
import org.xxpay.service.channel.tencent.common.report.protocol.ReportReqData;
import org.xxpay.service.channel.tencent.common.report.service.ReportService;
/**
* User: rizenguo
* Date: 2014/12/3
* Time: 11:42
*/
public class Reporter {
private ReportRunable r;
private Thread t;
private ReportService rs;
/**
* 请求统计上报API
* @param reportReqData 这个数据对象里面包含了API要求提交的各种数据字段
*/
public Reporter(ReportReqData reportReqData){
rs = new ReportService(reportReqData);
}
public void run(){
r = new ReportRunable(rs);
t = new Thread(r);
t.setDaemon(true); //后台线程
t.start();
}
}
package org.xxpay.service.channel.tencent.common.report;
import org.xxpay.service.channel.tencent.common.report.protocol.ReportReqData;
/**
* User: rizenguo
* Date: 2014/12/3
* Time: 17:44
*/
public class ReporterFactory {
/**
* 请求统计上报API
* @param reportReqData 这个数据对象里面包含了API要求提交的各种数据字段
* @return 返回一个Reporter
*/
public static Reporter getReporter(ReportReqData reportReqData){
return new Reporter(reportReqData);
}
}
package org.xxpay.service.channel.tencent.common.report.protocol;
import org.xxpay.service.channel.tencent.common.Configure;
import org.xxpay.service.channel.tencent.common.RandomStringGenerator;
import org.xxpay.service.channel.tencent.common.Signature;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
/**
* User: rizenguo
* Date: 2014/11/12
* Time: 17:05
*/
public class ReportReqData {
//每个字段具体的意思请查看API文档
private String appid;
private String mch_id;
private String sub_mch_id;
private String device_info;
private String nonce_str;
private String sign;
//上报对应的接口的完整URL,类似:https://api.mch.weixin.qq.com/pay/unifiedorder
private String interface_url;
//接口耗时情况,单位为毫秒
private int execute_time_cost;
//发起接口调用时的机器IP
private String user_ip;
//上报该统计请求时的系统时间,格式为yyyyMMddHHmmss
private String time;
//以下是API接口返回的对应数据
private String return_code;
private String return_msg;
private String result_code;
private String err_code;
private String err_code_des;
private String out_trade_no;
/**
* 请求统计上报API
* @param deviceInfo 微信支付分配的终端设备号,商户自定义
* @param interfaceUrl 上报对应的接口的完整URL,类似: https://api.mch.weixin.qq.com/pay/unifiedorder
* @param executeTimeCost 接口耗时情况,单位为毫秒
* @param returnCode API返回的对应字段
* @param returnMsg API返回的对应字段
* @param resultCode API返回的对应字段
* @param errCode API返回的对应字段
* @param errCodeDes API返回的对应字段
* @param outTradeNo API返回的对应字段
* @param userIp 发起接口调用时的机器IP
*/
public ReportReqData(Configure configure, String deviceInfo, String interfaceUrl, int executeTimeCost, String returnCode, String returnMsg, String resultCode, String errCode, String errCodeDes, String outTradeNo, String userIp){
//微信分配的公众号ID(开通公众号之后可以获取到)
setAppid(configure.getAppID());
//微信支付分配的商户号ID(开通公众号的微信支付功能之后可以获取到)
setMch_id(configure.getMchID());
//商户系统自己生成的唯一的订单号
setOut_trade_no(outTradeNo);
setDevice_info(deviceInfo);
setInterface_url(interfaceUrl);
setExecute_time_cost(executeTimeCost);
setReturn_code(returnCode);
setReturn_msg(returnMsg);
setResult_code(resultCode);
setErr_code(errCode);
setErr_code_des(errCodeDes);
setUser_ip(userIp);
setTime(getTime());
//随机字符串,不长于32 位
setNonce_str(RandomStringGenerator.getRandomStringByLength(32));
//根据API给的签名规则进行签名
String sign = Signature.getSign(toMap(), configure.getKey());
setSign(sign);//把签名数据设置到Sign这个属性中
}
public String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
public String getMch_id() {
return mch_id;
}
public void setMch_id(String mch_id) {
this.mch_id = mch_id;
}
public String getDevice_info() {
return device_info;
}
public void setDevice_info(String device_info) {
this.device_info = device_info;
}
public String getNonce_str() {
return nonce_str;
}
public void setNonce_str(String nonce_str) {
this.nonce_str = nonce_str;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getInterface_url() {
return interface_url;
}
public void setInterface_url(String interface_url) {
this.interface_url = interface_url;
}
public int getExecute_time_cost() {
return execute_time_cost;
}
public void setExecute_time_cost(int execute_time) {
this.execute_time_cost = execute_time;
}
public String getReturn_code() {
return return_code;
}
public void setReturn_code(String return_code) {
this.return_code = return_code;
}
public String getReturn_msg() {
return return_msg;
}
public void setReturn_msg(String return_msg) {
this.return_msg = return_msg;
}
public String getResult_code() {
return result_code;
}
public void setResult_code(String result_code) {
this.result_code = result_code;
}
public String getErr_code() {
return err_code;
}
public void setErr_code(String err_code) {
this.err_code = err_code;
}
public String getErr_code_des() {
return err_code_des;
}
public void setErr_code_des(String err_code_des) {
this.err_code_des = err_code_des;
}
public String getOut_trade_no() {
return out_trade_no;
}
public void setOut_trade_no(String out_trade_no) {
this.out_trade_no = out_trade_no;
}
public String getUser_ip() {
return user_ip;
}
public void setUser_ip(String user_ip) {
this.user_ip = user_ip;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public Map<String,Object> toMap(){
Map<String,Object> map = new HashMap<String, Object>();
Field[] fields = this.getClass().getDeclaredFields();
for (Field field : fields) {
Object obj;
try {
obj = field.get(this);
if(obj!=null){
map.put(field.getName(), obj);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return map;
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment