小柒2012 преди 7 години
родител
ревизия
3b8aefe387
променени са 27 файла, в които са добавени 5014 реда и са изтрити 10 реда
  1. 6 2
      README.md
  2. 7 0
      pom.xml
  3. 4 2
      src/main/java/com/itstyle/Application.java
  4. 133 0
      src/main/java/com/itstyle/modules/unionpay/controller/UnionPayController.java
  5. 41 0
      src/main/java/com/itstyle/modules/unionpay/service/IUnionPayService.java
  6. 221 0
      src/main/java/com/itstyle/modules/unionpay/service/impl/UnionPayServiceImpl.java
  7. 553 0
      src/main/java/com/itstyle/modules/unionpay/util/AcpService.java
  8. 121 0
      src/main/java/com/itstyle/modules/unionpay/util/BaseHttpSSLSocketFactory.java
  9. 730 0
      src/main/java/com/itstyle/modules/unionpay/util/CertUtil.java
  10. 306 0
      src/main/java/com/itstyle/modules/unionpay/util/HttpClient.java
  11. 561 0
      src/main/java/com/itstyle/modules/unionpay/util/SDKConfig.java
  12. 364 0
      src/main/java/com/itstyle/modules/unionpay/util/SDKConstants.java
  13. 292 0
      src/main/java/com/itstyle/modules/unionpay/util/SDKUtil.java
  14. 974 0
      src/main/java/com/itstyle/modules/unionpay/util/SecureUtil.java
  15. 166 0
      src/main/java/com/itstyle/modules/unionpay/util/UnionConfig.java
  16. 1 4
      src/main/java/com/itstyle/modules/weixinpay/service/impl/WeixinPayServiceImpl.java
  17. 51 0
      src/main/resources/acp_sdk.properties
  18. 2 0
      src/main/resources/application-dev.properties
  19. 25 0
      src/main/resources/assets/acp_test_enc.cer
  20. BIN
      src/main/resources/assets/acp_test_sign.pfx
  21. 25 0
      src/main/resources/assets/acp_test_verify_sign.cer
  22. 17 0
      src/main/resources/assets/readme.txt
  23. 2 1
      src/main/resources/cert/readme.txt
  24. 1 1
      src/main/resources/templates/index.html
  25. 183 0
      src/main/resources/templates/unionpay/css/unionpay.css
  26. 217 0
      src/main/resources/templates/unionpay/index.html
  27. 11 0
      src/main/resources/templates/unionpay/pay.html

+ 6 - 2
README.md

@@ -6,7 +6,7 @@
 - 电脑支付:https://docs.open.alipay.com/270
 - 扫码支付:https://docs.open.alipay.com/194
 - 手机支付:https://docs.open.alipay.com/203
-- 参数
+- 参数zfbinfo.properties
 
 ```
 支付宝网关名、partnerId和appId
@@ -42,7 +42,7 @@ heartbeat_duration = 900
 - 公众号支付:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1
 - H5支付:https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_1
 - 微信退款说明:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3
-- 参数
+- 参数wxinfo.properties
 
 ```
 服务号的应用ID
