From c6e431c93fe9cae625e3c944e09bd4bf0de5647f Mon Sep 17 00:00:00 2001 From: XinYi Song <2037158277@qq.com> Date: Wed, 29 Dec 2021 17:17:07 +0800 Subject: [PATCH] =?UTF-8?q?springboot=E6=95=B4=E5=90=88=E4=BA=86=E9=93=B6?= =?UTF-8?q?=E8=81=94=E6=94=AF=E4=BB=98=E6=89=AB=E7=A0=81=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 14 + .../xkrs/common/config/WebSecurityConfig.java | 1 + .../java/com/xkrs/unionpay/bean/DemoBase.java | 227 +++++ .../xkrs/unionpay/config/UnionpayConfig.java | 20 + .../controller/UnionpayController.java | 130 +++ .../unionpay/service/UnionpayService.java | 9 + .../service/impl/UnionpayServiceImpl.java | 91 ++ .../com/xkrs/unionpay/util/AcpService.java | 676 ++++++++++++++ .../util/BaseHttpSSLSocketFactory.java | 125 +++ .../java/com/xkrs/unionpay/util/CertUtil.java | 790 ++++++++++++++++ .../com/xkrs/unionpay/util/HttpClient.java | 316 +++++++ .../java/com/xkrs/unionpay/util/LogUtil.java | 115 +++ .../com/xkrs/unionpay/util/SDKConfig.java | 835 +++++++++++++++++ .../com/xkrs/unionpay/util/SDKConstants.java | 391 ++++++++ .../java/com/xkrs/unionpay/util/SDKUtil.java | 855 ++++++++++++++++++ .../com/xkrs/unionpay/util/SecureUtil.java | 577 ++++++++++++ src/main/resources/application.properties | 75 ++ src/main/resources/log4j2.xml | 6 +- 18 files changed, 5250 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/xkrs/unionpay/bean/DemoBase.java create mode 100644 src/main/java/com/xkrs/unionpay/config/UnionpayConfig.java create mode 100644 src/main/java/com/xkrs/unionpay/controller/UnionpayController.java create mode 100644 src/main/java/com/xkrs/unionpay/service/UnionpayService.java create mode 100644 src/main/java/com/xkrs/unionpay/service/impl/UnionpayServiceImpl.java create mode 100644 src/main/java/com/xkrs/unionpay/util/AcpService.java create mode 100644 src/main/java/com/xkrs/unionpay/util/BaseHttpSSLSocketFactory.java create mode 100644 src/main/java/com/xkrs/unionpay/util/CertUtil.java create mode 100644 src/main/java/com/xkrs/unionpay/util/HttpClient.java create mode 100644 src/main/java/com/xkrs/unionpay/util/LogUtil.java create mode 100644 src/main/java/com/xkrs/unionpay/util/SDKConfig.java create mode 100644 src/main/java/com/xkrs/unionpay/util/SDKConstants.java create mode 100644 src/main/java/com/xkrs/unionpay/util/SDKUtil.java create mode 100644 src/main/java/com/xkrs/unionpay/util/SecureUtil.java diff --git a/pom.xml b/pom.xml index 7564f54..0553bb8 100644 --- a/pom.xml +++ b/pom.xml @@ -236,6 +236,20 @@ gson + + + com.google.zxing + core + 3.3.0 + + + + + com.google.zxing + javase + 3.3.0 + + diff --git a/src/main/java/com/xkrs/common/config/WebSecurityConfig.java b/src/main/java/com/xkrs/common/config/WebSecurityConfig.java index 85c8dfb..700d8ea 100644 --- a/src/main/java/com/xkrs/common/config/WebSecurityConfig.java +++ b/src/main/java/com/xkrs/common/config/WebSecurityConfig.java @@ -46,6 +46,7 @@ class WebSecurityConfig extends WebSecurityConfigurerAdapter { .antMatchers(HttpMethod.POST,"/qiNiuUploadFile").permitAll() .antMatchers(HttpMethod.POST,"/merchantSettlement").permitAll() .antMatchers(HttpMethod.POST,"/api/user/updateRememberPassword").permitAll() + .antMatchers("/pay").permitAll() // 所有其它请求需要身份认证 .anyRequest().authenticated() .and() diff --git a/src/main/java/com/xkrs/unionpay/bean/DemoBase.java b/src/main/java/com/xkrs/unionpay/bean/DemoBase.java new file mode 100644 index 0000000..85f54ec --- /dev/null +++ b/src/main/java/com/xkrs/unionpay/bean/DemoBase.java @@ -0,0 +1,227 @@ +package com.xkrs.unionpay.bean; + +import com.xkrs.unionpay.util.SDKConfig; +import com.xkrs.unionpay.util.SDKConstants; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +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.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.TreeMap; + +/** + * 名称: demo中用到的方法
+ * 日期: 2015-09
+ + * 版权: 中国银联
+ * 说明:以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己需要,按照技术文档编写。该代码仅供参考。
+ */ +public class DemoBase { + + //默认配置的是UTF-8 + public static String encoding = "UTF-8"; + + //全渠道固定值 + public static String version = SDKConfig.getConfig().getVersion(); + + //后台服务对应的写法参照 FrontRcvResponse.java + public static String frontUrl = SDKConfig.getConfig().getFrontUrl(); + + //后台服务对应的写法参照 BackRcvResponse.java + public static String backUrl = SDKConfig.getConfig().getBackUrl();//受理方和发卡方自选填写的域[O]--后台通知地址 + + // 商户发送交易时间 格式: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 data){ + + TreeMap tree = new TreeMap(); + Iterator> it = data.entrySet().iterator(); + while (it.hasNext()) { + Entry en = it.next(); + tree.put(en.getKey(), en.getValue()); + } + it = tree.entrySet().iterator(); + StringBuffer sf = new StringBuffer(); + while (it.hasNext()) { + Entry en = it.next(); + String key = en.getKey(); + String value = en.getValue(); + if("respCode".equals(key)){ + sf.append(""+key + SDKConstants.EQUAL + value+"
"); + }else { + sf.append(key + SDKConstants.EQUAL + value+"
"); + } + } + return sf.toString(); + } + /** + * 功能:解析全渠道商户对账文件中的ZM文件并以List方式返回 + * 适用交易:对账文件下载后对文件的查看 + * @param filePath ZM文件全路径 + * @return 包含每一笔交易中 序列号 和 值 的map序列 + */ + public static List 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,32,98}; + return parseFile(filePath,lengthArray); + } + + /** + * 功能:解析全渠道商户对账文件中的ZME文件并以List方式返回 + * 适用交易:对账文件下载后对文件的查看 + * @param filePath ZME文件全路径 + * @return 包含每一笔交易中 序列号 和 值 的map序列 + */ + public static List parseZMEFile(String filePath){ + int lengthArray[] = {3,11,11,6,10,19,12,4,2,2,6,10,4,12,13,13,15,15,1,12,2,135}; + return parseFile(filePath,lengthArray); + } + + /** + * 功能:解析全渠道商户 ZM,ZME对账文件 + * @param filePath + * @param lengthArray 参照《全渠道平台接入接口规范 第3部分 文件接口》 全渠道商户对账文件 6.1 ZM文件和6.2 ZME 文件 格式的类型长度组成int型数组 + * @return + */ + private static List parseFile(String filePath,int lengthArray[]){ + List ZmDataList = new ArrayList(); + try { + String encoding="gbk"; //文件是gbk编码 + File file=new File(filePath); + if(file.isFile() && file.exists()){ //判断文件是否存在 + InputStreamReader read = new InputStreamReader( + new FileInputStream(file), "iso-8859-1"); + BufferedReader bufferedReader = new BufferedReader(read); + String lineTxt = null; + while((lineTxt = bufferedReader.readLine()) != null){ + byte[] bs = lineTxt.getBytes("iso-8859-1"); + //解析的结果MAP,key为对账文件列序号,value为解析的值 + Map ZmDataMap = new LinkedHashMap(); + //左侧游标 + int leftIndex = 0; + //右侧游标 + int rightIndex = 0; + for(int i=0;i dataList,String file){ + StringBuffer tableSb = new StringBuffer("对账文件的规范参考 https://open.unionpay.com/ajweb/help/file/ 产品接口规范->平台接口规范:文件接口
文件【"+file + "】解析后内容如下:"); + tableSb.append(""); + if(dataList.size() > 0){ + Map dataMapTmp = dataList.get(0); + tableSb.append(""); + for(Iterator it = dataMapTmp.keySet().iterator();it.hasNext();){ + Integer key = it.next(); + String value = dataMapTmp.get(key); + System.out.println("序号:"+ (key+1) + " 值: '"+ value +"'"); + tableSb.append(""); + } + tableSb.append(""); + } + + for(int i=0;i dataMapTmp = dataList.get(i); + tableSb.append(""); + for(Iterator it = dataMapTmp.keySet().iterator();it.hasNext();){ + Integer key = it.next(); + String value = dataMapTmp.get(key); + System.out.println("序号:"+ (key+1) + " 值: '"+ value +"'"); + tableSb.append(""); + } + tableSb.append(""); + } + tableSb.append("
序号"+(key+1)+"
"+value+"
"); + return tableSb.toString(); + } + + + public static List unzip(String zipFilePath,String outPutDirectory){ + List fileList = new ArrayList(); + try { + ZipInputStream zin = new ZipInputStream(new FileInputStream(zipFilePath));//输入源zip路径 + BufferedInputStream bin = new BufferedInputStream(zin); + BufferedOutputStream bout = null; + File file=null; + ZipEntry entry; + try { + while((entry = zin.getNextEntry())!=null && !entry.isDirectory()){ + file = new File(outPutDirectory,entry.getName()); + if(!file.exists()){ + (new File(file.getParent())).mkdirs(); + } + bout = new BufferedOutputStream(new FileOutputStream(file)); + int b; + while((b=bin.read())!=-1){ + bout.write(b); + } + bout.flush(); + fileList.add(file.getAbsolutePath()); + System.out.println(file+"解压成功"); + } + } catch (IOException e) { + e.printStackTrace(); + }finally{ + try { + bin.close(); + zin.close(); + if(bout!=null){ + bout.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + return fileList; + } + +} \ No newline at end of file diff --git a/src/main/java/com/xkrs/unionpay/config/UnionpayConfig.java b/src/main/java/com/xkrs/unionpay/config/UnionpayConfig.java new file mode 100644 index 0000000..3a8842a --- /dev/null +++ b/src/main/java/com/xkrs/unionpay/config/UnionpayConfig.java @@ -0,0 +1,20 @@ +package com.xkrs.unionpay.config; + +import com.xkrs.unionpay.util.SDKConfig; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.context.annotation.Configuration; + +/** + * @Author: XinYi Song + * @Date: 2021/12/29 11:25 + */ + +@Configuration +public class UnionpayConfig implements ApplicationRunner { + + @Override + public void run(ApplicationArguments args) { + SDKConfig.getConfig().loadPropertiesFromSrc(); + } +} diff --git a/src/main/java/com/xkrs/unionpay/controller/UnionpayController.java b/src/main/java/com/xkrs/unionpay/controller/UnionpayController.java new file mode 100644 index 0000000..dd97ea4 --- /dev/null +++ b/src/main/java/com/xkrs/unionpay/controller/UnionpayController.java @@ -0,0 +1,130 @@ +package com.xkrs.unionpay.controller; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.MultiFormatWriter; +import com.google.zxing.client.j2se.MatrixToImageWriter; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import com.xkrs.unionpay.bean.DemoBase; +import com.xkrs.unionpay.service.UnionpayService; +import com.xkrs.unionpay.util.AcpService; +import com.xkrs.unionpay.util.SDKConstants; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import javax.imageio.ImageIO; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; + +/** + * @Author: XinYi Song + * @Date: 2021/12/28 17:03 + */ +@RestController +public class UnionpayController { + + @Resource + private UnionpayService unionpayService; + + @RequestMapping("/pay") + public void pay(HttpServletResponse response) throws Exception{ + String qrCode=unionpayService.pay(DemoBase.getOrderId(),"200"); + makeQRCode(qrCode,response.getOutputStream()); + } + + private static final int BLACK = 0xFF000000; + private static final int WHITE = 0xFFFFFFFF; + + + public static BufferedImage toBufferedImage(BitMatrix matrix) { + int width = matrix.getWidth(); + int height = matrix.getHeight(); + BufferedImage image = new BufferedImage(width, height, + BufferedImage.TYPE_INT_RGB); + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE); + } + } + return image; + } + + public static void writeToFile(BitMatrix matrix, String format, File file) + throws IOException { + BufferedImage image = toBufferedImage(matrix); + if (!ImageIO.write(image, format, file)) { + throw new IOException("Could not write an image of format " + + format + " to " + file); + } + } + + public static void writeToStream(BitMatrix matrix, String format, + OutputStream stream) throws IOException { + BufferedImage image = toBufferedImage(matrix); + if (!ImageIO.write(image, format, stream)) { + throw new IOException("Could not write an image of format " + format); + } + } + + + public void makeQRCode(String content, OutputStream outputStream) throws Exception{ //生成二维码 + + + Hashtable hints = new Hashtable(); + hints.put(EncodeHintType.CHARACTER_SET, "utf-8"); // 内容所使用字符集编码 + + BitMatrix bitMatrix = new MultiFormatWriter().encode(content, + BarcodeFormat.QR_CODE, 200, 200, hints); + // 生成二维码 + FileOutputStream outputFile = new FileOutputStream("d:" + File.separator + "new.jpg"); + MatrixToImageWriter.writeToStream(bitMatrix, "jpg", outputFile); + + /*Map map=new HashMap<>(); + map.put(EncodeHintType.CHARACTER_SET, StandardCharsets.UTF_8); + map.put(EncodeHintType.MARGIN,2); + + QRCodeWriter qrCodeWriter=new QRCodeWriter(); + BitMatrix bitMatrix=qrCodeWriter.encode(content, BarcodeFormat.QR_CODE,200,200,map); + MatrixToImageWriter.writeToStream(bitMatrix,"png",outputStream);*/ + } + + @RequestMapping("/notify") + public void hello2(HttpServletRequest request, HttpServletResponse response) throws Exception{ + Map result=new HashMap<>(); + + System.out.println("======= 后台通知 ========"); + Enumeration names=request.getParameterNames(); + if (names!=null){ + while (names.hasMoreElements()){ + String name=names.nextElement(); + String value=request.getParameter(name); + result.put(name,value); + System.out.println(name+" ==> "+value); + + if (result.get(name)==null||"".equals(result.get(name))){ + result.remove(name); + } + } + } + + if (AcpService.validate(result, SDKConstants.UTF_8_ENCODING)){ + System.out.println("后台验签成功"); + }else { + System.out.println("后台验签失败"); + } + + response.getWriter().print("ok"); + } +} diff --git a/src/main/java/com/xkrs/unionpay/service/UnionpayService.java b/src/main/java/com/xkrs/unionpay/service/UnionpayService.java new file mode 100644 index 0000000..cfd92d0 --- /dev/null +++ b/src/main/java/com/xkrs/unionpay/service/UnionpayService.java @@ -0,0 +1,9 @@ +package com.xkrs.unionpay.service; + +/** + * @Author: XinYi Song + * @Date: 2021/12/28 16:59 + */ +public interface UnionpayService { + String pay(String orderId,String txnAmt); //支付接口 +} diff --git a/src/main/java/com/xkrs/unionpay/service/impl/UnionpayServiceImpl.java b/src/main/java/com/xkrs/unionpay/service/impl/UnionpayServiceImpl.java new file mode 100644 index 0000000..ad3f6da --- /dev/null +++ b/src/main/java/com/xkrs/unionpay/service/impl/UnionpayServiceImpl.java @@ -0,0 +1,91 @@ +package com.xkrs.unionpay.service.impl; + +import com.xkrs.unionpay.bean.DemoBase; +import com.xkrs.unionpay.service.UnionpayService; +import com.xkrs.unionpay.util.AcpService; +import com.xkrs.unionpay.util.LogUtil; +import com.xkrs.unionpay.util.SDKConfig; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +/** + * @Author: XinYi Song + * @Date: 2021/12/28 17:00 + */ +@Service +public class UnionpayServiceImpl implements UnionpayService { + + @Override + public String pay(String orderId, String txnAmt) { + Map contentData = new HashMap<>(); + + /***银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改***/ + contentData.put("version", DemoBase.version); //版本号 全渠道默认值 + contentData.put("encoding", DemoBase.encoding); //字符集编码 可以使用UTF-8,GBK两种方式 + contentData.put("signMethod", SDKConfig.getConfig().getSignMethod()); //签名方法 + contentData.put("txnType", "01"); //交易类型 01:消费 + contentData.put("txnSubType", "07"); //交易子类 07:申请消费二维码 + contentData.put("bizType", "000000"); //填写000000 + contentData.put("channelType", "08"); //渠道类型 08手机 + + /***商户接入参数***/ + contentData.put("merId", "777290058195927"); //商户号码,请改成自己申请的商户号或者open上注册得来的777商户号测试 + contentData.put("accessType", "0"); //接入类型,商户接入填0 ,不需修改(0:直连商户, 1: 收单机构 2:平台商户) + contentData.put("orderId", orderId); //商户订单号,8-40位数字字母,不能含“-”或“_”,可以自行定制规则 + contentData.put("txnTime", DemoBase.getCurrentTime()); //订单发送时间,取系统时间,格式为YYYYMMDDhhmmss,必须取当前时间,否则会报txnTime无效 + contentData.put("txnAmt", txnAmt); //交易金额 单位为分,不能带小数点 + contentData.put("currencyCode", "156"); //境内商户固定 156 人民币 + //contentData.put("termId", termId); //选填,原则是可以通过交易上送的终端编号准确定位商户每一个门店内每一台收银设备,建议按“门店编号+收银机编号”或“设备编号”组成8位终端编号在交易中上送。商户需将终端编号与门店对应关系反馈给银联。 + + // 请求方保留域,透传字段,查询、通知、对账文件中均会原样出现,如有需要请启用并修改自己希望透传的数据。 + // 出现部分特殊字符时可能影响解析,请按下面建议的方式填写: + // 1. 如果能确定内容不会出现&={}[]"'等符号时,可以直接填写数据,建议的方法如下。 + // contentData.put("reqReserved", "透传信息1|透传信息2|透传信息3"); + // 2. 内容可能出现&={}[]"'符号时: + // 1) 如果需要对账文件里能显示,可将字符替换成全角&={}【】“‘字符(自己写代码,此处不演示); + // 2) 如果对账文件没有显示要求,可做一下base64(如下)。 + // 注意控制数据长度,实际传输的数据长度不能超过1024位。 + // 查询、通知等接口解析时使用new String(Base64.decodeBase64(reqReserved), DemoBase.encoding);解base64后再对数据做后续解析。 + //contentData.put("reqReserved", Base64.encodeBase64String("任意格式的信息都可以".toString().getBytes(DemoBase.encoding))); + + //后台通知地址(需设置为外网能访问 http https均可),支付成功后银联会自动将异步通知报文post到商户上送的该地址,【支付失败的交易银联不会发送后台通知】 + //后台通知参数详见open.unionpay.com帮助中心 下载 产品接口规范 网关支付产品接口规范 消费交易 商户通知 + //注意:1.需设置为外网能访问,否则收不到通知 2.http https均可 3.收单后台通知后需要10秒内返回http200或302状态码 + // 4.如果银联通知服务器发送通知后10秒内未收到返回状态码或者应答码非http200或302,那么银联会间隔一段时间再次发送。总共发送5次,银联后续间隔1、2、4、5 分钟后会再次通知。 + // 5.后台通知地址如果上送了带有?的参数,例如:http://abc/web?a=b&c=d 在后台通知处理程序验证签名之前需要编写逻辑将这些字段去掉再验签,否则将会验签失败 + contentData.put("backUrl", DemoBase.backUrl); + + /**对请求参数进行签名并发送http post请求,接收同步应答报文**/ + Map reqData = AcpService.sign(contentData, DemoBase.encoding); //报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。 + String requestAppUrl = SDKConfig.getConfig().getBackRequestUrl(); //交易请求url从配置文件读取对应属性文件acp_sdk.properties中的 acpsdk.backTransUrl + Map rspData = AcpService.post(reqData, requestAppUrl, DemoBase.encoding); //发送请求报文并接受同步应答(默认连接超时时间30秒,读取返回结果超时时间30秒);这里调用signData之后,调用submitUrl之前不能对submitFromData中的键值对做任何修改,如果修改会导致验签不通过 + + String qrCode = null; + if (!rspData.isEmpty()) { + if (AcpService.validate(rspData, DemoBase.encoding)) { + LogUtil.writeLog("验证签名成功"); + String respCode = rspData.get("respCode"); + + if (("00").equals(respCode)) { + System.out.println("受理成功"); + //TODO + + qrCode=rspData.get("qrCode"); + } else { + //其他应答码为失败请排查原因或做失败处理 + //TODO + + System.out.println("受理失败"); + } + }else { + LogUtil.writeLog("验签失败"); + } + }else { + LogUtil.writeErrorLog("未获取到返回报文或返回http状态码非200"); + } + + return qrCode; + } +} diff --git a/src/main/java/com/xkrs/unionpay/util/AcpService.java b/src/main/java/com/xkrs/unionpay/util/AcpService.java new file mode 100644 index 0000000..f7bbe04 --- /dev/null +++ b/src/main/java/com/xkrs/unionpay/util/AcpService.java @@ -0,0 +1,676 @@ +package com.xkrs.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; + +/** + * @ClassName AcpService + * @Description acpsdk接口服务类,接入商户集成请可以直接参考使用本类中的方法 + * @date 2016-7-22 下午2:44:37 + * 声明:以下代码只是为了方便接入方测试而提供的样例代码,商户可以根据自己需要,按照技术文档编写。该代码仅供参考,不提供编码,性能,规范性等方面的保障 + */ +public class AcpService { + + /** + * 请求报文签名(使用配置文件中配置的私钥证书或者对称密钥签名)
+ * 功能:对请求报文进行签名,并计算赋值certid,signature字段并返回
+ * @param reqData 请求报文map
+ * @param encoding 上送请求报文域encoding字段的值
+ * @return 签名后的map对象
+ */ + public static Map sign(Map reqData,String encoding) { + reqData = SDKUtil.filterBlank(reqData); + SDKUtil.sign(reqData, encoding); + return reqData; + } + + /** + * 同signByCertInfo
+ * @param reqData + * @param certPath + * @param certPwd + * @param encoding + * @return + * @deprecated + */ + public static Map sign(Map reqData, String certPath, + String certPwd,String encoding) { + reqData = SDKUtil.filterBlank(reqData); + SDKUtil.signByCertInfo(reqData,certPath,certPwd,encoding); + return reqData; + } + + /** + * 多证书签名(通过传入私钥证书路径和密码签名)
+ * 功能:如果有多个商户号接入银联,每个商户号对应不同的证书可以使用此方法:传入私钥证书和密码(并且在acp_sdk.properties中 配置 acpsdk.singleMode=false)
+ * @param reqData 请求报文map
+ * @param certPath 签名私钥文件(带路径)
+ * @param certPwd 签名私钥密码
+ * @param encoding 上送请求报文域encoding字段的值
+ * @return 签名后的map对象
+ */ + public static Map signByCertInfo(Map reqData, String certPath, + String certPwd,String encoding) { + reqData = SDKUtil.filterBlank(reqData); + SDKUtil.signByCertInfo(reqData,certPath,certPwd,encoding); + return reqData; + } + + /** + * 多密钥签名(通过传入密钥签名)
+ * 功能:如果有多个商户号接入银联,每个商户号对应不同的证书可以使用此方法:传入私钥证书和密码(并且在acp_sdk.properties中 配置 acpsdk.singleMode=false)
+ * @param reqData 请求报文map
+ * @param secureKey 签名对称密钥
+ * @param encoding 上送请求报文域encoding字段的值
+ * @return 签名后的map对象
+ */ + public static Map signBySecureKey(Map reqData, String secureKey, String encoding) { + reqData = SDKUtil.filterBlank(reqData); + SDKUtil.signBySecureKey(reqData, secureKey, encoding); + return reqData; + } + + /** + * 验证签名(SHA-1摘要算法)
+ * @param resData 返回报文数据
+ * @param encoding 上送请求报文域encoding字段的值
+ * @return true 通过 false 未通过
+ */ + public static boolean validate(Map rspData, String encoding) { + return SDKUtil.validate(rspData, encoding); + } + + /** + * 多密钥验签(通过传入密钥签名)
+ * @param resData 返回报文数据
+ * @param encoding 上送请求报文域encoding字段的值
+ * @return true 通过 false 未通过
+ */ + public static boolean validateBySecureKey(Map rspData, String secureKey, String encoding) { + return SDKUtil.validateBySecureKey(rspData, secureKey, encoding); + } + + + /** + * @deprecated 5.1.0开发包已删除此方法,请直接参考5.1.0开发包中的VerifyAppData.java验签。 + * 对控件支付成功返回的结果信息中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) { + LogUtil.writeLog("控件应答信息验签处理开始:[" + 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 + .getValidatePublicKey(certId), SecureUtil.base64Decode(sign + .getBytes(encoding)), SecureUtil.sha1X16(data, + encoding)); + } catch (UnsupportedEncodingException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } catch (Exception e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } + return false; + } + + /** + * 功能:后台交易提交请求报文并接收同步应答报文
+ * @param reqData 请求报文
+ * @param rspData 应答报文
+ * @param reqUrl 请求地址
+ * @param encoding
+ * @return 应答http 200返回true ,其他false
+ */ + public static Map post( + Map reqData,String reqUrl,String encoding) { + Map rspData = new HashMap(); + LogUtil.writeLog("请求银联地址:" + 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 tmpRspData = SDKUtil.convertResultStringToMap(resultString); + rspData.putAll(tmpRspData); + } + }else{ + LogUtil.writeLog("返回http状态码["+status+"],请检查请求报文或者请求地址是否正确"); + } + } catch (Exception e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } + return rspData; + } + + /** + * 功能:http Get方法 便民缴费产品中使用
+ * @param reqUrl 请求地址
+ * @param encoding
+ * @return + */ + public static String get(String reqUrl,String encoding) { + + LogUtil.writeLog("请求银联地址:" + 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{ + LogUtil.writeLog("返回http状态码["+status+"],请检查请求报文或者请求地址是否正确"); + } + } catch (Exception e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } + return null; + } + + + /** + * 功能:前台交易构造HTTP POST自动提交表单
+ * @param action 表单提交地址
+ * @param hiddens 以MAP形式存储的表单键值
+ * @param encoding 上送请求报文域encoding字段的值
+ * @return 构造好的HTTP POST交易表单
+ */ + public static String createAutoFormHtml(String reqUrl, Map hiddens,String encoding) { + StringBuffer sf = new StringBuffer(); + sf.append(""); + sf.append("
"); + if (null != hiddens && 0 != hiddens.size()) { + Set> set = hiddens.entrySet(); + Iterator> it = set.iterator(); + while (it.hasNext()) { + Entry ey = it.next(); + String key = ey.getKey(); + String value = ey.getValue(); + sf.append(""); + } + } + sf.append(""); + sf.append(""); + sf.append(""); + sf.append(""); + return sf.toString(); + } + + + /** + * 功能:将批量文件内容使用DEFLATE压缩算法压缩,Base64编码生成字符串并返回
+ * 适用到的交易:批量代付,批量代收,批量退货
+ * @param filePath 批量文件-全路径文件名
+ * @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) { + LogUtil.writeErrorLog(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(SDKUtil.deflater(s)),encoding); + } + } catch (Exception e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } finally { + if (null != in) { + try { + in.close(); + } catch (IOException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } + } + } + return baseFileContent; + } + + /** + * 功能:解析交易返回的fileContent字符串并落地 ( 解base64,解DEFLATE压缩并落地)
+ * 适用到的交易:对账文件下载,批量交易状态查询
+ * @param resData 返回报文map
+ * @param fileDirectory 落地的文件目录(绝对路径) + * @param encoding 上送请求报文域encoding字段的值
+ */ + public static String deCodeFileContent(Map resData,String fileDirectory,String encoding) { + // 解析返回文件 + String filePath = null; + String fileContent = resData.get(SDKConstants.param_fileContent); + if (null != fileContent && !"".equals(fileContent)) { + FileOutputStream out = null; + try { + byte[] fileArray = SDKUtil.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(); + out = new FileOutputStream(file); + out.write(fileArray, 0, fileArray.length); + out.flush(); + } catch (UnsupportedEncodingException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } catch (IOException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + }finally{ + try { + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return filePath; + } + + /** + * 功能:将结果文件内容 转换成明文字符串:解base64,解压缩
+ * 适用到的交易:批量交易状态查询
+ * @param fileContent 批量交易状态查询返回的文件内容
+ * @return 内容明文
+ */ + public static String getFileContent(String fileContent,String encoding){ + String fc = ""; + try { + fc = new String(SDKUtil.inflater(SecureUtil.base64Decode(fileContent.getBytes())),encoding); + } catch (UnsupportedEncodingException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } catch (IOException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } + return fc; + } + + + /** + * 功能:持卡人信息域customerInfo构造
+ * 说明:不勾选对敏感信息加密权限使用旧的构造customerInfo域方式,不对敏感信息进行加密(对 phoneNo,cvn2, expired不加密),但如果送pin的话则加密
+ * @param customerInfoMap 信息域请求参数 key送域名value送值,必送
+ * 例如:customerInfoMap.put("certifTp", "01"); //证件类型
+ customerInfoMap.put("certifId", "341126197709218366"); //证件号码
+ customerInfoMap.put("customerNm", "互联网"); //姓名
+ customerInfoMap.put("phoneNo", "13552535506"); //手机号
+ customerInfoMap.put("smsCode", "123456"); //短信验证码
+ customerInfoMap.put("pin", "111111"); //密码(加密)
+ customerInfoMap.put("cvn2", "123"); //卡背面的cvn2三位数字(不加密)
+ customerInfoMap.put("expired", "2311"); //有效期 年在前月在后(不加密)
+ * @param accNo customerInfoMap送了密码那么卡号必送,如果customerInfoMap未送密码pin,此字段可以不送
+ * @param encoding 上送请求报文域encoding字段的值
+ * @return base64后的持卡人信息域字段
+ */ + public static String getCustomerInfo(Map customerInfoMap,String accNo,String encoding) { + + if(customerInfoMap.isEmpty()) + return "{}"; + StringBuffer sf = new StringBuffer("{"); + for(Iterator 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())){ + LogUtil.writeLog("送了密码(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(); + LogUtil.writeLog("组装的customerInfo明文:"+customerInfo); + try { + return new String(SecureUtil.base64Encode(sf.toString().getBytes( + encoding)),encoding); + } catch (UnsupportedEncodingException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } catch (IOException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } + return customerInfo; + } + + /** + * 功能:持卡人信息域customerInfo构造,勾选对敏感信息加密权限 适用新加密规范,对pin和phoneNo,cvn2,expired加密
+ * 适用到的交易:
+ * @param customerInfoMap 信息域请求参数 key送域名value送值,必送
+ * 例如:customerInfoMap.put("certifTp", "01"); //证件类型
+ customerInfoMap.put("certifId", "341126197709218366"); //证件号码
+ customerInfoMap.put("customerNm", "互联网"); //姓名
+ customerInfoMap.put("smsCode", "123456"); //短信验证码
+ customerInfoMap.put("pin", "111111"); //密码(加密)
+ customerInfoMap.put("phoneNo", "13552535506"); //手机号(加密)
+ customerInfoMap.put("cvn2", "123"); //卡背面的cvn2三位数字(加密)
+ customerInfoMap.put("expired", "2311"); //有效期 年在前月在后(加密)
+ * @param accNo customerInfoMap送了密码那么卡号必送,如果customerInfoMap未送密码PIN,此字段可以不送
+ * @param encoding 上送请求报文域encoding字段的值 + * @return base64后的持卡人信息域字段
+ */ + public static String getCustomerInfoWithEncrypt(Map customerInfoMap,String accNo,String encoding) { + if(customerInfoMap.isEmpty()) + return "{}"; + StringBuffer sf = new StringBuffer("{"); + //敏感信息加密域 + StringBuffer encryptedInfoSb = new StringBuffer(""); + + for(Iterator 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())){ + LogUtil.writeLog("送了密码(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);//去掉最后一个&符号 + LogUtil.writeLog("组装的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(); + LogUtil.writeLog("组装的customerInfo明文:"+customerInfo); + try { + return new String(SecureUtil.base64Encode(sf.toString().getBytes(encoding)),encoding); + } catch (UnsupportedEncodingException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } catch (IOException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } + return customerInfo; + } + + /** + * 解析返回报文(后台通知)中的customerInfo域:
+ * 解base64,如果带敏感信息加密 encryptedInfo 则将其解密并将 encryptedInfo中的域放到customerInfoMap返回
+ * @param customerInfo
+ * @param encoding
+ * @return + */ + public static Map parseCustomerInfo(String customerInfo,String encoding){ + Map customerInfoMap = null; + try { + byte[] b = SecureUtil.base64Decode(customerInfo.getBytes(encoding)); + String customerInfoNoBase64 = new String(b,encoding); + LogUtil.writeLog("解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 encryptedInfoMap = SDKUtil.parseQString(encryptedInfoStr); + customerInfoMap.putAll(encryptedInfoMap); + } + } catch (UnsupportedEncodingException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } catch (IOException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } + return customerInfoMap; + } + + /** + * 解析返回报文(后台通知)中的customerInfo域:
+ * 解base64,如果带敏感信息加密 encryptedInfo 则将其解密并将 encryptedInfo中的域放到customerInfoMap返回
+ * @param customerInfo
+ * @param encoding
+ * @return + */ + public static Map parseCustomerInfo(String customerInfo, String certPath, + String certPwd, String encoding){ + Map customerInfoMap = null; + try { + byte[] b = SecureUtil.base64Decode(customerInfo.getBytes(encoding)); + String customerInfoNoBase64 = new String(b,encoding); + LogUtil.writeLog("解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, certPath, certPwd, encoding); + Map encryptedInfoMap = SDKUtil.parseQString(encryptedInfoStr); + customerInfoMap.putAll(encryptedInfoMap); + } + } catch (UnsupportedEncodingException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } catch (IOException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } + return customerInfoMap; + } + + /** + * 密码加密并做base64
+ * @param accNo 卡号
+ * @param pwd 密码
+ * @param encoding
+ * @return 加密的内容
+ */ + public static String encryptPin(String accNo, String pin, String encoding) { + return SecureUtil.encryptPin(accNo, pin, encoding, CertUtil + .getEncryptCertPublicKey()); + } + + /** + * 敏感信息加密并做base64(卡号,手机号,cvn2,有效期)
+ * @param data 送 phoneNo,cvn2,有效期
+ * @param encoding
+ * @return 加密的密文
+ */ + public static String encryptData(String data, String encoding) { + return SecureUtil.encryptData(data, encoding, CertUtil + .getEncryptCertPublicKey()); + } + + /** + * 敏感信息解密,使用配置文件acp_sdk.properties解密
+ * @param base64EncryptedInfo 加密信息
+ * @param encoding
+ * @return 解密后的明文
+ */ + public static String decryptData(String base64EncryptedInfo, String encoding) { + return SecureUtil.decryptData(base64EncryptedInfo, encoding, CertUtil + .getSignCertPrivateKey()); + } + + /** + * 敏感信息解密,通过传入的私钥解密
+ * @param base64EncryptedInfo 加密信息
+ * @param certPath 私钥文件(带全路径)
+ * @param certPwd 私钥密码
+ * @param encoding
+ * @return + */ + public static String decryptData(String base64EncryptedInfo, String certPath, + String certPwd, String encoding) { + return SecureUtil.decryptData(base64EncryptedInfo, encoding, CertUtil + .getSignCertPrivateKeyByStoreMap(certPath, certPwd)); + } + + /** + * 5.0.0加密磁道信息,5.1.0接口请勿使用
+ * @param trackData 待加密磁道数据
+ * @param encoding 编码格式
+ * @return 加密的密文
+ * @deprecated + */ + public static String encryptTrack(String trackData, String encoding) { + return SecureUtil.encryptData(trackData, encoding, + CertUtil.getEncryptTrackPublicKey()); + } + + /** + * 获取敏感信息加密证书的物理序列号
+ * @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); + } + + + /** + * + * 有卡交易信息域(cardTransData)构造
+ * 所有子域需用“{}”包含,子域间以“&”符号链接。格式如下:{子域名1=值&子域名2=值&子域名3=值}
+ * 说明:本示例仅供参考,开发时请根据接口文档中的报文要素组装
+ * + * @param cardTransDataMap cardTransData的数据
+ * @param requestData 必须包含merId、orderId、txnTime、txnAmt,磁道加密时需要使用
+ * @param encoding 编码
+ * @return + */ + public static String getCardTransData(Map cardTransDataMap, + Map requestData, + String encoding) { { + + StringBuffer cardTransDataBuffer = new StringBuffer(); + + if(cardTransDataMap.containsKey("track2Data")){ + StringBuffer track2Buffer = new StringBuffer(); + track2Buffer.append(requestData.get("merId")) + .append(SDKConstants.COLON).append(requestData.get("orderId")) + .append(SDKConstants.COLON).append(requestData.get("txnTime")) + .append(SDKConstants.COLON).append(requestData.get("txnAmt")==null?0:requestData.get("txnAmt")) + .append(SDKConstants.COLON).append(cardTransDataMap.get("track2Data")); + cardTransDataMap.put("track2Data", + AcpService.encryptData(track2Buffer.toString(), encoding)); + } + + if(cardTransDataMap.containsKey("track3Data")){ + StringBuffer track3Buffer = new StringBuffer(); + track3Buffer.append(requestData.get("merId")) + .append(SDKConstants.COLON).append(requestData.get("orderId")) + .append(SDKConstants.COLON).append(requestData.get("txnTime")) + .append(SDKConstants.COLON).append(requestData.get("txnAmt")==null?0:requestData.get("txnAmt")) + .append(SDKConstants.COLON).append(cardTransDataMap.get("track3Data")); + cardTransDataMap.put("track3Data", + AcpService.encryptData(track3Buffer.toString(), encoding)); + } + + return cardTransDataBuffer.append(SDKConstants.LEFT_BRACE) + .append(SDKUtil.coverMap2String(cardTransDataMap)) + .append(SDKConstants.RIGHT_BRACE).toString(); + } + + } + + /** + * 获取应答报文中的加密公钥证书,并存储到本地,备份原始证书,并自动替换证书
+ * 更新成功则返回1,无更新返回0,失败异常返回-1
+ * @param resData 返回报文 + * @param encoding + * @return + */ + public static int updateEncryptCert(Map resData, + String encoding) { + return SDKUtil.getEncryptCert(resData, encoding); + } + + /** + * 获取 + * @param number + * @return + */ + public static int genLuhn(String number){ + return SecureUtil.genLuhn(number); + } +} diff --git a/src/main/java/com/xkrs/unionpay/util/BaseHttpSSLSocketFactory.java b/src/main/java/com/xkrs/unionpay/util/BaseHttpSSLSocketFactory.java new file mode 100644 index 0000000..d577d9a --- /dev/null +++ b/src/main/java/com/xkrs/unionpay/util/BaseHttpSSLSocketFactory.java @@ -0,0 +1,125 @@ +/** + * + * 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.xkrs.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; +/** + * + * @ClassName BaseHttpSSLSocketFactory + * @Description 忽略验证ssl证书 + * @date 2016-7-22 下午4:10:14 + * 声明:以下代码只是为了方便接入方测试而提供的样例代码,商户可以根据自己需要,按照技术文档编写。该代码仅供参考,不提供编码,性能,规范性等方面的保障 + */ +public class BaseHttpSSLSocketFactory extends SSLSocketFactory { + 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() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String[] getDefaultCipherSuites() { + // TODO Auto-generated method stub + 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) { + LogUtil.writeErrorLog(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 + */ + public static class TrustAnyHostnameVerifier implements HostnameVerifier { + public boolean verify(String hostname, SSLSession session) { + //直接返回true + return true; + } + } +} diff --git a/src/main/java/com/xkrs/unionpay/util/CertUtil.java b/src/main/java/com/xkrs/unionpay/util/CertUtil.java new file mode 100644 index 0000000..e4e2769 --- /dev/null +++ b/src/main/java/com/xkrs/unionpay/util/CertUtil.java @@ -0,0 +1,790 @@ +/** + * + * 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.xkrs.unionpay.util; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +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.CertPathBuilder; +import java.security.cert.CertStore; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.CollectionCertStoreParameters; +import java.security.cert.PKIXBuilderParameters; +import java.security.cert.PKIXCertPathBuilderResult; +import java.security.cert.TrustAnchor; +import java.security.cert.X509CertSelector; +import java.security.cert.X509Certificate; +import java.security.spec.RSAPublicKeySpec; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import static com.xkrs.unionpay.util.SDKConstants.UNIONPAY_CNNAME; +import static com.xkrs.unionpay.util.SDKUtil.isEmpty; +/** + * @ClassName: CertUtil + * @Description: acpsdk证书工具类,主要用于对证书的加载和使用 + * @date 2016-7-22 下午2:46:20 + * 声明:以下代码只是为了方便接入方测试而提供的样例代码,商户可以根据自己需要,按照技术文档编写。该代码仅供参考,不提供编码,性能,规范性等方面的保障 + */ +public class CertUtil { + /** 证书容器,存储对商户请求报文签名私钥证书. */ + private static KeyStore keyStore = null; + /** 敏感信息加密公钥证书 */ + private static X509Certificate encryptCert = null; + /** 磁道加密公钥 */ + private static PublicKey encryptTrackKey = null; + /** 验证银联返回报文签名证书. */ + private static X509Certificate validateCert = null; + /** 验签中级证书 */ + private static X509Certificate middleCert = null; + /** 验签根证书 */ + private static X509Certificate rootCert = null; + /** 验证银联返回报文签名的公钥证书存储Map. */ + private static Map certMap = new HashMap(); + /** 商户私钥存储Map */ + private final static Map keyStoreMap = new ConcurrentHashMap(); + + static { + init(); + } + + /** + * 初始化所有证书. + */ + private static void init() { + try { + addProvider();//向系统添加BC provider + initSignCert();//初始化签名私钥证书 + initMiddleCert();//初始化验签证书的中级证书 + initRootCert();//初始化验签证书的根证书 + initEncryptCert();//初始化加密公钥 + initTrackKey();//构建磁道加密公钥 + initValidateCertFromDir();//初始化所有的验签证书 + } catch (Exception e) { + LogUtil.writeErrorLog("init失败。(如果是用对称密钥签名的可无视此异常。)", e); + } + } + + /** + * 添加签名,验签,加密算法提供者 + */ + private static void addProvider(){ + if (Security.getProvider("BC") == null) { + LogUtil.writeLog("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()); + LogUtil.writeLog("re-add BC provider"); + } + printSysInfo(); + } + + /** + * 用配置文件acp_sdk.properties中配置的私钥路径和密码 加载签名证书 + */ + private static void initSignCert() { + if(!"01".equals(SDKConfig.getConfig().getSignMethod())){ + LogUtil.writeLog("非rsa签名方式,不加载签名证书。"); + return; + } + if (SDKConfig.getConfig().getSignCertPath() == null + || SDKConfig.getConfig().getSignCertPwd() == null + || SDKConfig.getConfig().getSignCertType() == null) { + LogUtil.writeErrorLog("WARN: " + SDKConfig.SDK_SIGNCERT_PATH + "或" + SDKConfig.SDK_SIGNCERT_PWD + + "或" + SDKConfig.SDK_SIGNCERT_TYPE + "为空。 停止加载签名证书。"); + return; + } + if (null != keyStore) { + keyStore = null; + } + try { + keyStore = getKeyInfo(SDKConfig.getConfig().getSignCertPath(), + SDKConfig.getConfig().getSignCertPwd(), SDKConfig + .getConfig().getSignCertType()); + LogUtil.writeLog("InitSignCert Successful. CertId=[" + + getSignCertId() + "]"); + } catch (IOException e) { + LogUtil.writeErrorLog("InitSignCert Error", e); + } + } + + /** + * 用配置文件acp_sdk.properties配置路径 加载敏感信息加密证书 + */ + private static void initMiddleCert() { + LogUtil.writeLog("加载中级证书==>"+SDKConfig.getConfig().getMiddleCertPath()); + if (!isEmpty(SDKConfig.getConfig().getMiddleCertPath())) { + middleCert = initCert(SDKConfig.getConfig().getMiddleCertPath()); + LogUtil.writeLog("Load MiddleCert Successful"); + } else { + LogUtil.writeLog("WARN: acpsdk.middle.path is empty"); + } + } + + /** + * 用配置文件acp_sdk.properties配置路径 加载敏感信息加密证书 + */ + private static void initRootCert() { + LogUtil.writeLog("加载根证书==>"+SDKConfig.getConfig().getRootCertPath()); + if (!isEmpty(SDKConfig.getConfig().getRootCertPath())) { + rootCert = initCert(SDKConfig.getConfig().getRootCertPath()); + LogUtil.writeLog("Load RootCert Successful"); + } else { + LogUtil.writeLog("WARN: acpsdk.rootCert.path is empty"); + } + } + + /** + * 用配置文件acp_sdk.properties配置路径 加载银联公钥上级证书(中级证书) + */ + private static void initEncryptCert() { + LogUtil.writeLog("加载敏感信息加密证书==>"+SDKConfig.getConfig().getEncryptCertPath()); + if (!isEmpty(SDKConfig.getConfig().getEncryptCertPath())) { + encryptCert = initCert(SDKConfig.getConfig().getEncryptCertPath()); + LogUtil.writeLog("Load EncryptCert Successful"); + } else { + LogUtil.writeLog("WARN: acpsdk.encryptCert.path is empty"); + } + } + + /** + * 用配置文件acp_sdk.properties配置路径 加载磁道公钥 + */ + private static void initTrackKey() { + if (!isEmpty(SDKConfig.getConfig().getEncryptTrackKeyModulus()) + && !isEmpty(SDKConfig.getConfig().getEncryptTrackKeyExponent())) { + encryptTrackKey = getPublicKey(SDKConfig.getConfig().getEncryptTrackKeyModulus(), + SDKConfig.getConfig().getEncryptTrackKeyExponent()); + LogUtil.writeLog("LoadEncryptTrackKey Successful"); + } else { + LogUtil.writeLog("WARN: acpsdk.encryptTrackKey.modulus or acpsdk.encryptTrackKey.exponent is empty"); + } + } + + /** + * 用配置文件acp_sdk.properties配置路径 加载验证签名证书 + */ + private static void initValidateCertFromDir() { + if(!"01".equals(SDKConfig.getConfig().getSignMethod())){ + LogUtil.writeLog("非rsa签名方式,不加载验签证书。"); + return; + } + certMap.clear(); + String dir = SDKConfig.getConfig().getValidateCertDir(); + LogUtil.writeLog("加载验证签名证书目录==>" + dir +" 注:如果请求报文中version=5.1.0那么此验签证书目录使用不到,可以不需要设置(version=5.0.0必须设置)。"); + if (isEmpty(dir)) { + LogUtil.writeErrorLog("WARN: acpsdk.validateCert.dir is empty"); + return; + } + CertificateFactory cf = null; + FileInputStream in = null; + try { + cf = CertificateFactory.getInstance("X.509", "BC"); + }catch (NoSuchProviderException e) { + LogUtil.writeErrorLog("LoadVerifyCert Error: No BC Provider", e); + return ; + } catch (CertificateException e) { + LogUtil.writeErrorLog("LoadVerifyCert Error", e); + return ; + } + File fileDir = new File(dir); + File[] files = fileDir.listFiles(new CerFilter()); + for (int i = 0; i < files.length; i++) { + File file = files[i]; + try { + in = new FileInputStream(file.getAbsolutePath()); + validateCert = (X509Certificate) cf.generateCertificate(in); + if(validateCert == null) { + LogUtil.writeErrorLog("Load verify cert error, " + file.getAbsolutePath() + " has error cert content."); + continue; + } + certMap.put(validateCert.getSerialNumber().toString(), + validateCert); + // 打印证书加载信息,供测试阶段调试 + LogUtil.writeLog("[" + file.getAbsolutePath() + "][CertId=" + + validateCert.getSerialNumber().toString() + "]"); + } catch (CertificateException e) { + LogUtil.writeErrorLog("LoadVerifyCert Error", e); + }catch (FileNotFoundException e) { + LogUtil.writeErrorLog("LoadVerifyCert Error File Not Found", e); + }finally { + if (null != in) { + try { + in.close(); + } catch (IOException e) { + LogUtil.writeErrorLog(e.toString()); + } + } + } + } + LogUtil.writeLog("LoadVerifyCert Finish"); + } + + /** + * 用给定的路径和密码 加载签名证书,并保存到certKeyStoreMap + * + * @param certFilePath + * @param certPwd + */ + private static void loadSignCert(String certFilePath, String certPwd) { + KeyStore keyStore = null; + try { + keyStore = getKeyInfo(certFilePath, certPwd, "PKCS12"); + keyStoreMap.put(certFilePath, keyStore); + LogUtil.writeLog("LoadRsaCert Successful"); + } catch (IOException e) { + LogUtil.writeErrorLog("LoadRsaCert Error", e); + } + } + + /** + * 通过证书路径初始化为公钥证书 + * @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); + // 打印证书加载信息,供测试阶段调试 + LogUtil.writeLog("[" + path + "][CertId=" + + encryptCertTemp.getSerialNumber().toString() + "]"); + } catch (CertificateException e) { + LogUtil.writeErrorLog("InitCert Error", e); + } catch (FileNotFoundException e) { + LogUtil.writeErrorLog("InitCert Error File Not Found", e); + } catch (NoSuchProviderException e) { + LogUtil.writeErrorLog("LoadVerifyCert Error No BC Provider", e); + } finally { + if (null != in) { + try { + in.close(); + } catch (IOException e) { + LogUtil.writeErrorLog(e.toString()); + } + } + } + return encryptCertTemp; + } + + /** + * 通过keyStore 获取私钥签名证书PrivateKey对象 + * + * @return + */ + public static PrivateKey getSignCertPrivateKey() { + try { + Enumeration 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) { + LogUtil.writeErrorLog("getSignCertPrivateKey Error", e); + return null; + } catch (UnrecoverableKeyException e) { + LogUtil.writeErrorLog("getSignCertPrivateKey Error", e); + return null; + } catch (NoSuchAlgorithmException e) { + LogUtil.writeErrorLog("getSignCertPrivateKey Error", e); + return null; + } + } + /** + * 通过指定路径的私钥证书 获取PrivateKey对象 + * + * @return + */ + public static PrivateKey getSignCertPrivateKeyByStoreMap(String certPath, + String certPwd) { + if (!keyStoreMap.containsKey(certPath)) { + loadSignCert(certPath, certPwd); + } + try { + Enumeration aliasenum = keyStoreMap.get(certPath) + .aliases(); + String keyAlias = null; + if (aliasenum.hasMoreElements()) { + keyAlias = aliasenum.nextElement(); + } + PrivateKey privateKey = (PrivateKey) keyStoreMap.get(certPath) + .getKey(keyAlias, certPwd.toCharArray()); + return privateKey; + } catch (KeyStoreException e) { + LogUtil.writeErrorLog("getSignCertPrivateKeyByStoreMap Error", e); + return null; + } catch (UnrecoverableKeyException e) { + LogUtil.writeErrorLog("getSignCertPrivateKeyByStoreMap Error", e); + return null; + } catch (NoSuchAlgorithmException e) { + LogUtil.writeErrorLog("getSignCertPrivateKeyByStoreMap Error", e); + return null; + } + } + + /** + * 获取敏感信息加密证书PublicKey + * + * @return + */ + public static PublicKey getEncryptCertPublicKey() { + if (null == encryptCert) { + String path = SDKConfig.getConfig().getEncryptCertPath(); + if (!isEmpty(path)) { + encryptCert = initCert(path); + return encryptCert.getPublicKey(); + } else { + LogUtil.writeErrorLog("acpsdk.encryptCert.path is empty"); + return null; + } + } else { + return encryptCert.getPublicKey(); + } + } + + /** + * 重置敏感信息加密证书公钥 + */ + public static void resetEncryptCertPublicKey() { + encryptCert = null; + } + + /** + * 获取磁道加密证书PublicKey + * + * @return + */ + public static PublicKey getEncryptTrackPublicKey() { + if (null == encryptTrackKey) { + initTrackKey(); + } + return encryptTrackKey; + } + + /** + * 通过certId获取验签证书Map中对应证书PublicKey + * + * @param certId 证书物理序号 + * @return 通过证书编号获取到的公钥 + */ + public static PublicKey getValidatePublicKey(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 { + LogUtil.writeErrorLog("缺少certId=[" + certId + "]对应的验签证书."); + return null; + } + } + } + + /** + * 获取配置文件acp_sdk.properties中配置的签名私钥证书certId + * + * @return 证书的物理编号 + */ + public static String getSignCertId() { + try { + Enumeration 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) { + LogUtil.writeErrorLog("getSignCertId Error", e); + return null; + } + } + + /** + * 获取敏感信息加密证书的certId + * + * @return + */ + public static String getEncryptCertId() { + if (null == encryptCert) { + String path = SDKConfig.getConfig().getEncryptCertPath(); + if (!isEmpty(path)) { + encryptCert = initCert(path); + return encryptCert.getSerialNumber().toString(); + } else { + LogUtil.writeErrorLog("acpsdk.encryptCert.path is empty"); + return null; + } + } else { + return encryptCert.getSerialNumber().toString(); + } + } + + /** + * 将签名私钥证书文件读取为证书存储对象 + * + * @param pfxkeyfile + * 证书文件名 + * @param keypwd + * 证书密码 + * @param type + * 证书类型 + * @return 证书对象 + * @throws IOException + */ + private static KeyStore getKeyInfo(String pfxkeyfile, String keypwd, + String type) throws IOException { + LogUtil.writeLog("加载签名证书==>" + pfxkeyfile); + FileInputStream fis = null; + try { + KeyStore ks = KeyStore.getInstance(type, "BC"); + LogUtil.writeLog("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) { + LogUtil.writeErrorLog("getKeyInfo Error", e); + return null; + } finally { + if(null!=fis) + fis.close(); + } + } + + /** + * 通过签名私钥证书路径,密码获取私钥证书certId + * @param certPath + * @param certPwd + * @return + */ + public static String getCertIdByKeyStoreMap(String certPath, String certPwd) { + if (!keyStoreMap.containsKey(certPath)) { + // 缓存中未查询到,则加载RSA证书 + loadSignCert(certPath, certPwd); + } + return getCertIdIdByStore(keyStoreMap.get(certPath)); + } + + /** + * 通过keystore获取私钥证书的certId值 + * @param keyStore + * @return + */ + private static String getCertIdIdByStore(KeyStore keyStore) { + Enumeration 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) { + LogUtil.writeErrorLog("getCertIdIdByStore Error", e); + return null; + } + } + + /** + * 使用模和指数生成RSA公钥 注意:此代码用了默认补位方式,为RSA/None/PKCS1Padding,不同JDK默认的补位方式可能不同 + * + * @param modulus + * 模 + * @param exponent + * 指数 + * @return + */ + private 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) { + LogUtil.writeErrorLog("构造RSA公钥失败:" + e); + return null; + } + } + + /** + * 将字符串转换为X509Certificate对象. + * + * @param x509CertString + * @return + */ + public static X509Certificate genCertificateByStr(String x509CertString) { + X509Certificate x509Cert = null; + try { + CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC"); + InputStream tIn = new ByteArrayInputStream( + x509CertString.getBytes("ISO-8859-1")); + x509Cert = (X509Certificate) cf.generateCertificate(tIn); + } catch (Exception e) { + LogUtil.writeErrorLog("gen certificate error", e); + } + return x509Cert; + } + + /** + * 从配置文件acp_sdk.properties中获取验签公钥使用的中级证书 + * @return + */ + public static X509Certificate getMiddleCert() { + if (null == middleCert) { + String path = SDKConfig.getConfig().getMiddleCertPath(); + if (!isEmpty(path)) { + initMiddleCert(); + } else { + LogUtil.writeErrorLog(SDKConfig.SDK_MIDDLECERT_PATH + " not set in " + SDKConfig.FILE_NAME); + return null; + } + } + return middleCert; + } + + /** + * 从配置文件acp_sdk.properties中获取验签公钥使用的根证书 + * @return + */ + public static X509Certificate getRootCert() { + if (null == rootCert) { + String path = SDKConfig.getConfig().getRootCertPath(); + if (!isEmpty(path)) { + initRootCert(); + } else { + LogUtil.writeErrorLog(SDKConfig.SDK_ROOTCERT_PATH + " not set in " + SDKConfig.FILE_NAME); + return null; + } + } + return rootCert; + } + + /** + * 获取证书的CN + * @param aCert + * @return + */ + private static String getIdentitiesFromCertficate(X509Certificate aCert) { + String tDN = aCert.getSubjectDN().toString(); + String tPart = ""; + if ((tDN != null)) { + String tSplitStr[] = tDN.substring(tDN.indexOf("CN=")).split("@"); + if (tSplitStr != null && tSplitStr.length > 2 + && tSplitStr[2] != null) + tPart = tSplitStr[2]; + } + return tPart; + } + + /** + * 验证书链。 + * @param cert + * @return + */ + private static boolean verifyCertificateChain(X509Certificate cert){ + + if ( null == cert) { + LogUtil.writeErrorLog("cert must Not null"); + return false; + } + + X509Certificate middleCert = CertUtil.getMiddleCert(); + if (null == middleCert) { + LogUtil.writeErrorLog("middleCert must Not null"); + return false; + } + + X509Certificate rootCert = CertUtil.getRootCert(); + if (null == rootCert) { + LogUtil.writeErrorLog("rootCert or cert must Not null"); + return false; + } + + try { + + X509CertSelector selector = new X509CertSelector(); + selector.setCertificate(cert); + + Set trustAnchors = new HashSet(); + trustAnchors.add(new TrustAnchor(rootCert, null)); + PKIXBuilderParameters pkixParams = new PKIXBuilderParameters( + trustAnchors, selector); + + Set intermediateCerts = new HashSet(); + intermediateCerts.add(rootCert); + intermediateCerts.add(middleCert); + intermediateCerts.add(cert); + + pkixParams.setRevocationEnabled(false); + + CertStore intermediateCertStore = CertStore.getInstance("Collection", + new CollectionCertStoreParameters(intermediateCerts), "BC"); + pkixParams.addCertStore(intermediateCertStore); + + CertPathBuilder builder = CertPathBuilder.getInstance("PKIX", "BC"); + + @SuppressWarnings("unused") + PKIXCertPathBuilderResult result = (PKIXCertPathBuilderResult) builder + .build(pkixParams); + LogUtil.writeLog("verify certificate chain succeed."); + return true; + } catch (java.security.cert.CertPathBuilderException e){ + LogUtil.writeErrorLog("verify certificate chain fail.", e); + } catch (Exception e) { + LogUtil.writeErrorLog("verify certificate chain exception: ", e); + } + return false; + } + + /** + * 检查证书链 + * + * @param rootCerts + * 根证书 + * @param cert + * 待验证的证书 + * @return + */ + public static boolean verifyCertificate(X509Certificate cert) { + + if ( null == cert) { + LogUtil.writeErrorLog("cert must Not null"); + return false; + } + try { + cert.checkValidity();//验证有效期 +// cert.verify(middleCert.getPublicKey()); + if(!verifyCertificateChain(cert)){ + return false; + } + } catch (Exception e) { + LogUtil.writeErrorLog("verifyCertificate fail", e); + return false; + } + + if(SDKConfig.getConfig().isIfValidateCNName()){ + // 验证公钥是否属于银联 + if(!UNIONPAY_CNNAME.equals(CertUtil.getIdentitiesFromCertficate(cert))) { + LogUtil.writeErrorLog("cer owner is not CUP:" + CertUtil.getIdentitiesFromCertficate(cert)); + return false; + } + } else { + // 验证公钥是否属于银联 + if(!UNIONPAY_CNNAME.equals(CertUtil.getIdentitiesFromCertficate(cert)) + && !"00040000:SIGN".equals(CertUtil.getIdentitiesFromCertficate(cert))) { + LogUtil.writeErrorLog("cer owner is not CUP:" + CertUtil.getIdentitiesFromCertficate(cert)); + return false; + } + } + return true; + } + + /** + * 打印系统环境信息 + */ + private static void printSysInfo() { + LogUtil.writeLog("================= SYS INFO begin===================="); + LogUtil.writeLog("os_name:" + System.getProperty("os.name")); + LogUtil.writeLog("os_arch:" + System.getProperty("os.arch")); + LogUtil.writeLog("os_version:" + System.getProperty("os.version")); + LogUtil.writeLog("java_vm_specification_version:" + + System.getProperty("java.vm.specification.version")); + LogUtil.writeLog("java_vm_specification_vendor:" + + System.getProperty("java.vm.specification.vendor")); + LogUtil.writeLog("java_vm_specification_name:" + + System.getProperty("java.vm.specification.name")); + LogUtil.writeLog("java_vm_version:" + + System.getProperty("java.vm.version")); + LogUtil.writeLog("java_vm_name:" + System.getProperty("java.vm.name")); + LogUtil.writeLog("java.version:" + System.getProperty("java.version")); + LogUtil.writeLog("java.vm.vendor=[" + System.getProperty("java.vm.vendor") + "]"); + LogUtil.writeLog("java.version=[" + System.getProperty("java.version") + "]"); + printProviders(); + LogUtil.writeLog("================= SYS INFO end====================="); + } + + /** + * 打jre中印算法提供者列表 + */ + private static void printProviders() { + LogUtil.writeLog("Providers List:"); + Provider[] providers = Security.getProviders(); + for (int i = 0; i < providers.length; i++) { + LogUtil.writeLog(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); + } + } + +} diff --git a/src/main/java/com/xkrs/unionpay/util/HttpClient.java b/src/main/java/com/xkrs/unionpay/util/HttpClient.java new file mode 100644 index 0000000..bd5b21f --- /dev/null +++ b/src/main/java/com/xkrs/unionpay/util/HttpClient.java @@ -0,0 +1,316 @@ +/** + * + * 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.xkrs.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 com.xkrs.unionpay.util.BaseHttpSSLSocketFactory.TrustAnyHostnameVerifier; +/** + * + * @ClassName HttpClient + * @Description acpsdk发送后台http请求类 + * @date 2016-7-22 下午4:03:25 + * 声明:以下代码只是为了方便接入方测试而提供的样例代码,商户可以根据自己需要,按照技术文档编写。该代码仅供参考,不提供编码,性能,规范性等方面的保障 + */ +public class HttpClient { + /** + * 目标地址 + */ + 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) { + LogUtil.writeErrorLog(e.getMessage(), e); + } + } + + /** + * 发送信息到服务端 + * @param data + * @param encoding + * @return + * @throws Exception + */ + public int send(Map data, String encoding) throws Exception { + try { + HttpURLConnection httpURLConnection = createConnection(encoding); + if (null == httpURLConnection) { + throw new Exception("Create httpURLConnection Failure"); + } + String sendData = this.getRequestParamString(data, encoding); + LogUtil.writeLog("请求报文(对每个报文域的值均已做url编码):[" + sendData + "]"); + this.requestServer(httpURLConnection, sendData, + encoding); + this.result = this.response(httpURLConnection, encoding); + LogUtil.writeLog("Response message:[" + 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); + LogUtil.writeLog("同步返回报文:[" + 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)); + } + LogUtil.writeLog("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) { + LogUtil.writeErrorLog(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; + //是否验证https证书,测试环境请设置false,生产环境建议优先尝试true,不行再false + if(!SDKConfig.getConfig().isIfValidateRemoteCert()){ + 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) { + LogUtil.writeErrorLog(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; + //是否验证https证书,测试环境请设置false,生产环境建议优先尝试true,不行再false + if(!SDKConfig.getConfig().isIfValidateRemoteCert()){ + 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 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 en : requestParam.entrySet()) { + try { + sf.append(en.getKey() + + "=" + + (null == en.getValue() || "".equals(en.getValue()) ? "" : URLEncoder + .encode(en.getValue(), coder)) + "&"); + } catch (UnsupportedEncodingException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + return ""; + } + } + reqstr = sf.substring(0, sf.length() - 1); + } + LogUtil.writeLog("Request Message:[" + reqstr + "]"); + return reqstr; + } + +} diff --git a/src/main/java/com/xkrs/unionpay/util/LogUtil.java b/src/main/java/com/xkrs/unionpay/util/LogUtil.java new file mode 100644 index 0000000..10fab94 --- /dev/null +++ b/src/main/java/com/xkrs/unionpay/util/LogUtil.java @@ -0,0 +1,115 @@ +/** + * + * 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.xkrs.unionpay.util; + +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +/** + * + * @ClassName LogUtil + * @Description acpsdk日志工具类 + * @date 2016-7-22 下午4:04:35 + * 声明:以下代码只是为了方便接入方测试而提供的样例代码,商户可以根据自己需要,按照技术文档编写。该代码仅供参考,不提供编码,性能,规范性等方面的保障 + */ +public class LogUtil { + + private final static Logger GATELOG = LoggerFactory.getLogger("ACP_SDK_LOG"); + private final static Logger GATELOG_ERROR = LoggerFactory.getLogger("SDK_ERR_LOG"); + private final static Logger GATELOG_MESSAGE = LoggerFactory.getLogger("SDK_MSG_LOG"); + + final static String LOG_STRING_REQ_MSG_BEGIN = "============================== SDK REQ MSG BEGIN =============================="; + final static String LOG_STRING_REQ_MSG_END = "============================== SDK REQ MSG END =============================="; + final static String LOG_STRING_RSP_MSG_BEGIN = "============================== SDK RSP MSG BEGIN =============================="; + final static String LOG_STRING_RSP_MSG_END = "============================== SDK RSP MSG END =============================="; + + /** + * 记录普通日志 + * + * @param cont + */ + public static void writeLog(String cont) { + GATELOG.info(cont); + } + + /** + * 记录ERORR日志 + * + * @param cont + */ + public static void writeErrorLog(String cont) { + GATELOG_ERROR.error(cont); + } + + /** + * 记录ERROR日志 + * + * @param cont + * @param ex + */ + public static void writeErrorLog(String cont, Throwable ex) { + GATELOG_ERROR.error(cont, ex); + } + + /** + * 记录通信报文 + * + * @param msg + */ + public static void writeMessage(String msg) { + GATELOG_MESSAGE.info(msg); + } + + /** + * 打印请求报文 + * + * @param reqParam + */ + public static void printRequestLog(Map reqParam) { + writeMessage(LOG_STRING_REQ_MSG_BEGIN); + Iterator> it = reqParam.entrySet().iterator(); + while (it.hasNext()) { + Entry en = it.next(); + writeMessage("[" + en.getKey() + "] = [" + en.getValue() + "]"); + } + writeMessage(LOG_STRING_REQ_MSG_END); + } + + /** + * 打印响应报文. + * + * @param res + */ + public static void printResponseLog(String res) { + writeMessage(LOG_STRING_RSP_MSG_BEGIN); + writeMessage(res); + writeMessage(LOG_STRING_RSP_MSG_END); + } + + /** + * debug方法 + * + * @param cont + */ + public static void debug(String cont) { + if (GATELOG.isDebugEnabled()) { + GATELOG.debug(cont); + } + } +} diff --git a/src/main/java/com/xkrs/unionpay/util/SDKConfig.java b/src/main/java/com/xkrs/unionpay/util/SDKConfig.java new file mode 100644 index 0000000..c1d89cb --- /dev/null +++ b/src/main/java/com/xkrs/unionpay/util/SDKConfig.java @@ -0,0 +1,835 @@ +/** + * + * 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.xkrs.unionpay.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +/** + * + * @ClassName SDKConfig + * @Description acpsdk配置文件acp_sdk.properties配置信息类 + * @date 2016-7-22 下午4:04:55 + * 声明:以下代码只是为了方便接入方测试而提供的样例代码,商户可以根据自己需要,按照技术文档编写。该代码仅供参考,不提供编码,性能,规范性等方面的保障 + */ +public class SDKConfig { + public static final String FILE_NAME = "application.properties"; + /** 前台请求URL. */ + private String frontRequestUrl; + /** 后台请求URL. */ + private String backRequestUrl; + /** 二维码统一下单请求URL. */ + private String orderRequestUrl; + /** 单笔查询 */ + 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; + /** 安全密钥(SHA256和SM3计算时使用) */ + private String secureKey; + /** 中级证书路径 */ + private String middleCertPath; + /** 根证书路径 */ + private String rootCertPath; + /** 是否验证验签证书CN,除了false都验 */ + private boolean ifValidateCNName = true; + /** 是否验证https证书,默认都不验 */ + private boolean ifValidateRemoteCert = false; + /** signMethod,没配按01吧 */ + private String signMethod = "01"; + /** version,没配按5.0.0 */ + private String version = "5.0.0"; + /** frontUrl */ + private String frontUrl; + /** backUrl */ + private String backUrl; + /** frontFailUrl */ + private String frontFailUrl; + + /*缴费相关地址*/ + private String jfFrontRequestUrl; + private String jfBackRequestUrl; + private String jfSingleQueryUrl; + private String jfCardRequestUrl; + private String jfAppRequestUrl; + + private String qrcBackTransUrl; + private String qrcB2cIssBackTransUrl; + private String qrcB2cMerBackTransUrl; + + //综合认证 + private String zhrzFrontRequestUrl; + private String zhrzBackRequestUrl; + private String zhrzSingleQueryUrl; + private String zhrzCardRequestUrl; + private String zhrzAppRequestUrl; + private String zhrzFaceRequestUrl; + + + + /** 配置文件中的前台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_ORDER_URL = "acpsdk.orderTransUrl"; + /** 配置文件中的单笔交易查询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 QRC_BACK_TRANS_URL="acpsdk.qrcBackTransUrl"; + // 人到人 + public static final String QRC_B2C_ISS_BACK_TRANS_URL="acpsdk.qrcB2cIssBackTransUrl"; + // 人到人 + public static final String QRC_B2C_MER_BACK_TRANS_URL="acpsdk.qrcB2cMerBackTransUrl"; + + /** 以下综合认证产品使用,其余产品用不到,无视即可 */ + // 前台请求地址 + public static final String ZHRZ_SDK_FRONT_TRANS_URL= "acpsdk.zhrzFrontTransUrl"; + // 后台请求地址 + public static final String ZHRZ_SDK_BACK_TRANS_URL="acpsdk.zhrzBackTransUrl"; + // 单笔查询请求地址 + public static final String ZHRZ_SDK_SINGLE_QUERY_URL="acpsdk.zhrzSingleQueryUrl"; + // 有卡交易地址 + public static final String ZHRZ_SDK_CARD_TRANS_URL="acpsdk.zhrzCardTransUrl"; + // App交易地址 + public static final String ZHRZ_SDK_APP_TRANS_URL="acpsdk.zhrzAppTransUrl"; + // 图片识别交易地址 + public static final String ZHRZ_SDK_FACE_TRANS_URL="acpsdk.zhrzFaceTransUrl"; + + + /** 配置文件中签名证书路径常量. */ + 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"; + /** 配置文件中安全密钥 */ + public static final String SDK_SECURITYKEY = "acpsdk.secureKey"; + /** 配置文件中根证书路径常量 */ + public static final String SDK_ROOTCERT_PATH = "acpsdk.rootCert.path"; + /** 配置文件中根证书路径常量 */ + public static final String SDK_MIDDLECERT_PATH = "acpsdk.middleCert.path"; + /** 配置是否需要验证验签证书CN,除了false之外的值都当true处理 */ + public static final String SDK_IF_VALIDATE_CN_NAME = "acpsdk.ifValidateCNName"; + /** 配置是否需要验证https证书,除了true之外的值都当false处理 */ + public static final String SDK_IF_VALIDATE_REMOTE_CERT = "acpsdk.ifValidateRemoteCert"; + /** signmethod */ + public static final String SDK_SIGN_METHOD ="acpsdk.signMethod"; + /** version */ + public static final String SDK_VERSION = "acpsdk.version"; + /** 后台通知地址 */ + public static final String SDK_BACKURL = "acpsdk.backUrl"; + /** 前台通知地址 */ + public static final String SDK_FRONTURL = "acpsdk.frontUrl"; + /** 前台失败通知地址 */ + public static final String SDK_FRONT_FAIL_URL = "acpsdk.frontFailUrl"; + + /** 操作对象. */ + private static SDKConfig config = new SDKConfig(); + /** 属性文件对象. */ + private Properties properties; + + private SDKConfig() { + super(); + } + + /** + * 获取config对象. + * @return + */ + public static SDKConfig getConfig() { + return config; + } + + /** + * 从properties文件加载 + * + * @param rootPath + * 不包含文件名的目录. + */ + public void loadPropertiesFromPath(String rootPath) { + if (rootPath != null && !"".equals(rootPath.trim())) { + LogUtil.writeLog("从路径读取配置文件: " + rootPath+File.separator+FILE_NAME); + File file = new File(rootPath + File.separator + FILE_NAME); + InputStream in = null; + if (file.exists()) { + try { + in = new FileInputStream(file); + properties = new Properties(); + properties.load(in); + loadProperties(properties); + } catch (FileNotFoundException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } catch (IOException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } finally { + if (null != in) { + try { + in.close(); + } catch (IOException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } + } + } + } else { + // 由于此时可能还没有完成LOG的加载,因此采用标准输出来打印日志信息 + LogUtil.writeErrorLog(rootPath + FILE_NAME + "不存在,加载参数失败"); + } + } else { + loadPropertiesFromSrc(); + } + + } + + /** + * 从classpath路径下加载配置参数 + */ + public void loadPropertiesFromSrc() { + InputStream in = null; + try { + LogUtil.writeLog("从classpath: " +SDKConfig.class.getClassLoader().getResource("").getPath()+" 获取属性文件"+FILE_NAME); + in = SDKConfig.class.getClassLoader().getResourceAsStream(FILE_NAME); + if (null != in) { + properties = new Properties(); + try { + properties.load(in); + } catch (IOException e) { + throw e; + } + } else { + LogUtil.writeErrorLog(FILE_NAME + "属性文件未能在classpath指定的目录下 "+SDKConfig.class.getClassLoader().getResource("").getPath()+" 找到!"); + return; + } + loadProperties(properties); + } catch (IOException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } finally { + if (null != in) { + try { + in.close(); + } catch (IOException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } + } + } + } + + /** + * 根据传入的 {@link #load(Properties)}对象设置配置参数 + * + * @param pro + */ + public void loadProperties(Properties pro) { + LogUtil.writeLog("开始从属性文件中加载配置项"); + String value = null; + + value = pro.getProperty(SDK_SIGNCERT_PATH); + if (!SDKUtil.isEmpty(value)) { + this.signCertPath = value.trim(); + LogUtil.writeLog("配置项:私钥签名证书路径==>"+SDK_SIGNCERT_PATH +"==>"+ value+" 已加载"); + } + value = pro.getProperty(SDK_SIGNCERT_PWD); + if (!SDKUtil.isEmpty(value)) { + this.signCertPwd = value.trim(); + LogUtil.writeLog("配置项:私钥签名证书密码==>"+SDK_SIGNCERT_PWD +" 已加载"); + } + value = pro.getProperty(SDK_SIGNCERT_TYPE); + if (!SDKUtil.isEmpty(value)) { + this.signCertType = value.trim(); + LogUtil.writeLog("配置项:私钥签名证书类型==>"+SDK_SIGNCERT_TYPE +"==>"+ value+" 已加载"); + } + value = pro.getProperty(SDK_ENCRYPTCERT_PATH); + if (!SDKUtil.isEmpty(value)) { + this.encryptCertPath = value.trim(); + LogUtil.writeLog("配置项:敏感信息加密证书==>"+SDK_ENCRYPTCERT_PATH +"==>"+ value+" 已加载"); + } + value = pro.getProperty(SDK_VALIDATECERT_DIR); + if (!SDKUtil.isEmpty(value)) { + this.validateCertDir = value.trim(); + LogUtil.writeLog("配置项:验证签名证书路径(这里配置的是目录,不要指定到公钥文件)==>"+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_ORDER_URL); + if (!SDKUtil.isEmpty(value)) { + this.orderRequestUrl = 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(SDK_SECURITYKEY); + if (!SDKUtil.isEmpty(value)) { + this.secureKey = value.trim(); + } + value = pro.getProperty(SDK_ROOTCERT_PATH); + if (!SDKUtil.isEmpty(value)) { + this.rootCertPath = value.trim(); + } + value = pro.getProperty(SDK_MIDDLECERT_PATH); + if (!SDKUtil.isEmpty(value)) { + this.middleCertPath = 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(QRC_BACK_TRANS_URL); + if (!SDKUtil.isEmpty(value)) { + this.qrcBackTransUrl = value.trim(); + } + + value = pro.getProperty(QRC_B2C_ISS_BACK_TRANS_URL); + if (!SDKUtil.isEmpty(value)) { + this.qrcB2cIssBackTransUrl = value.trim(); + } + + value = pro.getProperty(QRC_B2C_MER_BACK_TRANS_URL); + if (!SDKUtil.isEmpty(value)) { + this.qrcB2cMerBackTransUrl = value.trim(); + } + + /**综合认证**/ + value = pro.getProperty(ZHRZ_SDK_FRONT_TRANS_URL); + if (!SDKUtil.isEmpty(value)) { + this.zhrzFrontRequestUrl = value.trim(); + } + + value = pro.getProperty(ZHRZ_SDK_BACK_TRANS_URL); + if (!SDKUtil.isEmpty(value)) { + this.zhrzBackRequestUrl = value.trim(); + } + + value = pro.getProperty(ZHRZ_SDK_SINGLE_QUERY_URL); + if (!SDKUtil.isEmpty(value)) { + this.zhrzSingleQueryUrl = value.trim(); + } + + value = pro.getProperty(ZHRZ_SDK_CARD_TRANS_URL); + if (!SDKUtil.isEmpty(value)) { + this.zhrzCardRequestUrl = value.trim(); + } + + value = pro.getProperty(ZHRZ_SDK_APP_TRANS_URL); + if (!SDKUtil.isEmpty(value)) { + this.zhrzAppRequestUrl = value.trim(); + } + + value = pro.getProperty(ZHRZ_SDK_FACE_TRANS_URL); + if (!SDKUtil.isEmpty(value)) { + this.zhrzFaceRequestUrl = 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(); + } + + value = pro.getProperty(SDK_IF_VALIDATE_CN_NAME); + if (!SDKUtil.isEmpty(value)) { + if( SDKConstants.FALSE_STRING.equals(value.trim())) + this.ifValidateCNName = false; + } + + value = pro.getProperty(SDK_IF_VALIDATE_REMOTE_CERT); + if (!SDKUtil.isEmpty(value)) { + if( SDKConstants.TRUE_STRING.equals(value.trim())) + this.ifValidateRemoteCert = true; + } + + value = pro.getProperty(SDK_SIGN_METHOD); + if (!SDKUtil.isEmpty(value)) { + this.signMethod = value.trim(); + } + + value = pro.getProperty(SDK_SIGN_METHOD); + if (!SDKUtil.isEmpty(value)) { + this.signMethod = value.trim(); + } + value = pro.getProperty(SDK_VERSION); + if (!SDKUtil.isEmpty(value)) { + this.version = value.trim(); + } + value = pro.getProperty(SDK_FRONTURL); + if (!SDKUtil.isEmpty(value)) { + this.frontUrl = value.trim(); + } + value = pro.getProperty(SDK_BACKURL); + if (!SDKUtil.isEmpty(value)) { + this.backUrl = value.trim(); + } + value = pro.getProperty(SDK_FRONT_FAIL_URL); + if (!SDKUtil.isEmpty(value)) { + this.frontFailUrl = 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 getOrderRequestUrl() { + return orderRequestUrl; + } + + public void setOrderRequestUrl(String orderRequestUrl) { + this.orderRequestUrl = orderRequestUrl; + } + + 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 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; + } + + public String getSecureKey() { + return secureKey; + } + + public void setSecureKey(String securityKey) { + this.secureKey = securityKey; + } + + public String getMiddleCertPath() { + return middleCertPath; + } + + public void setMiddleCertPath(String middleCertPath) { + this.middleCertPath = middleCertPath; + } + + public boolean isIfValidateCNName() { + return ifValidateCNName; + } + + public void setIfValidateCNName(boolean ifValidateCNName) { + this.ifValidateCNName = ifValidateCNName; + } + + public boolean isIfValidateRemoteCert() { + return ifValidateRemoteCert; + } + + public void setIfValidateRemoteCert(boolean ifValidateRemoteCert) { + this.ifValidateRemoteCert = ifValidateRemoteCert; + } + + public String getSignMethod() { + return signMethod; + } + + public void setSignMethod(String signMethod) { + this.signMethod = signMethod; + } + public String getQrcBackTransUrl() { + return qrcBackTransUrl; + } + + public void setQrcBackTransUrl(String qrcBackTransUrl) { + this.qrcBackTransUrl = qrcBackTransUrl; + } + + public String getQrcB2cIssBackTransUrl() { + return qrcB2cIssBackTransUrl; + } + + public void setQrcB2cIssBackTransUrl(String qrcB2cIssBackTransUrl) { + this.qrcB2cIssBackTransUrl = qrcB2cIssBackTransUrl; + } + + public String getQrcB2cMerBackTransUrl() { + return qrcB2cMerBackTransUrl; + } + + public void setQrcB2cMerBackTransUrl(String qrcB2cMerBackTransUrl) { + this.qrcB2cMerBackTransUrl = qrcB2cMerBackTransUrl; + } + + public String getZhrzFrontRequestUrl() { + return zhrzFrontRequestUrl; + } + + public String getZhrzBackRequestUrl() { + return zhrzBackRequestUrl; + } + + public String getZhrzSingleQueryUrl() { + return zhrzSingleQueryUrl; + } + + public String getZhrzCardRequestUrl() { + return zhrzCardRequestUrl; + } + + public String getZhrzAppRequestUrl() { + return zhrzAppRequestUrl; + } + + public String getZhrzFaceRequestUrl() { + return zhrzFaceRequestUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getFrontUrl() { + return frontUrl; + } + + public void setFrontUrl(String frontUrl) { + this.frontUrl = frontUrl; + } + + public String getBackUrl() { + return backUrl; + } + + public void setBackUrl(String backUrl) { + this.backUrl = backUrl; + } + + public String getFrontFailUrl() { + return frontFailUrl; + } + + public String getRootCertPath() { + return rootCertPath; + } + + public void setRootCertPath(String rootCertPath) { + this.rootCertPath = rootCertPath; + } + +} diff --git a/src/main/java/com/xkrs/unionpay/util/SDKConstants.java b/src/main/java/com/xkrs/unionpay/util/SDKConstants.java new file mode 100644 index 0000000..2a39d3d --- /dev/null +++ b/src/main/java/com/xkrs/unionpay/util/SDKConstants.java @@ -0,0 +1,391 @@ +/** + * + * 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.xkrs.unionpay.util; +/** + * + * @ClassName SDKConstants + * @Description acpsdk常量类 + * @date 2016-7-22 下午4:05:54 + * 声明:以下代码只是为了方便接入方测试而提供的样例代码,商户可以根据自己需要,按照技术文档编写。该代码仅供参考,不提供编码,性能,规范性等方面的保障 + */ +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="; + + public static final String VERSION_1_0_0 = "1.0.0"; + public static final String VERSION_5_0_0 = "5.0.0"; + public static final String VERSION_5_0_1 = "5.0.1"; + public static final String VERSION_5_1_0 = "5.1.0"; + public static final String SIGNMETHOD_RSA = "01"; + public static final String SIGNMETHOD_SHA256 = "11"; + public static final String SIGNMETHOD_SM3 = "12"; + public static final String UNIONPAY_CNNAME = "中国银联股份有限公司"; + public static final String CERTTYPE_01 = "01";// 敏感信息加密公钥 + public static final String CERTTYPE_02 = "02";// 磁道加密公钥 + + /******************************************** 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_signMethod = "signMethod"; + /** 编码方式. */ + 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"; + + /** 签名公钥证书 */ + public static final String param_signPubKeyCert = "signPubKeyCert"; + + /** 加密公钥证书 */ + public static final String param_encryptPubKeyCert = "encryptPubKeyCert"; + + /** 证书类型 */ + public static final String param_certType = "certType"; + +} diff --git a/src/main/java/com/xkrs/unionpay/util/SDKUtil.java b/src/main/java/com/xkrs/unionpay/util/SDKUtil.java new file mode 100644 index 0000000..3d05a14 --- /dev/null +++ b/src/main/java/com/xkrs/unionpay/util/SDKUtil.java @@ -0,0 +1,855 @@ +/** + * + * 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.xkrs.unionpay.util; + +import static com.xkrs.unionpay.util.SDKConstants.CERTTYPE_01; +import static com.xkrs.unionpay.util.SDKConstants.CERTTYPE_02; +import static com.xkrs.unionpay.util.SDKConstants.POINT; +import static com.xkrs.unionpay.util.SDKConstants.SIGNMETHOD_RSA; +import static com.xkrs.unionpay.util.SDKConstants.SIGNMETHOD_SHA256; +import static com.xkrs.unionpay.util.SDKConstants.SIGNMETHOD_SM3; +import static com.xkrs.unionpay.util.SDKConstants.VERSION_5_0_0; +import static com.xkrs.unionpay.util.SDKConstants.VERSION_1_0_0; +import static com.xkrs.unionpay.util.SDKConstants.VERSION_5_1_0; +import static com.xkrs.unionpay.util.SDKConstants.VERSION_5_0_1; +import static com.xkrs.unionpay.util.SDKConstants.param_signMethod; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.security.cert.X509Certificate; +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 java.util.zip.Deflater; +import java.util.zip.Inflater; + +/** + * + * @ClassName SDKUtil + * @Description acpsdk工具类 + * @date 2016-7-22 下午4:06:18 + * 声明:以下代码只是为了方便接入方测试而提供的样例代码,商户可以根据自己需要,按照技术文档编写。该代码仅供参考,不提供编码,性能,规范性等方面的保障 + */ +public class SDKUtil { + + /** + * 根据signMethod的值,提供三种计算签名的方法 + * + * @param data + * 待签名数据Map键值对形式 + * @param encoding + * 编码 + * @return 签名是否成功 + */ + public static boolean sign(Map data, String encoding) { + + if (isEmpty(encoding)) { + encoding = "UTF-8"; + } + String signMethod = data.get(param_signMethod); + String version = data.get(SDKConstants.param_version); + if (!VERSION_1_0_0.equals(version) && !VERSION_5_0_1.equals(version) && isEmpty(signMethod)) { + LogUtil.writeErrorLog("signMethod must Not null"); + return false; + } + + if (isEmpty(version)) { + LogUtil.writeErrorLog("version must Not null"); + return false; + } + if (SIGNMETHOD_RSA.equals(signMethod)|| VERSION_1_0_0.equals(version) || VERSION_5_0_1.equals(version)) { + if (VERSION_5_0_0.equals(version)|| VERSION_1_0_0.equals(version) || VERSION_5_0_1.equals(version)) { + // 设置签名证书序列号 + data.put(SDKConstants.param_certId, CertUtil.getSignCertId()); + // 将Map信息转换成key1=value1&key2=value2的形式 + String stringData = coverMap2String(data); + LogUtil.writeLog("打印排序后待签名请求报文串(交易返回11验证签名失败时可以用来同正确的进行比对):[" + stringData + "]"); + byte[] byteSign = null; + String stringSign = null; + try { + // 通过SHA1进行摘要并转16进制 + byte[] signDigest = SecureUtil + .sha1X16(stringData, encoding); + LogUtil.writeLog("打印摘要(交易返回11验证签名失败可以用来同正确的进行比对):[" + new String(signDigest)+ "]"); + byteSign = SecureUtil.base64Encode(SecureUtil.signBySoft( + CertUtil.getSignCertPrivateKey(), signDigest)); + stringSign = new String(byteSign); + // 设置签名域值 + data.put(SDKConstants.param_signature, stringSign); + return true; + } catch (Exception e) { + LogUtil.writeErrorLog("Sign Error", e); + return false; + } + } else if (VERSION_5_1_0.equals(version)) { + // 设置签名证书序列号 + data.put(SDKConstants.param_certId, CertUtil.getSignCertId()); + // 将Map信息转换成key1=value1&key2=value2的形式 + String stringData = coverMap2String(data); + LogUtil.writeLog("打印待签名请求报文串(交易返回11验证签名失败时可以用来同正确的进行比对):[" + stringData + "]"); + byte[] byteSign = null; + String stringSign = null; + try { + // 通过SHA256进行摘要并转16进制 + byte[] signDigest = SecureUtil + .sha256X16(stringData, encoding); + LogUtil.writeLog("打印摘要(交易返回11验证签名失败可以用来同正确的进行比对):[" + new String(signDigest)+ "]"); + byteSign = SecureUtil.base64Encode(SecureUtil.signBySoft256( + CertUtil.getSignCertPrivateKey(), signDigest)); + stringSign = new String(byteSign); + // 设置签名域值 + data.put(SDKConstants.param_signature, stringSign); + return true; + } catch (Exception e) { + LogUtil.writeErrorLog("Sign Error", e); + return false; + } + } + } else if (SIGNMETHOD_SHA256.equals(signMethod)) { + return signBySecureKey(data, SDKConfig.getConfig() + .getSecureKey(), encoding); + } else if (SIGNMETHOD_SM3.equals(signMethod)) { + return signBySecureKey(data, SDKConfig.getConfig() + .getSecureKey(), encoding); + } + return false; + } + + /** + * 通过传入的证书绝对路径和证书密码读取签名证书进行签名并返回签名值
+ * + * @param data + * 待签名数据Map键值对形式 + * @param encoding + * 编码 + * @param certPath + * 证书绝对路径 + * @param certPwd + * 证书密码 + * @return 签名值 + */ + public static boolean signBySecureKey(Map data, String secureKey, + String encoding) { + + if (isEmpty(encoding)) { + encoding = "UTF-8"; + } + if (isEmpty(secureKey)) { + LogUtil.writeErrorLog("secureKey is empty"); + return false; + } + String signMethod = data.get(param_signMethod); + if (isEmpty(signMethod)) { + LogUtil.writeErrorLog("signMethod must Not null"); + return false; + } + + if (SIGNMETHOD_SHA256.equals(signMethod)) { + // 将Map信息转换成key1=value1&key2=value2的形式 + String stringData = coverMap2String(data); + LogUtil.writeLog("待签名请求报文串:[" + stringData + "]"); + String strBeforeSha256 = stringData + + SDKConstants.AMPERSAND + + SecureUtil.sha256X16Str(secureKey, encoding); + String strAfterSha256 = SecureUtil.sha256X16Str(strBeforeSha256, + encoding); + // 设置签名域值 + data.put(SDKConstants.param_signature, strAfterSha256); + return true; + } else if (SIGNMETHOD_SM3.equals(signMethod)) { + String stringData = coverMap2String(data); + LogUtil.writeLog("待签名请求报文串:[" + stringData + "]"); + String strBeforeSM3 = stringData + + SDKConstants.AMPERSAND + + SecureUtil.sm3X16Str(secureKey, encoding); + String strAfterSM3 = SecureUtil.sm3X16Str(strBeforeSM3, encoding); + // 设置签名域值 + data.put(SDKConstants.param_signature, strAfterSM3); + return true; + } + return false; + } + + /** + * 通过传入的签名密钥进行签名并返回签名值
+ * + * @param data + * 待签名数据Map键值对形式 + * @param encoding + * 编码 + * @param certPath + * 证书绝对路径 + * @param certPwd + * 证书密码 + * @return 签名值 + */ + public static boolean signByCertInfo(Map data, + String certPath, String certPwd, String encoding) { + + if (isEmpty(encoding)) { + encoding = "UTF-8"; + } + if (isEmpty(certPath) || isEmpty(certPwd)) { + LogUtil.writeErrorLog("CertPath or CertPwd is empty"); + return false; + } + String signMethod = data.get(param_signMethod); + String version = data.get(SDKConstants.param_version); + if (!VERSION_1_0_0.equals(version) && !VERSION_5_0_1.equals(version) && isEmpty(signMethod)) { + LogUtil.writeErrorLog("signMethod must Not null"); + return false; + } + if (isEmpty(version)) { + LogUtil.writeErrorLog("version must Not null"); + return false; + } + + if (SIGNMETHOD_RSA.equals(signMethod) || VERSION_1_0_0.equals(version) || VERSION_5_0_1.equals(version)) { + if (VERSION_5_0_0.equals(version) || VERSION_1_0_0.equals(version) || VERSION_5_0_1.equals(version)) { + // 设置签名证书序列号 + data.put(SDKConstants.param_certId, CertUtil.getCertIdByKeyStoreMap(certPath, certPwd)); + // 将Map信息转换成key1=value1&key2=value2的形式 + String stringData = coverMap2String(data); + LogUtil.writeLog("待签名请求报文串:[" + stringData + "]"); + byte[] byteSign = null; + String stringSign = null; + try { + // 通过SHA1进行摘要并转16进制 + 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) { + LogUtil.writeErrorLog("Sign Error", e); + return false; + } + } else if (VERSION_5_1_0.equals(version)) { + // 设置签名证书序列号 + data.put(SDKConstants.param_certId, CertUtil.getCertIdByKeyStoreMap(certPath, certPwd)); + // 将Map信息转换成key1=value1&key2=value2的形式 + String stringData = coverMap2String(data); + LogUtil.writeLog("待签名请求报文串:[" + stringData + "]"); + byte[] byteSign = null; + String stringSign = null; + try { + // 通过SHA256进行摘要并转16进制 + byte[] signDigest = SecureUtil + .sha256X16(stringData, encoding); + byteSign = SecureUtil.base64Encode(SecureUtil.signBySoft256( + CertUtil.getSignCertPrivateKeyByStoreMap(certPath, certPwd), signDigest)); + stringSign = new String(byteSign); + // 设置签名域值 + data.put(SDKConstants.param_signature, stringSign); + return true; + } catch (Exception e) { + LogUtil.writeErrorLog("Sign Error", e); + return false; + } + } + + } + return false; + } + + /** + * 验证签名 + * + * @param resData + * 返回报文数据 + * @param encoding + * 编码格式 + * @return + */ + public static boolean validateBySecureKey(Map resData, String secureKey, String encoding) { + LogUtil.writeLog("验签处理开始"); + if (isEmpty(encoding)) { + encoding = "UTF-8"; + } + if (isEmpty(secureKey)) { + LogUtil.writeErrorLog("secureKey is empty"); + return false; + } + String signMethod = resData.get(SDKConstants.param_signMethod); + if (SIGNMETHOD_SHA256.equals(signMethod)) { + // 1.进行SHA256验证 + String stringSign = resData.get(SDKConstants.param_signature); + LogUtil.writeLog("签名原文:["+stringSign+"]"); + // 将Map信息转换成key1=value1&key2=value2的形式 + String stringData = coverMap2String(resData); + LogUtil.writeLog("待验签返回报文串:["+stringData+"]"); + String strBeforeSha256 = stringData + + SDKConstants.AMPERSAND + + SecureUtil.sha256X16Str(secureKey, encoding); + String strAfterSha256 = SecureUtil.sha256X16Str(strBeforeSha256, + encoding); + return stringSign.equals(strAfterSha256); + } else if (SIGNMETHOD_SM3.equals(signMethod)) { + // 1.进行SM3验证 + String stringSign = resData.get(SDKConstants.param_signature); + LogUtil.writeLog("签名原文:["+stringSign+"]"); + // 将Map信息转换成key1=value1&key2=value2的形式 + String stringData = coverMap2String(resData); + LogUtil.writeLog("待验签返回报文串:["+stringData+"]"); + String strBeforeSM3 = stringData + + SDKConstants.AMPERSAND + + SecureUtil.sm3X16Str(secureKey, encoding); + String strAfterSM3 = SecureUtil + .sm3X16Str(strBeforeSM3, encoding); + return stringSign.equals(strAfterSM3); + } + return false; + } + + /** + * 验证签名 + * + * @param resData + * 返回报文数据 + * @param encoding + * 编码格式 + * @return + */ + public static boolean validate(Map resData, String encoding) { + LogUtil.writeLog("验签处理开始"); + if (isEmpty(encoding)) { + encoding = "UTF-8"; + } + String signMethod = resData.get(SDKConstants.param_signMethod); + String version = resData.get(SDKConstants.param_version); + if (SIGNMETHOD_RSA.equals(signMethod) || VERSION_1_0_0.equals(version) || VERSION_5_0_1.equals(version)) { + // 获取返回报文的版本号 + if (VERSION_5_0_0.equals(version) || VERSION_1_0_0.equals(version) || VERSION_5_0_1.equals(version)) { + String stringSign = resData.get(SDKConstants.param_signature); + LogUtil.writeLog("签名原文:["+stringSign+"]"); + // 从返回报文中获取certId ,然后去证书静态Map中查询对应验签证书对象 + String certId = resData.get(SDKConstants.param_certId); + LogUtil.writeLog("对返回报文串验签使用的验签公钥序列号:["+certId+"]"); + // 将Map信息转换成key1=value1&key2=value2的形式 + String stringData = coverMap2String(resData); + LogUtil.writeLog("待验签返回报文串:["+stringData+"]"); + try { + // 验证签名需要用银联发给商户的公钥证书. + return SecureUtil.validateSignBySoft(CertUtil + .getValidatePublicKey(certId), SecureUtil + .base64Decode(stringSign.getBytes(encoding)), + SecureUtil.sha1X16(stringData, encoding)); + } catch (UnsupportedEncodingException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } catch (Exception e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } + } else if (VERSION_5_1_0.equals(version)) { + // 1.从返回报文中获取公钥信息转换成公钥对象 + String strCert = resData.get(SDKConstants.param_signPubKeyCert); +// LogUtil.writeLog("验签公钥证书:["+strCert+"]"); + X509Certificate x509Cert = CertUtil.genCertificateByStr(strCert); + if(x509Cert == null) { + LogUtil.writeErrorLog("convert signPubKeyCert failed"); + return false; + } + // 2.验证证书链 + if (!CertUtil.verifyCertificate(x509Cert)) { + LogUtil.writeErrorLog("验证公钥证书失败,证书信息:["+strCert+"]"); + return false; + } + + // 3.验签 + String stringSign = resData.get(SDKConstants.param_signature); + LogUtil.writeLog("签名原文:["+stringSign+"]"); + // 将Map信息转换成key1=value1&key2=value2的形式 + String stringData = coverMap2String(resData); + LogUtil.writeLog("待验签返回报文串:["+stringData+"]"); + try { + // 验证签名需要用银联发给商户的公钥证书. + boolean result = SecureUtil.validateSignBySoft256(x509Cert + .getPublicKey(), SecureUtil.base64Decode(stringSign + .getBytes(encoding)), SecureUtil.sha256X16( + stringData, encoding)); + LogUtil.writeLog("验证签名" + (result? "成功":"失败")); + return result; + } catch (UnsupportedEncodingException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } catch (Exception e) { + LogUtil.writeErrorLog(e.getMessage(), e); + } + } + + } else if (SIGNMETHOD_SHA256.equals(signMethod)) { + return validateBySecureKey(resData, SDKConfig.getConfig().getSecureKey(), encoding); + } else if (SIGNMETHOD_SM3.equals(signMethod)) { + return validateBySecureKey(resData, SDKConfig.getConfig().getSecureKey(), encoding); + } + return false; + } + + /** + * 将Map中的数据转换成key1=value1&key2=value2的形式 不包含签名域signature + * + * @param data + * 待拼接的Map数据 + * @return 拼接好后的字符串 + */ + public static String coverMap2String(Map data) { + TreeMap tree = new TreeMap(); + Iterator> it = data.entrySet().iterator(); + while (it.hasNext()) { + Entry 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 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 coverResultString2Map(String result) { + return convertResultStringToMap(result); + } + + /** + * 将形如key=value&key=value的字符串转换为相应的Map对象 + * + * @param result + * @return + */ + public static Map convertResultStringToMap(String result) { + Map map = null; + + if (result != null && !"".equals(result.trim())) { + if (result.startsWith("{") && result.endsWith("}")) { + result = result.substring(1, result.length() - 1); + } + map = parseQString(result); + } + + return map; + } + + + /** + * 解析应答字符串,生成应答要素 + * + * @param str + * 需要解析的字符串 + * @return 解析的结果map + * @throws UnsupportedEncodingException + */ + public static Map parseQString(String str) { + + Map map = new HashMap(); + 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 map) { + 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()); + } + } + + /** + * + * 获取应答报文中的加密公钥证书,并存储到本地,并备份原始证书
+ * 更新成功则返回1,无更新返回0,失败异常返回-1。 + * + * @param resData + * @param encoding + * @return + */ + public static int getEncryptCert(Map resData, + String encoding) { + String strCert = resData.get(SDKConstants.param_encryptPubKeyCert); + String certType = resData.get(SDKConstants.param_certType); + if (isEmpty(strCert) || isEmpty(certType)) + return -1; + X509Certificate x509Cert = CertUtil.genCertificateByStr(strCert); + if (CERTTYPE_01.equals(certType)) { + // 更新敏感信息加密公钥 + if (!CertUtil.getEncryptCertId().equals( + x509Cert.getSerialNumber().toString())) { + // ID不同时进行本地证书更新操作 + String localCertPath = SDKConfig.getConfig().getEncryptCertPath(); + String newLocalCertPath = genBackupName(localCertPath); + // 1.将本地证书进行备份存储 + if (!copyFile(localCertPath, newLocalCertPath)) + return -1; + // 2.备份成功,进行新证书的存储 + if (!writeFile(localCertPath, strCert, encoding)) + return -1; + LogUtil.writeLog("save new encryptPubKeyCert success"); + CertUtil.resetEncryptCertPublicKey(); + return 1; + }else { + return 0; + } + + } else if (CERTTYPE_02.equals(certType)) { +// // 更新磁道加密公钥 +// if (!CertUtil.getEncryptTrackCertId().equals( +// x509Cert.getSerialNumber().toString())) { +// // ID不同时进行本地证书更新操作 +// String localCertPath = SDKConfig.getConfig().getEncryptTrackCertPath(); +// String newLocalCertPath = genBackupName(localCertPath); +// // 1.将本地证书进行备份存储 +// if (!copyFile(localCertPath, newLocalCertPath)) +// return -1; +// // 2.备份成功,进行新证书的存储 +// if (!writeFile(localCertPath, strCert, encoding)) +// return -1; +// LogUtil.writeLog("save new encryptPubKeyCert success"); +// CertUtil.resetEncryptTrackCertPublicKey(); +// return 1; +// }else { + return 0; +// } + }else { + LogUtil.writeLog("unknown cerType:"+certType); + return -1; + } + } + + /** + * 文件拷贝方法 + * + * @param srcFile + * 源文件 + * @param destFile + * 目标文件 + * @return + * @throws IOException + */ + public static boolean copyFile(String srcFile, String destFile) { + boolean flag = false; + FileInputStream fin = null; + FileOutputStream fout = null; + FileChannel fcin = null; + FileChannel fcout = null; + try { + // 获取源文件和目标文件的输入输出流 + fin = new FileInputStream(srcFile); + fout = new FileOutputStream(destFile); + // 获取输入输出通道 + fcin = fin.getChannel(); + fcout = fout.getChannel(); + // 创建缓冲区 + ByteBuffer buffer = ByteBuffer.allocate(1024); + while (true) { + // clear方法重设缓冲区,使它可以接受读入的数据 + buffer.clear(); + // 从输入通道中将数据读到缓冲区 + int r = fcin.read(buffer); + // read方法返回读取的字节数,可能为零,如果该通道已到达流的末尾,则返回-1 + if (r == -1) { + flag = true; + break; + } + // flip方法让缓冲区可以将新读入的数据写入另一个通道 + buffer.flip(); + // 从输出通道中将数据写入缓冲区 + fcout.write(buffer); + } + fout.flush(); + } catch (IOException e) { + LogUtil.writeErrorLog("CopyFile fail", e); + } finally { + try { + if (null != fin) + fin.close(); + if (null != fout) + fout.close(); + if (null != fcin) + fcin.close(); + if (null != fcout) + fcout.close(); + } catch (IOException ex) { + LogUtil.writeErrorLog("Releases any system resources fail", ex); + } + } + return flag; + } + + /** + * 写文件方法 + * + * @param filePath + * 文件路径 + * @param fileContent + * 文件内容 + * @param encoding + * 编码 + * @return + */ + public static boolean writeFile(String filePath, String fileContent, + String encoding) { + FileOutputStream fout = null; + FileChannel fcout = null; + File file = new File(filePath); + if (file.exists()) { + file.delete(); + } + + try { + fout = new FileOutputStream(filePath); + // 获取输出通道 + fcout = fout.getChannel(); + // 创建缓冲区 + // ByteBuffer buffer = ByteBuffer.allocate(1024); + ByteBuffer buffer = ByteBuffer.wrap(fileContent.getBytes(encoding)); + fcout.write(buffer); + fout.flush(); + } catch (FileNotFoundException e) { + LogUtil.writeErrorLog("WriteFile fail", e); + return false; + } catch (IOException ex) { + LogUtil.writeErrorLog("WriteFile fail", ex); + return false; + } finally { + try { + if (null != fout) + fout.close(); + if (null != fcout) + fcout.close(); + } catch (IOException ex) { + LogUtil.writeErrorLog("Releases any system resources fail", ex); + return false; + } + } + return true; + } + + /** + * 将传入的文件名(xxx)改名
+ * 结果为: xxx_backup.cer + * + * @param fileName + * @return + */ + public static String genBackupName(String fileName) { + if (isEmpty(fileName)) + return ""; + int i = fileName.lastIndexOf(POINT); + String leftFileName = fileName.substring(0, i); + String rightFileName = fileName.substring(i + 1); + String newFileName = leftFileName + "_backup" + POINT + rightFileName; + return newFileName; + } + + + public static byte[] readFileByNIO(String filePath) { + FileInputStream in = null; + FileChannel fc = null; + ByteBuffer bf = null; + try { + in = new FileInputStream(filePath); + fc = in.getChannel(); + bf = ByteBuffer.allocate((int) fc.size()); + fc.read(bf); + return bf.array(); + } catch (Exception e) { + LogUtil.writeErrorLog(e.getMessage()); + return null; + } finally { + try { + if (null != fc) { + fc.close(); + } + if (null != in) { + in.close(); + } + } catch (Exception e) { + LogUtil.writeErrorLog(e.getMessage()); + return null; + } + } + } + + /** + * 过滤请求报文中的空字符串或者空字符串 + * @param contentData + * @return + */ + public static Map filterBlank(Map contentData){ + LogUtil.writeLog("打印请求报文域 :"); + Map submitFromData = new HashMap(); + Set keyset = contentData.keySet(); + + for(String key:keyset){ + String value = contentData.get(key); + if (value != null && !"".equals(value.trim())) { + // 对value值进行去除前后空处理 + submitFromData.put(key, value.trim()); + LogUtil.writeLog(key + "-->" + String.valueOf(value)); + } + } + return submitFromData; + } + + /** + * 解压缩. + * + * @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(); + } + + /** + * 判断字符串是否为NULL或空 + * + * @param s + * 待判断的字符串数据 + * @return 判断结果 true-是 false-否 + */ + public static boolean isEmpty(String s) { + return null == s || "".equals(s.trim()); + } + +} diff --git a/src/main/java/com/xkrs/unionpay/util/SecureUtil.java b/src/main/java/com/xkrs/unionpay/util/SecureUtil.java new file mode 100644 index 0000000..67b1632 --- /dev/null +++ b/src/main/java/com/xkrs/unionpay/util/SecureUtil.java @@ -0,0 +1,577 @@ +/** + * + * 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.xkrs.unionpay.util; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; + +import javax.crypto.Cipher; + +import org.apache.commons.codec.binary.Base64; +import org.bouncycastle.crypto.digests.SM3Digest; +/** + * + * @ClassName SecureUtil + * @Description acpsdk安全算法工具类 + * @date 2016-7-22 下午4:08:32 + * 声明:以下代码只是为了方便接入方测试而提供的样例代码,商户可以根据自己需要,按照技术文档编写。该代码仅供参考,不提供编码,性能,规范性等方面的保障 + */ +public class SecureUtil { + /** + * 算法常量: SHA1 + */ + private static final String ALGORITHM_SHA1 = "SHA-1"; + /** + * 算法常量: SHA256 + */ + private static final String ALGORITHM_SHA256 = "SHA-256"; + /** + * 算法常量:SHA1withRSA + */ + private static final String BC_PROV_ALGORITHM_SHA1RSA = "SHA1withRSA"; + /** + * 算法常量:SHA256withRSA + */ + private static final String BC_PROV_ALGORITHM_SHA256RSA = "SHA256withRSA"; + + /** + * sm3计算后进行16进制转换 + * + * @param data + * 待计算的数据 + * @param encoding + * 编码 + * @return 计算结果 + */ + public static String sm3X16Str(String data, String encoding) { + byte[] bytes = sm3(data, encoding); + StringBuilder sm3StrBuff = new StringBuilder(); + for (int i = 0; i < bytes.length; i++) { + if (Integer.toHexString(0xFF & bytes[i]).length() == 1) { + sm3StrBuff.append("0").append( + Integer.toHexString(0xFF & bytes[i])); + } else { + sm3StrBuff.append(Integer.toHexString(0xFF & bytes[i])); + } + } + return sm3StrBuff.toString(); + } + + /** + * 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) { + LogUtil.writeErrorLog(e.getMessage(), e); + return null; + } + } + + + /** + * sha256计算后进行16进制转换 + * + * @param data + * 待计算的数据 + * @param encoding + * 编码 + * @return 计算结果 + */ + public static String sha256X16Str(String data, String encoding) { + byte[] bytes = sha256(data, encoding); + StringBuilder sha256StrBuff = new StringBuilder(); + for (int i = 0; i < bytes.length; i++) { + if (Integer.toHexString(0xFF & bytes[i]).length() == 1) { + sha256StrBuff.append("0").append( + Integer.toHexString(0xFF & bytes[i])); + } else { + sha256StrBuff.append(Integer.toHexString(0xFF & bytes[i])); + } + } + return sha256StrBuff.toString(); + } + + /** + * sha256计算后进行16进制转换 + * + * @param data + * 待计算的数据 + * @param encoding + * 编码 + * @return 计算结果 + */ + public static byte[] sha256X16(String data, String encoding) { + byte[] bytes = sha256(data, encoding); + StringBuilder sha256StrBuff = new StringBuilder(); + for (int i = 0; i < bytes.length; i++) { + if (Integer.toHexString(0xFF & bytes[i]).length() == 1) { + sha256StrBuff.append("0").append( + Integer.toHexString(0xFF & bytes[i])); + } else { + sha256StrBuff.append(Integer.toHexString(0xFF & bytes[i])); + } + } + try { + return sha256StrBuff.toString().getBytes(encoding); + } catch (UnsupportedEncodingException e) { + LogUtil.writeErrorLog(e.getMessage(), e); + return null; + } + } + + /** + * sha1计算. + * + * @param datas + * 待计算的数据 + * @return 计算结果 + */ + private 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) { + LogUtil.writeErrorLog("SHA1计算失败", e); + return null; + } + } + + /** + * sha256计算. + * + * @param datas + * 待计算的数据 + * @return 计算结果 + */ + private static byte[] sha256(byte[] data) { + MessageDigest md = null; + try { + md = MessageDigest.getInstance(ALGORITHM_SHA256); + md.reset(); + md.update(data); + return md.digest(); + } catch (Exception e) { + LogUtil.writeErrorLog("SHA256计算失败", e); + return null; + } + } + + /** + * SM3计算. + * + * @param datas + * 待计算的数据 + * @return 计算结果 + */ + private static byte[] sm3(byte[] data) { + SM3Digest sm3 = new SM3Digest(); + sm3.update(data, 0, data.length); + byte[] result = new byte[sm3.getDigestSize()]; + sm3.doFinal(result, 0); + return result; + } + + /** + * sha1计算 + * + * @param datas + * 待计算的数据 + * @param encoding + * 字符集编码 + * @return + */ + private static byte[] sha1(String datas, String encoding) { + try { + return sha1(datas.getBytes(encoding)); + } catch (UnsupportedEncodingException e) { + LogUtil.writeErrorLog("SHA1计算失败", e); + return null; + } + } + + /** + * sha256计算 + * + * @param datas + * 待计算的数据 + * @param encoding + * 字符集编码 + * @return + */ + private static byte[] sha256(String datas, String encoding) { + try { + return sha256(datas.getBytes(encoding)); + } catch (UnsupportedEncodingException e) { + LogUtil.writeErrorLog("SHA256计算失败", e); + return null; + } + } + + /** + * sm3计算 + * + * @param datas + * 待计算的数据 + * @param encoding + * 字符集编码 + * @return + */ + private static byte[] sm3(String datas, String encoding) { + try { + return sm3(datas.getBytes(encoding)); + } catch (UnsupportedEncodingException e) { + LogUtil.writeErrorLog("SM3计算失败", e); + return null; + } + } + + /** + * + * @param privateKey + * @param data + * @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 privateKey + * @param data + * @return + * @throws Exception + */ + public static byte[] signBySoft256(PrivateKey privateKey, byte[] data) + throws Exception { + byte[] result = null; + Signature st = Signature.getInstance(BC_PROV_ALGORITHM_SHA256RSA, "BC"); + st.initSign(privateKey); + st.update(data); + result = st.sign(); + return result; + } + + 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); + } + + public static boolean validateSignBySoft256(PublicKey publicKey, + byte[] signData, byte[] srcData) throws Exception { + Signature st = Signature.getInstance(BC_PROV_ALGORITHM_SHA256RSA, "BC"); + st.initVerify(publicKey); + st.update(srcData); + return st.verify(signData); + } + + /** + * 对数据通过公钥进行加密,并进行base64计算 + * + * @param dataString + * 待处理数据 + * @param encoding + * 字符编码 + * @param key + * 公钥 + * @return + */ + public static String encryptData(String dataString, String encoding, + PublicKey key) { + /** 使用公钥对密码加密 **/ + byte[] data = null; + try { + data = encryptData(key, dataString.getBytes(encoding)); + return new String(SecureUtil.base64Encode(data), encoding); + } catch (Exception e) { + LogUtil.writeErrorLog(e.getMessage(), e); + return ""; + } + } + + /** + * 对数据通过公钥进行加密,并进行base64计算 + * + * @param dataString + * 待处理数据 + * @param encoding + * 字符编码 + * @param key + * 公钥 + * @return + */ + public static String encryptPin(String accNo, String pin, String encoding, + PublicKey key) { + /** 使用公钥对密码加密 **/ + byte[] data = null; + try { + data = pin2PinBlockWithCardNO(pin, accNo); + data = encryptData(key, data); + return new String(SecureUtil.base64Encode(data), encoding); + } catch (Exception e) { + LogUtil.writeErrorLog(e.getMessage(), e); + return ""; + } + } + + /** + * 通过私钥解密 + * + * @param dataString + * base64过的数据 + * @param encoding + * 编码 + * @param key + * 私钥 + * @return 解密后的数据 + */ + public static String decryptData(String dataString, String encoding, + PrivateKey key) { + byte[] data = null; + try { + data = SecureUtil.base64Decode(dataString.getBytes(encoding)); + data = decryptData(key, data); + return new String(data, encoding); + } catch (Exception e) { + LogUtil.writeErrorLog(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); + } + + /** + * 加密除pin之外的其他信息 + * + * @param publicKey + * @param plainData + * @return + * @throws Exception + */ + private static byte[] encryptData(PublicKey publicKey, byte[] plainData) + throws Exception { + try { + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding","BC"); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + return cipher.doFinal(plainData); + } catch (Exception e) { + throw new Exception(e.getMessage()); + } + } + + /** + * @param privateKey + * @param cryptPin + * @return + * @throws Exception + */ + private static byte[] decryptData(PrivateKey privateKey, byte[] data) + throws Exception { + try { + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding","BC"); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + return cipher.doFinal(data); + } catch (Exception e) { + LogUtil.writeErrorLog("解密失败", 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 + */ + private 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; + } + + /** + * luhn算法 + * + * @param number + * @return + */ + public static int genLuhn(String number) { + number = number + "0"; + int s1 = 0, s2 = 0; + String reverse = new StringBuffer(number).reverse().toString(); + for (int i = 0; i < reverse.length(); i++) { + int digit = Character.digit(reverse.charAt(i), 10); + if (i % 2 == 0) {// this is for odd digits, they are 1-indexed in // + // the algorithm + s1 += digit; + } else {// add 2 * digit for 0-4, add 2 * digit - 9 for 5-9 + s2 += 2 * digit; + if (digit >= 5) { + s2 -= 9; + } + } + } + int check = 10 - ((s1 + s2) % 10); + if (check == 10) + check = 0; + return check; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 76c0094..4c53332 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -101,3 +101,78 @@ qiniu.bucket=business-leyihang # 外链默认域名 qiniu.domain=http://r4el6ttsm.hb-bkt.clouddn.com + +##银联支付相关配置 +##############SDK配置文件(证书方式签名)################ +# 说明: +# 1. 使用时请将此文件复制到src文件夹下替换原来的acp_sdk.properties。 +# 2. 具体配置项请根据注释修改。 +# +################################################ + +##########################入网测试环境交易发送地址(线上测试需要使用生产环境交易请求地址)############################# + +##交易请求地址 +acpsdk.frontTransUrl=https://gateway.test.95516.com/gateway/api/frontTransReq.do +acpsdk.backTransUrl=https://gateway.test.95516.com/gateway/api/backTransReq.do +acpsdk.singleQueryUrl=https://gateway.test.95516.com/gateway/api/queryTrans.do +acpsdk.batchTransUrl=https://gateway.test.95516.com/gateway/api/batchTrans.do +acpsdk.fileTransUrl=https://filedownload.test.95516.com/ +acpsdk.appTransUrl=https://gateway.test.95516.com/gateway/api/appTransReq.do +acpsdk.cardTransUrl=https://gateway.test.95516.com/gateway/api/cardTransReq.do +acpsdk.orderTransUrl=https://gateway.test.95516.com/gateway/api/order.do + +#以下缴费产品使用,其余产品用不到 +acpsdk.jfFrontTransUrl=https://gateway.test.95516.com/jiaofei/api/frontTransReq.do +acpsdk.jfBackTransUrl=https://gateway.test.95516.com/jiaofei/api/backTransReq.do +acpsdk.jfSingleQueryUrl=https://gateway.test.95516.com/jiaofei/api/queryTrans.do +acpsdk.jfCardTransUrl=https://gateway.test.95516.com/jiaofei/api/cardTransReq.do +acpsdk.jfAppTransUrl=https://gateway.test.95516.com/jiaofei/api/appTransReq.do + +######################################################################## + +# 报文版本号,固定5.1.0,请勿改动 +acpsdk.version=5.1.0 + +# 签名方式,证书方式固定01,请勿改动 +acpsdk.signMethod=01 + +# 是否验证验签证书的CN,测试环境请设置false,生产环境请设置true。非false的值默认都当true处理。 +acpsdk.ifValidateCNName=false + +# 是否验证https证书,测试环境请设置false,生产环境建议优先尝试true,不行再false。非true的值默认都当false处理。 +acpsdk.ifValidateRemoteCert=false + +#后台通知地址,填写接收银联后台通知的地址,必须外网能访问 +acpsdk.backUrl=http://222.222.222.222:8080/ACPSample_QRC/backRcvResponse + +#前台通知地址,填写处理银联前台通知的地址,必须外网能访问(二维码产品用不到) +acpsdk.frontUrl=http://localhost:8080/ACPSample_QRC/frontRcvResponse + +#########################入网测试环境签名证书配置 ################################ +# 多证书的情况证书路径为代码指定,可不对此块做配置。 +# 签名证书路径,必须使用绝对路径,如果不想使用绝对路径,可以自行实现相对路径获取证书的方法;测试证书所有商户共用开发包中的测试签名证书,生产环境请从cfca下载得到。 +# windows样例: +acpsdk.signCert.path=D:/certs/acp_test_sign.pfx +# linux样例(注意:在linux下读取证书需要保证证书有被应用读的权限)(后续其他路径配置也同此条说明) +#acpsdk.signCert.path=/SERVICE01/usr/ac_frnas/conf/ACPtest/acp_test_sign.pfx + +# 签名证书密码,测试环境固定000000,生产环境请修改为从cfca下载的正式证书的密码,正式环境证书密码位数需小于等于6位,否则上传到商户服务网站会失败 +acpsdk.signCert.pwd=000000 +# 签名证书类型,固定不需要修改 +acpsdk.signCert.type=PKCS12 + +##########################加密证书配置################################ +# 敏感信息加密证书路径(商户号开通了商户对敏感信息加密的权限,需要对 卡号accNo,pin和phoneNo,cvn2,expired加密(如果这些上送的话),对敏感信息加密使用) +acpsdk.encryptCert.path=d:/certs/acp_test_enc.cer + +##########################验签证书配置################################ +# 验签中级证书路径(银联提供) +acpsdk.middleCert.path=D:/certs/acp_test_middle.cer +# 验签根证书路径(银联提供) +acpsdk.rootCert.path=D:/certs/acp_test_root.cer + +acpsdk.validateCert.dir=D:/certs/ + + + diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 2216be2..9f00d48 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -10,10 +10,10 @@ - - + + - +