@@ -61,6 +61,10 @@ SIGN_TYPE = MD5
 CERT_PATH = apiclient_cert.p12
 ```
 
+##银联
+- 开放平台:https://open.unionpay.com/ajweb/index
+- 商家中心:https://merchant.unionpay.com/join/
+
 #注意
 由于工作原因,项目正在完善中 ,随时更新日志,有疑问请留言或者加群
 

+ 7 - 0
pom.xml

@@ -101,6 +101,13 @@
 		    <artifactId>dom4j</artifactId>
 		    <version>1.6.1</version><!--$NO-MVN-MAN-VER$-->
 		</dependency>
+		<!-- bcprov-jdk16 -->
+		<dependency>
+		    <groupId>org.bouncycastle</groupId>
+		    <artifactId>bcprov-jdk16</artifactId>
+		    <version>1.46</version>
+		</dependency>
+		
 		 
 	</dependencies>
 	<build>

+ 4 - 2
src/main/java/com/itstyle/Application.java

@@ -11,6 +11,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
 
+import com.itstyle.modules.unionpay.util.SDKConfig;
+
 /**
  * 支付主控 
  * 创建者 科帮网
@@ -38,10 +40,10 @@ public class Application extends WebMvcConfigurerAdapter {
 	public static void main(String[] args) throws InterruptedException,
 			IOException {
 		SpringApplication.run(Application.class, args);
-		// 初始化 支付宝 微信参数 涉及机密 此文件不提交 请自行配置加载
-		// 依赖 commons.configuration 修改会自动更新相关配置
+		// 初始化 支付宝 微信 银联 参数 涉及机密 此文件不提交 请自行配置加载
 		//Configs.init("zfbinfo.properties");
 		//ConfigUtil.init("wxinfo.properties");
+		SDKConfig.getConfig().loadPropertiesFromSrc();
 		logger.info("支付项目启动 ");
 	}
 

+ 133 - 0
src/main/java/com/itstyle/modules/unionpay/controller/UnionPayController.java

@@ -0,0 +1,133 @@
+package com.itstyle.modules.unionpay.controller;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import com.itstyle.common.constants.PayWay;
+import com.itstyle.common.model.Product;
+import com.itstyle.modules.alipay.controller.AliPayController;
+import com.itstyle.modules.unionpay.service.IUnionPayService;
+import com.itstyle.modules.unionpay.util.AcpService;
+import com.itstyle.modules.unionpay.util.SDKConstants;
+/**
+ * 银联支付
+ * 创建者 科帮网
+ * 创建时间	2017年8月2日
+ *
+ */
+@Controller
+@RequestMapping(value = "unionpay")
+public class UnionPayController {
+	private static final Logger logger = LoggerFactory.getLogger(AliPayController.class);
+	
+	@Autowired
+	private IUnionPayService unionPayService;
+
+	@RequestMapping("/index")
+    public String   index() {
+        return "unionpay/index";
+    }
+	
+	@RequestMapping("/pcPay")
+    public String  pcPay(Product product,ModelMap map) {
+		logger.info("电脑支付");
+		product.setPayWay(PayWay.PC.getCode());
+		String form  =  unionPayService.unionPay(product);
+		map.addAttribute("form", form);
+		return "unionpay/pay";
+    }
+	@RequestMapping("/mobilePay")
+    public String  mobilePay(Product product,ModelMap map) {
+		logger.info("手机H5支付");
+		product.setPayWay(PayWay.MOBILE.getCode());
+		String form  =  unionPayService.unionPay(product);
+		map.addAttribute("form", form);
+		return "unionpay/pay";
+    }
+	
+	/**
+	 * 其实我小时候的梦想并不是要当什么程序员,
+     * 我只是幻想自己是地主家的少爷,家有良田万顷,
+     * 终日不学无术,没事领着一群狗奴才上街去调戏一下良家少女。
+     * 然后这个方法的基本作用就是 银联支付回调 通知我们支付是否成功。
+	 * @Author  科帮网
+	 * @param request
+	 * @param response
+	 * @throws Exception  void
+	 * @Date	2017年8月2日
+	 * 更新日志
+	 * 2017年8月2日  科帮网 首次创建
+	 *
+	 */
+	@RequestMapping(value = "pay")
+	public void union_notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
+        logger.info("银联接收后台通知开始");
+		String encoding = request.getParameter(SDKConstants.param_encoding);
+		// 获取银联通知服务器发送的后台通知参数
+		Map<String, String> reqParam = getAllRequestParam(request);
+		//打印参数
+		logger.info(reqParam.toString());
+		Map<String, String> valideData = null;
+		if (null != reqParam && !reqParam.isEmpty()) {
+			Iterator<Entry<String, String>> it = reqParam.entrySet().iterator();
+			valideData = new HashMap<String, String>(reqParam.size());
+			while (it.hasNext()) {
+				Entry<String, String> e = it.next();
+				String key = (String) e.getKey();
+				String value = (String) e.getValue();
+				value = new String(value.getBytes(encoding), encoding);
+				valideData.put(key, value);
+			}
+		}
+		//重要!验证签名前不要修改reqParam中的键值对的内容,否则会验签不过
+		if (!AcpService.validate(valideData, encoding)) {
+			logger.info("银联验证签名结果[失败].");
+		} else {
+			logger.info("银联验证签名结果[成功].");
+			String outtradeno =valideData.get("orderId");//订单号
+			String reqReserved = valideData.get("reqReserved");//辅助信息(字段穿透)
+			logger.info("处理相关业务逻辑{},{}",outtradeno,reqReserved);
+			response.getWriter().print("ok");//返回给银联服务器http 200  状态码
+		}
+	}
+	/**
+	 * 获取请求参数中所有的信息
+	 * @Author  科帮网
+	 * @param request
+	 * @return  Map<String,String>
+	 * @Date	2017年8月2日
+	 * 更新日志
+	 * 2017年8月2日  科帮网 首次创建
+	 *
+	 */
+	public static Map<String, String> getAllRequestParam(final HttpServletRequest request) {
+		Map<String, String> res = new HashMap<String, String>();
+		Enumeration<?> temp = request.getParameterNames();
+		if (null != temp) {
+			while (temp.hasMoreElements()) {
+				String en = (String) temp.nextElement();
+				String value = request.getParameter(en);
+				res.put(en, value);
+				//在报文上送时,如果字段的值为空,则不上送<下面的处理为在获取所有参数数据时,判断若值为空,则删除这个字段>
+				//System.out.println("ServletUtil类247行  temp数据的键=="+en+"     值==="+value);
+				if (null == res.get(en) || "".equals(res.get(en))) {
+					res.remove(en);
+				}
+			}
+		}
+		return res;
+	}
+}

+ 41 - 0
src/main/java/com/itstyle/modules/unionpay/service/IUnionPayService.java

@@ -0,0 +1,41 @@
+package com.itstyle.modules.unionpay.service;
+
+import java.util.Map;
+
+import com.itstyle.common.model.Product;
+
+
+public interface IUnionPayService {
+	/**
+	 * 银联支付
+	 * @Author  科帮网
+	 * @param product
+	 * @return  String
+	 * @Date	2017年8月2日
+	 * 更新日志
+	 * 2017年8月2日  科帮网 首次创建
+	 *
+	 */
+	String unionPay(Product product);
+	/**
+	 * 前台回调验证
+	 * @Author  科帮网
+	 * @param valideData
+	 * @param encoding
+	 * @return  String
+	 * @Date	2017年8月2日
+	 * 更新日志
+	 * 2017年8月2日  科帮网 首次创建
+	 *
+	 */
+	String validate(Map<String, String> valideData, String encoding);
+	/**
+	 * 对账单下载
+	 * @Author  科帮网  void
+	 * @Date	2017年8月2日
+	 * 更新日志
+	 * 2017年8月2日  科帮网 首次创建
+	 *
+	 */
+	void fileTransfer();
+}

+ 221 - 0
src/main/java/com/itstyle/modules/unionpay/service/impl/UnionPayServiceImpl.java

@@ -0,0 +1,221 @@
+package com.itstyle.modules.unionpay.service.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import com.itstyle.common.constants.Constants;
+import com.itstyle.common.constants.PayWay;
+import com.itstyle.common.model.Product;
+import com.itstyle.common.utils.CommonUtil;
+import com.itstyle.modules.unionpay.service.IUnionPayService;
+import com.itstyle.modules.unionpay.util.AcpService;
+import com.itstyle.modules.unionpay.util.SDKConfig;
+import com.itstyle.modules.unionpay.util.UnionConfig;
+@Service("unionPayService")
+public class UnionPayServiceImpl implements IUnionPayService{
+	private static final Logger logger = LoggerFactory.getLogger(UnionPayServiceImpl.class);
+	
+	@Value("${unionpay.notify.url}")
+	private String notify_url;
+	
+    /**
+     * 银联支付返回一个form表单
+     * @Author  科帮网
+     * @param product
+     * @return 
+     * @Date	2017年8月2日
+     * 更新日志
+     * 2017年8月2日  科帮网 首次创建
+     *
+     */
+	@Override
+	public String unionPay(Product product) {
+		Map<String, String> requestData = new HashMap<String, String>();
+		/***银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改***/
+		requestData.put("version", UnionConfig.version);   			  //版本号,全渠道默认值
+		requestData.put("encoding", UnionConfig.encoding_UTF8); 	  //字符集编码,可以使用UTF-8,GBK两种方式
+		requestData.put("signMethod", "01");            			  //签名方法,只支持 01:RSA方式证书加密
+		requestData.put("txnType", "01");               			  //交易类型 ,01:消费
+		requestData.put("txnSubType", "01");            			  //交易子类型, 01:自助消费
+		requestData.put("bizType", "000201");           			  //业务类型,B2C网关支付,手机wap支付
+		//渠道类型,这个字段区分B2C网关支付和手机wap支付;07:PC,平板  08:手机
+		if(product.getPayWay()==PayWay.MOBILE.getCode()){//手机支付
+			requestData.put("channelType", "08");     
+		}else{//PC支付
+			requestData.put("channelType", "07");
+		}
+		//前台回调地址(自定义)
+		String frontUrl = "http://git.oschina.net/52itstyle";
+		requestData.put("frontUrl", frontUrl);
+		/***商户接入参数 测试账号***/
+		requestData.put("merId", UnionConfig.merId);    	          //商户号码,请改成自己申请的正式商户号或者open上注册得来的777测试商户号
+		requestData.put("accessType", "0");             			  //接入类型,0:直连商户 
+		requestData.put("orderId", product.getOutTradeNo());          //商户订单号,8-40位数字字母,不能含“-”或“_”,可以自行定制规则		
+		requestData.put("txnTime", UnionConfig.getCurrentTime());     //订单发送时间,取系统时间,格式为YYYYMMDDhhmmss,必须取当前时间,否则会报txnTime无效
+		requestData.put("currencyCode", "156");         			  //交易币种(境内商户一般是156 人民币)
+		requestData.put("txnAmt", CommonUtil.subZeroAndDot(product.getTotalFee()));             //交易金额,单位分,不要带小数点
+		//这里组织穿透数据 业务以及交易类型(使用json数据报错)
+		requestData.put("reqReserved","自定义参数");	      //请求方保留域,如需使用请启用即可;透传字段(可以实现商户自定义参数的追踪)本交易的后台通知,对本交易的交易状态查询交易、对账文件中均会原样返回,商户可以按需上传,长度为1-1024个字节		
+		
+		//前台通知地址 (需设置为外网能访问 http https均可),支付成功后的页面 点击“返回商户”按钮的时候将异步通知报文post到该地址
+		//如果想要实现过几秒中自动跳转回商户页面权限,需联系银联业务申请开通自动返回商户权限
+		//异步通知参数详见open.unionpay.com帮助中心 下载  产品接口规范  网关支付产品接口规范 消费交易 商户通知
+		//requestData.put("frontUrl", UnionConfig.frontUrl);
+		
+		//后台通知地址(需设置为【外网】能访问 http https均可),支付成功后银联会自动将异步通知报文post到商户上送的该地址,失败的交易银联不会发送后台通知
+		//后台通知参数详见open.unionpay.com帮助中心 下载  产品接口规范  网关支付产品接口规范 消费交易 商户通知
+		//注意:1.需设置为外网能访问,否则收不到通知    2.http https均可  3.收单后台通知后需要10秒内返回http200或302状态码 
+		//    4.如果银联通知服务器发送通知后10秒内未收到返回状态码或者应答码非http200,那么银联会间隔一段时间再次发送。总共发送5次,每次的间隔时间为0,1,2,4分钟。
+		//    5.后台通知地址如果上送了带有?的参数,例如:http://abc/web?a=b&c=d 在后台通知处理程序验证签名之前需要编写逻辑将这些字段去掉再验签,否则将会验签失败
+		requestData.put("backUrl", notify_url);
+		
+		//////////////////////////////////////////////////
+		//
+		//       报文中特殊用法请查看 PCwap网关跳转支付特殊用法.txt
+		//
+		//////////////////////////////////////////////////
+		
+		/**请求参数设置完毕,以下对请求参数进行签名并生成html表单,将表单写入浏览器跳转打开银联页面**/
+		Map<String, String> submitFromData = AcpService.sign(requestData,UnionConfig.encoding_UTF8);  //报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。
+		
+		String requestFrontUrl = SDKConfig.getConfig().getFrontRequestUrl();  //获取请求银联的前台地址:对应属性文件acp_sdk.properties文件中的acpsdk.frontTransUrl
+		String form = AcpService.createAutoFormHtml(requestFrontUrl, submitFromData,UnionConfig.encoding_UTF8);   //生成自动跳转的Html表单
+		
+		logger.info("打印请求HTML,此为请求报文,为联调排查问题的依据:{}",form);
+		//将生成的html写到浏览器中完成自动跳转打开银联支付页面;这里调用signData之后,将html写到浏览器跳转到银联页面之前均不能对html中的表单项的名称和值进行修改,如果修改会导致验签不通过
+		//resp.getWriter().write(html);
+		return form;
+	}
+
+	@Override
+	public String validate(Map<String, String> valideData, String encoding) {
+		String message = Constants.SUCCESS;
+		if (!AcpService.validate(valideData, encoding)) {
+			message = Constants.FAIL;
+		}
+		return message;
+	}
+	@Override
+	public void fileTransfer() {
+		Map<String, String> data = new HashMap<String, String>();
+
+		/*** 银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改 ***/
+
+		// 版本号 全渠道默认值
+		data.put("version", UnionConfig.version);
+		
+		// 字符集编码 可以使用UTF-8,GBK两种方式
+		data.put("encoding", UnionConfig.encoding_UTF8);
+
+		// 签名方法
+		data.put("signMethod", "01");
+
+		// 交易类型 76-对账文件下载
+		data.put("txnType", "76");
+
+		// 交易子类型 01-对账文件下载
+		data.put("txnSubType", "01");
+
+		// 业务类型,固定
+		data.put("bizType", "000000");
+
+		/*** 商户接入参数 ***/
+
+		// 接入类型,商户接入填0,不需修改
+
+		data.put("accessType", "0");
+
+		// 商户代码,请替换正式商户号测试,如使用的是自助化平台注册的777开头的商户号,该商户号没有权限测文件下载接口的,
+
+		// 请使用测试参数里写的文件下载的商户号和日期测。如需777商户号的真实交易的对账文件,请使用自助化平台下载文件。
+
+		data.put("merId", UnionConfig.merId);
+
+		// 清算日期,如果使用正式商户号测试则要修改成自己想要获取对账文件的日期,
+		// 测试环境如果使用700000000000001商户号则固定填写0119
+
+		//data.put("settleDate", settleDate);
+
+		// 订单发送时间,取系统时间,格式为YYYYMMDDhhmmss,必须取当前时间,否则会报txnTime无效
+
+		data.put("txnTime", UnionConfig.getCurrentTime());
+
+		// 文件类型,一般商户填写00即可
+
+		data.put("fileType", "00");
+
+		/** 请求参数设置完毕,以下对请求参数进行签名并发送http post请求,接收同步应答报文-------------> **/
+
+		Map<String, String> reqData = AcpService.sign(data,
+				UnionConfig.encoding_UTF8);
+
+		// 报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。
+
+		String url = SDKConfig.getConfig().getFileTransUrl();
+
+		// 获取请求银联的前台地址:对应属性文件acp_sdk.properties文件中的acpsdk.fileTransUrl
+
+		Map<String, String> rspData = AcpService.post(reqData, url,
+				UnionConfig.encoding_UTF8);
+
+		if (!rspData.isEmpty()) {
+
+			if (AcpService.validate(rspData, UnionConfig.encoding_UTF8)) {
+
+				logger.info("验证签名成功");
+
+				String respCode = rspData.get("respCode");
+
+				if ("00".equals(respCode)) {
+
+					// 交易成功,解析返回报文中的fileContent并落地
+
+					/*	String zipFilePath = AcpService.deCodeFileContent(rspData,
+							"d:\\", UnionConfig.encoding_UTF8);
+
+					// 对落地的zip文件解压缩并解析
+
+					String outPutDirectory = "d:\\";
+
+					List<String> fileList = UnionConfig.unzip(zipFilePath,
+							outPutDirectory);
+
+					// 解析ZM,ZME文件
+
+					for (String file : fileList) {
+
+						if (file.indexOf("ZM_") != -1) {
+
+							List<Map> ZmDataList = UnionConfig.parseZMFile(file);
+
+							fileContentDispaly = UnionConfig.getFileContentTable(
+									ZmDataList, file);
+
+						} else if (file.indexOf("ZME_") != -1) {
+
+							UnionConfig.parseZMEFile(file);
+
+						}
+
+					}*/
+					// TODO
+				} else {
+					// 其他应答码为失败请排查原因
+					// TODO
+				}
+
+			} else {
+				logger.info("验证签名失败");
+				// TODO 检查验证签名失败的原因
+			}
+		} else {
+			// 未返回正确的http状态
+			logger.info("未获取到返回报文或返回http状态码非200");
+		}
+	}
+}

+ 553 - 0
src/main/java/com/itstyle/modules/unionpay/util/AcpService.java

@@ -0,0 +1,553 @@
+package com.itstyle.modules.unionpay.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AcpService {
+	private static final Logger logger = LoggerFactory.getLogger(AcpService.class);
+	/**
+	 * 请求报文签名(使用配置文件中配置的私钥证书加密)<br>
+	 * 功能:对请求报文进行签名,并计算赋值certid,signature字段并返回<br>
+	 * @param reqData 请求报文map<br>
+	 * @param encoding 上送请求报文域encoding字段的值<br>
+	 * @return 签名后的map对象<br>
+	 */
+	public static Map<String, String> sign(Map<String, String> reqData,String encoding) {
+		Map<String, String> submitData = SDKUtil.filterBlank(reqData);
+		SDKUtil.sign(submitData, encoding);
+		return submitData;
+	}
+	
+	/**
+	 * 多证书签名(通过传入私钥证书路径和密码加密)<br>
+	 * 功能:如果有多个商户号接入银联,每个商户号对应不同的证书可以使用此方法:传入私钥证书和密码(并且在acp_sdk.properties中 配置 acpsdk.singleMode=false)<br>
+	 * @param reqData 请求报文map<br>
+	 * @param certPath 签名私钥文件(带路径)<br>
+	 * @param certPwd 签名私钥密码<br>
+	 * @param encoding 上送请求报文域encoding字段的值<br>
+	 * @return 签名后的map对象<br>
+	 */
+	public static Map<String, String> sign(Map<String, String> reqData,String certPath, 
+			String certPwd,String encoding) {
+		Map<String, String> submitData = SDKUtil.filterBlank(reqData);
+		SDKUtil.signByCertInfo(submitData,certPath,certPwd,encoding);
+		return submitData;
+	}
+	
+	/**
+	 * 验证签名(SHA-1摘要算法)<br>
+	 * @param resData 返回报文数据<br>
+	 * @param encoding 上送请求报文域encoding字段的值<br>
+	 * @return true 通过 false 未通过<br>
+	 */
+	public static boolean validate(Map<String, String> rspData, String encoding) {
+		logger.info("验签处理开始");
+		if (SDKUtil.isEmpty(encoding)) {
+			encoding = "UTF-8";
+		}
+		String stringSign = rspData.get(SDKConstants.param_signature);
+
+		// 从返回报文中获取certId ,然后去证书静态Map中查询对应验签证书对象
+		String certId = rspData.get(SDKConstants.param_certId);
+		
+		logger.info("对返回报文串验签使用的验签公钥序列号:["+certId+"]");
+		
+		// 将Map信息转换成key1=value1&key2=value2的形式
+		String stringData = SDKUtil.coverMap2String(rspData);
+
+		logger.info("待验签返回报文串:["+stringData+"]");
+		
+		try {
+			// 验证签名需要用银联发给商户的公钥证书.
+			return SecureUtil.validateSignBySoft(CertUtil
+					.getValidateKey(certId), SecureUtil.base64Decode(stringSign
+					.getBytes(encoding)), SecureUtil.sha1X16(stringData,
+					encoding));
+		} catch (UnsupportedEncodingException e) {
+			logger.error(e.getMessage(), e);
+		} catch (Exception e) {
+			logger.error(e.getMessage(), e);
+		}
+		return false;
+	}
+	
+
+	/**
+	 * 对控件支付成功返回的结果信息中data域进行验签(控件端获取的应答信息)
+	 * @param jsonData json格式数据,例如:{"sign" : "J6rPLClQ64szrdXCOtV1ccOMzUmpiOKllp9cseBuRqJ71pBKPPkZ1FallzW18gyP7CvKh1RxfNNJ66AyXNMFJi1OSOsteAAFjF5GZp0Xsfm3LeHaN3j/N7p86k3B1GrSPvSnSw1LqnYuIBmebBkC1OD0Qi7qaYUJosyA1E8Ld8oGRZT5RR2gLGBoiAVraDiz9sci5zwQcLtmfpT5KFk/eTy4+W9SsC0M/2sVj43R9ePENlEvF8UpmZBqakyg5FO8+JMBz3kZ4fwnutI5pWPdYIWdVrloBpOa+N4pzhVRKD4eWJ0CoiD+joMS7+C0aPIEymYFLBNYQCjM0KV7N726LA==",  "data" : "pay_result=success&tn=201602141008032671528&cert_id=68759585097"}
+	 * @return 是否成功
+	 */
+	public static boolean validateAppResponse(String jsonData, String encoding) {
+		logger.info("控件应答信息验签处理开始:[" + jsonData + "]");
+		if (SDKUtil.isEmpty(encoding)) {
+			encoding = "UTF-8";
+		}
+
+        Pattern p = Pattern.compile("\\s*\"sign\"\\s*:\\s*\"([^\"]*)\"\\s*");
+		Matcher m = p.matcher(jsonData);
+		if(!m.find()) return false;
+		String sign = m.group(1);
+
+		p = Pattern.compile("\\s*\"data\"\\s*:\\s*\"([^\"]*)\"\\s*");
+		m = p.matcher(jsonData);
+		if(!m.find()) return false;
+		String data = m.group(1);
+
+		p = Pattern.compile("cert_id=(\\d*)");
+		m = p.matcher(jsonData);
+		if(!m.find()) return false;
+		String certId = m.group(1);
+
+		try {
+			// 验证签名需要用银联发给商户的公钥证书.
+			return SecureUtil.validateSignBySoft(CertUtil
+					.getValidateKey(certId), SecureUtil.base64Decode(sign
+					.getBytes(encoding)), SecureUtil.sha1X16(data,
+					encoding));
+		} catch (UnsupportedEncodingException e) {
+			logger.error(e.getMessage(), e);
+		} catch (Exception e) {
+			logger.error(e.getMessage(), e);
+		}
+		return false;
+	}
+	
+	/**
+	 * 功能:后台交易提交请求报文并接收同步应答报文<br>
+	 * @param reqData 请求报文<br>
+	 * @param rspData 应答报文<br>
+	 * @param reqUrl  请求地址<br>
+	 * @param encoding<br>
+	 * @return 应答http 200返回true ,其他false<br>
+	 */
+	public static Map<String,String> post(
+			Map<String, String> reqData,String reqUrl,String encoding) {
+		Map<String, String> rspData = new HashMap<String,String>();
+		logger.info("请求银联地址:" + reqUrl);
+		//发送后台请求数据
+		HttpClient hc = new HttpClient(reqUrl, 30000, 30000);
+		try {
+			int status = hc.send(reqData, encoding);
+			if (200 == status) {
+				String resultString = hc.getResult();
+				if (null != resultString && !"".equals(resultString)) {
+					// 将返回结果转换为map
+					Map<String,String> tmpRspData  = SDKUtil.convertResultStringToMap(resultString);
+					rspData.putAll(tmpRspData);
+				}
+			}else{
+				logger.info("返回http状态码["+status+"],请检查请求报文或者请求地址是否正确");
+			}
+		} catch (Exception e) {
+			logger.error(e.getMessage(), e);
+		}
+		return rspData;
+	}
+	
+	/**
+	 * 功能:http Get方法 便民缴费产品中使用<br>
+	 * @param reqUrl
+	 * @param encoding
+	 * @return
+	 */
+	public static String get(String reqUrl,String encoding) {
+		
+		logger.info("请求银联地址:" + reqUrl);
+		//发送后台请求数据
+		HttpClient hc = new HttpClient(reqUrl, 30000, 30000);
+		try {
+			int status = hc.sendGet(encoding);
+			if (200 == status) {
+				String resultString = hc.getResult();
+				if (null != resultString && !"".equals(resultString)) {
+					return resultString;
+				}
+			}else{
+				logger.info("返回http状态码["+status+"],请检查请求报文或者请求地址是否正确");
+			}
+		} catch (Exception e) {
+			logger.error(e.getMessage(), e);
+		}
+		return null;
+	}
+	
+	
+	/**
+	 * 功能:前台交易构造HTTP POST自动提交表单<br>
+	 * @param action 表单提交地址<br>
+	 * @param hiddens 以MAP形式存储的表单键值<br>
+	 * @param encoding 上送请求报文域encoding字段的值<br>
+	 * @return 构造好的HTTP POST交易表单<br>
+	 */
+	public static String createAutoFormHtml(String reqUrl, Map<String, String> hiddens,String encoding) {
+		StringBuffer sf = new StringBuffer();
+		sf.append("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset="+encoding+"\"/></head><body>");
+		sf.append("<form id = \"pay_form\" action=\"" + reqUrl
+				+ "\" method=\"post\">");
+		if (null != hiddens && 0 != hiddens.size()) {
+			Set<Entry<String, String>> set = hiddens.entrySet();
+			Iterator<Entry<String, String>> it = set.iterator();
+			while (it.hasNext()) {
+				Entry<String, String> ey = it.next();
+				String key = ey.getKey();
+				String value = ey.getValue();
+				sf.append("<input type=\"hidden\" name=\"" + key + "\" id=\""
+						+ key + "\" value=\"" + value + "\"/>");
+			}
+		}
+		sf.append("</form>");
+		sf.append("</body>");
+		sf.append("<script type=\"text/javascript\">");
+		sf.append("document.all.pay_form.submit();");
+		sf.append("</script>");
+		sf.append("</html>");
+		return sf.toString();
+	}
+
+	
+	/**
+	 * 功能:将批量文件内容使用DEFLATE压缩算法压缩,Base64编码生成字符串并返回<br>
+	 * 适用到的交易:批量代付,批量代收,批量退货<br>
+	 * @param filePath 批量文件-全路径文件名<br>
+	 * @return
+	 */
+	public static String enCodeFileContent(String filePath,String encoding){
+		String baseFileContent = "";
+		
+		File file = new File(filePath);
+		if (!file.exists()) {
+			try {
+				file.createNewFile();
+			} catch (IOException e) {
+				logger.error(e.getMessage(), e);
+			}
+		}
+		InputStream in = null;
+		try {
+			in = new FileInputStream(file);
+			int fl = in.available();
+			if (null != in) {
+				byte[] s = new byte[fl];
+				in.read(s, 0, fl);
+				// 压缩编码.
+				baseFileContent = new String(SecureUtil.base64Encode(SecureUtil.deflater(s)),encoding);
+			}
+		} catch (Exception e) {
+			logger.error(e.getMessage(), e);
+		} finally {
+			if (null != in) {
+				try {
+					in.close();
+				} catch (IOException e) {
+					logger.error(e.getMessage(), e);
+				}
+			}
+		}
+		return baseFileContent;
+	}
+	
+	/**
+	 * 功能:解析交易返回的fileContent字符串并落地 ( 解base64,解DEFLATE压缩并落地)<br>
+	 * 适用到的交易:对账文件下载,批量交易状态查询<br>
+	 * @param resData 返回报文map<br>
+	 * @param fileDirectory 落地的文件目录(绝对路径)
+	 * @param encoding 上送请求报文域encoding字段的值<br>	
+	 */
+	public static String deCodeFileContent(Map<String, String> resData,String fileDirectory,String encoding) {
+		// 解析返回文件
+		String fileContent = resData.get(SDKConstants.param_fileContent);
+		String filePath = null;
+		if (null != fileContent && !"".equals(fileContent)) {
+			try {
+				byte[] fileArray = SecureUtil.inflater(SecureUtil
+						.base64Decode(fileContent.getBytes(encoding)));
+				if (SDKUtil.isEmpty(resData.get("fileName"))) {
+					filePath = fileDirectory + File.separator + resData.get("merId")
+							+ "_" + resData.get("batchNo") + "_"
+							+ resData.get("txnTime") + ".txt";
+				} else {
+					filePath = fileDirectory + File.separator + resData.get("fileName");
+				}
+				File file = new File(filePath);
+				if (file.exists()) {
+					file.delete();
+				}
+				file.createNewFile();
+				FileOutputStream out = new FileOutputStream(file);
+				out.write(fileArray, 0, fileArray.length);
+				out.flush();
+				out.close();
+			} catch (UnsupportedEncodingException e) {
+				logger.error(e.getMessage(), e);
+			} catch (IOException e) {
+				logger.error(e.getMessage(), e);
+			}
+		}
+		return filePath;
+	}
+
+	/**
+	 * 将结果文件内容 转换成明文字符串:解base64,解压缩<br>
+	 * 适用到的交易:批量交易状态查询<br>
+	 * @param fileContent 批量交易状态查询返回的文件内容<br>
+	 * @return 内容明文<br>
+	 */
+	public static String getFileContent(String fileContent,String encoding){
+		String fc = "";
+		try {
+			fc = new String(SecureUtil.inflater(SecureUtil.base64Decode(fileContent.getBytes())),encoding);
+		} catch (UnsupportedEncodingException e) {
+			logger.error(e.getMessage(), e);
+		} catch (IOException e) {
+			logger.error(e.getMessage(), e);
+		}
+		return fc;
+	}
+	
+	
+	/**
+	 * 持卡人信息域customerInfo构造<br>
+	 * 说明:不勾选对敏感信息加密权限使用旧的构造customerInfo域方式,不对敏感信息进行加密(对 phoneNo,cvn2, expired不加密),但如果送pin的话则加密<br>
+	 * @param customerInfoMap 信息域请求参数 key送域名value送值,必送<br>
+	 *        例如:customerInfoMap.put("certifTp", "01");					//证件类型<br>
+				  customerInfoMap.put("certifId", "341126197709218366");	//证件号码<br>
+				  customerInfoMap.put("customerNm", "互联网");				//姓名<br>
+				  customerInfoMap.put("phoneNo", "13552535506");			//手机号<br>
+				  customerInfoMap.put("smsCode", "123456");					//短信验证码<br>
+				  customerInfoMap.put("pin", "111111");						//密码(加密)<br>
+				  customerInfoMap.put("cvn2", "123");           			//卡背面的cvn2三位数字(不加密)<br>
+				  customerInfoMap.put("expired", "1711");  				    //有效期 年在前月在后(不加密)<br>
+	 * @param accNo  customerInfoMap送了密码那么卡号必送,如果customerInfoMap未送密码pin,此字段可以不送<br>
+	 * @param encoding 上送请求报文域encoding字段的值<br>				  
+	 * @return base64后的持卡人信息域字段<br>
+	 */
+	public static String getCustomerInfo(Map<String,String> customerInfoMap,String accNo,String encoding) {
+		
+		if(customerInfoMap.isEmpty())
+			return "{}";
+		StringBuffer sf = new StringBuffer("{");
+		for(Iterator<String> it = customerInfoMap.keySet().iterator(); it.hasNext();){
+			String key = it.next();
+			String value = customerInfoMap.get(key);
+			if(key.equals("pin")){
+				if(null == accNo || "".equals(accNo.trim())){
+					logger.info("送了密码(PIN),必须在getCustomerInfo参数中上传卡号");
+					throw new RuntimeException("加密PIN没送卡号无法后续处理");
+				}else{
+					value = encryptPin(accNo,value,encoding);
+				}
+			}
+			sf.append(key).append(SDKConstants.EQUAL).append(value);
+			if(it.hasNext())
+				sf.append(SDKConstants.AMPERSAND);
+		}
+		String customerInfo = sf.append("}").toString();
+		logger.info("组装的customerInfo明文:"+customerInfo);
+		try {
+			return new String(SecureUtil.base64Encode(sf.toString().getBytes(
+					encoding)),encoding);
+		} catch (UnsupportedEncodingException e) {
+			logger.error(e.getMessage(), e);
+		} catch (IOException e) {
+			logger.error(e.getMessage(), e);
+		}
+		return customerInfo;
+	}
+	
+	/**
+	 * 持卡人信息域customerInfo构造,勾选对敏感信息加密权限 适用新加密规范,对pin和phoneNo,cvn2,expired加密 <br>
+	 * 适用到的交易: <br>
+	 * @param customerInfoMap 信息域请求参数 key送域名value送值,必送 <br>
+	 *        例如:customerInfoMap.put("certifTp", "01");					//证件类型 <br>
+				  customerInfoMap.put("certifId", "341126197709218366");	//证件号码 <br>
+				  customerInfoMap.put("customerNm", "互联网");				//姓名 <br>
+				  customerInfoMap.put("smsCode", "123456");					//短信验证码 <br>
+				  customerInfoMap.put("pin", "111111");						//密码(加密) <br>
+				  customerInfoMap.put("phoneNo", "13552535506");			//手机号(加密) <br>
+				  customerInfoMap.put("cvn2", "123");           			//卡背面的cvn2三位数字(加密) <br>
+				  customerInfoMap.put("expired", "1711");  				    //有效期 年在前月在后(加密) <br>
+	 * @param accNo  customerInfoMap送了密码那么卡号必送,如果customerInfoMap未送密码PIN,此字段可以不送<br>
+	 * @param encoding 上送请求报文域encoding字段的值
+	 * @return base64后的持卡人信息域字段 <br>
+	 */
+	public static String getCustomerInfoWithEncrypt(Map<String,String> customerInfoMap,String accNo,String encoding) {
+		if(customerInfoMap.isEmpty())
+			return "{}";
+		StringBuffer sf = new StringBuffer("{");
+		//敏感信息加密域
+		StringBuffer encryptedInfoSb = new StringBuffer("");
+		
+		for(Iterator<String> it = customerInfoMap.keySet().iterator(); it.hasNext();){
+			String key = it.next();
+			String value = customerInfoMap.get(key);
+			if(key.equals("phoneNo") || key.equals("cvn2") || key.equals("expired")){
+				encryptedInfoSb.append(key).append(SDKConstants.EQUAL).append(value).append(SDKConstants.AMPERSAND);
+			}else{
+				if(key.equals("pin")){
+					if(null == accNo || "".equals(accNo.trim())){
+						logger.info("送了密码(PIN),必须在getCustomerInfoWithEncrypt参数中上传卡号");
+						throw new RuntimeException("加密PIN没送卡号无法后续处理");
+					}else{
+						value = encryptPin(accNo,value,encoding);
+					}
+				}
+				sf.append(key).append(SDKConstants.EQUAL).append(value).append(SDKConstants.AMPERSAND);
+			}
+		}
+		
+		if(!encryptedInfoSb.toString().equals("")){
+			encryptedInfoSb.setLength(encryptedInfoSb.length()-1);//去掉最后一个&符号
+			logger.info("组装的customerInfo encryptedInfo明文:"+ encryptedInfoSb.toString());
+			sf.append("encryptedInfo").append(SDKConstants.EQUAL).append(encryptData(encryptedInfoSb.toString(), encoding));
+		}else{
+			sf.setLength(sf.length()-1);
+		}
+		
+		String customerInfo = sf.append("}").toString();
+		logger.info("组装的customerInfo明文:"+customerInfo);
+		try {
+			return new String(SecureUtil.base64Encode(sf.toString().getBytes(encoding)),encoding);
+		} catch (UnsupportedEncodingException e) {
+			logger.error(e.getMessage(), e);
+		} catch (IOException e) {
+			logger.error(e.getMessage(), e);
+		}
+		return customerInfo;
+	}
+	
+	/**
+	 * 解析返回报文(后台通知)中的customerInfo域:解base64,如果带敏感信息加密 encryptedInfo 则将其解密并将 encryptedInfo中的域放到customerInfoMap返回
+	 * @param customerInfo
+	 * @param encoding
+	 * @return
+	 */
+	public static Map<String,String> parseCustomerInfo(String customerInfo,String encoding){
+		Map<String,String> customerInfoMap = null;
+		try {
+				byte[] b = SecureUtil.base64Decode(customerInfo.getBytes(encoding));
+				String customerInfoNoBase64 = new String(b,encoding);
+				logger.info("解base64后===>" +customerInfoNoBase64);
+				//去掉前后的{}
+				customerInfoNoBase64 = customerInfoNoBase64.substring(1, customerInfoNoBase64.length()-1);
+				customerInfoMap = SDKUtil.parseQString(customerInfoNoBase64);
+				if(customerInfoMap.containsKey("encryptedInfo")){
+					String encInfoStr = customerInfoMap.get("encryptedInfo");
+					customerInfoMap.remove("encryptedInfo");
+					String encryptedInfoStr = decryptData(encInfoStr, encoding);
+					Map<String,String> encryptedInfoMap = SDKUtil.parseQString(encryptedInfoStr);
+					customerInfoMap.putAll(encryptedInfoMap);
+				}
+			} catch (UnsupportedEncodingException e) {
+				logger.error(e.getMessage(), e);
+			} catch (IOException e) {
+				logger.error(e.getMessage(), e);
+			}
+		return customerInfoMap;
+	}
+	
+	
+	
+	/**
+	 * 密码加密并做base64<br>
+	 * @param accNo 卡号<br>
+	 * @param pwd 密码<br>
+	 * @param encoding<br>
+	 * @return 加密的内容<br>
+	 */
+	public static String encryptPin(String accNo, String pwd, String encoding) {
+		return SecureUtil.EncryptPin(pwd, accNo, encoding, CertUtil
+				.getEncryptCertPublicKey());
+	}
+	
+	/**
+	 * 敏感信息加密并做base64(卡号,手机号,cvn2,有效期)<br>
+	 * @param data 送 phoneNo,cvn2,有效期<br>
+	 * @param encoding<br>
+	 * @return 加密的密文<br>
+	 */
+	public static String encryptData(String data, String encoding) {
+		return SecureUtil.EncryptData(data, encoding, CertUtil
+				.getEncryptCertPublicKey());
+	}
+	
+	/**
+	 * 敏感信息解密
+	 * @param base64EncryptedInfo
+	 * @param encoding
+	 * @return 解密后的明文
+	 */
+	public static String decryptData(String base64EncryptedInfo, String encoding) {
+		return SecureUtil.DecryptedData(base64EncryptedInfo, encoding, CertUtil
+				.getSignCertPrivateKey());
+	}
+	
+	/**
+	 * 加密磁道信息,使用公钥文件<br>
+	 * @param trackData 待加密磁道数据<br>
+	 * @param encoding 编码格式<br>
+	 * @return 加密的密文<br>
+	 */
+	public static String encryptTrack(String trackData, String encoding) {
+		return SecureUtil.EncryptData(trackData, encoding,
+				CertUtil.getEncryptTrackPublicKey());
+	}
+	
+	/**
+	 * 加密磁道信息,使用模和指数<br>
+	 * @param trackData 待加密磁道数据<br>
+	 * @param encoding 编码格式<br>
+	 * @param modulus 模<br>
+	 * @param exponent 指数<br>
+	 * @return 加密的密文<br>
+	 */
+	public static String encryptTrack(String trackData, String encoding,
+			String modulus, String exponent) {
+		return SecureUtil.EncryptData(trackData, encoding,
+				CertUtil.getEncryptTrackCertPublicKey(modulus, exponent));
+	}
+	
+	/**
+	 * 获取敏感信息加密证书的物理序列号<br>
+	 * @return
+	 */
+	public static String getEncryptCertId(){
+		return CertUtil.getEncryptCertId();
+	}
+	
+	/**
+	 * 对字符串做base64
+	 * @param rawStr
+	 * @param encoding
+	 * @return
+	 * @throws IOException
+	 */
+	public static String base64Encode(String rawStr,String encoding) throws IOException{
+		byte [] rawByte = rawStr.getBytes(encoding);
+		return new String(SecureUtil.base64Encode(rawByte),encoding);
+	}
+	/**
+	 * 对base64的字符串解base64
+	 * @param base64Str
+	 * @param encoding
+	 * @return
+	 * @throws IOException
+	 */
+	public static String base64Decode(String base64Str,String encoding) throws IOException{
+		byte [] rawByte = base64Str.getBytes(encoding);
+		return new String(SecureUtil.base64Decode(rawByte),encoding);	
+	}
+
+}

+ 121 - 0
src/main/java/com/itstyle/modules/unionpay/util/BaseHttpSSLSocketFactory.java

@@ -0,0 +1,121 @@
+/**
+ *
+ * Licensed Property to China UnionPay Co., Ltd.
+ * 
+ * (C) Copyright of China UnionPay Co., Ltd. 2010
+ *     All Rights Reserved.
+ *
+ * 
+ * Modification History:
+ * =============================================================================
+ *   Author         Date          Description
+ *   ------------ ---------- ---------------------------------------------------
+ *   xshu       2014-05-28     SSLSocket 链接工具类(用于https)
+ * =============================================================================
+ */
+package com.itstyle.modules.unionpay.util;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class BaseHttpSSLSocketFactory extends SSLSocketFactory {
+	private static final Logger logger = LoggerFactory.getLogger(BaseHttpSSLSocketFactory.class);
+	private SSLContext getSSLContext() {
+		return createEasySSLContext();
+	}
+
+	@Override
+	public Socket createSocket(InetAddress arg0, int arg1, InetAddress arg2,
+			int arg3) throws IOException {
+		return getSSLContext().getSocketFactory().createSocket(arg0, arg1,
+				arg2, arg3);
+	}
+
+	@Override
+	public Socket createSocket(String arg0, int arg1, InetAddress arg2, int arg3)
+			throws IOException, UnknownHostException {
+		return getSSLContext().getSocketFactory().createSocket(arg0, arg1,
+				arg2, arg3);
+	}
+
+	@Override
+	public Socket createSocket(InetAddress arg0, int arg1) throws IOException {
+		return getSSLContext().getSocketFactory().createSocket(arg0, arg1);
+	}
+
+	@Override
+	public Socket createSocket(String arg0, int arg1) throws IOException,
+			UnknownHostException {
+		return getSSLContext().getSocketFactory().createSocket(arg0, arg1);
+	}
+
+	@Override
+	public String[] getSupportedCipherSuites() {
+		return null;
+	}
+
+	@Override
+	public String[] getDefaultCipherSuites() {
+		return null;
+	}
+
+	@Override
+	public Socket createSocket(Socket arg0, String arg1, int arg2, boolean arg3)
+			throws IOException {
+		return getSSLContext().getSocketFactory().createSocket(arg0, arg1,
+				arg2, arg3);
+	}
+
+	private SSLContext createEasySSLContext() {
+		try {
+			SSLContext context = SSLContext.getInstance("SSL");
+			context.init(null,
+					new TrustManager[] { MyX509TrustManager.manger }, null);
+			return context;
+		} catch (Exception e) {
+			logger.error(e.getMessage(), e);
+			return null;
+		}
+	}
+
+	public static class MyX509TrustManager implements X509TrustManager {
+
+		static MyX509TrustManager manger = new MyX509TrustManager();
+
+		public MyX509TrustManager() {
+		}
+
+		public X509Certificate[] getAcceptedIssuers() {
+			return null;
+		}
+
+		public void checkClientTrusted(X509Certificate[] chain, String authType) {
+		}
+
+		public void checkServerTrusted(X509Certificate[] chain, String authType) {
+		}
+	}
+
+	/**
+	 * 解决由于服务器证书问题导致HTTPS无法访问的情况 PS:HTTPS hostname wrong: should be <localhost>
+	 */
+	public static class TrustAnyHostnameVerifier implements HostnameVerifier {
+		public boolean verify(String hostname, SSLSession session) {
+			// 直接返回true
+			return true;
+		}
+	}
+}

+ 730 - 0
src/main/java/com/itstyle/modules/unionpay/util/CertUtil.java

@@ -0,0 +1,730 @@
+/**
+ *
+ * Licensed Property to China UnionPay Co., Ltd.
+ * 
+ * (C) Copyright of China UnionPay Co., Ltd. 2010
+ *     All Rights Reserved.
+ *
+ * 
+ * Modification History:
+ * =============================================================================
+ *   Author         Date          Description
+ *   ------------ ---------- ---------------------------------------------------
+ *   xshu       2014-05-28       证书工具类.
+ * =============================================================================
+ */
+package com.itstyle.modules.unionpay.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import static com.itstyle.modules.unionpay.util.SDKUtil.isEmpty;
+public class CertUtil {
+	private static final Logger logger = LoggerFactory.getLogger(CertUtil.class);
+	/** 证书容器. */
+	private static KeyStore keyStore = null;
+	/** 密码加密证书 */
+	private static X509Certificate encryptCert = null;
+//	/** 磁道加密证书 */
+//	private static X509Certificate encryptTrackCert = null;
+	/** 磁道加密公钥 */
+	private static PublicKey encryptTrackKey = null;
+	
+	/** 验证签名证书. */
+	private static X509Certificate validateCert = null;
+	/** 验签证书存储Map. */
+	private static Map<String, X509Certificate> certMap = new HashMap<String, X509Certificate>();
+	/** 根据传入证书文件路径和密码读取指定的证书容器.(一种线程安全的实现方式) */
+	private final static ThreadLocal<KeyStore> certKeyStoreLocal = new ThreadLocal<KeyStore>();
+	/** 基于Map存储多商户RSA私钥 */
+	private final static Map<String, KeyStore> certKeyStoreMap = new ConcurrentHashMap<String, KeyStore>();
+	
+	static {
+		init();
+	}
+
+	/**
+	 * 添加签名,验签,加密算法提供者
+	 */
+	private static void addProvider(){
+		if (Security.getProvider("BC") == null) {
+			logger.info("add BC provider");
+			Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
+		} else {
+			Security.removeProvider("BC"); //解决eclipse调试时tomcat自动重新加载时,BC存在不明原因异常的问题。
+			Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
+			logger.info("re-add BC provider");
+		}
+		printSysInfo();
+	}
+	
+	/**
+	 * 初始化所有证书.
+	 */
+	public static void init() {
+		addProvider();
+		if (SDKConstants.TRUE_STRING.equals(SDKConfig.getConfig()
+				.getSingleMode())) {
+			// 单证书模式,初始化配置文件中的签名证书
+			initSignCert();
+		}
+		initEncryptCert();// 初始化加密公钥证书
+		initTrackKey();
+		initValidateCertFromDir();// 初始化所有的验签证书
+	}
+
+	/**
+	 * 加载签名证书
+	 */
+	public static void initSignCert() {
+		if (null != keyStore) {
+			keyStore = null;
+		}
+		try {
+			keyStore = getKeyInfo(SDKConfig.getConfig().getSignCertPath(),
+					SDKConfig.getConfig().getSignCertPwd(), SDKConfig
+							.getConfig().getSignCertType());
+			logger.info("InitSignCert Successful. CertId=["
+					+ getSignCertId() + "]");
+		} catch (IOException e) {
+			logger.error("InitSignCert Error", e);
+		}
+	}
+
+	/**
+	 * 根据传入的证书文件路径和证书密码加载指定的签名证书
+	 * @deprecated
+	 */
+	public static void initSignCert(String certFilePath, String certPwd) {
+		logger.info("加载证书文件[" + certFilePath + "]和证书密码[" + certPwd
+				+ "]的签名证书开始.");
+		certKeyStoreLocal.remove();
+		File files = new File(certFilePath);
+		if (!files.exists()) {
+			logger.info("证书文件不存在,初始化签名证书失败.");
+			return;
+		}
+		try {
+			certKeyStoreLocal.set(getKeyInfo(certFilePath, certPwd, "PKCS12"));
+		} catch (IOException e) {
+			logger.error("加载签名证书失败", e);
+		}
+		logger.info("加载证书文件[" + certFilePath + "]和证书密码[" + certPwd
+				+ "]的签名证书结束.");
+	}
+	
+
+	/**
+	 * 加载RSA签名证书
+	 * 
+	 * @param certFilePath
+	 * @param certPwd
+	 */
+	public static void loadRsaCert(String certFilePath, String certPwd) {
+		KeyStore keyStore = null;
+		try {
+			keyStore = getKeyInfo(certFilePath, certPwd, "PKCS12");
+			certKeyStoreMap.put(certFilePath, keyStore);
+			logger.info("LoadRsaCert Successful");
+		} catch (IOException e) {
+			logger.error("LoadRsaCert Error", e);
+		}
+	}
+
+	/**
+	 * 加载密码加密证书 目前支持有两种加密
+	 */
+	private static void initEncryptCert() {
+		logger.info("加载敏感信息加密证书==>"+SDKConfig.getConfig().getEncryptCertPath());
+		if (!isEmpty(SDKConfig.getConfig().getEncryptCertPath())) {
+			encryptCert = initCert(SDKConfig.getConfig().getEncryptCertPath());
+			logger.info("LoadEncryptCert Successful");
+		} else {
+			logger.info("WARN: acpsdk.encryptCert.path is empty");
+		}
+//		if (!isEmpty(SDKConfig.getConfig().getEncryptTrackCertPath())) {
+//			encryptTrackCert = initCert(SDKConfig.getConfig()
+//					.getEncryptTrackCertPath());
+//			logger.info("LoadEncryptTrackCert Successful");
+//		} else {
+//			logger.info("WARN: acpsdk.encryptTrackCert.path is empty");
+//		}
+	}
+	
+	/**
+	 * 加载磁道公钥
+	 */
+	private static void initTrackKey() {
+		if (!isEmpty(SDKConfig.getConfig().getEncryptTrackKeyModulus())
+				&& !isEmpty(SDKConfig.getConfig().getEncryptTrackKeyExponent())) {
+			encryptTrackKey = SecureUtil.getPublicKey(SDKConfig.getConfig().getEncryptTrackKeyModulus(), 
+					SDKConfig.getConfig().getEncryptTrackKeyExponent());
+			logger.info("LoadEncryptTrackKey Successful");
+		} else {
+			logger.info("WARN: acpsdk.encryptTrackKey.modulus or acpsdk.encryptTrackKey.exponent is empty");
+		}
+	}
+	/**
+	 * 
+	 * @param path
+	 * @return
+	 */
+	private static X509Certificate initCert(String path) {
+		X509Certificate encryptCertTemp = null;
+		CertificateFactory cf = null;
+		FileInputStream in = null;
+		try {
+			cf = CertificateFactory.getInstance("X.509", "BC");
+			in = new FileInputStream(path);
+			encryptCertTemp = (X509Certificate) cf.generateCertificate(in);
+			// 打印证书加载信息,供测试阶段调试
+			logger.info("[" + path + "][CertId="
+					+ encryptCertTemp.getSerialNumber().toString() + "]");
+		} catch (CertificateException e) {
+			logger.error("InitCert Error", e);
+		} catch (FileNotFoundException e) {
+			logger.error("InitCert Error File Not Found", e);
+		} catch (NoSuchProviderException e) {
+			logger.error("LoadVerifyCert Error No BC Provider", e);
+		} finally {
+			if (null != in) {
+				try {
+					in.close();
+				} catch (IOException e) {
+					logger.error("initCert方法",e);
+				}
+			}
+		}
+		return encryptCertTemp;
+	}
+
+	/**
+	 * 从指定目录下加载验证签名证书
+	 * 
+	 */
+	private static void initValidateCertFromDir() {
+		certMap.clear();
+		String dir = SDKConfig.getConfig().getValidateCertDir();
+		logger.info("加载验证签名证书目录==>" + dir);
+		if (isEmpty(dir)) {
+			logger.info("ERROR: acpsdk.validateCert.dir is empty");
+			return;
+		}
+		CertificateFactory cf = null;
+		FileInputStream in = null;
+		try {
+			cf = CertificateFactory.getInstance("X.509", "BC");
+			File fileDir = new File(dir);
+			File[] files = fileDir.listFiles(new CerFilter());
+			for (int i = 0; i < files.length; i++) {
+				File file = files[i];
+				in = new FileInputStream(file.getAbsolutePath());
+				validateCert = (X509Certificate) cf.generateCertificate(in);
+				certMap.put(validateCert.getSerialNumber().toString(),
+						validateCert);
+				// 打印证书加载信息,供测试阶段调试
+				logger.info("[" + file.getAbsolutePath() + "][CertId="
+						+ validateCert.getSerialNumber().toString() + "]");
+			}
+			logger.info("LoadVerifyCert Successful");
+		} catch (CertificateException e) {
+			logger.error("LoadVerifyCert Error", e);
+		} catch (FileNotFoundException e) {
+			logger.error("LoadVerifyCert Error File Not Found", e);
+		} catch (NoSuchProviderException e) {
+			logger.error("LoadVerifyCert Error No BC Provider", e);
+		} finally {
+			if (null != in) {
+				try {
+					in.close();
+				} catch (IOException e) {
+					logger.error("initValidateCertFromDir方法",e);
+				}
+			}
+		}
+	}
+
+
+	/**
+	 * 获取签名证书私钥(单证书模式)
+	 * 
+	 * @return
+	 */
+	public static PrivateKey getSignCertPrivateKey() {
+		try {
+			Enumeration<String> aliasenum = keyStore.aliases();
+			String keyAlias = null;
+			if (aliasenum.hasMoreElements()) {
+				keyAlias = aliasenum.nextElement();
+			}
+			PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias,
+					SDKConfig.getConfig().getSignCertPwd().toCharArray());
+			return privateKey;
+		} catch (KeyStoreException e) {
+			logger.error("getSignCertPrivateKey Error", e);
+			return null;
+		} catch (UnrecoverableKeyException e) {
+			logger.error("getSignCertPrivateKey Error", e);
+			return null;
+		} catch (NoSuchAlgorithmException e) {
+			logger.error("getSignCertPrivateKey Error", e);
+			return null;
+		}
+	}
+	
+
+	/**
+	 * 通过传入证书绝对路径和证书密码获取所对应的签名证书私钥
+	 * 
+	 * @param certPath
+	 *            证书绝对路径
+	 * @param certPwd
+	 *            证书密码
+	 * @return 证书私钥
+	 * 
+	 * @deprecated
+	 */
+	public static PrivateKey getSignCertPrivateKeyByThreadLocal(
+			String certPath, String certPwd) {
+		if (null == certKeyStoreLocal.get()) {
+			// 初始化指定certPath和certPwd的签名证书容器
+			initSignCert(certPath, certPwd);
+		}
+		try {
+			Enumeration<String> aliasenum = certKeyStoreLocal.get().aliases();
+			String keyAlias = null;
+			if (aliasenum.hasMoreElements()) {
+				keyAlias = aliasenum.nextElement();
+			}
+			PrivateKey privateKey = (PrivateKey) certKeyStoreLocal.get()
+					.getKey(keyAlias, certPwd.toCharArray());
+			return privateKey;
+		} catch (Exception e) {
+			logger.error("获取[" + certPath + "]的签名证书的私钥失败", e);
+			return null;
+		}
+	}
+	
+	public static PrivateKey getSignCertPrivateKeyByStoreMap(String certPath,
+			String certPwd) {
+		if (!certKeyStoreMap.containsKey(certPath)) {
+			loadRsaCert(certPath, certPwd);
+		}
+		try {
+			Enumeration<String> aliasenum = certKeyStoreMap.get(certPath)
+					.aliases();
+			String keyAlias = null;
+			if (aliasenum.hasMoreElements()) {
+				keyAlias = aliasenum.nextElement();
+			}
+			PrivateKey privateKey = (PrivateKey) certKeyStoreMap.get(certPath)
+					.getKey(keyAlias, certPwd.toCharArray());
+			return privateKey;
+		} catch (KeyStoreException e) {
+			logger.error("getSignCertPrivateKeyByStoreMap Error", e);
+			return null;
+		} catch (UnrecoverableKeyException e) {
+			logger.error("getSignCertPrivateKeyByStoreMap Error", e);
+			return null;
+		} catch (NoSuchAlgorithmException e) {
+			logger.error("getSignCertPrivateKeyByStoreMap Error", e);
+			return null;
+		}
+	}
+
+	/**
+	 * 获取加密证书公钥.密码加密时需要
+	 * 
+	 * @return
+	 */
+	public static PublicKey getEncryptCertPublicKey() {
+		if (null == encryptCert) {
+			String path = SDKConfig.getConfig().getEncryptCertPath();
+			if (!isEmpty(path)) {
+				encryptCert = initCert(path);
+				return encryptCert.getPublicKey();
+			} else {
+				logger.info("ERROR: acpsdk.encryptCert.path is empty");
+				return null;
+			}
+		} else {
+			return encryptCert.getPublicKey();
+		}
+	}
+	
+	/**
+	 * 获取加密证书公钥.密码加密时需要
+	 * 加密磁道信息证书
+	 * 
+	 * @return
+	 */
+	public static PublicKey getEncryptTrackPublicKey() {
+//		if (null == encryptTrackCert) {
+//			String path = SDKConfig.getConfig().getEncryptTrackCertPath();
+//			if (!isEmpty(path)) {
+//				encryptTrackCert = initCert(path);
+//				return encryptTrackCert.getPublicKey();
+//			} else {
+//				logger.info("ERROR: acpsdk.encryptTrackCert.path is empty");
+//				return null;
+//			}
+//		} else {
+//			return encryptTrackCert.getPublicKey();
+//		}
+		if (null == encryptTrackKey) {
+			initTrackKey();
+		}
+		return encryptTrackKey;
+	}
+
+	/**
+	 * 验证签名证书
+	 * 
+	 * @return 验证签名证书的公钥
+	 */
+	public static PublicKey getValidateKey() {
+		if (null == validateCert) {
+			return null;
+		}
+		return validateCert.getPublicKey();
+	}
+
+	/**
+	 * 通过certId获取证书Map中对应证书的公钥
+	 * 
+	 * @param certId
+	 *            证书物理序号
+	 * @return 通过证书编号获取到的公钥
+	 */
+	public static PublicKey getValidateKey(String certId) {
+		X509Certificate cf = null;
+		if (certMap.containsKey(certId)) {
+			// 存在certId对应的证书对象
+			cf = certMap.get(certId);
+			return cf.getPublicKey();
+		} else {
+			// 不存在则重新Load证书文件目录
+			initValidateCertFromDir();
+			if (certMap.containsKey(certId)) {
+				// 存在certId对应的证书对象
+				cf = certMap.get(certId);
+				return cf.getPublicKey();
+			} else {
+				logger.info("缺少certId=[" + certId + "]对应的验签证书.");
+				return null;
+			}
+		}
+	}
+
+	
+
+	/**
+	 * 获取签名证书中的证书序列号(单证书)
+	 * 
+	 * @return 证书的物理编号
+	 */
+	public static String getSignCertId() {
+		try {
+			Enumeration<String> aliasenum = keyStore.aliases();
+			String keyAlias = null;
+			if (aliasenum.hasMoreElements()) {
+				keyAlias = aliasenum.nextElement();
+			}
+			X509Certificate cert = (X509Certificate) keyStore
+					.getCertificate(keyAlias);
+			return cert.getSerialNumber().toString();
+		} catch (Exception e) {
+			logger.error("getSignCertId Error", e);
+			return null;
+		}
+	}
+
+	/**
+	 * 获取加密证书的证书序列号
+	 * 
+	 * @return
+	 */
+	public static String getEncryptCertId() {
+		if (null == encryptCert) {
+			String path = SDKConfig.getConfig().getEncryptCertPath();
+			if (!isEmpty(path)) {
+				encryptCert = initCert(path);
+				return encryptCert.getSerialNumber().toString();
+			} else {
+				logger.info("ERROR: acpsdk.encryptCert.path is empty");
+				return null;
+			}
+		} else {
+			return encryptCert.getSerialNumber().toString();
+		}
+	}
+	
+	/**
+	 * 获取磁道加密证书的证书序列号
+	 * @deprecated 磁道根本没给证书啊啊啊啊啊啊啊
+	 * @return
+	 */
+	public static String getEncryptTrackCertId() {
+//		if (null == encryptTrackCert) {
+//			String path = SDKConfig.getConfig().getEncryptTrackCertPath();
+//			if (!isEmpty(path)) {
+//				encryptTrackCert = initCert(path);
+//				return encryptTrackCert.getSerialNumber().toString();
+//			} else {
+//				logger.info("ERROR: acpsdk.encryptTrackCert.path is empty");
+//				return null;
+//			}
+//		} else {
+//			return encryptTrackCert.getSerialNumber().toString();
+//		}
+		return "";
+	}
+
+	/**
+	 * 获取签名证书公钥对象
+	 * 
+	 * @return
+	 */
+	public static PublicKey getSignPublicKey() {
+		try {
+			Enumeration<String> aliasenum = keyStore.aliases();
+			String keyAlias = null;
+			if (aliasenum.hasMoreElements()) // we are readin just one
+			// certificate.
+			{
+				keyAlias = (String) aliasenum.nextElement();
+			}
+			Certificate cert = keyStore.getCertificate(keyAlias);
+			PublicKey pubkey = cert.getPublicKey();
+			return pubkey;
+		} catch (Exception e) {
+			logger.error("获取签名证书公钥对象",e);
+			return null;
+		}
+	}
+	
+	
+	/**
+	 * 将证书文件读取为证书存储对象
+	 * 
+	 * @param pfxkeyfile
+	 *            证书文件名
+	 * @param keypwd
+	 *            证书密码
+	 * @param type
+	 *            证书类型
+	 * @return 证书对象
+	 * @throws IOException 
+	 */
+	public static KeyStore getKeyInfo(String pfxkeyfile, String keypwd,
+			String type) throws IOException {
+		logger.info("加载签名证书==>" + pfxkeyfile);
+		FileInputStream fis = null;
+		try {
+			KeyStore ks = KeyStore.getInstance(type, "BC");
+			logger.info("Load RSA CertPath=[" + pfxkeyfile + "],Pwd=["+ keypwd + "],type=["+type+"]");
+			fis = new FileInputStream(pfxkeyfile);
+			char[] nPassword = null;
+			nPassword = null == keypwd || "".equals(keypwd.trim()) ? null: keypwd.toCharArray();
+			if (null != ks) {
+				ks.load(fis, nPassword);
+			}
+			return ks;
+		} catch (Exception e) {
+			if (Security.getProvider("BC") == null) {
+				logger.info("BC Provider not installed.");
+			}
+			logger.error("getKeyInfo Error", e);
+			if ((e instanceof KeyStoreException) && "PKCS12".equals(type)) {
+				Security.removeProvider("BC");
+			}
+			return null;
+		} finally {
+			if(null!=fis)
+				fis.close();
+		}
+	}
+
+	// 打印系统环境信息
+	public static void printSysInfo() {
+		logger.info("================= SYS INFO begin====================");
+		logger.info("os_name:" + System.getProperty("os.name"));
+		logger.info("os_arch:" + System.getProperty("os.arch"));
+		logger.info("os_version:" + System.getProperty("os.version"));
+		logger.info("java_vm_specification_version:"
+				+ System.getProperty("java.vm.specification.version"));
+		logger.info("java_vm_specification_vendor:"
+				+ System.getProperty("java.vm.specification.vendor"));
+		logger.info("java_vm_specification_name:"
+				+ System.getProperty("java.vm.specification.name"));
+		logger.info("java_vm_version:"
+				+ System.getProperty("java.vm.version"));
+		logger.info("java_vm_name:" + System.getProperty("java.vm.name"));
+		logger.info("java.version:" + System.getProperty("java.version"));
+		logger.info("java.vm.vendor=[" + System.getProperty("java.vm.vendor") + "]");
+		logger.info("java.version=[" + System.getProperty("java.version") + "]");
+		
+		printProviders();
+		logger.info("================= SYS INFO end=====================");
+	}
+	
+	public static void printProviders() {
+		logger.info("Providers List:");
+		Provider[] providers = Security.getProviders();
+		for (int i = 0; i < providers.length; i++) {
+			logger.info(i + 1 + "." + providers[i].getName());
+		}
+	}
+
+	/**
+	 * 证书文件过滤器
+	 * 
+	 */
+	static class CerFilter implements FilenameFilter {
+		public boolean isCer(String name) {
+			if (name.toLowerCase().endsWith(".cer")) {
+				return true;
+			} else {
+				return false;
+			}
+		}
+		public boolean accept(File dir, String name) {
+			return isCer(name);
+		}
+	}
+	
+	/**
+	 * <pre>
+	 * 从一个ThreadLocal中获取当前KeyStore中的CertId,
+	 * 如果获取失败则重新初始化这个KeyStore并存入ThreadLocal
+	 * </pre>>
+	 * @deprecated
+	 * @param certPath
+	 * @param certPwd
+	 * @return
+	 */
+	public static String getCertIdByThreadLocal(String certPath, String certPwd) {
+		// 初始化指定certPath和certPwd的签名证书容器
+		initSignCert(certPath, certPwd);
+		try {
+			Enumeration<String> aliasenum = certKeyStoreLocal.get().aliases();
+			String keyAlias = null;
+			if (aliasenum.hasMoreElements()) {
+				keyAlias = aliasenum.nextElement();
+			}
+			X509Certificate cert = (X509Certificate) certKeyStoreLocal.get()
+					.getCertificate(keyAlias);
+			return cert.getSerialNumber().toString();
+		} catch (Exception e) {
+			logger.error("获取签名证书的序列号失败", e);
+			return "";
+		}
+	}
+	
+	
+	public static String getCertIdByKeyStoreMap(String certPath, String certPwd) {
+		if (!certKeyStoreMap.containsKey(certPath)) {
+			// 缓存中未查询到,则加载RSA证书
+			loadRsaCert(certPath, certPwd);
+		}
+		return getCertIdIdByStore(certKeyStoreMap.get(certPath));
+	}
+
+	private static String getCertIdIdByStore(KeyStore keyStore) {
+		Enumeration<String> aliasenum = null;
+		try {
+			aliasenum = keyStore.aliases();
+			String keyAlias = null;
+			if (aliasenum.hasMoreElements()) {
+				keyAlias = aliasenum.nextElement();
+			}
+			X509Certificate cert = (X509Certificate) keyStore
+					.getCertificate(keyAlias);
+			return cert.getSerialNumber().toString();
+		} catch (KeyStoreException e) {
+			logger.error("getCertIdIdByStore Error", e);
+			return null;
+		}
+	}
+	
+
+	/**
+	 * 获取证书容器
+	 * 
+	 * @return
+	 */
+	public static Map<String, X509Certificate> getCertMap() {
+		return certMap;
+	}
+
+	/**
+	 * 设置证书容器
+	 * 
+	 * @param certMap
+	 */
+	public static void setCertMap(Map<String, X509Certificate> certMap) {
+		CertUtil.certMap = certMap;
+	}
+	
+	/**
+	 * 使用模和指数生成RSA公钥 注意:此代码用了默认补位方式,为RSA/None/PKCS1Padding,不同JDK默认的补位方式可能不同
+	 * 
+	 * @param modulus
+	 *            模
+	 * @param exponent
+	 *            指数
+	 * @return
+	 */
+	public static PublicKey getPublicKey(String modulus, String exponent) {
+		try {
+			BigInteger b1 = new BigInteger(modulus);
+			BigInteger b2 = new BigInteger(exponent);
+			KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
+			RSAPublicKeySpec keySpec = new RSAPublicKeySpec(b1, b2);
+			return keyFactory.generatePublic(keySpec);
+		} catch (Exception e) {
+			logger.error("构造RSA公钥失败:",e);
+			return null;
+		}
+	}
+	
+	/**
+	 * 使用模和指数的方式获取公钥对象
+	 * 
+	 * @return
+	 */
+	public static PublicKey getEncryptTrackCertPublicKey(String modulus,
+			String exponent) {
+		if (SDKUtil.isEmpty(modulus) || SDKUtil.isEmpty(exponent)) {
+			logger.info("使用模和指数的方式获取公钥对象[modulus] OR [exponent] invalid");
+			return null;
+		}
+		return getPublicKey(modulus, exponent);
+	}
+	
+}

+ 306 - 0
src/main/java/com/itstyle/modules/unionpay/util/HttpClient.java

@@ -0,0 +1,306 @@
+/**
+ *
+ * Licensed Property to China UnionPay Co., Ltd.
+ * 
+ * (C) Copyright of China UnionPay Co., Ltd. 2010
+ *     All Rights Reserved.
+ *
+ * 
+ * Modification History:
+ * =============================================================================
+ *   Author         Date          Description
+ *   ------------ ---------- ---------------------------------------------------
+ *   xshu       2014-05-28       HTTP通信工具类
+ * =============================================================================
+ */
+package com.itstyle.modules.unionpay.util;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.ProtocolException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.net.ssl.HttpsURLConnection;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import com.itstyle.modules.unionpay.util.BaseHttpSSLSocketFactory.TrustAnyHostnameVerifier;
+public class HttpClient {
+	private static final Logger logger = LoggerFactory.getLogger(HttpClient.class);
+	/**
+	 * 目标地址
+	 */
+	private URL url;
+
+	/**
+	 * 通信连接超时时间
+	 */
+	private int connectionTimeout;
+
+	/**
+	 * 通信读超时时间
+	 */
+	private int readTimeOut;
+
+	/**
+	 * 通信结果
+	 */
+	private String result;
+
+	/**
+	 * 获取通信结果
+	 * @return
+	 */
+	public String getResult() {
+		return result;
+	}
+
+	/**
+	 * 设置通信结果
+	 * @param result
+	 */
+	public void setResult(String result) {
+		this.result = result;
+	}
+
+	/**
+	 * 构造函数
+	 * @param url 目标地址
+	 * @param connectionTimeout HTTP连接超时时间
+	 * @param readTimeOut HTTP读写超时时间
+	 */
+	public HttpClient(String url, int connectionTimeout, int readTimeOut) {
+		try {
+			this.url = new URL(url);
+			this.connectionTimeout = connectionTimeout;
+			this.readTimeOut = readTimeOut;
+		} catch (MalformedURLException e) {
+			logger.error(e.getMessage(), e);
+		}
+	}
+
+	/**
+	 * 发送信息到服务端
+	 * @param data
+	 * @param encoding
+	 * @return
+	 * @throws Exception
+	 */
+	public int send(Map<String, String> data, String encoding) throws Exception {
+		try {
+			HttpURLConnection httpURLConnection = createConnection(encoding);
+			if(null == httpURLConnection){
+				throw new Exception("创建联接失败");
+			}
+			String sendData = this.getRequestParamString(data, encoding);
+			logger.info("请求报文:[" + sendData + "]");
+			this.requestServer(httpURLConnection, sendData,
+					encoding);
+			this.result = this.response(httpURLConnection, encoding);
+			logger.info("同步返回报文:[" + result + "]");
+			return httpURLConnection.getResponseCode();
+		} catch (Exception e) {
+			throw e;
+		}
+	}
+
+	/**
+	 * 发送信息到服务端 GET方式
+	 * @param data
+	 * @param encoding
+	 * @return
+	 * @throws Exception
+	 */
+	public int sendGet(String encoding) throws Exception {
+		try {
+			HttpURLConnection httpURLConnection = createConnectionGet(encoding);
+			if(null == httpURLConnection){
+				throw new Exception("创建联接失败");
+			}
+			this.result = this.response(httpURLConnection, encoding);
+			logger.info("同步返回报文:[" + result + "]");
+			return httpURLConnection.getResponseCode();
+		} catch (Exception e) {
+			throw e;
+		}
+	}
+
+	
+	/**
+	 * HTTP Post发送消息
+	 *
+	 * @param connection
+	 * @param message
+	 * @throws IOException
+	 */
+	private void requestServer(final URLConnection connection, String message, String encoder)
+			throws Exception {
+		PrintStream out = null;
+		try {
+			connection.connect();
+			out = new PrintStream(connection.getOutputStream(), false, encoder);
+			out.print(message);
+			out.flush();
+		} catch (Exception e) {
+			throw e;
+		} finally {
+			if (null != out) {
+				out.close();
+			}
+		}
+	}
+
+	/**
+	 * 显示Response消息
+	 *
+	 * @param connection
+	 * @param CharsetName
+	 * @return
+	 * @throws URISyntaxException
+	 * @throws IOException
+	 */
+	private String response(final HttpURLConnection connection, String encoding)
+			throws URISyntaxException, IOException, Exception {
+		InputStream in = null;
+		StringBuilder sb = new StringBuilder(1024);
+		BufferedReader br = null;
+		try {
+			if (200 == connection.getResponseCode()) {
+				in = connection.getInputStream();
+				sb.append(new String(read(in), encoding));
+			} else {
+				in = connection.getErrorStream();
+				sb.append(new String(read(in), encoding));
+			}
+			logger.info("HTTP Return Status-Code:["
+					+ connection.getResponseCode() + "]");
+			return sb.toString();
+		} catch (Exception e) {
+			throw e;
+		} finally {
+			if (null != br) {
+				br.close();
+			}
+			if (null != in) {
+				in.close();
+			}
+			if (null != connection) {
+				connection.disconnect();
+			}
+		}
+	}
+	
+	public static byte[] read(InputStream in) throws IOException {
+		byte[] buf = new byte[1024];
+		int length = 0;
+		ByteArrayOutputStream bout = new ByteArrayOutputStream();
+		while ((length = in.read(buf, 0, buf.length)) > 0) {
+			bout.write(buf, 0, length);
+		}
+		bout.flush();
+		return bout.toByteArray();
+	}
+	
+	/**
+	 * 创建连接
+	 *
+	 * @return
+	 * @throws ProtocolException
+	 */
+	private HttpURLConnection createConnection(String encoding) throws ProtocolException {
+		HttpURLConnection httpURLConnection = null;
+		try {
+			httpURLConnection = (HttpURLConnection) url.openConnection();
+		} catch (IOException e) {
+			logger.error(e.getMessage(), e);
+			return null;
+		}
+		httpURLConnection.setConnectTimeout(this.connectionTimeout);// 连接超时时间
+		httpURLConnection.setReadTimeout(this.readTimeOut);// 读取结果超时时间
+		httpURLConnection.setDoInput(true); // 可读
+		httpURLConnection.setDoOutput(true); // 可写
+		httpURLConnection.setUseCaches(false);// 取消缓存
+		httpURLConnection.setRequestProperty("Content-type",
+				"application/x-www-form-urlencoded;charset=" + encoding);
+		httpURLConnection.setRequestMethod("POST");
+		if ("https".equalsIgnoreCase(url.getProtocol())) {
+			HttpsURLConnection husn = (HttpsURLConnection) httpURLConnection;
+			husn.setSSLSocketFactory(new BaseHttpSSLSocketFactory());
+			husn.setHostnameVerifier(new TrustAnyHostnameVerifier());//解决由于服务器证书问题导致HTTPS无法访问的情况
+			return husn;
+		}
+		return httpURLConnection;
+	}
+
+	/**
+	 * 创建连接
+	 *
+	 * @return
+	 * @throws ProtocolException
+	 */
+	private HttpURLConnection createConnectionGet(String encoding) throws ProtocolException {
+		HttpURLConnection httpURLConnection = null;
+		try {
+			httpURLConnection = (HttpURLConnection) url.openConnection();
+		} catch (IOException e) {
+			logger.error(e.getMessage(), e);
+			return null;
+		}
+		httpURLConnection.setConnectTimeout(this.connectionTimeout);// 连接超时时间
+		httpURLConnection.setReadTimeout(this.readTimeOut);// 读取结果超时时间
+		httpURLConnection.setUseCaches(false);// 取消缓存
+		httpURLConnection.setRequestProperty("Content-type",
+				"application/x-www-form-urlencoded;charset=" + encoding);
+		httpURLConnection.setRequestMethod("GET");
+		if ("https".equalsIgnoreCase(url.getProtocol())) {
+			HttpsURLConnection husn = (HttpsURLConnection) httpURLConnection;
+			husn.setSSLSocketFactory(new BaseHttpSSLSocketFactory());
+			husn.setHostnameVerifier(new TrustAnyHostnameVerifier());//解决由于服务器证书问题导致HTTPS无法访问的情况
+			return husn;
+		}
+		return httpURLConnection;
+	}
+	
+	/**
+	 * 将Map存储的对象,转换为key=value&key=value的字符
+	 *
+	 * @param requestParam
+	 * @param coder
+	 * @return
+	 */
+	private String getRequestParamString(Map<String, String> requestParam, String coder) {
+		if (null == coder || "".equals(coder)) {
+			coder = "UTF-8";
+		}
+		StringBuffer sf = new StringBuffer("");
+		String reqstr = "";
+		if (null != requestParam && 0 != requestParam.size()) {
+			for (Entry<String, String> en : requestParam.entrySet()) {
+				try {
+					sf.append(en.getKey()
+							+ "="
+							+ (null == en.getValue() || "".equals(en.getValue()) ? "" : URLEncoder
+									.encode(en.getValue(), coder)) + "&");
+				} catch (UnsupportedEncodingException e) {
+					logger.error(e.getMessage(), e);
+					return "";
+				}
+			}
+			reqstr = sf.substring(0, sf.length() - 1);
+		}
+		logger.info("请求报文(已做过URLEncode编码):[" + reqstr + "]");
+		return reqstr;
+	}
+
+}

+ 561 - 0
src/main/java/com/itstyle/modules/unionpay/util/SDKConfig.java

@@ -0,0 +1,561 @@
+/**
+ *
+ * Licensed Property to China UnionPay Co., Ltd.
+ * 
+ * (C) Copyright of China UnionPay Co., Ltd. 2010
+ *     All Rights Reserved.
+ *
+ * 
+ * Modification History:
+ * =============================================================================
+ *   Author         Date          Description
+ *   ------------ ---------- ---------------------------------------------------
+ *   xshu       2014-05-28       MPI基本参数工具类
+ * =============================================================================
+ */
+package com.itstyle.modules.unionpay.util;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Properties;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 软件开发工具包 配制
+ * 创建者 科帮网
+ * 创建时间	2017年8月2日
+ *
+ */
+public class SDKConfig {
+	private static final Logger logger = LoggerFactory.getLogger(SDKUtil.class);
+	/**
+	 * 重要:联调接入的时候请务必仔细阅读注释!!!
+	 * 
+	 * 功能:从应用的classpath下加载acp_sdk.properties属性文件并将该属性文件中的键值对赋值到SDKConfig类中 <br>
+	 * 
+	 */
+	public static final String FILE_NAME = "acp_sdk.properties";
+
+	/** 前台请求URL. */
+	private String frontRequestUrl;
+	/** 后台请求URL. */
+	private String backRequestUrl;
+	/** 单笔查询 */
+	private String singleQueryUrl;
+	/** 批量查询 */
+	private String batchQueryUrl;
+	/** 批量交易 */
+	private String batchTransUrl;
+	/** 文件传输 */
+	private String fileTransUrl;
+	/** 签名证书路径. */
+	private String signCertPath;
+	/** 签名证书密码. */
+	private String signCertPwd;
+	/** 签名证书类型. */
+	private String signCertType;
+	/** 加密公钥证书路径. */
+	private String encryptCertPath;
+	/** 验证签名公钥证书目录. */
+	private String validateCertDir;
+	/** 按照商户代码读取指定签名证书目录. */
+	private String signCertDir;
+	/** 磁道加密证书路径. */
+	private String encryptTrackCertPath;
+	/** 磁道加密公钥模数. */
+	private String encryptTrackKeyModulus;
+	/** 磁道加密公钥指数. */
+	private String encryptTrackKeyExponent;
+	/** 有卡交易. */
+	private String cardRequestUrl;
+	/** app交易 */
+	private String appRequestUrl;
+	/** 证书使用模式(单证书/多证书) */
+	private String singleMode;
+
+	/*缴费相关地址*/
+	private String jfFrontRequestUrl;
+	private String jfBackRequestUrl;
+	private String jfSingleQueryUrl;
+	private String jfCardRequestUrl;
+	private String jfAppRequestUrl;
+
+	
+	/** 配置文件中的前台URL常量. */
+	public static final String SDK_FRONT_URL = "acpsdk.frontTransUrl";
+	/** 配置文件中的后台URL常量. */
+	public static final String SDK_BACK_URL = "acpsdk.backTransUrl";
+	/** 配置文件中的单笔交易查询URL常量. */
+	public static final String SDK_SIGNQ_URL = "acpsdk.singleQueryUrl";
+	/** 配置文件中的批量交易查询URL常量. */
+	public static final String SDK_BATQ_URL = "acpsdk.batchQueryUrl";
+	/** 配置文件中的批量交易URL常量. */
+	public static final String SDK_BATTRANS_URL = "acpsdk.batchTransUrl";
+	/** 配置文件中的文件类交易URL常量. */
+	public static final String SDK_FILETRANS_URL = "acpsdk.fileTransUrl";
+	/** 配置文件中的有卡交易URL常量. */
+	public static final String SDK_CARD_URL = "acpsdk.cardTransUrl";
+	/** 配置文件中的app交易URL常量. */
+	public static final String SDK_APP_URL = "acpsdk.appTransUrl";
+
+	
+	/** 以下缴费产品使用,其余产品用不到,无视即可 */
+	// 前台请求地址
+	public static final String JF_SDK_FRONT_TRANS_URL= "acpsdk.jfFrontTransUrl";
+	// 后台请求地址
+	public static final String JF_SDK_BACK_TRANS_URL="acpsdk.jfBackTransUrl";
+	// 单笔查询请求地址
+	public static final String JF_SDK_SINGLE_QUERY_URL="acpsdk.jfSingleQueryUrl";
+	// 有卡交易地址
+	public static final String JF_SDK_CARD_TRANS_URL="acpsdk.jfCardTransUrl";
+	// App交易地址
+	public static final String JF_SDK_APP_TRANS_URL="acpsdk.jfAppTransUrl";
+	
+	
+	/** 配置文件中签名证书路径常量. */
+	public static final String SDK_SIGNCERT_PATH = "acpsdk.signCert.path";
+	/** 配置文件中签名证书密码常量. */
+	public static final String SDK_SIGNCERT_PWD = "acpsdk.signCert.pwd";
+	/** 配置文件中签名证书类型常量. */
+	public static final String SDK_SIGNCERT_TYPE = "acpsdk.signCert.type";
+	/** 配置文件中密码加密证书路径常量. */
+	public static final String SDK_ENCRYPTCERT_PATH = "acpsdk.encryptCert.path";
+	/** 配置文件中磁道加密证书路径常量. */
+	public static final String SDK_ENCRYPTTRACKCERT_PATH = "acpsdk.encryptTrackCert.path";
+	/** 配置文件中磁道加密公钥模数常量. */
+	public static final String SDK_ENCRYPTTRACKKEY_MODULUS = "acpsdk.encryptTrackKey.modulus";
+	/** 配置文件中磁道加密公钥指数常量. */
+	public static final String SDK_ENCRYPTTRACKKEY_EXPONENT = "acpsdk.encryptTrackKey.exponent";
+	/** 配置文件中验证签名证书目录常量. */
+	public static final String SDK_VALIDATECERT_DIR = "acpsdk.validateCert.dir";
+
+	/** 配置文件中是否加密cvn2常量. */
+	public static final String SDK_CVN_ENC = "acpsdk.cvn2.enc";
+	/** 配置文件中是否加密cvn2有效期常量. */
+	public static final String SDK_DATE_ENC = "acpsdk.date.enc";
+	/** 配置文件中是否加密卡号常量. */
+	public static final String SDK_PAN_ENC = "acpsdk.pan.enc";
+	/** 配置文件中证书使用模式 */
+	public static final String SDK_SINGLEMODE = "acpsdk.singleMode";
+	/** 操作对象. */
+	private static SDKConfig config;
+	/** 属性文件对象. */
+	private Properties properties;
+
+
+	/**
+	 * 获取config对象.
+	 * 
+	 * @return
+	 */
+	public static SDKConfig getConfig() {
+		if (null == config) {
+			config = new SDKConfig();
+		}
+		return config;
+	}
+
+	/**
+	 * 从properties文件加载
+	 * 
+	 * @param rootPath
+	 *            不包含文件名的目录.
+	 */
+	public void loadPropertiesFromPath(String rootPath) {
+		if (StringUtils.isNotBlank(rootPath)) {
+			File file = new File(rootPath + File.separator + FILE_NAME);
+			InputStream in = null;
+			if (file.exists()) {
+				try {
+					in = new FileInputStream(file);
+					BufferedReader bf = new BufferedReader(
+							new InputStreamReader(in, "utf-8"));
+					properties = new Properties();
+					properties.load(bf);
+					loadProperties(properties);
+				} catch (FileNotFoundException e) {
+					logger.error(e.getMessage(), e);
+				} catch (IOException e) {
+					logger.error(e.getMessage(), e);
+				} finally {
+					if (null != in) {
+						try {
+							in.close();
+						} catch (IOException e) {
+							logger.error(e.getMessage(), e);
+						}
+					}
+				}
+			} else {
+				// 由于此时可能还没有完成LOG的加载,因此采用标准输出来打印日志信息
+				System.out.println(rootPath + FILE_NAME + "不存在,加载参数失败");
+			}
+		} else {
+			loadPropertiesFromSrc();
+		}
+
+	}
+
+	/**
+	 * 从classpath路径下加载配置参数
+	 */
+	public void loadPropertiesFromSrc() {
+		InputStream in = null;
+		try {
+			// Properties pro = null;
+			logger.info("从classpath: " +SDKConfig.class.getClassLoader().getResource("").getPath()+" 获取属性文件"+FILE_NAME);
+			in = SDKConfig.class.getClassLoader()
+					.getResourceAsStream(FILE_NAME);
+			if (null != in) {
+				BufferedReader bf = new BufferedReader(new InputStreamReader(
+						in, "utf-8"));
+				properties = new Properties();
+				try {
+					properties.load(bf);
+				} catch (IOException e) {
+					throw e;
+				}
+			} else {
+				logger.info(FILE_NAME + "属性文件未能在classpath指定的目录下 "+SDKConfig.class.getClassLoader().getResource("").getPath()+" 找到!");
+				return;
+			}
+			loadProperties(properties);
+		} catch (IOException e) {
+			logger.error(e.getMessage(), e);
+		} finally {
+			if (null != in) {
+				try {
+					in.close();
+				} catch (IOException e) {
+					logger.error(e.getMessage(), e);
+				}
+			}
+		}
+	}
+
+	/**
+	 * 根据传入的 {@link #load(java.util.Properties)}对象设置配置参数
+	 * 
+	 * @param pro
+	 */
+	public void loadProperties(Properties pro) {
+		logger.info("开始从属性文件中加载配置项");
+		String value = null;
+		value = pro.getProperty(SDK_SINGLEMODE);
+		if (SDKUtil.isEmpty(value) || SDKConstants.TRUE_STRING.equals(value)) {
+			this.singleMode = SDKConstants.TRUE_STRING;
+			logger.info("单证书模式,使用配置文件配置的私钥签名证书,SingleCertMode:[" + this.singleMode + "]");
+			// 单证书模式
+			value = pro.getProperty(SDK_SIGNCERT_PATH);
+			
+			if (!SDKUtil.isEmpty(value)) {
+				this.signCertPath = value.trim();
+				logger.info("配置项:私钥签名证书路径==>"+SDK_SIGNCERT_PATH +"==>"+ value+" 已加载");
+			}
+			value = pro.getProperty(SDK_SIGNCERT_PWD);
+			if (!SDKUtil.isEmpty(value)) {
+				this.signCertPwd = value.trim();
+				logger.info("配置项:私钥签名证书密码==>"+SDK_SIGNCERT_PWD +" 已加载");
+			}
+			value = pro.getProperty(SDK_SIGNCERT_TYPE);
+			if (!SDKUtil.isEmpty(value)) {
+				this.signCertType = value.trim();
+				logger.info("配置项:私钥签名证书类型==>"+SDK_SIGNCERT_TYPE +"==>"+ value+" 已加载");
+			}
+		} else {
+			// 多证书模式
+			this.singleMode = SDKConstants.FALSE_STRING;
+			logger.info("多证书模式,不需要加载配置文件中配置的私钥签名证书,SingleMode:[" + this.singleMode + "]");
+		}
+		value = pro.getProperty(SDK_ENCRYPTCERT_PATH);
+		if (!SDKUtil.isEmpty(value)) {
+			this.encryptCertPath = value.trim();
+			logger.info("配置项:敏感信息加密证书==>"+SDK_ENCRYPTCERT_PATH +"==>"+ value+" 已加载");
+		}
+		value = pro.getProperty(SDK_VALIDATECERT_DIR);
+		if (!SDKUtil.isEmpty(value)) {
+			this.validateCertDir = value.trim();
+			logger.info("配置项:验证签名证书路径(这里配置的是目录,不要指定到公钥文件)==>"+SDK_VALIDATECERT_DIR +"==>"+ value+" 已加载");
+		}
+		value = pro.getProperty(SDK_FRONT_URL);
+		if (!SDKUtil.isEmpty(value)) {
+			this.frontRequestUrl = value.trim();
+		}
+		value = pro.getProperty(SDK_BACK_URL);
+		if (!SDKUtil.isEmpty(value)) {
+			this.backRequestUrl = value.trim();
+		}
+		value = pro.getProperty(SDK_BATQ_URL);
+		if (!SDKUtil.isEmpty(value)) {
+			this.batchQueryUrl = value.trim();
+		}
+		value = pro.getProperty(SDK_BATTRANS_URL);
+		if (!SDKUtil.isEmpty(value)) {
+			this.batchTransUrl = value.trim();
+		}
+		value = pro.getProperty(SDK_FILETRANS_URL);
+		if (!SDKUtil.isEmpty(value)) {
+			this.fileTransUrl = value.trim();
+		}
+		value = pro.getProperty(SDK_SIGNQ_URL);
+		if (!SDKUtil.isEmpty(value)) {
+			this.singleQueryUrl = value.trim();
+		}
+		value = pro.getProperty(SDK_CARD_URL);
+		if (!SDKUtil.isEmpty(value)) {
+			this.cardRequestUrl = value.trim();
+		}
+		value = pro.getProperty(SDK_APP_URL);
+		if (!SDKUtil.isEmpty(value)) {
+			this.appRequestUrl = value.trim();
+		}
+		value = pro.getProperty(SDK_ENCRYPTTRACKCERT_PATH);
+		if (!SDKUtil.isEmpty(value)) {
+			this.encryptTrackCertPath = value.trim();
+		}
+		
+		/**缴费部分**/
+		value = pro.getProperty(JF_SDK_FRONT_TRANS_URL);
+		if (!SDKUtil.isEmpty(value)) {
+			this.jfFrontRequestUrl = value.trim();
+		}
+
+		value = pro.getProperty(JF_SDK_BACK_TRANS_URL);
+		if (!SDKUtil.isEmpty(value)) {
+			this.jfBackRequestUrl = value.trim();
+		}
+		
+		value = pro.getProperty(JF_SDK_SINGLE_QUERY_URL);
+		if (!SDKUtil.isEmpty(value)) {
+			this.jfSingleQueryUrl = value.trim();
+		}
+		
+		value = pro.getProperty(JF_SDK_CARD_TRANS_URL);
+		if (!SDKUtil.isEmpty(value)) {
+			this.jfCardRequestUrl = value.trim();
+		}
+		
+		value = pro.getProperty(JF_SDK_APP_TRANS_URL);
+		if (!SDKUtil.isEmpty(value)) {
+			this.jfAppRequestUrl = value.trim();
+		}
+		
+		value = pro.getProperty(SDK_ENCRYPTTRACKKEY_EXPONENT);
+		if (!SDKUtil.isEmpty(value)) {
+			this.encryptTrackKeyExponent = value.trim();
+		}
+
+		value = pro.getProperty(SDK_ENCRYPTTRACKKEY_MODULUS);
+		if (!SDKUtil.isEmpty(value)) {
+			this.encryptTrackKeyModulus = value.trim();
+		}
+	}
+
+
+	public String getFrontRequestUrl() {
+		return frontRequestUrl;
+	}
+
+	public void setFrontRequestUrl(String frontRequestUrl) {
+		this.frontRequestUrl = frontRequestUrl;
+	}
+
+	public String getBackRequestUrl() {
+		return backRequestUrl;
+	}
+
+	public void setBackRequestUrl(String backRequestUrl) {
+		this.backRequestUrl = backRequestUrl;
+	}
+
+	public String getSignCertPath() {
+		return signCertPath;
+	}
+
+	public void setSignCertPath(String signCertPath) {
+		this.signCertPath = signCertPath;
+	}
+
+	public String getSignCertPwd() {
+		return signCertPwd;
+	}
+
+	public void setSignCertPwd(String signCertPwd) {
+		this.signCertPwd = signCertPwd;
+	}
+
+	public String getSignCertType() {
+		return signCertType;
+	}
+
+	public void setSignCertType(String signCertType) {
+		this.signCertType = signCertType;
+	}
+
+	public String getEncryptCertPath() {
+		return encryptCertPath;
+	}
+
+	public void setEncryptCertPath(String encryptCertPath) {
+		this.encryptCertPath = encryptCertPath;
+	}
+	
+	public String getValidateCertDir() {
+		return validateCertDir;
+	}
+
+	public void setValidateCertDir(String validateCertDir) {
+		this.validateCertDir = validateCertDir;
+	}
+
+	public String getSingleQueryUrl() {
+		return singleQueryUrl;
+	}
+
+	public void setSingleQueryUrl(String singleQueryUrl) {
+		this.singleQueryUrl = singleQueryUrl;
+	}
+
+	public String getBatchQueryUrl() {
+		return batchQueryUrl;
+	}
+
+	public void setBatchQueryUrl(String batchQueryUrl) {
+		this.batchQueryUrl = batchQueryUrl;
+	}
+
+	public String getBatchTransUrl() {
+		return batchTransUrl;
+	}
+
+	public void setBatchTransUrl(String batchTransUrl) {
+		this.batchTransUrl = batchTransUrl;
+	}
+
+	public String getFileTransUrl() {
+		return fileTransUrl;
+	}
+
+	public void setFileTransUrl(String fileTransUrl) {
+		this.fileTransUrl = fileTransUrl;
+	}
+
+	public String getSignCertDir() {
+		return signCertDir;
+	}
+
+	public void setSignCertDir(String signCertDir) {
+		this.signCertDir = signCertDir;
+	}
+
+	public Properties getProperties() {
+		return properties;
+	}
+
+	public void setProperties(Properties properties) {
+		this.properties = properties;
+	}
+
+	public String getCardRequestUrl() {
+		return cardRequestUrl;
+	}
+
+	public void setCardRequestUrl(String cardRequestUrl) {
+		this.cardRequestUrl = cardRequestUrl;
+	}
+
+	public String getAppRequestUrl() {
+		return appRequestUrl;
+	}
+
+	public void setAppRequestUrl(String appRequestUrl) {
+		this.appRequestUrl = appRequestUrl;
+	}
+	
+	public String getEncryptTrackCertPath() {
+		return encryptTrackCertPath;
+	}
+
+	public void setEncryptTrackCertPath(String encryptTrackCertPath) {
+		this.encryptTrackCertPath = encryptTrackCertPath;
+	}
+	
+	public String getJfFrontRequestUrl() {
+		return jfFrontRequestUrl;
+	}
+
+	public void setJfFrontRequestUrl(String jfFrontRequestUrl) {
+		this.jfFrontRequestUrl = jfFrontRequestUrl;
+	}
+
+	public String getJfBackRequestUrl() {
+		return jfBackRequestUrl;
+	}
+
+	public void setJfBackRequestUrl(String jfBackRequestUrl) {
+		this.jfBackRequestUrl = jfBackRequestUrl;
+	}
+
+	public String getJfSingleQueryUrl() {
+		return jfSingleQueryUrl;
+	}
+
+	public void setJfSingleQueryUrl(String jfSingleQueryUrl) {
+		this.jfSingleQueryUrl = jfSingleQueryUrl;
+	}
+
+	public String getJfCardRequestUrl() {
+		return jfCardRequestUrl;
+	}
+
+	public void setJfCardRequestUrl(String jfCardRequestUrl) {
+		this.jfCardRequestUrl = jfCardRequestUrl;
+	}
+
+	public String getJfAppRequestUrl() {
+		return jfAppRequestUrl;
+	}
+
+	public void setJfAppRequestUrl(String jfAppRequestUrl) {
+		this.jfAppRequestUrl = jfAppRequestUrl;
+	}
+
+	public String getSingleMode() {
+		return singleMode;
+	}
+
+	public void setSingleMode(String singleMode) {
+		this.singleMode = singleMode;
+	}
+
+	public SDKConfig() {
+		super();
+	}
+
+	public String getEncryptTrackKeyExponent() {
+		return encryptTrackKeyExponent;
+	}
+
+	public void setEncryptTrackKeyExponent(String encryptTrackKeyExponent) {
+		this.encryptTrackKeyExponent = encryptTrackKeyExponent;
+	}
+
+	public String getEncryptTrackKeyModulus() {
+		return encryptTrackKeyModulus;
+	}
+
+	public void setEncryptTrackKeyModulus(String encryptTrackKeyModulus) {
+		this.encryptTrackKeyModulus = encryptTrackKeyModulus;
+	}
+
+
+
+}

+ 364 - 0
src/main/java/com/itstyle/modules/unionpay/util/SDKConstants.java

@@ -0,0 +1,364 @@
+/**
+ *
+ * Licensed Property to China UnionPay Co., Ltd.
+ * 
+ * (C) Copyright of China UnionPay Co., Ltd. 2010
+ *     All Rights Reserved.
+ *
+ * 
+ * Modification History:
+ * =============================================================================
+ *   Author         Date          Description
+ *   ------------ ---------- ---------------------------------------------------
+ *   xshu       2014-05-28       MPI插件包常量定义
+ * =============================================================================
+ */
+package com.itstyle.modules.unionpay.util;
+
+public class SDKConstants {
+
+	public final static String COLUMN_DEFAULT = "-";
+
+	public final static String KEY_DELIMITER = "#";
+
+	/** memeber variable: blank. */
+	public static final String BLANK = "";
+
+	/** member variabel: space. */
+	public static final String SPACE = " ";
+
+	/** memeber variable: unline. */
+	public static final String UNLINE = "_";
+
+	/** memeber varibale: star. */
+	public static final String STAR = "*";
+
+	/** memeber variable: line. */
+	public static final String LINE = "-";
+
+	/** memeber variable: add. */
+	public static final String ADD = "+";
+
+	/** memeber variable: colon. */
+	public final static String COLON = "|";
+
+	/** memeber variable: point. */
+	public final static String POINT = ".";
+
+	/** memeber variable: comma. */
+	public final static String COMMA = ",";
+
+	/** memeber variable: slash. */
+	public final static String SLASH = "/";
+
+	/** memeber variable: div. */
+	public final static String DIV = "/";
+
+	/** memeber variable: left . */
+	public final static String LB = "(";
+
+	/** memeber variable: right. */
+	public final static String RB = ")";
+
+	/** memeber variable: rmb. */
+	public final static String CUR_RMB = "RMB";
+
+	/** memeber variable: .page size */
+	public static final int PAGE_SIZE = 10;
+
+	/** memeber variable: String ONE. */
+	public static final String ONE = "1";
+
+	/** memeber variable: String ZERO. */
+	public static final String ZERO = "0";
+
+	/** memeber variable: number six. */
+	public static final int NUM_SIX = 6;
+
+	/** memeber variable: equal mark. */
+	public static final String EQUAL = "=";
+
+	/** memeber variable: operation ne. */
+	public static final String NE = "!=";
+
+	/** memeber variable: operation le. */
+	public static final String LE = "<=";
+
+	/** memeber variable: operation ge. */
+	public static final String GE = ">=";
+
+	/** memeber variable: operation lt. */
+	public static final String LT = "<";
+
+	/** memeber variable: operation gt. */
+	public static final String GT = ">";
+
+	/** memeber variable: list separator. */
+	public static final String SEP = "./";
+
+	/** memeber variable: Y. */
+	public static final String Y = "Y";
+
+	/** memeber variable: AMPERSAND. */
+	public static final String AMPERSAND = "&";
+
+	/** memeber variable: SQL_LIKE_TAG. */
+	public static final String SQL_LIKE_TAG = "%";
+
+	/** memeber variable: @. */
+	public static final String MAIL = "@";
+
+	/** memeber variable: number zero. */
+	public static final int NZERO = 0;
+
+	public static final String LEFT_BRACE = "{";
+
+	public static final String RIGHT_BRACE = "}";
+
+	/** memeber variable: string true. */
+	public static final String TRUE_STRING = "true";
+	/** memeber variable: string false. */
+	public static final String FALSE_STRING = "false";
+
+	/** memeber variable: forward success. */
+	public static final String SUCCESS = "success";
+	/** memeber variable: forward fail. */
+	public static final String FAIL = "fail";
+	/** memeber variable: global forward success. */
+	public static final String GLOBAL_SUCCESS = "$success";
+	/** memeber variable: global forward fail. */
+	public static final String GLOBAL_FAIL = "$fail";
+
+	public static final String UTF_8_ENCODING = "UTF-8";
+	public static final String GBK_ENCODING = "GBK";
+	public static final String CONTENT_TYPE = "Content-type";
+	public static final String APP_XML_TYPE = "application/xml;charset=utf-8";
+	public static final String APP_FORM_TYPE = "application/x-www-form-urlencoded;charset=";
+
+	/******************************************** 5.0报文接口定义 ********************************************/
+	/** 版本号. */
+	public static final String param_version = "version";
+	/** 证书ID. */
+	public static final String param_certId = "certId";
+	/** 签名. */
+	public static final String param_signature = "signature";
+	/** 编码方式. */
+	public static final String param_encoding = "encoding";
+	/** 交易类型. */
+	public static final String param_txnType = "txnType";
+	/** 交易子类. */
+	public static final String param_txnSubType = "txnSubType";
+	/** 业务类型. */
+	public static final String param_bizType = "bizType";
+	/** 前台通知地址 . */
+	public static final String param_frontUrl = "frontUrl";
+	/** 后台通知地址. */
+	public static final String param_backUrl = "backUrl";
+	/** 接入类型. */
+	public static final String param_accessType = "accessType";
+	/** 收单机构代码. */
+	public static final String param_acqInsCode = "acqInsCode";
+	/** 商户类别. */
+	public static final String param_merCatCode = "merCatCode";
+	/** 商户类型. */
+	public static final String param_merType = "merType";
+	/** 商户代码. */
+	public static final String param_merId = "merId";
+	/** 商户名称. */
+	public static final String param_merName = "merName";
+	/** 商户简称. */
+	public static final String param_merAbbr = "merAbbr";
+	/** 二级商户代码. */
+	public static final String param_subMerId = "subMerId";
+	/** 二级商户名称. */
+	public static final String param_subMerName = "subMerName";
+	/** 二级商户简称. */
+	public static final String param_subMerAbbr = "subMerAbbr";
+	/** Cupsecure 商户代码. */
+	public static final String param_csMerId = "csMerId";
+	/** 商户订单号. */
+	public static final String param_orderId = "orderId";
+	/** 交易时间. */
+	public static final String param_txnTime = "txnTime";
+	/** 发送时间. */
+	public static final String param_txnSendTime = "txnSendTime";
+	/** 订单超时时间间隔. */
+	public static final String param_orderTimeoutInterval = "orderTimeoutInterval";
+	/** 支付超时时间. */
+	public static final String param_payTimeoutTime = "payTimeoutTime";
+	/** 默认支付方式. */
+	public static final String param_defaultPayType = "defaultPayType";
+	/** 支持支付方式. */
+	public static final String param_supPayType = "supPayType";
+	/** 支付方式. */
+	public static final String param_payType = "payType";
+	/** 自定义支付方式. */
+	public static final String param_customPayType = "customPayType";
+	/** 物流标识. */
+	public static final String param_shippingFlag = "shippingFlag";
+	/** 收货地址-国家. */
+	public static final String param_shippingCountryCode = "shippingCountryCode";
+	/** 收货地址-省. */
+	public static final String param_shippingProvinceCode = "shippingProvinceCode";
+	/** 收货地址-市. */
+	public static final String param_shippingCityCode = "shippingCityCode";
+	/** 收货地址-地区. */
+	public static final String param_shippingDistrictCode = "shippingDistrictCode";
+	/** 收货地址-详细. */
+	public static final String param_shippingStreet = "shippingStreet";
+	/** 商品总类. */
+	public static final String param_commodityCategory = "commodityCategory";
+	/** 商品名称. */
+	public static final String param_commodityName = "commodityName";
+	/** 商品URL. */
+	public static final String param_commodityUrl = "commodityUrl";
+	/** 商品单价. */
+	public static final String param_commodityUnitPrice = "commodityUnitPrice";
+	/** 商品数量. */
+	public static final String param_commodityQty = "commodityQty";
+	/** 是否预授权. */
+	public static final String param_isPreAuth = "isPreAuth";
+	/** 币种. */
+	public static final String param_currencyCode = "currencyCode";
+	/** 账户类型. */
+	public static final String param_accType = "accType";
+	/** 账号. */
+	public static final String param_accNo = "accNo";
+	/** 支付卡类型. */
+	public static final String param_payCardType = "payCardType";
+	/** 发卡机构代码. */
+	public static final String param_issInsCode = "issInsCode";
+	/** 持卡人信息. */
+	public static final String param_customerInfo = "customerInfo";
+	/** 交易金额. */
+	public static final String param_txnAmt = "txnAmt";
+	/** 余额. */
+	public static final String param_balance = "balance";
+	/** 地区代码. */
+	public static final String param_districtCode = "districtCode";
+	/** 附加地区代码. */
+	public static final String param_additionalDistrictCode = "additionalDistrictCode";
+	/** 账单类型. */
+	public static final String param_billType = "billType";
+	/** 账单号码. */
+	public static final String param_billNo = "billNo";
+	/** 账单月份. */
+	public static final String param_billMonth = "billMonth";
+	/** 账单查询要素. */
+	public static final String param_billQueryInfo = "billQueryInfo";
+	/** 账单详情. */
+	public static final String param_billDetailInfo = "billDetailInfo";
+	/** 账单金额. */
+	public static final String param_billAmt = "billAmt";
+	/** 账单金额符号. */
+	public static final String param_billAmtSign = "billAmtSign";
+	/** 绑定标识号. */
+	public static final String param_bindId = "bindId";
+	/** 风险级别. */
+	public static final String param_riskLevel = "riskLevel";
+	/** 绑定信息条数. */
+	public static final String param_bindInfoQty = "bindInfoQty";
+	/** 绑定信息集. */
+	public static final String param_bindInfoList = "bindInfoList";
+	/** 批次号. */
+	public static final String param_batchNo = "batchNo";
+	/** 总笔数. */
+	public static final String param_totalQty = "totalQty";
+	/** 总金额. */
+	public static final String param_totalAmt = "totalAmt";
+	/** 文件类型. */
+	public static final String param_fileType = "fileType";
+	/** 文件名称. */
+	public static final String param_fileName = "fileName";
+	/** 批量文件内容. */
+	public static final String param_fileContent = "fileContent";
+	/** 商户摘要. */
+	public static final String param_merNote = "merNote";
+	/** 商户自定义域. */
+	// public static final String param_merReserved = "merReserved";//接口变更删除
+	/** 请求方保留域. */
+	public static final String param_reqReserved = "reqReserved";// 新增接口
+	/** 保留域. */
+	public static final String param_reserved = "reserved";
+	/** 终端号. */
+	public static final String param_termId = "termId";
+	/** 终端类型. */
+	public static final String param_termType = "termType";
+	/** 交互模式. */
+	public static final String param_interactMode = "interactMode";
+	/** 发卡机构识别模式. */
+	// public static final String param_recognitionMode = "recognitionMode";
+	public static final String param_issuerIdentifyMode = "issuerIdentifyMode";// 接口名称变更
+	/** 商户端用户号. */
+	public static final String param_merUserId = "merUserId";
+	/** 持卡人IP. */
+	public static final String param_customerIp = "customerIp";
+	/** 查询流水号. */
+	public static final String param_queryId = "queryId";
+	/** 原交易查询流水号. */
+	public static final String param_origQryId = "origQryId";
+	/** 系统跟踪号. */
+	public static final String param_traceNo = "traceNo";
+	/** 交易传输时间. */
+	public static final String param_traceTime = "traceTime";
+	/** 清算日期. */
+	public static final String param_settleDate = "settleDate";
+	/** 清算币种. */
+	public static final String param_settleCurrencyCode = "settleCurrencyCode";
+	/** 清算金额. */
+	public static final String param_settleAmt = "settleAmt";
+	/** 清算汇率. */
+	public static final String param_exchangeRate = "exchangeRate";
+	/** 兑换日期. */
+	public static final String param_exchangeDate = "exchangeDate";
+	/** 响应时间. */
+	public static final String param_respTime = "respTime";
+	/** 原交易应答码. */
+	public static final String param_origRespCode = "origRespCode";
+	/** 原交易应答信息. */
+	public static final String param_origRespMsg = "origRespMsg";
+	/** 应答码. */
+	public static final String param_respCode = "respCode";
+	/** 应答码信息. */
+	public static final String param_respMsg = "respMsg";
+	// 新增四个报文字段merUserRegDt merUserEmail checkFlag activateStatus
+	/** 商户端用户注册时间. */
+	public static final String param_merUserRegDt = "merUserRegDt";
+	/** 商户端用户注册邮箱. */
+	public static final String param_merUserEmail = "merUserEmail";
+	/** 验证标识. */
+	public static final String param_checkFlag = "checkFlag";
+	/** 开通状态. */
+	public static final String param_activateStatus = "activateStatus";
+	/** 加密证书ID. */
+	public static final String param_encryptCertId = "encryptCertId";
+	/** 用户MAC、IMEI串号、SSID. */
+	public static final String param_userMac = "userMac";
+	/** 关联交易. */
+	// public static final String param_relationTxnType = "relationTxnType";
+	/** 短信类型 */
+	public static final String param_smsType = "smsType";
+
+	/** 风控信息域 */
+	public static final String param_riskCtrlInfo = "riskCtrlInfo";
+
+	/** IC卡交易信息域 */
+	public static final String param_ICTransData = "ICTransData";
+
+	/** VPC交易信息域 */
+	public static final String param_VPCTransData = "VPCTransData";
+
+	/** 安全类型 */
+	public static final String param_securityType = "securityType";
+
+	/** 银联订单号 */
+	public static final String param_tn = "tn";
+
+	/** 分期付款手续费率 */
+	public static final String param_instalRate = "instalRate";
+
+	/** 分期付款手续费率 */
+	public static final String param_mchntFeeSubsidy = "mchntFeeSubsidy";
+
+	
+}

+ 292 - 0
src/main/java/com/itstyle/modules/unionpay/util/SDKUtil.java

@@ -0,0 +1,292 @@
+/**
+ *
+ * Licensed Property to China UnionPay Co., Ltd.
+ * 
+ * (C) Copyright of China UnionPay Co., Ltd. 2010
+ *     All Rights Reserved.
+ *
+ * 
+ * Modification History:
+ * =============================================================================
+ *   Author         Date          Description
+ *   ------------ ---------- ---------------------------------------------------
+ *   xshu       2014-05-28      MPI工具类
+ * =============================================================================
+ */
+package com.itstyle.modules.unionpay.util;
+
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SDKUtil {
+	private static final Logger logger = LoggerFactory.getLogger(SDKUtil.class);
+	/**
+	 * 生成签名值(SHA1摘要算法)
+	 * 
+	 * @param data
+	 *            待签名数据Map键值对形式
+	 * @param encoding
+	 *            编码
+	 * @return 签名是否成功
+	 */
+	public static boolean sign(Map<String, String> data, String encoding) {
+		if (isEmpty(encoding)) {
+			encoding = "UTF-8";
+		}
+		// 设置签名证书序列号
+		data.put(SDKConstants.param_certId, CertUtil.getSignCertId());
+		// 将Map信息转换成key1=value1&key2=value2的形式
+		String stringData = coverMap2String(data);
+		logger.info("待签名请求报文串:[" + stringData + "]");
+		/**
+		 * 签名\base64编码
+		 */
+		byte[] byteSign = null;
+		String stringSign = null;
+		try {
+			// 通过SHA1进行摘要并转16进制
+			byte[] signDigest = SecureUtil.sha1X16(stringData, encoding);
+			byteSign = SecureUtil.base64Encode(SecureUtil.signBySoft(
+					CertUtil.getSignCertPrivateKey(), signDigest));
+			stringSign = new String(byteSign);
+			// 设置签名域值
+			data.put(SDKConstants.param_signature, stringSign);
+			return true;
+		} catch (Exception e) {
+			logger.error("签名异常", e);
+			return false;
+		}
+	}
+
+	/**
+	 * 通过传入的证书绝对路径和证书密码读取签名证书进行签名并返回签名值<br>
+	 * 
+	 * @param data
+	 *            待签名数据Map键值对形式
+	 * @param encoding
+	 *            编码
+	 * @param certPath
+	 *            证书绝对路径
+	 * @param certPwd
+	 *            证书密码
+	 * @return 签名值
+	 */
+	public static boolean signByCertInfo(Map<String, String> data,
+			 String certPath, String certPwd,String encoding) {
+		if (isEmpty(encoding)) {
+			encoding = "UTF-8";
+		}
+		if (isEmpty(certPath) || isEmpty(certPwd)) {
+			logger.info("Invalid Parameter:CertPath=[" + certPath
+					+ "],CertPwd=[" + certPwd + "]");
+			return false;
+		}
+		// 设置签名证书序列号
+		data.put(SDKConstants.param_certId,
+				CertUtil.getCertIdByKeyStoreMap(certPath, certPwd));
+		// 将Map信息转换成key1=value1&key2=value2的形式
+		String stringData = coverMap2String(data);
+		/**
+		 * 签名\base64编码
+		 */
+		byte[] byteSign = null;
+		String stringSign = null;
+		try {
+			byte[] signDigest = SecureUtil.sha1X16(stringData, encoding);
+			byteSign = SecureUtil.base64Encode(SecureUtil.signBySoft(
+					CertUtil.getSignCertPrivateKeyByStoreMap(certPath, certPwd),
+					signDigest));
+			stringSign = new String(byteSign);
+			// 设置签名域值
+			data.put(SDKConstants.param_signature, stringSign);
+			return true;
+		} catch (Exception e) {
+			logger.error("签名异常", e);
+			return false;
+		}
+	}
+
+
+	/**
+	 * 将Map中的数据转换成按照Key的ascii码排序后的key1=value1&key2=value2的形式 不包含签名域signature
+	 * 
+	 * @param data
+	 *            待拼接的Map数据
+	 * @return 拼接好后的字符串
+	 */
+	public static String coverMap2String(Map<String, String> data) {
+		TreeMap<String, String> tree = new TreeMap<String, String>();
+		Iterator<Entry<String, String>> it = data.entrySet().iterator();
+		while (it.hasNext()) {
+			Entry<String, String> en = it.next();
+			if (SDKConstants.param_signature.equals(en.getKey().trim())) {
+				continue;
+			}
+			tree.put(en.getKey(), en.getValue());
+		}
+		it = tree.entrySet().iterator();
+		StringBuffer sf = new StringBuffer();
+		while (it.hasNext()) {
+			Entry<String, String> en = it.next();
+			sf.append(en.getKey() + SDKConstants.EQUAL + en.getValue()
+					+ SDKConstants.AMPERSAND);
+		}
+		return sf.substring(0, sf.length() - 1);
+	}
+
+
+	/**
+	 * 兼容老方法 将形如key=value&key=value的字符串转换为相应的Map对象
+	 * 
+	 * @param result
+	 * @return
+	 */
+	public static Map<String, String> coverResultString2Map(String result) {
+		return convertResultStringToMap(result);
+	}
+
+	/**
+	 * 将形如key=value&key=value的字符串转换为相应的Map对象
+	 * 
+	 * @param result
+	 * @return
+	 */
+	public static Map<String, String> convertResultStringToMap(String result) {
+		Map<String, String> map =null;
+		try {
+			
+			if(StringUtils.isNotBlank(result)){
+				if(result.startsWith("{") && result.endsWith("}")){
+					System.out.println(result.length());
+					result = result.substring(1, result.length()-1);
+				}
+				 map = parseQString(result);
+			}
+			
+		} catch (UnsupportedEncodingException e) {
+			logger.error(e.getMessage(), e);
+		}
+		return map;
+	}
+
+	
+	/**
+	 * 解析应答字符串,生成应答要素
+	 * 
+	 * @param str
+	 *            需要解析的字符串
+	 * @return 解析的结果map
+	 * @throws UnsupportedEncodingException
+	 */
+	public static Map<String, String> parseQString(String str)
+			throws UnsupportedEncodingException {
+
+		Map<String, String> map = new HashMap<String, String>();
+		int len = str.length();
+		StringBuilder temp = new StringBuilder();
+		char curChar;
+		String key = null;
+		boolean isKey = true;
+		boolean isOpen = false;//值里有嵌套
+		char openName = 0;
+		if(len>0){
+			for (int i = 0; i < len; i++) {// 遍历整个带解析的字符串
+				curChar = str.charAt(i);// 取当前字符
+				if (isKey) {// 如果当前生成的是key
+					
+					if (curChar == '=') {// 如果读取到=分隔符 
+						key = temp.toString();
+						temp.setLength(0);
+						isKey = false;
+					} else {
+						temp.append(curChar);
+					}
+				} else  {// 如果当前生成的是value
+					if(isOpen){
+						if(curChar == openName){
+							isOpen = false;
+						}
+						
+					}else{//如果没开启嵌套
+						if(curChar == '{'){//如果碰到,就开启嵌套
+							isOpen = true;
+							openName ='}';
+						}
+						if(curChar == '['){
+							isOpen = true;
+							openName =']';
+						}
+					}
+					if (curChar == '&' && !isOpen) {// 如果读取到&分割符,同时这个分割符不是值域,这时将map里添加
+						putKeyValueToMap(temp, isKey, key, map);
+						temp.setLength(0);
+						isKey = true;
+					}else{
+						temp.append(curChar);
+					}
+				}
+				
+			}
+			putKeyValueToMap(temp, isKey, key, map);
+		}
+		return map;
+	}
+
+	private static void putKeyValueToMap(StringBuilder temp, boolean isKey,
+			String key, Map<String, String> map)
+			throws UnsupportedEncodingException {
+		if (isKey) {
+			key = temp.toString();
+			if (key.length() == 0) {
+				throw new RuntimeException("QString format illegal");
+			}
+			map.put(key, "");
+		} else {
+			if (key.length() == 0) {
+				throw new RuntimeException("QString format illegal");
+			}
+			map.put(key, temp.toString());
+		}
+	}
+
+	/**
+	 * 判断字符串是否为NULL或空
+	 * 
+	 * @param s
+	 *            待判断的字符串数据
+	 * @return 判断结果 true-是 false-否
+	 */
+	public static boolean isEmpty(String s) {
+		return null == s || "".equals(s.trim());
+	}
+
+	/**
+	 * 过滤请求报文中的空字符串或者空字符串
+	 * @param contentData
+	 * @return
+	 */
+	public static Map<String, String> filterBlank(Map<String, String> contentData){
+		logger.info("打印请求报文域 :");
+		Map<String, String> submitFromData = new HashMap<String, String>();
+		Set<String> keyset = contentData.keySet();
+		
+		for(String key:keyset){
+			String value = contentData.get(key);
+			if (StringUtils.isNotBlank(value)) {
+				// 对value值进行去除前后空处理
+				submitFromData.put(key, value.trim());
+				logger.info(key + "-->" + String.valueOf(value));
+			}
+		}
+		return submitFromData;
+	}
+}

+ 974 - 0
src/main/java/com/itstyle/modules/unionpay/util/SecureUtil.java

@@ -0,0 +1,974 @@
+/**
+ *
+ * Licensed Property to China UnionPay Co., Ltd.
+ * 
+ * (C) Copyright of China UnionPay Co., Ltd. 2010
+ *     All Rights Reserved.
+ *
+ * 
+ * Modification History:
+ * =============================================================================
+ *   Author         Date          Description
+ *   ------------ ---------- ---------------------------------------------------
+ *   xshu       2014-05-28     报文加密解密等操作的工具类
+ * =============================================================================
+ */
+package com.itstyle.modules.unionpay.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.MessageDigest;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Signature;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.zip.Deflater;
+import java.util.zip.Inflater;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.commons.codec.binary.Base64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class SecureUtil {
+	private static final Logger logger = LoggerFactory.getLogger(SecureUtil.class);
+	/**
+	 * 算法常量: MD5
+	 */
+	private static final String ALGORITHM_MD5 = "MD5";
+	/**
+	 * 算法常量: SHA1
+	 */
+	private static final String ALGORITHM_SHA1 = "SHA-1";
+
+	/**
+	 * 算法常量:SHA1withRSA
+	 */
+	private static final String BC_PROV_ALGORITHM_SHA1RSA = "SHA1withRSA";
+
+	/**
+	 * md5计算.
+	 * 
+	 * @param datas
+	 *            待计算的数据
+	 * @return 计算结果
+	 */
+	public static byte[] md5(byte[] datas) {
+		MessageDigest md = null;
+		try {
+			md = MessageDigest.getInstance(ALGORITHM_MD5);
+			md.reset();
+			md.update(datas);
+			return md.digest();
+		} catch (Exception e) {
+			logger.error("MD5计算失败", e);
+			return null;
+		}
+	}
+
+	/**
+	 * sha1计算.
+	 * 
+	 * @param datas
+	 *            待计算的数据
+	 * @return 计算结果
+	 */
+	public static byte[] sha1(byte[] data) {
+		MessageDigest md = null;
+		try {
+			md = MessageDigest.getInstance(ALGORITHM_SHA1);
+			md.reset();
+			md.update(data);
+			return md.digest();
+		} catch (Exception e) {
+			logger.error("SHA1计算失败", e);
+			return null;
+		}
+	}
+
+	/**
+	 * md5计算后进行16进制转换
+	 * 
+	 * @param datas
+	 *            待计算的数据
+	 * @param encoding
+	 *            编码
+	 * @return 计算结果
+	 */
+	public static byte[] md5X16(String datas, String encoding) {
+		byte[] bytes = md5(datas, encoding);
+		StringBuilder md5StrBuff = new StringBuilder();
+		for (int i = 0; i < bytes.length; i++) {
+			if (Integer.toHexString(0xFF & bytes[i]).length() == 1) {
+				md5StrBuff.append("0").append(
+						Integer.toHexString(0xFF & bytes[i]));
+			} else {
+				md5StrBuff.append(Integer.toHexString(0xFF & bytes[i]));
+			}
+		}
+		try {
+			return md5StrBuff.toString().getBytes(encoding);
+		} catch (UnsupportedEncodingException e) {
+			logger.error(e.getMessage(), e);
+			return null;
+		}
+	}
+
+	/**
+	 * sha1计算后进行16进制转换
+	 * 
+	 * @param data
+	 *            待计算的数据
+	 * @param encoding
+	 *            编码
+	 * @return 计算结果
+	 */
+	public static byte[] sha1X16(String data, String encoding) {
+		byte[] bytes = sha1(data, encoding);
+		StringBuilder sha1StrBuff = new StringBuilder();
+		for (int i = 0; i < bytes.length; i++) {
+			if (Integer.toHexString(0xFF & bytes[i]).length() == 1) {
+				sha1StrBuff.append("0").append(
+						Integer.toHexString(0xFF & bytes[i]));
+			} else {
+				sha1StrBuff.append(Integer.toHexString(0xFF & bytes[i]));
+			}
+		}
+		try {
+			return sha1StrBuff.toString().getBytes(encoding);
+		} catch (UnsupportedEncodingException e) {
+			logger.error(e.getMessage(), e);
+			return null;
+		}
+	}
+
+	/**
+	 * md5计算
+	 * 
+	 * @param datas
+	 *            待计算的数据
+	 * @param encoding
+	 *            字符集编码
+	 * @return
+	 */
+	public static byte[] md5(String datas, String encoding) {
+		try {
+			return md5(datas.getBytes(encoding));
+		} catch (UnsupportedEncodingException e) {
+			logger.error("MD5计算失败", e);
+			return null;
+		}
+	}
+
+	/**
+	 * sha1计算
+	 * 
+	 * @param datas
+	 *            待计算的数据
+	 * @param encoding
+	 *            字符集编码
+	 * @return
+	 */
+	public static byte[] sha1(String datas, String encoding) {
+		try {
+			return sha1(datas.getBytes(encoding));
+		} catch (UnsupportedEncodingException e) {
+			logger.error("SHA1计算失败", e);
+			return null;
+		}
+	}
+
+	/**
+	 * 软签名
+	 * 
+	 * @param privateKey
+	 *            私钥
+	 * @param data
+	 *            待签名数据
+	 * @param signMethod
+	 *            签名方法
+	 * @return 结果
+	 * @throws Exception
+	 */
+	public static byte[] signBySoft(PrivateKey privateKey, byte[] data)
+			throws Exception {
+		byte[] result = null;
+		Signature st = Signature.getInstance(BC_PROV_ALGORITHM_SHA1RSA, "BC");
+		st.initSign(privateKey);
+		st.update(data);
+		result = st.sign();
+		return result;
+	}
+
+	/**
+	 * 软验证签名
+	 * 
+	 * @param publicKey
+	 *            公钥
+	 * @param signData
+	 *            签名数据
+	 * @param srcData
+	 *            摘要
+	 * @param validateMethod
+	 *            签名方法.
+	 * @return
+	 * @throws Exception
+	 */
+	public static boolean validateSignBySoft(PublicKey publicKey,
+			byte[] signData, byte[] srcData) throws Exception {
+		Signature st = Signature.getInstance(BC_PROV_ALGORITHM_SHA1RSA, "BC");
+		st.initVerify(publicKey);
+		st.update(srcData);
+		return st.verify(signData);
+	}
+
+	/**
+	 * 解压缩.
+	 * 
+	 * @param inputByte
+	 *            byte[]数组类型的数据
+	 * @return 解压缩后的数据
+	 * @throws IOException
+	 */
+	public static byte[] inflater(final byte[] inputByte) throws IOException {
+		int compressedDataLength = 0;
+		Inflater compresser = new Inflater(false);
+		compresser.setInput(inputByte, 0, inputByte.length);
+		ByteArrayOutputStream o = new ByteArrayOutputStream(inputByte.length);
+		byte[] result = new byte[1024];
+		try {
+			while (!compresser.finished()) {
+				compressedDataLength = compresser.inflate(result);
+				if (compressedDataLength == 0) {
+					break;
+				}
+				o.write(result, 0, compressedDataLength);
+			}
+		} catch (Exception ex) {
+			System.err.println("Data format error!\n");
+			ex.printStackTrace();
+		} finally {
+			o.close();
+		}
+		compresser.end();
+		return o.toByteArray();
+	}
+
+	/**
+	 * 压缩.
+	 * 
+	 * @param inputByte
+	 *            需要解压缩的byte[]数组
+	 * @return 压缩后的数据
+	 * @throws IOException
+	 */
+	public static byte[] deflater(final byte[] inputByte) throws IOException {
+		int compressedDataLength = 0;
+		Deflater compresser = new Deflater();
+		compresser.setInput(inputByte);
+		compresser.finish();
+		ByteArrayOutputStream o = new ByteArrayOutputStream(inputByte.length);
+		byte[] result = new byte[1024];
+		try {
+			while (!compresser.finished()) {
+				compressedDataLength = compresser.deflate(result);
+				o.write(result, 0, compressedDataLength);
+			}
+		} finally {
+			o.close();
+		}
+		compresser.end();
+		return o.toByteArray();
+	}
+
+	/**
+	 * 密码加密,进行base64加密
+	 * 
+	 * @param pin
+	 *            密码
+	 * @param card
+	 *            卡号
+	 * @param encoding
+	 *            字符编码
+	 * @param key
+	 *            公钥
+	 * @return 转PIN结果
+	 */
+	public static String EncryptPin(String pin, String card, String encoding,
+			PublicKey key) {
+		/** 生成PIN Block **/
+		byte[] pinBlock = pin2PinBlockWithCardNO(pin, card);
+		/** 使用公钥对密码加密 **/
+		byte[] data = null;
+		try {
+			data = encryptedPin(key, pinBlock);
+			return new String(SecureUtil.base64Encode(data), encoding);
+		} catch (Exception e) {
+			logger.error(e.getMessage(), e);
+			return "";
+		}
+	}
+
+	/**
+	 * 对数据通过公钥进行加密,并进行base64计算
+	 * 
+	 * @param dataString
+	 *            待处理数据
+	 * @param encoding
+	 *            字符编码
+	 * @param key
+	 *            公钥
+	 * @return
+	 */
+	public static String EncryptData(String dataString, String encoding,
+			PublicKey key) {
+		/** 使用公钥对密码加密 **/
+		byte[] data = null;
+		try {
+			data = encryptedPin(key, dataString.getBytes(encoding));
+			return new String(SecureUtil.base64Encode(data), encoding);
+		} catch (Exception e) {
+			logger.error(e.getMessage(), e);
+			return "";
+		}
+	}
+
+	/**
+	 * 通过私钥解密
+	 * 
+	 * @param dataString
+	 *            base64过的数据
+	 * @param encoding
+	 *            编码
+	 * @param key
+	 *            私钥
+	 * @return 解密后的数据
+	 */
+	public static String DecryptedData(String dataString, String encoding,
+			PrivateKey key) {
+		byte[] data = null;
+		try {
+			data = decryptedPin(key, dataString.getBytes(encoding));
+			return new String(data, encoding);
+		} catch (Exception e) {
+			logger.error(e.getMessage(), e);
+			return "";
+		}
+	}
+
+	/**
+	 * BASE64解码
+	 * 
+	 * @param inputByte
+	 *            待解码数据
+	 * @return 解码后的数据
+	 * @throws IOException
+	 */
+	public static byte[] base64Decode(byte[] inputByte) throws IOException {
+		return Base64.decodeBase64(inputByte);
+	}
+
+	/**
+	 * BASE64编码
+	 * 
+	 * @param inputByte
+	 *            待编码数据
+	 * @return 解码后的数据
+	 * @throws IOException
+	 */
+	public static byte[] base64Encode(byte[] inputByte) throws IOException {
+		return Base64.encodeBase64(inputByte);
+	}
+
+	/**
+	 * 将字符串转换为byte数组
+	 * 
+	 * @param str
+	 *            待转换的字符串
+	 * @return 转换结果
+	 */
+	public byte[] Str2Hex(String str) {
+		char[] ch = str.toCharArray();
+		byte[] b = new byte[ch.length / 2];
+		for (int i = 0; i < ch.length; i++) {
+			if (ch[i] == 0) {
+				break;
+			}
+			if (ch[i] >= '0' && ch[i] <= '9') {
+				ch[i] = (char) (ch[i] - '0');
+			} else if (ch[i] >= 'A' && ch[i] <= 'F') {
+				ch[i] = (char) (ch[i] - 'A' + 10);
+			}
+		}
+		for (int i = 0; i < b.length; i++) {
+			b[i] = (byte) (((ch[2 * i] << 4) & 0xf0) + (ch[2 * i + 1] & 0x0f));
+		}
+		return b;
+	}
+
+	/**
+	 * 将byte数组转换为可见的字符串
+	 * 
+	 * @param b
+	 *            待转换的byte数组
+	 * @return 转换后的字符串
+	 */
+	public static String Hex2Str(byte[] b) {
+		StringBuffer d = new StringBuffer(b.length * 2);
+		for (int i = 0; i < b.length; i++) {
+			char hi = Character.forDigit((b[i] >> 4) & 0x0F, 16);
+			char lo = Character.forDigit(b[i] & 0x0F, 16);
+			d.append(Character.toUpperCase(hi));
+			d.append(Character.toUpperCase(lo));
+		}
+		return d.toString();
+	}
+
+	public static String ByteToHex(byte[] bytes) {
+		StringBuffer sha1StrBuff = new StringBuffer();
+		for (int i = 0; i < bytes.length; i++) {
+			if (Integer.toHexString(0xFF & bytes[i]).length() == 1) {
+				sha1StrBuff.append("0").append(
+						Integer.toHexString(0xFF & bytes[i]));
+			} else {
+				sha1StrBuff.append(Integer.toHexString(0xFF & bytes[i]));
+			}
+		}
+		return sha1StrBuff.toString();
+	}
+
+	/**
+	 * 将byte数组转换为可见的字符串
+	 * 
+	 * @param b
+	 *            待转换的byte数组
+	 * @param len
+	 *            转换长度
+	 * @return
+	 */
+	public static String Hex2Str(byte[] b, int len) {
+		String str = "";
+		char[] ch = new char[len * 2];
+
+		for (int i = 0; i < len; i++) {
+			if ((((b[i] >> 4) & 0x0f) < 0x0a) && (((b[i] >> 4) & 0x0f) >= 0x0)) {
+				ch[i * 2] = (char) (((b[i] >> 4) & 0x0f) + '0');
+			} else {
+				ch[i * 2] = (char) (((b[i] >> 4) & 0x0f) + 'A' - 10);
+			}
+
+			if ((((b[i]) & 0x0f) < 0x0a) && (((b[i]) & 0x0f) >= 0x0)) {
+				ch[i * 2 + 1] = (char) (((b[i]) & 0x0f) + '0');
+			} else {
+				ch[i * 2 + 1] = (char) (((b[i]) & 0x0f) + 'A' - 10);
+			}
+
+		}
+		str = new String(ch);
+		return str;
+	}
+
+	/**
+	 * 将byte数组转换为可见的大写字符串
+	 * 
+	 * @param b
+	 *            待转换的byte数组
+	 * @return 转换后的结果
+	 */
+	public String byte2hex(byte[] b) {
+		String hs = "";
+		String stmp = "";
+		for (int n = 0; n < b.length; n++) {
+			stmp = (java.lang.Integer.toHexString(b[n] & 0XFF));
+			if (stmp.length() == 1) {
+				hs = hs + "0" + stmp;
+			} else {
+				hs = hs + stmp;
+			}
+			if (n < b.length - 1) {
+				hs = hs + ":";
+			}
+		}
+		return hs.toUpperCase();
+	}
+
+	/**
+	 * 计算MAC
+	 * 
+	 * @param inputByte
+	 *            待计算数据
+	 * @param inputkey
+	 *            密钥
+	 * @return 计算出的MAC值
+	 * @throws Exception
+	 */
+	public String genmac(byte[] inputByte, byte[] inputkey) throws Exception {
+		try {
+			Mac mac = Mac.getInstance("HmacMD5");
+			SecretKey key = new SecretKeySpec(inputkey, "DES");
+			mac.init(key);
+
+			byte[] macCode = mac.doFinal(inputByte);
+			String strMac = this.byte2hex(macCode);
+			return strMac;
+		} catch (Exception ex) {
+			ex.printStackTrace();
+			throw ex;
+		}
+	}
+
+	/**
+	 * MAC校验
+	 * 
+	 * @param inputByte
+	 *            待计算的数据
+	 * @param inputkey
+	 *            密钥
+	 * @param inputmac
+	 *            比较MAC
+	 * @return 校验结果
+	 * @throws Exception
+	 */
+	public boolean checkmac(byte[] inputByte, byte[] inputkey, String inputmac)
+			throws Exception {
+		try {
+			Mac mac = Mac.getInstance("HmacMD5");
+			SecretKey key = new SecretKeySpec(inputkey, "DES");
+			mac.init(key);
+
+			byte[] macCode = mac.doFinal(inputByte);
+			String strMacCode = this.byte2hex(macCode);
+
+			if (strMacCode.equals(inputmac)) {
+				return true;
+			} else {
+				return false;
+			}
+		} catch (Exception ex) {
+			throw ex;
+		}
+	}
+
+	/**
+	 * 字符串填充
+	 * 
+	 * @param string
+	 *            源串
+	 * @param filler
+	 *            填充值
+	 * @param totalLength
+	 *            填充总长度
+	 * @param atEnd
+	 *            头尾填充表急,true - 尾部填充;false - 头部填充
+	 * @return
+	 */
+	public static String fillString(String string, char filler,
+			int totalLength, boolean atEnd) {
+		byte[] tempbyte = string.getBytes();
+		int currentLength = tempbyte.length;
+		int delta = totalLength - currentLength;
+
+		for (int i = 0; i < delta; i++) {
+			if (atEnd) {
+				string += filler;
+			} else {
+				string = filler + string;
+			}
+		}
+		return string;
+
+	}
+
+	/**
+	 * 使用网关公钥对持卡人密码进行加密,并返回byte[]类型
+	 * 
+	 * @param publicKey
+	 * @param plainPin
+	 * @return
+	 * @throws Exception
+	 */
+	public static byte[] encryptedPin(PublicKey publicKey, byte[] plainPin)
+			throws Exception {
+		try {
+			// y
+			// Cipher cipher = Cipher.getInstance("DES",
+			// new org.bouncycastle.jce.provider.BouncyCastleProvider());
+
+			// 本土的
+//			Cipher cipher = CliperInstance.getInstance();
+			Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding","BC");
+			cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+			int blockSize = cipher.getBlockSize();
+			int outputSize = cipher.getOutputSize(plainPin.length);
+			int leavedSize = plainPin.length % blockSize;
+			int blocksSize = leavedSize != 0 ? plainPin.length / blockSize + 1
+					: plainPin.length / blockSize;
+			byte[] raw = new byte[outputSize * blocksSize];
+			int i = 0;
+			while (plainPin.length - i * blockSize > 0) {
+				if (plainPin.length - i * blockSize > blockSize) {
+					cipher.doFinal(plainPin, i * blockSize, blockSize, raw, i
+							* outputSize);
+				} else {
+					cipher.doFinal(plainPin, i * blockSize, plainPin.length - i
+							* blockSize, raw, i * outputSize);
+				}
+				i++;
+			}
+			return raw;
+			
+			/*Cipher cipher = CliperInstance.getInstance();
+			cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+			byte[] output = cipher.doFinal(plainPin);
+			return output;*/
+			
+		} catch (Exception e) {
+			throw new Exception(e.getMessage());
+		}
+	}
+
+	/**
+	 * 
+	 * @param publicKey
+	 * @param plainData
+	 * @return
+	 * @throws Exception
+	 */
+	public byte[] encryptedData(PublicKey publicKey, byte[] plainData)
+			throws Exception {
+		try {
+//			Cipher cipher = CliperInstance.getInstance();
+			Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding","BC");
+			cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+			int blockSize = cipher.getBlockSize();
+			int outputSize = cipher.getOutputSize(plainData.length);
+			int leavedSize = plainData.length % blockSize;
+			int blocksSize = leavedSize != 0 ? plainData.length / blockSize + 1
+					: plainData.length / blockSize;
+			byte[] raw = new byte[outputSize * blocksSize];
+			int i = 0;
+			while (plainData.length - i * blockSize > 0) {
+				if (plainData.length - i * blockSize > blockSize) {
+					cipher.doFinal(plainData, i * blockSize, blockSize, raw, i
+							* outputSize);
+				} else {
+					cipher.doFinal(plainData, i * blockSize, plainData.length
+							- i * blockSize, raw, i * outputSize);
+				}
+				i++;
+			}
+			return raw;
+		} catch (Exception e) {
+			throw new Exception(e.getMessage());
+		}
+	}
+
+	/**
+	 * 
+	 * @param privateKey
+	 * @param cryptPin
+	 * @return
+	 * @throws Exception
+	 */
+	public static byte[] decryptedPin(PrivateKey privateKey, byte[] cryptPin)
+			throws Exception {
+
+		try {
+			/** 生成PIN Block **/
+			byte[] pinBlock = SecureUtil.base64Decode(cryptPin);
+			// 本土的
+//			Cipher cipher = CliperInstance.getInstance();
+			Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding","BC");
+			cipher.init(Cipher.DECRYPT_MODE, privateKey);
+			int blockSize = cipher.getBlockSize();
+			int outputSize = cipher.getOutputSize(pinBlock.length);
+			int leavedSize = pinBlock.length % blockSize;
+			int blocksSize = leavedSize != 0 ? pinBlock.length / blockSize + 1
+					: pinBlock.length / blockSize;
+			byte[] pinData = new byte[outputSize * blocksSize];
+			int i = 0;
+			while (pinBlock.length - i * blockSize > 0) {
+				if (pinBlock.length - i * blockSize > blockSize) {
+					cipher.doFinal(pinBlock, i * blockSize, blockSize, pinData,
+							i * outputSize);
+				} else {
+					cipher.doFinal(pinBlock, i * blockSize, pinBlock.length - i
+							* blockSize, pinData, i * outputSize);
+				}
+				i++;
+			}
+			return pinData;
+		} catch (Exception e) {
+			logger.error("解密失败", e);
+		}
+		return null;
+	}
+
+	/**
+	 * 
+	 * @param aPin
+	 * @return
+	 */
+	private static byte[] pin2PinBlock(String aPin) {
+		int tTemp = 1;
+		int tPinLen = aPin.length();
+
+		byte[] tByte = new byte[8];
+		try {
+			/*******************************************************************
+			 * if (tPinLen > 9) { tByte[0] = (byte) Integer.parseInt(new
+			 * Integer(tPinLen) .toString(), 16); } else { tByte[0] = (byte)
+			 * Integer.parseInt(new Integer(tPinLen) .toString(), 10); }
+			 ******************************************************************/
+//			tByte[0] = (byte) Integer.parseInt(new Integer(tPinLen).toString(),
+//					10);
+			tByte[0] = (byte) Integer.parseInt(Integer.toString(tPinLen), 10);
+			if (tPinLen % 2 == 0) {
+				for (int i = 0; i < tPinLen;) {
+					String a = aPin.substring(i, i + 2);
+					tByte[tTemp] = (byte) Integer.parseInt(a, 16);
+					if (i == (tPinLen - 2)) {
+						if (tTemp < 7) {
+							for (int x = (tTemp + 1); x < 8; x++) {
+								tByte[x] = (byte) 0xff;
+							}
+						}
+					}
+					tTemp++;
+					i = i + 2;
+				}
+			} else {
+				for (int i = 0; i < tPinLen - 1;) {
+					String a;
+					a = aPin.substring(i, i + 2);
+					tByte[tTemp] = (byte) Integer.parseInt(a, 16);
+					if (i == (tPinLen - 3)) {
+						String b = aPin.substring(tPinLen - 1) + "F";
+						tByte[tTemp + 1] = (byte) Integer.parseInt(b, 16);
+						if ((tTemp + 1) < 7) {
+							for (int x = (tTemp + 2); x < 8; x++) {
+								tByte[x] = (byte) 0xff;
+							}
+						}
+					}
+					tTemp++;
+					i = i + 2;
+				}
+			}
+		} catch (Exception e) {
+		}
+
+		return tByte;
+	}
+
+	/**
+	 * 
+	 * @param aPan
+	 * @return
+	 */
+	private static byte[] formatPan(String aPan) {
+		int tPanLen = aPan.length();
+		byte[] tByte = new byte[8];
+		;
+		int temp = tPanLen - 13;
+		try {
+			tByte[0] = (byte) 0x00;
+			tByte[1] = (byte) 0x00;
+			for (int i = 2; i < 8; i++) {
+				String a = aPan.substring(temp, temp + 2);
+				tByte[i] = (byte) Integer.parseInt(a, 16);
+				temp = temp + 2;
+			}
+		} catch (Exception e) {
+		}
+		return tByte;
+	}
+
+	/**
+	 * 
+	 * @param aPin
+	 * @param aCardNO
+	 * @return
+	 */
+	public static byte[] pin2PinBlockWithCardNO(String aPin, String aCardNO) {
+		byte[] tPinByte = pin2PinBlock(aPin);
+		if (aCardNO.length() == 11) {
+			aCardNO = "00" + aCardNO;
+		} else if (aCardNO.length() == 12) {
+			aCardNO = "0" + aCardNO;
+		}
+		byte[] tPanByte = formatPan(aCardNO);
+		byte[] tByte = new byte[8];
+		for (int i = 0; i < 8; i++) {
+			tByte[i] = (byte) (tPinByte[i] ^ tPanByte[i]);
+		}
+		return tByte;
+	}
+
+	/**
+	 * 
+	 * @param aBytesText
+	 * @param aBlockSize
+	 * @return
+	 */
+	private static byte[] addPKCS1Padding(byte[] aBytesText, int aBlockSize) {
+		if (aBytesText.length > (aBlockSize - 3)) {
+			return null;
+		}
+		SecureRandom tRandom = new SecureRandom();
+		byte[] tAfterPaddingBytes = new byte[aBlockSize];
+		tRandom.nextBytes(tAfterPaddingBytes);
+		tAfterPaddingBytes[0] = 0x00;
+		tAfterPaddingBytes[1] = 0x02;
+		int i = 2;
+		for (; i < aBlockSize - 1 - aBytesText.length; i++) {
+			if (tAfterPaddingBytes[i] == 0x00) {
+				tAfterPaddingBytes[i] = (byte) tRandom.nextInt();
+			}
+		}
+		tAfterPaddingBytes[i] = 0x00;
+		System.arraycopy(aBytesText, 0, tAfterPaddingBytes, (i + 1),
+				aBytesText.length);
+
+		return tAfterPaddingBytes;
+	}
+
+	/**
+	 * 
+	 * @param tPIN
+	 * @param iPan
+	 * @param publicKey
+	 * @return
+	 */
+	public String assymEncrypt(String tPIN, String iPan, RSAPublicKey publicKey) {
+
+		System.out.println("SampleHashMap::assymEncrypt([" + tPIN + "])");
+		System.out.println("SampleHashMap::assymEncrypt(PIN =[" + tPIN + "])");
+
+		try {
+			int tKeyLength = 1024;
+			int tBlockSize = tKeyLength / 8;
+
+			byte[] tTemp = null;
+
+			tTemp = SecureUtil.pin2PinBlockWithCardNO(tPIN, iPan);
+			tTemp = addPKCS1Padding(tTemp, tBlockSize);
+
+			BigInteger tPlainText = new BigInteger(tTemp);
+			BigInteger tCipherText = tPlainText.modPow(publicKey
+					.getPublicExponent(), publicKey.getModulus());
+
+			byte[] tCipherBytes = tCipherText.toByteArray();
+			int tCipherLength = tCipherBytes.length;
+			if (tCipherLength > tBlockSize) {
+				byte[] tTempBytes = new byte[tBlockSize];
+				System.arraycopy(tCipherBytes, tCipherLength - tBlockSize,
+						tTempBytes, 0, tBlockSize);
+				tCipherBytes = tTempBytes;
+			} else if (tCipherLength < tBlockSize) {
+				byte[] tTempBytes = new byte[tBlockSize];
+				for (int i = 0; i < tBlockSize - tCipherLength; i++) {
+					tTempBytes[i] = 0x00;
+				}
+				System.arraycopy(tCipherBytes, 0, tTempBytes, tBlockSize
+						- tCipherLength, tCipherLength);
+				tCipherBytes = tTempBytes;
+			}
+			String tEncryptPIN = new String(SecureUtil
+					.base64Encode(tCipherBytes));
+
+			System.out.println("SampleHashMap::assymEncrypt(EncryptCardNo =["
+					+ tEncryptPIN + "])");
+
+			return tEncryptPIN;
+		} catch (Exception e) {
+			e.printStackTrace(System.out);
+			return tPIN;
+		} catch (Error e) {
+			e.printStackTrace(System.out);
+			return tPIN;
+		}
+	}
+
+	/**
+	 * 以16进制对照的方式打印byte数组
+	 * 
+	 * @param inBytes
+	 * @return
+	 */
+	public static String trace(byte[] inBytes) {
+		int i, j = 0;
+		byte[] temp = new byte[76];
+		bytesSet(temp, ' ');
+		StringBuffer strc = new StringBuffer("");
+		strc
+				.append("----------------------------------------------------------------------------"
+						+ "\n");
+		for (i = 0; i < inBytes.length; i++) {
+			if (j == 0) {
+				System.arraycopy(String.format("%03d: ", i).getBytes(), 0,
+						temp, 0, 5);
+				System.arraycopy(String.format(":%03d", i + 15).getBytes(), 0,
+						temp, 72, 4);
+			}
+			System.arraycopy(String.format("%02X ", inBytes[i]).getBytes(), 0,
+					temp, j * 3 + 5 + (j > 7 ? 1 : 0), 3);
+			if (inBytes[i] == 0x00) {
+				temp[j + 55 + ((j > 7 ? 1 : 0))] = '.';
+			} else {
+				temp[j + 55 + ((j > 7 ? 1 : 0))] = inBytes[i];
+			}
+			j++;
+			if (j == 16) {
+				strc.append(new String(temp)).append("\n");
+				bytesSet(temp, ' ');
+				j = 0;
+			}
+		}
+		if (j != 0) {
+			strc.append(new String(temp)).append("\n");
+			bytesSet(temp, ' ');
+		}
+		strc
+				.append("----------------------------------------------------------------------------"
+						+ "\n");
+		return strc.toString();
+	}
+
+	/**
+	 * 
+	 * @param inBytes
+	 * @param fill
+	 */
+	private static void bytesSet(byte[] inBytes, char fill) {
+		if (inBytes.length == 0) {
+			return;
+		}
+		for (int i = 0; i < inBytes.length; i++) {
+			inBytes[i] = (byte) fill;
+		}
+	}
+	
+    public static PublicKey getPublicKey(String modulus, String exponent) {
+		try {
+			BigInteger b1 = new BigInteger(modulus);
+			BigInteger b2 = new BigInteger(exponent);
+			KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
+			RSAPublicKeySpec keySpec = new RSAPublicKeySpec(b1, b2);
+			return keyFactory.generatePublic(keySpec);
+		} catch (Exception e) {
+			throw new RuntimeException("getPublicKey error", e);
+		}
+	}
+    
+}

+ 166 - 0
src/main/java/com/itstyle/modules/unionpay/util/UnionConfig.java

@@ -0,0 +1,166 @@
+package com.itstyle.modules.unionpay.util;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+/**
+ * 基础配置参数
+ * 创建者 科帮网
+ * 创建时间	2017年8月2日
+ *
+ */
+public class UnionConfig {
+	
+	public static String merId="777290058110048";
+	
+	//默认配置的是UTF-8
+	public static String encoding_UTF8 = "UTF-8";
+	
+	public static String encoding_GBK = "GBK";
+	//全渠道固定值
+	public static String version = "5.0.0";
+	
+	// 商户发送交易时间 格式:YYYYMMDDhhmmss
+	public static String getCurrentTime() {
+		return new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
+	}
+	
+	// AN8..40 商户订单号,不能含"-"或"_"
+	public static String getOrderId() {
+		return new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
+	}
+	
+   /**
+	 * 组装请求,返回报文字符串用于显示
+	 * @param data
+	 * @return
+	 */
+    public static String genHtmlResult(Map<String, String> data){
+
+    	TreeMap<String, String> tree = new TreeMap<String, String>();
+		Iterator<Entry<String, String>> it = data.entrySet().iterator();
+		while (it.hasNext()) {
+			Entry<String, String> en = it.next();
+			tree.put(en.getKey(), en.getValue());
+		}
+		it = tree.entrySet().iterator();
+		StringBuffer sf = new StringBuffer();
+		while (it.hasNext()) {
+			Entry<String, String> en = it.next();
+			String key = en.getKey(); 
+			String value =  en.getValue();
+			if("respCode".equals(key)){
+				sf.append("<b>"+key + SDKConstants.EQUAL + value+"</br></b>");
+			}else
+				sf.append(key + SDKConstants.EQUAL + value+"</br>");
+		}
+		return sf.toString();
+    }
+    /**
+	 * 功能:解析全渠道商户对账文件中的ZM文件并以List<Map>方式返回
+	 * 适用交易:对账文件下载后对文件的查看
+	 * @param filePath ZM文件全路径
+	 * @return 包含每一笔交易中 序列号 和 值 的map序列
+	 */
+	@SuppressWarnings("rawtypes")
+	public static List<Map> parseZMFile(String filePath){
+		int lengthArray[] = {3,11,11,6,10,19,12,4,2,21,2,32,2,6,10,13,13,4,15,2,2,6,2,4,32,1,21,15,1,15,32,13,13,8,32,13,13,12,2,1,131};
+		return parseFile(filePath,lengthArray);
+	}
+	
+	/**
+	 * 功能:解析全渠道商户对账文件中的ZME文件并以List<Map>方式返回
+	 * 适用交易:对账文件下载后对文件的查看
+	 * @param filePath ZME文件全路径
+	 * @return 包含每一笔交易中 序列号 和 值 的map序列
+	 */
+	@SuppressWarnings("rawtypes")
+	public static List<Map> parseZMEFile(String filePath){
+		int lengthArray[] = {3,11,11,6,10,19,12,4,2,21,2,32,2,6,10,13,13,4,15,2,2,6,2,4,32,1,21,15,1,15,32,13,13,8,32,13,13,12,2,1,131};
+		return parseFile(filePath,lengthArray);
+	}
+	
+	/**
+	 * 功能:解析全渠道商户 ZM,ZME对账文件
+	 * @param filePath
+	 * @param lengthArray 参照《全渠道平台接入接口规范 第3部分 文件接口》 全渠道商户对账文件 6.1 ZM文件和6.2 ZME 文件 格式的类型长度组成int型数组
+	 * @return
+	 */
+	 @SuppressWarnings({ "rawtypes", "unchecked" })
+	private static List<Map> parseFile(String filePath,int lengthArray[]){
+	 	List<Map> ZmDataList = new ArrayList<Map>();
+	 	try {
+            String encoding="UTF-8";
+            File file=new File(filePath);
+            if(file.isFile() && file.exists()){ //判断文件是否存在
+                InputStreamReader read = new InputStreamReader(
+                new FileInputStream(file),encoding);//考虑到编码格式
+                BufferedReader bufferedReader = new BufferedReader(read);
+                String lineTxt = null;
+                while((lineTxt = bufferedReader.readLine()) != null){
+                	//解析的结果MAP,key为对账文件列序号,value为解析的值
+        		 	Map<Integer,String> ZmDataMap = new LinkedHashMap<Integer,String>();
+                    //左侧游标
+                    int leftIndex = 0;
+                    //右侧游标
+                    int rightIndex = 0;
+                    for(int i=0;i<lengthArray.length;i++){
+                    	rightIndex = leftIndex + lengthArray[i];
+                    	String filed = lineTxt.substring(leftIndex,rightIndex);
+                    	leftIndex = rightIndex+1;
+                    	ZmDataMap.put(i, filed);
+                    }
+                    ZmDataList.add(ZmDataMap);
+                }
+                read.close();
+        }else{
+            System.out.println("找不到指定的文件");
+        }
+        } catch (Exception e) {
+            System.out.println("读取文件内容出错");
+            e.printStackTrace();
+        }
+	 	for(int i=0;i<ZmDataList.size();i++){
+	 		System.out.println("行数: "+ (i+1));
+	 		Map<Integer,String> ZmDataMapTmp = ZmDataList.get(i);
+	 		
+	 		for(Iterator<Integer> it = ZmDataMapTmp.keySet().iterator();it.hasNext();){
+	 			Integer key = it.next();
+	 			String value = ZmDataMapTmp.get(key);
+		 		System.out.println("序号:"+ key + " 值: '"+ value +"'");
+		 	}
+	 	}
+		return ZmDataList;	
+	}
+
+		
+	public static void main(String[] args) {
+		System.out.println(AcpService.encryptTrack("12", "utf-8"));
+		SDKConfig.getConfig().loadPropertiesFromSrc();
+		
+		Map<String,String> customerInfoMap = new HashMap<String,String>();
+		//customerInfoMap.put("certifTp", "01");
+		//customerInfoMap.put("certifId", "341126197709218366");
+		//customerInfoMap.put("customerNm", "互联网");
+		customerInfoMap.put("phoneNo", "13552535506");
+		//customerInfoMap.put("smsCode", "123456");
+		//customerInfoMap.put("pin", "626262");						//密码加密
+		//customerInfoMap.put("cvn2", "123");           				//卡背面的cvn2三位数字
+		//customerInfoMap.put("expired", "1711");  				    //有效期 年在前月在后
+		
+		//System.out.println(getCustomerInfoWithEncrypt(customerInfoMap,"6217001210048797565"));
+		
+		parseZMFile("C:\\Users\\wulh\\Desktop\\802310048993424_20150905\\INN15090588ZM_802310048993424");
+	}
+}

+ 1 - 4
src/main/java/com/itstyle/modules/weixinpay/service/impl/WeixinPayServiceImpl.java

@@ -51,7 +51,6 @@ public class WeixinPayServiceImpl implements IWeixinPayService {
 			packageParams.put("spbill_create_ip", product.getSpbillCreateIp());// 发起人IP地址
 			packageParams.put("notify_url", notify_url);// 回调地址
 			packageParams.put("trade_type", trade_type);// 交易类型
-			System.out.println(packageParams);
 			String sign = PayCommonUtil.createSign("UTF-8", packageParams, key);
 			packageParams.put("sign", sign);// 签名
 
@@ -97,9 +96,7 @@ public class WeixinPayServiceImpl implements IWeixinPayService {
 			packageParams.put("out_trade_no", product.getOutTradeNo());// 商户订单号
 			packageParams.put("out_refund_no", product.getOutTradeNo());//商户退款单号
 			String totalFee = product.getTotalFee();
-			if(totalFee.indexOf(".")>0){//去除小数点 invalide total_fee  报错
-				totalFee = totalFee.substring(0, totalFee.indexOf("."));
-			}
+			totalFee =  CommonUtil.subZeroAndDot(totalFee);
 			packageParams.put("total_fee", totalFee);// 总金额
 			packageParams.put("refund_fee", totalFee);//退款金额
 			packageParams.put("op_user_id", mch_id);//操作员帐号, 默认为商户号

Файловите разлики са ограничени, защото са твърде много
+ 51 - 0
src/main/resources/acp_sdk.properties


+ 2 - 0
src/main/resources/application-dev.properties

@@ -34,5 +34,7 @@ server.context.url = http://192.168.1.66:8080/springboot_pay/
 alipay.notify.url=https://blog.52itstyle.com/alipay/pay
 #\u5fae\u4fe1\u540e\u53f0\u56de\u8c03
 wexinpay.notify.url=https://blog.52itstyle.com/weixin/pay
+#\u94f6\u8054\u540e\u53f0\u56de\u8c03
+unionpay.notify.url=https://blog.52itstyle.com/union/pay
 
 

+ 25 - 0
src/main/resources/assets/acp_test_enc.cer

@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIEQzCCAyugAwIBAgIFEAJkkicwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UEBhMC
+Q04xMDAuBgNVBAoTJ0NoaW5hIEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1dGhv
+cml0eTEXMBUGA1UEAxMOQ0ZDQSBURVNUIE9DQTEwHhcNMTUxMjE1MDkxMTM1WhcN
+MTcxMjE1MDkxMTM1WjCBgTELMAkGA1UEBhMCY24xFzAVBgNVBAoTDkNGQ0EgVEVT
+VCBPQ0ExMRIwEAYDVQQLEwlDRkNBIFRFU1QxFDASBgNVBAsTC0VudGVycHJpc2Vz
+MS8wLQYDVQQDFCYwNDFAWjIwMTQtMTEtMTFAMDAwNDAwMDA6U0lHTkAwMDAwMDAw
+NTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANHnoPx0JZKZmFjIURxN
+AbLlWAw2jiFFWBnDF2MIGkya2r0fGiR0knq8zkKUnoIyC+tzEiOavniQaSu0ucuv
+/V4ugz66PSRxw1gaPcR2dDVdgojF00TcewxlJEA65fK3eKhUYfC3NbRaVQOMMdwv
+7nNEvzxvdExE47ceMya7FmsUPyLFu9X++chFQiYfr8nH+wdDeYo8w8vCX+Jd2vRu
+qDOah29CQfkAmXsx3D68zg0q4AjlLI1t5gLKiU5YoG6yWrigPyreEHh716rV8HkT
+jGWx3cxF/HsLZ/E4SgIr5yIZA6qw8RFqaSXuyw3iDrNf6aSJGO0GKlvxnvD20oGR
+JokCAwEAAaOB6TCB5jAfBgNVHSMEGDAWgBTPcJ1h6518Lrj3ywJA9wmd/jN0gDBI
+BgNVHSAEQTA/MD0GCGCBHIbvKgEBMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cu
+Y2ZjYS5jb20uY24vdXMvdXMtMTQuaHRtMDgGA1UdHwQxMC8wLaAroCmGJ2h0dHA6
+Ly91Y3JsLmNmY2EuY29tLmNuL1JTQS9jcmw0NTE3LmNybDALBgNVHQ8EBAMCA+gw
+HQYDVR0OBBYEFGjriHHUE1MnYX7H6GrFi+8R2zmUMBMGA1UdJQQMMAoGCCsGAQUF
+BwMCMA0GCSqGSIb3DQEBBQUAA4IBAQAjsN0fyDqcxS9YKMpY3CIdlarCjvnus+wS
+ExjNnPv7n2urqhz2Jf3yJuhxVVPzdgKT51C2UiR+/i1OJPWFx0IUos/v8js/TM5j
+mTdPkBsRSxSDieHHiuE1nPUwGXUEO7mlOVkkzmLI75bJ86foxNflbQCF0+VvpMe7
+KwQoNOR8DxIBxHdlsjSxE2RKM/ftXLhptrK4GK3K4FAcSiqBMEn5PF/5V9mHp5N6
+3LdkMYqBj4pRcy8vrclucq99b2glmMLw7CI6Kxu22WVoRnZESjcgXiMVLLe+qy55
+0pWcZ2BChS7Ln19tj49LnS3vFp6xf4qNSqxEBaQuNLEx0ObjI6pz
+-----END CERTIFICATE-----

BIN
src/main/resources/assets/acp_test_sign.pfx


+ 25 - 0
src/main/resources/assets/acp_test_verify_sign.cer

@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIEOjCCAyKgAwIBAgIFEAJkAUkwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UEBhMC
+Q04xMDAuBgNVBAoTJ0NoaW5hIEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1dGhv
+cml0eTEXMBUGA1UEAxMOQ0ZDQSBURVNUIE9DQTEwHhcNMTUxMjA0MDMyNTIxWhcN
+MTcxMjA0MDMyNTIxWjB5MQswCQYDVQQGEwJjbjEXMBUGA1UEChMOQ0ZDQSBURVNU
+IE9DQTExEjAQBgNVBAsTCUNGQ0EgVEVTVDEUMBIGA1UECxMLRW50ZXJwcmlzZXMx
+JzAlBgNVBAMUHjA0MUBaMTJAMDAwNDAwMDA6U0lHTkAwMDAwMDA2MjCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMUDYYCLYvv3c911zhRDrSWCedAYDJQe
+fJUjZKI2avFtB2/bbSmKQd0NVvh+zXtehCYLxKOltO6DDTRHwH9xfhRY3CBMmcOv
+d2xQQvMJcV9XwoqtCKqhzguoDxJfYeGuit7DpuRsDGI0+yKgc1RY28v1VtuXG845
+fTP7PRtJrareQYlQXghMgHFAZ/vRdqlLpVoNma5C56cJk5bfr2ngDlXbUqPXLi1j
+iXAFb/y4b8eGEIl1LmKp3aPMDPK7eshc7fLONEp1oQ5Jd1nE/GZj+lC345aNWmLs
+l/09uAvo4Lu+pQsmGyfLbUGR51KbmHajF4Mrr6uSqiU21Ctr1uQGkccCAwEAAaOB
+6TCB5jAfBgNVHSMEGDAWgBTPcJ1h6518Lrj3ywJA9wmd/jN0gDBIBgNVHSAEQTA/
+MD0GCGCBHIbvKgEBMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuY2ZjYS5jb20u
+Y24vdXMvdXMtMTQuaHRtMDgGA1UdHwQxMC8wLaAroCmGJ2h0dHA6Ly91Y3JsLmNm
+Y2EuY29tLmNuL1JTQS9jcmw0NDkxLmNybDALBgNVHQ8EBAMCA+gwHQYDVR0OBBYE
+FAFmIOdt15XLqqz13uPbGQwtj4PAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMA0GCSqG
+SIb3DQEBBQUAA4IBAQB8YuMQWDH/Ze+e+2pr/914cBt94FQpYqZOmrBIQ8kq7vVm
+TTy94q9UL0pMMHDuFJV6Wxng4Me/cfVvWmjgLg/t7bdz0n6UNj4StJP17pkg68WG
+zMlcjuI7/baxtDrD+O8dKpHoHezqhx7dfh1QWq8jnqd3DFzfkhEpuIt6QEaUqoWn
+t5FxSUiykTfjnaNEEGcn3/n2LpwrQ+upes12/B778MQETOsVv4WX8oE1Qsv1XLRW
+i0DQetTU2RXTrynv+l4kMy0h9b/Hdlbuh2s0QZqlUMXx2biy0GvpF2pR8f+OaLuT
+AtaKdU4T2+jO44+vWNNN2VoAaw0xY6IZ3/A1GL0x
+-----END CERTIFICATE-----

+ 17 - 0
src/main/resources/assets/readme.txt

@@ -0,0 +1,17 @@
+银联支付相关签名证书 自行配置 此处为测试证书
+acp_prod_enc.cer
+acp_prod_sign_inst.pfx
+acp_prod_verify_sign.cer
+
+##测试账号
+
+卡号 :
+6216261000000000018
+
+证件号 :
+341126197709218366
+
+姓名:全渠道
+控件短信验证码 :123456
+
+测试浏览器360兼容模式,个别浏览器可能无法支付。

+ 2 - 1
src/main/resources/cert/readme.txt

@@ -1 +1,2 @@
-微信支付证书存放路径地址(微信退款 需要证书认证)
+微信支付证书存放路径地址(微信退款 需要证书认证)
+apiclient_cert.p12

+ 1 - 1
src/main/resources/templates/index.html

@@ -9,7 +9,7 @@
 	<div style="text-align: center">
 		<a th:href="@{/alipay/index}">阿里支付</a> 
 		<a th:href="@{/weixin/index}">微信支付</a>
-		<a th:href="@{/alipay/index}">银联支付</a>
+		<a th:href="@{/unionpay/index}">银联支付</a>
 	</div>
 </body>
 </html>

+ 183 - 0
src/main/resources/templates/unionpay/css/unionpay.css

@@ -0,0 +1,183 @@
+* {
+	margin: 0;
+	padding: 0;
+}
+
+ul, ol {
+	list-style: none;
+}
+
+body {
+	font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande",
+		sans-serif;
+}
+
+.tab-head {
+	margin-left: 120px;
+	margin-bottom: 10px;
+}
+
+.tab-content {
+	clear: left;
+	display: none;
+}
+
+h2 {
+	border-bottom: solid #7680de 2px;
+	width: 200px;
+	height: 25px;
+	margin: 0;
+	float: left;
+	text-align: center;
+	font-size: 16px;
+}
+
+.selected {
+	color: #FFFFFF;
+	background-color: #7680de;
+}
+
+.show {
+	clear: left;
+	display: block;
+}
+
+.hidden {
+	display: none;
+}
+
+.new-btn-login-sp {
+	padding: 1px;
+	display: inline-block;
+	width: 75%;
+}
+
+.new-btn-login {
+	background-color: #7680de;
+	color: #FFFFFF;
+	font-weight: bold;
+	border: none;
+	width: 100%;
+	height: 30px;
+	border-radius: 5px;
+	font-size: 16px;
+}
+
+#main {
+	width: 100%;
+	margin: 0 auto;
+	font-size: 14px;
+}
+
+.red-star {
+	color: #f00;
+	width: 10px;
+	display: inline-block;
+}
+
+.null-star {
+	color: #fff;
+}
+
+.content {
+	margin-top: 5px;
+}
+
+.content dt {
+	width: 100px;
+	display: inline-block;
+	float: left;
+	margin-left: 20px;
+	color: #666;
+	font-size: 13px;
+	margin-top: 8px;
+}
+
+.content dd {
+	margin-left: 120px;
+	margin-bottom: 5px;
+}
+
+.content dd input {
+	width: 85%;
+	height: 28px;
+	border: 0;
+	-webkit-border-radius: 0;
+	-webkit-appearance: none;
+}
+
+#foot {
+	margin-top: 10px;
+	position: absolute;
+	bottom: 15px;
+	width: 100%;
+}
+
+.foot-ul {
+	width: 100%;
+}
+
+.foot-ul li {
+	width: 100%;
+	text-align: center;
+	color: #666;
+}
+
+.note-help {
+	color: #999999;
+	font-size: 12px;
+	line-height: 130%;
+	margin-top: 5px;
+	width: 100%;
+	display: block;
+}
+
+#btn-dd {
+	margin: 20px;
+	text-align: center;
+}
+
+.foot-ul {
+	width: 100%;
+}
+
+.one_line {
+	display: block;
+	height: 1px;
+	border: 0;
+	border-top: 1px solid #eeeeee;
+	width: 100%;
+	margin-left: 20px;
+}
+
+.am-header {
+	display: -webkit-box;
+	display: -ms-flexbox;
+	display: box;
+	width: 100%;
+	position: relative;
+	padding: 7px 0;
+	-webkit-box-sizing: border-box;
+	-ms-box-sizing: border-box;
+	box-sizing: border-box;
+	background: #1D222D;
+	height: 50px;
+	text-align: center;
+	-webkit-box-pack: center;
+	-ms-flex-pack: center;
+	box-pack: center;
+	-webkit-box-align: center;
+	-ms-flex-align: center;
+	box-align: center;
+}
+
+.am-header h1 {
+	-webkit-box-flex: 1;
+	-ms-flex: 1;
+	box-flex: 1;
+	line-height: 18px;
+	text-align: center;
+	font-size: 18px;
+	font-weight: 300;
+	color: #fff;
+}

+ 217 - 0
src/main/resources/templates/unionpay/index.html

@@ -0,0 +1,217 @@
+<!DOCTYPE html>
+<!-- 哈哈哈 我是个天才 -->
+<html xmlns:th="http://www.thymeleaf.org">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta>
+<link rel="stylesheet" th:href="@{/unionpay/css/unionpay.css}" />
+<title>银联支付</title>
+</head>
+<body text=#000000 bgColor="#ffffff" leftMargin=0 topMargin=5>
+	<header class="am-header">
+	<h1>银联支付体验入口页</h1>
+	</header>
+	<div id="main">
+		<div id="tabhead" class="tab-head">
+			<h2 id="tab1" class="selected" name="tab">付 款</h2>
+			<h2 id="tab2" name="tab">交 易 查 询</h2>
+			<h2 id="tab3" name="tab">退 款</h2>
+			<h2 id="tab4" name="tab">退 款 查 询</h2>
+			<h2 id="tab5" name="tab">交 易 关 闭</h2>
+		</div>
+		<form name=alipayment action=pcPay method=post
+			target="_blank">
+			<div id="body1" class="show" name="divcontent">
+				<dl class="content">
+					<dt>商户订单号 :</dt>
+					<dd>
+						<input id="WIDout_trade_no" name="outTradeNo" />
+					</dd>
+					<hr class="one_line">
+					<dt>订单名称 :</dt>
+					<dd>
+						<input id="WIDsubject" name="subject" />
+					</dd>
+					<hr class="one_line">
+					<dt>付款金额 :</dt>
+					<dd>
+						<input id="WIDtotal_amount" name="totalFee" />
+					</dd>
+					<hr class="one_line">
+					<dt>商品描述:</dt>
+					<dd>
+						<input id="WIDbody" name="body" />
+					</dd>
+					<hr class="one_line">
+					<dt></dt>
+					<dd id="btn-dd">
+						<span class="new-btn-login-sp">
+							<button class="new-btn-login" type="submit"
+								style="text-align: center;">付 款</button>
+						</span> <span class="note-help">如果您点击“付款”按钮,即表示您同意该次的执行操作。</span>
+					</dd>
+				</dl>
+			</div>
+		</form>
+		<form name=tradequery action=alipay.trade.query.jsp method=post
+			target="_blank">
+			<div id="body2" class="tab-content" name="divcontent">
+				<dl class="content">
+					<dt>商户订单号 :</dt>
+					<dd>
+						<input id="WIDTQout_trade_no" name="WIDTQout_trade_no" />
+					</dd>
+					<hr class="one_line">
+					<dt>支付宝交易号 :</dt>
+					<dd>
+						<input id="WIDTQtrade_no" name="WIDTQtrade_no" />
+					</dd>
+					<hr class="one_line">
+					<dt></dt>
+					<dd id="btn-dd">
+						<span class="new-btn-login-sp">
+							<button class="new-btn-login" type="submit"
+								style="text-align: center;">交 易 查 询</button>
+						</span> <span class="note-help">商户订单号与支付宝交易号二选一,如果您点击“交易查询”按钮,即表示您同意该次的执行操作。</span>
+					</dd>
+				</dl>
+			</div>
+		</form>
+		<form name=traderefund action=alipay.trade.refund.jsp method=post
+			target="_blank">
+			<div id="body3" class="tab-content" name="divcontent">
+				<dl class="content">
+					<dt>商户订单号 :</dt>
+					<dd>
+						<input id="WIDTRout_trade_no" name="WIDTRout_trade_no" />
+					</dd>
+					<hr class="one_line">
+					<dt>支付宝交易号 :</dt>
+					<dd>
+						<input id="WIDTRtrade_no" name="WIDTRtrade_no" />
+					</dd>
+					<hr class="one_line">
+					<dt>退款金额 :</dt>
+					<dd>
+						<input id="WIDTRrefund_amount" name="WIDTRrefund_amount" />
+					</dd>
+					<hr class="one_line">
+					<dt>退款原因 :</dt>
+					<dd>
+						<input id="WIDTRrefund_reason" name="WIDTRrefund_reason" />
+					</dd>
+					<hr class="one_line">
+					<dt>退款请求号 :</dt>
+					<dd>
+						<input id="WIDTRout_request_no" name="WIDTRout_request_no" />
+					</dd>
+					<hr class="one_line">
+					<dt></dt>
+					<dd id="btn-dd">
+						<span class="new-btn-login-sp">
+							<button class="new-btn-login" type="submit"
+								style="text-align: center;">退 款</button>
+						</span> <span class="note-help">商户订单号与支付宝交易号二选一,如果您点击“退款”按钮,即表示您同意该次的执行操作。</span>
+					</dd>
+				</dl>
+			</div>
+		</form>
+		<form name=traderefundquery
+			action=alipay.trade.fastpay.refund.query.jsp method=post
+			target="_blank">
+			<div id="body4" class="tab-content" name="divcontent">
+				<dl class="content">
+					<dt>商户订单号 :</dt>
+					<dd>
+						<input id="WIDRQout_trade_no" name="WIDRQout_trade_no" />
+					</dd>
+					<hr class="one_line">
+					<dt>支付宝交易号 :</dt>
+					<dd>
+						<input id="WIDRQtrade_no" name="WIDRQtrade_no" />
+					</dd>
+					<hr class="one_line">
+					<dt>退款请求号 :</dt>
+					<dd>
+						<input id="WIDRQout_request_no" name="WIDRQout_request_no" />
+					</dd>
+					<hr class="one_line">
+					<dt></dt>
+					<dd id="btn-dd">
+						<span class="new-btn-login-sp">
+							<button class="new-btn-login" type="submit"
+								style="text-align: center;">退 款 查 询</button>
+						</span> <span class="note-help">商户订单号与支付宝交易号二选一,如果您点击“退款查询”按钮,即表示您同意该次的执行操作。</span>
+					</dd>
+				</dl>
+			</div>
+		</form>
+		<form name=tradeclose action=alipay.trade.close.jsp method=post
+			target="_blank">
+			<div id="body5" class="tab-content" name="divcontent">
+				<dl class="content">
+					<dt>商户订单号 :</dt>
+					<dd>
+						<input id="WIDTCout_trade_no" name="WIDTCout_trade_no" />
+					</dd>
+					<hr class="one_line">
+					<dt>支付宝交易号 :</dt>
+					<dd>
+						<input id="WIDTCtrade_no" name="WIDTCtrade_no" />
+					</dd>
+					<hr class="one_line">
+					<dt></dt>
+					<dd id="btn-dd">
+						<span class="new-btn-login-sp">
+							<button class="new-btn-login" type="submit"
+								style="text-align: center;">交 易 关 闭</button>
+						</span> <span class="note-help">商户订单号与支付宝交易号二选一,如果您点击“交易关闭”按钮,即表示您同意该次的执行操作。</span>
+					</dd>
+				</dl>
+			</div>
+		</form>
+		<div id="foot">
+			<ul class="foot-ul">
+				<li>银联版权所有 2015-2018 ALIPAY.COM</li>
+			</ul>
+		</div>
+	</div>
+</body>
+<script language="javascript">
+	var tabs = document.getElementsByName('tab');
+	var contents = document.getElementsByName('divcontent');
+	
+	(function changeTab(tab) {
+	    for(var i = 0, len = tabs.length; i < len; i++) {
+	        tabs[i].onmouseover = showTab;
+	    }
+	})();
+	
+	function showTab() {
+	    for(var i = 0, len = tabs.length; i < len; i++) {
+	        if(tabs[i] === this) {
+	            tabs[i].className = 'selected';
+	            contents[i].className = 'show';
+	        } else {
+	            tabs[i].className = '';
+	            contents[i].className = 'tab-content';
+	        }
+	    }
+	}
+	
+	function GetDateNow() {
+		var vNow = new Date();
+		var sNow = "";
+		sNow += String(vNow.getFullYear());
+		sNow += String(vNow.getMonth() + 1);
+		sNow += String(vNow.getDate());
+		sNow += String(vNow.getHours());
+		sNow += String(vNow.getMinutes());
+		sNow += String(vNow.getSeconds());
+		sNow += String(vNow.getMilliseconds());
+		document.getElementById("WIDout_trade_no").value =  sNow;
+		document.getElementById("WIDsubject").value = "科帮网测试支付";
+		document.getElementById("WIDtotal_amount").value = "100";
+	}
+	GetDateNow();
+</script>
+</html>

+ 11 - 0
src/main/resources/templates/unionpay/pay.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<!-- 哈哈哈 我是个天才 -->
+<html xmlns:th="http://www.thymeleaf.org">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta>
+<title>银联支付</title>
+</head>
+<body th:utext=${form}>
+	电脑支付
+</body>
+</html>