Commit 4f9a20eb authored by huangcb's avatar huangcb

增加货主支付功能

parent 802de6d9
...@@ -181,8 +181,7 @@ ...@@ -181,8 +181,7 @@
<directory>src/main/resources</directory> <directory>src/main/resources</directory>
<excludes> <excludes>
<exclude>*.yml</exclude> <exclude>*.yml</exclude>
<exclude>application*.properties</exclude> <exclude>*.properties</exclude>
<exclude>bootstrap.properties</exclude>
<exclude>logback-spring.xml</exclude> <exclude>logback-spring.xml</exclude>
</excludes> </excludes>
<filtering>true</filtering> <filtering>true</filtering>
......
...@@ -23,11 +23,28 @@ public class ApplicationLoadRunner implements ApplicationRunner { ...@@ -23,11 +23,28 @@ public class ApplicationLoadRunner implements ApplicationRunner {
@Value("${spring.application.name}") @Value("${spring.application.name}")
private String applicationName; private String applicationName;
@Value("${spring.profiles.active}")
private String profilesActive;
@Value("${unionpay.acp-sdk.properties.path}")
private String acpSdkPropertiesPath;
@Override @Override
public void run(ApplicationArguments var) { public void run(ApplicationArguments var) {
log.info("-------------------- [{}]初始化完成 --------------------", applicationName); log.info("-------------------- [{}]初始化完成 --------------------", applicationName);
// 从classpath加载acp_sdk.properties文件 // 初始化银联配置文件
initUnionpayProperties();
}
/**
* 初始化银联配置文件
**/
private void initUnionpayProperties() {
if ("dev".equals(profilesActive)) {
SDKConfig.getConfig().loadPropertiesFromSrc(); SDKConfig.getConfig().loadPropertiesFromSrc();
} else {
SDKConfig.getConfig().loadPropertiesFromPath(acpSdkPropertiesPath);
}
} }
} }
...@@ -322,4 +322,7 @@ public class ErrorMessageComponent { ...@@ -322,4 +322,7 @@ public class ErrorMessageComponent {
@Value("${error-message.contract.online.get-by-number.1001}") @Value("${error-message.contract.online.get-by-number.1001}")
private String contractOnlineGetByNumber1001; private String contractOnlineGetByNumber1001;
@Value("${error-message.pay.customer.order.create.1001}")
private String payCustomerOrderCreate1001;
} }
package com.esv.freight.customer.common.constants;
/**
* @description: 银联支付:acpsdk常量类
* @project: freight-customer-service
* @name: com.esv.freight.customer.common.constants.SDKConstants
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/05/30 14:12
* @version:1.0
*/
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";
}
...@@ -10,11 +10,9 @@ import lombok.extern.slf4j.Slf4j; ...@@ -10,11 +10,9 @@ import lombok.extern.slf4j.Slf4j;
import javax.servlet.*; import javax.servlet.*;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.*; import java.io.IOException;
import java.nio.charset.Charset; import java.io.UnsupportedEncodingException;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/** /**
* @description: Rest请求日志Filter * @description: Rest请求日志Filter
...@@ -55,55 +53,6 @@ public class RestLogFilter implements Filter { ...@@ -55,55 +53,6 @@ public class RestLogFilter implements Filter {
this.logRes(requestWrapper, responseWrapper); this.logRes(requestWrapper, responseWrapper);
} }
/**
* 获取请求返回体
**/
private String getPostBodyStr(HttpServletRequest request) {
String bodyStr = null;
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = request.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName(CommonConstants.DEFAULT_CHARACTER_ENCODING)));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
if (0 == sb.length()) {
Map<String, String> bodyMap = new HashMap<>();
Map<String, String[]> parameterMap = request.getParameterMap();
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
for (String value : entry.getValue()) {
bodyMap.put(entry.getKey(), value);
}
}
bodyStr = bodyMap.toString();
} else {
bodyStr = sb.toString();
}
} catch (IOException e) {
log.error("解析post参数时发生错误:{}", e.getMessage(), e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
return bodyStr;
}
/** /**
* 日志输出请求体 * 日志输出请求体
**/ **/
...@@ -122,7 +71,7 @@ public class RestLogFilter implements Filter { ...@@ -122,7 +71,7 @@ public class RestLogFilter implements Filter {
} }
reqBody = reqBody.replaceFirst("&", ""); reqBody = reqBody.replaceFirst("&", "");
} else if (CommonConstants.HTTP_REQUEST_METHOD_POST.equalsIgnoreCase(method)) { } else if (CommonConstants.HTTP_REQUEST_METHOD_POST.equalsIgnoreCase(method)) {
reqBody = this.getPostBodyStr(requestWrapper); reqBody = ReqUtils.getPostBody(requestWrapper);
} }
// 请求体日志截取 // 请求体日志截取
......
package com.esv.freight.customer.common.pojo;
import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
/**
* @description: 银联APP终端创建支付订单请求参数
* @project: freight-customer-service
* @name: com.esv.freight.customer.common.pojo.AppUnionPayOrder
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/05/30 13:48
* @version:1.0
*/
@Data
public class AppUnionPayOrderReq {
/**
* 商户代码(网络货运平台在支付平台的商户号)
*/
private String merId;
/**
* 账单号
*/
private String billId;
/**
* 账单描述
*/
private String billDesc;
/**
* 交易金额(单位为分)
*/
private Long txnAmt;
/**
* 请求支付平台订单发送时间
*/
private String txnTime;
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}
}
package com.esv.freight.customer.common.pojo;
import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
/**
* @description: 银联APP终端创建支付订单返回参数
* @project: freight-customer-service
* @name: com.esv.freight.customer.common.pojo.AppUnionPayOrder
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/05/30 13:48
* @version:1.0
*/
@Data
public class AppUnionPayOrderRes {
/**
* 银联受理订单号
*/
private String tn;
/**
* 请求参数
*/
private String reqMessage;
/**
* 返回参数
*/
private String rspMessage;
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}
}
package com.esv.freight.customer.common.unionpay;
import com.esv.freight.customer.common.constants.SDKConstants;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.util.regex.Pattern.compile;
/**
* @description: acpsdk接口服务类,接入商户集成请可以直接参考使用本类中的方法
* @project: freight-customer-service
* @name: com.esv.freight.customer.common.unionpay.AcpService
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/05/30 14:41
* @version:1.0
*/
@Slf4j
public class AcpService {
/**
* 请求报文签名(使用配置文件中配置的私钥证书或者对称密钥签名)<br>
* 功能:对请求报文进行签名,并计算赋值certid,signature字段并返回<br>
*
* @param reqData 请求报文map<br>
* @param encoding 上送请求报文域encoding字段的值<br>
* @return 签名后的map对象<br>
*/
public static Map<String, String> sign(Map<String, String> reqData, String encoding) {
reqData = SDKUtil.filterBlank(reqData);
SDKUtil.sign(reqData, encoding);
return reqData;
}
/**
* 同signByCertInfo<br>
*
* @param reqData
* @param certPath
* @param certPwd
* @param encoding
* @return
* @deprecated
*/
public static Map<String, String> sign(Map<String, String> reqData, String certPath, String certPwd, String encoding) {
reqData = SDKUtil.filterBlank(reqData);
SDKUtil.signByCertInfo(reqData, certPath, certPwd, encoding);
return reqData;
}
/**
* 多证书签名(通过传入私钥证书路径和密码签名)<br>
* 功能:如果有多个商户号接入银联,每个商户号对应不同的证书可以使用此方法:传入私钥证书和密码(并且在acp_sdk.properties中 配置 acpsdk.singleMode=false)<br>
*
* @param reqData 请求报文map<br>
* @param certPath 签名私钥文件(带路径)<br>
* @param certPwd 签名私钥密码<br>
* @param encoding 上送请求报文域encoding字段的值<br>
* @return 签名后的map对象<br>
*/
public static Map<String, String> signByCertInfo(Map<String, String> reqData, String certPath, String certPwd, String encoding) {
reqData = SDKUtil.filterBlank(reqData);
SDKUtil.signByCertInfo(reqData, certPath, certPwd, encoding);
return reqData;
}
/**
* 多密钥签名(通过传入密钥签名)<br>
* 功能:如果有多个商户号接入银联,每个商户号对应不同的证书可以使用此方法:传入私钥证书和密码(并且在acp_sdk.properties中 配置 acpsdk.singleMode=false)<br>
*
* @param reqData 请求报文map<br>
* @param secureKey 签名对称密钥<br>
* @param encoding 上送请求报文域encoding字段的值<br>
* @return 签名后的map对象<br>
*/
public static Map<String, String> signBySecureKey(Map<String, String> reqData, String secureKey, String encoding) {
reqData = SDKUtil.filterBlank(reqData);
SDKUtil.signBySecureKey(reqData, secureKey, encoding);
return reqData;
}
/**
* 验证签名(SHA-1摘要算法)<br>
*
* @param rspData 返回报文数据<br>
* @param encoding 上送请求报文域encoding字段的值<br>
* @return true 通过 false 未通过<br>
*/
public static boolean validate(Map<String, String> rspData, String encoding) {
return SDKUtil.validate(rspData, encoding);
}
/**
* 多密钥验签(通过传入密钥签名)<br>
*
* @param rspData 返回报文数据<br>
* @param encoding 上送请求报文域encoding字段的值<br>
* @return true 通过 false 未通过<br>
*/
public static boolean validateBySecureKey(Map<String, String> rspData, String secureKey, String encoding) {
return SDKUtil.validateBySecureKey(rspData, secureKey, encoding);
}
/**
* @param jsonData json格式数据,例如:{"sign" : "J6rPLClQ64szrdXCOtV1ccOMzUmpiOKllp9cseBuRqJ71pBKPPkZ1FallzW18gyP7CvKh1RxfNNJ66AyXNMFJi1OSOsteAAFjF5GZp0Xsfm3LeHaN3j/N7p86k3B1GrSPvSnSw1LqnYuIBmebBkC1OD0Qi7qaYUJosyA1E8Ld8oGRZT5RR2gLGBoiAVraDiz9sci5zwQcLtmfpT5KFk/eTy4+W9SsC0M/2sVj43R9ePENlEvF8UpmZBqakyg5FO8+JMBz3kZ4fwnutI5pWPdYIWdVrloBpOa+N4pzhVRKD4eWJ0CoiD+joMS7+C0aPIEymYFLBNYQCjM0KV7N726LA==", "data" : "pay_result=success&tn=201602141008032671528&cert_id=68759585097"}
* @return 是否成功
* @deprecated 5.1.0开发包已删除此方法,请直接参考5.1.0开发包中的VerifyAppData.java验签。
* 对控件支付成功返回的结果信息中data域进行验签(控件端获取的应答信息)<br>
*/
public static boolean validateAppResponse(String jsonData, String encoding) {
log.info("控件应答信息验签处理开始:[" + jsonData + "]");
if (SDKUtil.isEmpty(encoding)) {
encoding = "UTF-8";
}
Pattern p = compile("\\s*\"sign\"\\s*:\\s*\"([^\"]*)\"\\s*");
Matcher m = p.matcher(jsonData);
if (!m.find()) {
return false;
}
String sign = m.group(1);
p = compile("\\s*\"data\"\\s*:\\s*\"([^\"]*)\"\\s*");
m = p.matcher(jsonData);
if (!m.find()) {
return false;
}
String data = m.group(1);
p = 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) {
log.error(e.getMessage(), e);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return false;
}
/**
* 功能:后台交易提交请求报文并接收同步应答报文<br>
*
* @param reqData 请求报文<br>
* @param reqUrl 请求地址<br>
* @param encoding<br>
* @return 应答http 200返回true ,其他false<br>
*/
public static Map<String, String> post(Map<String, String> reqData, String reqUrl, String encoding) {
Map<String, String> rspData = new HashMap<>(32);
log.info("请求银联地址:" + reqUrl);
//发送后台请求数据
HttpClient hc = new HttpClient(reqUrl, 5000, 5000);//连接超时时间,读超时时间(可自行判断,修改)
try {
int status = hc.send(reqData, encoding);
if (200 == status) {
String resultString = hc.getResult();
if (null != resultString && !"".equals(resultString)) {
// 将返回结果转换为map
Map<String, String> tmpRspData = SDKUtil.convertResultStringToMap(resultString);
rspData.putAll(tmpRspData);
}
} else {
log.info("返回http状态码[" + status + "],请检查请求报文或者请求地址是否正确");
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return rspData;
}
/**
* 功能:http Get方法 便民缴费产品中使用<br>
*
* @param reqUrl 请求地址<br>
* @param encoding<br>
* @return
*/
public static String get(String reqUrl, String encoding) {
log.info("请求银联地址:" + reqUrl);
//发送后台请求数据
HttpClient hc = new HttpClient(reqUrl, 5000, 5000);
try {
int status = hc.sendGet(encoding);
if (200 == status) {
String resultString = hc.getResult();
if (null != resultString && !"".equals(resultString)) {
return resultString;
}
} else {
log.info("返回http状态码[" + status + "],请检查请求报文或者请求地址是否正确");
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return null;
}
/**
* 功能:前台交易构造HTTP POST自动提交表单<br>
*
* @param reqUrl 表单提交地址<br>
* @param hiddens 以MAP形式存储的表单键值<br>
* @param encoding 上送请求报文域encoding字段的值<br>
* @return 构造好的HTTP POST交易表单<br>
*/
public static String createAutoFormHtml(String reqUrl, Map<String, String> hiddens, String encoding) {
StringBuffer sf = new StringBuffer();
sf.append("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=" + encoding + "\"/></head><body>");
sf.append("<form id = \"pay_form\" action=\"" + reqUrl
+ "\" method=\"post\">");
if (null != hiddens && 0 != hiddens.size()) {
Set<Entry<String, String>> set = hiddens.entrySet();
Iterator<Entry<String, String>> it = set.iterator();
while (it.hasNext()) {
Entry<String, String> ey = it.next();
String key = ey.getKey();
String value = ey.getValue();
sf.append("<input type=\"hidden\" name=\"" + key + "\" id=\""
+ key + "\" value=\"" + value + "\"/>");
}
}
sf.append("</form>");
sf.append("</body>");
sf.append("<script type=\"text/javascript\">");
sf.append("document.all.pay_form.submit();");
sf.append("</script>");
sf.append("</html>");
return sf.toString();
}
/**
* 功能:将批量文件内容使用DEFLATE压缩算法压缩,Base64编码生成字符串并返回<br>
* 适用到的交易:批量代付,批量代收,批量退货<br>
*
* @param filePath 批量文件-全路径文件名<br>
* @return
*/
public static String enCodeFileContent(String filePath, String encoding) {
String baseFileContent = "";
File file = new File(filePath);
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
InputStream in = null;
try {
in = new FileInputStream(file);
int fl = in.available();
if (null != in) {
byte[] s = new byte[fl];
in.read(s, 0, fl);
// 压缩编码.
baseFileContent = new String(SecureUtil.base64Encode(SDKUtil.deflater(s)), encoding);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
if (null != in) {
try {
in.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
return baseFileContent;
}
/**
* 功能:解析交易返回的fileContent字符串并落地 ( 解base64,解DEFLATE压缩并落地)<br>
* 适用到的交易:对账文件下载,批量交易状态查询<br>
*
* @param resData 返回报文map<br>
* @param fileDirectory 落地的文件目录(绝对路径)
* @param encoding 上送请求报文域encoding字段的值<br>
*/
public static String deCodeFileContent(Map<String, String> resData, String fileDirectory, String encoding) {
// 解析返回文件
String 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) {
log.error(e.getMessage(), e);
} catch (IOException e) {
log.error(e.getMessage(), e);
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return filePath;
}
/**
* 功能:将结果文件内容 转换成明文字符串:解base64,解压缩<br>
* 适用到的交易:批量交易状态查询<br>
*
* @param fileContent 批量交易状态查询返回的文件内容<br>
* @return 内容明文<br>
*/
public static String getFileContent(String fileContent, String encoding) {
String fc = "";
try {
fc = new String(SDKUtil.inflater(SecureUtil.base64Decode(fileContent.getBytes())), encoding);
} catch (UnsupportedEncodingException e) {
log.error(e.getMessage(), e);
} catch (IOException e) {
log.error(e.getMessage(), e);
}
return fc;
}
/**
* 功能:持卡人信息域customerInfo构造<br>
* 说明:不勾选对敏感信息加密权限使用旧的构造customerInfo域方式,不对敏感信息进行加密(对 phoneNo,cvn2, expired不加密),但如果送pin的话则加密<br>
*
* @param customerInfoMap 信息域请求参数 key送域名value送值,必送<br>
* 例如:customerInfoMap.put("certifTp", "01"); //证件类型<br>
* customerInfoMap.put("certifId", "341126197709218366"); //证件号码<br>
* customerInfoMap.put("customerNm", "互联网"); //姓名<br>
* customerInfoMap.put("phoneNo", "13552535506"); //手机号<br>
* customerInfoMap.put("smsCode", "123456"); //短信验证码<br>
* customerInfoMap.put("pin", "111111"); //密码(加密)<br>
* customerInfoMap.put("cvn2", "123"); //卡背面的cvn2三位数字(不加密)<br>
* customerInfoMap.put("expired", "1711"); //有效期 年在前月在后(不加密)<br>
* @param accNo customerInfoMap送了密码那么卡号必送,如果customerInfoMap未送密码pin,此字段可以不送<br>
* @param encoding 上送请求报文域encoding字段的值<br>
* @return base64后的持卡人信息域字段<br>
*/
public static String getCustomerInfo(Map<String, String> customerInfoMap, String accNo, String encoding) {
if (customerInfoMap.isEmpty()) {
return "{}";
}
StringBuffer sf = new StringBuffer("{");
for (Iterator<String> it = customerInfoMap.keySet().iterator(); it.hasNext(); ) {
String key = it.next();
String value = customerInfoMap.get(key);
if (key.equals("pin")) {
if (null == accNo || "".equals(accNo.trim())) {
log.info("送了密码(PIN),必须在getCustomerInfo参数中上传卡号");
throw new RuntimeException("加密PIN没送卡号无法后续处理");
} else {
value = encryptPin(accNo, value, encoding);
}
}
sf.append(key).append(SDKConstants.EQUAL).append(value);
if (it.hasNext()) {
sf.append(SDKConstants.AMPERSAND);
}
}
String customerInfo = sf.append("}").toString();
log.info("组装的customerInfo明文:" + customerInfo);
try {
return new String(SecureUtil.base64Encode(sf.toString().getBytes(
encoding)), encoding);
} catch (UnsupportedEncodingException e) {
log.error(e.getMessage(), e);
} catch (IOException e) {
log.error(e.getMessage(), e);
}
return customerInfo;
}
/**
* 功能:持卡人信息域customerInfo构造,勾选对敏感信息加密权限 适用新加密规范,对pin和phoneNo,cvn2,expired加密 <br>
* 适用到的交易: <br>
*
* @param customerInfoMap 信息域请求参数 key送域名value送值,必送 <br>
* 例如:customerInfoMap.put("certifTp", "01"); //证件类型 <br>
* customerInfoMap.put("certifId", "341126197709218366"); //证件号码 <br>
* customerInfoMap.put("customerNm", "互联网"); //姓名 <br>
* customerInfoMap.put("smsCode", "123456"); //短信验证码 <br>
* customerInfoMap.put("pin", "111111"); //密码(加密) <br>
* customerInfoMap.put("phoneNo", "13552535506"); //手机号(加密) <br>
* customerInfoMap.put("cvn2", "123"); //卡背面的cvn2三位数字(加密) <br>
* customerInfoMap.put("expired", "1711"); //有效期 年在前月在后(加密) <br>
* @param accNo customerInfoMap送了密码那么卡号必送,如果customerInfoMap未送密码PIN,此字段可以不送<br>
* @param encoding 上送请求报文域encoding字段的值
* @return base64后的持卡人信息域字段 <br>
*/
public static String getCustomerInfoWithEncrypt(Map<String, String> customerInfoMap, String accNo, String encoding) {
if (customerInfoMap.isEmpty()) {
return "{}";
}
StringBuffer sf = new StringBuffer("{");
//敏感信息加密域
StringBuffer encryptedInfoSb = new StringBuffer("");
for (Iterator<String> it = customerInfoMap.keySet().iterator(); it.hasNext(); ) {
String key = it.next();
String value = customerInfoMap.get(key);
if (key.equals("phoneNo") || key.equals("cvn2") || key.equals("expired")) {
encryptedInfoSb.append(key).append(SDKConstants.EQUAL).append(value).append(SDKConstants.AMPERSAND);
} else {
if (key.equals("pin")) {
if (null == accNo || "".equals(accNo.trim())) {
log.info("送了密码(PIN),必须在getCustomerInfoWithEncrypt参数中上传卡号");
throw new RuntimeException("加密PIN没送卡号无法后续处理");
} else {
value = encryptPin(accNo, value, encoding);
}
}
sf.append(key).append(SDKConstants.EQUAL).append(value).append(SDKConstants.AMPERSAND);
}
}
if (!encryptedInfoSb.toString().equals("")) {
encryptedInfoSb.setLength(encryptedInfoSb.length() - 1);//去掉最后一个&符号
log.info("组装的customerInfo encryptedInfo明文:" + encryptedInfoSb.toString());
sf.append("encryptedInfo").append(SDKConstants.EQUAL).append(encryptData(encryptedInfoSb.toString(), encoding));
} else {
sf.setLength(sf.length() - 1);
}
String customerInfo = sf.append("}").toString();
log.info("组装的customerInfo明文:" + customerInfo);
try {
return new String(SecureUtil.base64Encode(sf.toString().getBytes(encoding)), encoding);
} catch (UnsupportedEncodingException e) {
log.error(e.getMessage(), e);
} catch (IOException e) {
log.error(e.getMessage(), e);
}
return customerInfo;
}
/**
* 解析返回报文(后台通知)中的customerInfo域:<br>
* 解base64,如果带敏感信息加密 encryptedInfo 则将其解密并将 encryptedInfo中的域放到customerInfoMap返回<br>
*
* @param customerInfo<br>
* @param encoding<br>
* @return
*/
public static Map<String, String> parseCustomerInfo(String customerInfo, String encoding) {
Map<String, String> customerInfoMap = null;
try {
byte[] b = SecureUtil.base64Decode(customerInfo.getBytes(encoding));
String customerInfoNoBase64 = new String(b, encoding);
log.info("解base64后===>" + customerInfoNoBase64);
//去掉前后的{}
customerInfoNoBase64 = customerInfoNoBase64.substring(1, customerInfoNoBase64.length() - 1);
customerInfoMap = SDKUtil.parseQString(customerInfoNoBase64);
if (customerInfoMap.containsKey("encryptedInfo")) {
String encInfoStr = customerInfoMap.get("encryptedInfo");
customerInfoMap.remove("encryptedInfo");
String encryptedInfoStr = decryptData(encInfoStr, encoding);
Map<String, String> encryptedInfoMap = SDKUtil.parseQString(encryptedInfoStr);
customerInfoMap.putAll(encryptedInfoMap);
}
} catch (UnsupportedEncodingException e) {
log.error(e.getMessage(), e);
} catch (IOException e) {
log.error(e.getMessage(), e);
}
return customerInfoMap;
}
/**
* 解析返回报文(后台通知)中的customerInfo域:<br>
* 解base64,如果带敏感信息加密 encryptedInfo 则将其解密并将 encryptedInfo中的域放到customerInfoMap返回<br>
*
* @param customerInfo<br>
* @param encoding<br>
* @return
*/
public static Map<String, String> parseCustomerInfo(String customerInfo, String certPath, String certPwd, String encoding) {
Map<String, String> customerInfoMap = null;
try {
byte[] b = SecureUtil.base64Decode(customerInfo.getBytes(encoding));
String customerInfoNoBase64 = new String(b, encoding);
log.info("解base64后===>" + customerInfoNoBase64);
//去掉前后的{}
customerInfoNoBase64 = customerInfoNoBase64.substring(1, customerInfoNoBase64.length() - 1);
customerInfoMap = SDKUtil.parseQString(customerInfoNoBase64);
if (customerInfoMap.containsKey("encryptedInfo")) {
String encInfoStr = customerInfoMap.get("encryptedInfo");
customerInfoMap.remove("encryptedInfo");
String encryptedInfoStr = decryptData(encInfoStr, certPath, certPwd, encoding);
Map<String, String> encryptedInfoMap = SDKUtil.parseQString(encryptedInfoStr);
customerInfoMap.putAll(encryptedInfoMap);
}
} catch (UnsupportedEncodingException e) {
log.error(e.getMessage(), e);
} catch (IOException e) {
log.error(e.getMessage(), e);
}
return customerInfoMap;
}
/**
* 密码加密并做base64<br>
*
* @param accNo 卡号<br>
* @param pin 密码<br>
* @param encoding<br>
* @return 加密的内容<br>
*/
public static String encryptPin(String accNo, String pin, String encoding) {
return SecureUtil.encryptPin(accNo, pin, encoding, CertUtil.getEncryptCertPublicKey());
}
/**
* 敏感信息加密并做base64(卡号,手机号,cvn2,有效期)<br>
*
* @param data 送 phoneNo,cvn2,有效期<br>
* @param encoding<br>
* @return 加密的密文<br>
*/
public static String encryptData(String data, String encoding) {
return SecureUtil.encryptData(data, encoding, CertUtil.getEncryptCertPublicKey());
}
/**
* 敏感信息解密,使用配置文件acp_sdk.properties解密<br>
*
* @param base64EncryptedInfo 加密信息<br>
* @param encoding<br>
* @return 解密后的明文<br>
*/
public static String decryptData(String base64EncryptedInfo, String encoding) {
return SecureUtil.decryptData(base64EncryptedInfo, encoding, CertUtil.getSignCertPrivateKey());
}
/**
* 敏感信息解密,通过传入的私钥解密<br>
*
* @param base64EncryptedInfo 加密信息<br>
* @param certPath 私钥文件(带全路径)<br>
* @param certPwd 私钥密码<br>
* @param encoding<br>
* @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接口请勿使用<br>
*
* @param trackData 待加密磁道数据<br>
* @param encoding 编码格式<br>
* @return 加密的密文<br>
* @deprecated
*/
public static String encryptTrack(String trackData, String encoding) {
return SecureUtil.encryptData(trackData, encoding, CertUtil.getEncryptTrackPublicKey());
}
/**
* 获取敏感信息加密证书的物理序列号<br>
*
* @return
*/
public static String getEncryptCertId() {
return CertUtil.getEncryptCertId();
}
/**
* 对字符串做base64<br>
*
* @param rawStr<br>
* @param encoding<br>
* @throws IOException
* @return<br>
*/
public static String base64Encode(String rawStr, String encoding) throws IOException {
byte[] rawByte = rawStr.getBytes(encoding);
return new String(SecureUtil.base64Encode(rawByte), encoding);
}
/**
* 对base64的字符串解base64<br>
*
* @param base64Str<br>
* @param encoding<br>
* @throws IOException
* @return<br>
*/
public static String base64Decode(String base64Str, String encoding) throws IOException {
byte[] rawByte = base64Str.getBytes(encoding);
return new String(SecureUtil.base64Decode(rawByte), encoding);
}
/**
* 有卡交易信息域(cardTransData)构造<br>
* 所有子域需用“{}”包含,子域间以“&”符号链接。格式如下:{子域名1=值&子域名2=值&子域名3=值}<br>
* 说明:本示例仅供参考,开发时请根据接口文档中的报文要素组装<br>
*
* @param cardTransDataMap cardTransData的数据<br>
* @param requestData 必须包含merId、orderId、txnTime、txnAmt,磁道加密时需要使用<br>
* @param encoding 编码<br>
* @return
*/
public static String getCardTransData(Map<String, String> cardTransDataMap, Map<String, String> 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();
}
}
/**
* 获取应答报文中的加密公钥证书,并存储到本地,备份原始证书,并自动替换证书<br>
* 更新成功则返回1,无更新返回0,失败异常返回-1<br>
*
* @param resData 返回报文
* @param encoding
* @return
*/
public static int updateEncryptCert(Map<String, String> resData, String encoding) {
return SDKUtil.getEncryptCert(resData, encoding);
}
/**
* 获取
*
* @param number
* @return
*/
public static int genLuhn(String number) {
return SecureUtil.genLuhn(number);
}
}
package com.esv.freight.customer.common.unionpay;
import lombok.extern.slf4j.Slf4j;
import javax.net.ssl.*;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.cert.X509Certificate;
/**
* @description: 忽略验证ssl证书
* @project: freight-customer-service
* @name: com.esv.freight.customer.common.unionpay.BaseHttpSSLSocketFactory
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/05/30 14:44
* @version:1.0
*/
@Slf4j
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) {
log.error(e.getMessage(), e);
return null;
}
}
public static class MyX509TrustManager implements X509TrustManager {
static MyX509TrustManager manger = new MyX509TrustManager();
public MyX509TrustManager() {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
}
/**
* 解决由于服务器证书问题导致HTTPS无法访问的情况 PS:HTTPS hostname wrong: should be <localhost>
*/
public static class TrustAnyHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String hostname, SSLSession session) {
//直接返回true
return true;
}
}
}
package com.esv.freight.customer.common.unionpay;
import com.esv.freight.customer.common.constants.SDKConstants;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.*;
import java.security.spec.RSAPublicKeySpec;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* @description: 银联支付:acpsdk证书工具类,主要用于对证书的加载和使用
* @project: freight-customer-service
* @name: com.esv.freight.customer.common.unionpay.CertUtil
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/05/30 14:20
* @version:1.0
*/
@Slf4j
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<String, X509Certificate> certMap = new HashMap<String, X509Certificate>();
/**
* 商户私钥存储Map
*/
private final static Map<String, KeyStore> keyStoreMap = new ConcurrentHashMap<String, KeyStore>();
static {
init();
}
/**
* 初始化所有证书.
*/
private static void init() {
try {
addProvider();//向系统添加BC provider
initSignCert();//初始化签名私钥证书
initMiddleCert();//初始化验签证书的中级证书
initRootCert();//初始化验签证书的根证书
initEncryptCert();//初始化加密公钥
initTrackKey();//构建磁道加密公钥
initValidateCertFromDir();//初始化所有的验签证书
} catch (Exception e) {
log.error("init失败。(如果是用对称密钥签名的可无视此异常。)", e);
}
}
/**
* 添加签名,验签,加密算法提供者
*/
private static void addProvider() {
if (Security.getProvider("BC") == null) {
log.info("add BC provider");
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
} else {
Security.removeProvider("BC"); //解决eclipse调试时tomcat自动重新加载时,BC存在不明原因异常的问题。
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
log.info("re-add BC provider");
}
printSysInfo();
}
/**
* 用配置文件acp_sdk.properties中配置的私钥路径和密码 加载签名证书
*/
private static void initSignCert() {
if (!"01".equals(SDKConfig.getConfig().getSignMethod())) {
log.info("非rsa签名方式,不加载签名证书。");
return;
}
if (SDKConfig.getConfig().getSignCertPath() == null
|| SDKConfig.getConfig().getSignCertPwd() == null
|| SDKConfig.getConfig().getSignCertType() == null) {
log.error("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());
log.info("InitSignCert Successful. CertId=[" + getSignCertId() + "]");
} catch (IOException e) {
log.error("InitSignCert Error", e);
}
}
/**
* 用配置文件acp_sdk.properties配置路径 加载敏感信息加密证书
*/
private static void initMiddleCert() {
log.info("加载中级证书==>" + SDKConfig.getConfig().getMiddleCertPath());
if (!SDKUtil.isEmpty(SDKConfig.getConfig().getMiddleCertPath())) {
middleCert = initCert(SDKConfig.getConfig().getMiddleCertPath());
log.info("Load MiddleCert Successful");
} else {
log.info("WARN: acpsdk.middle.path is empty");
}
}
/**
* 用配置文件acp_sdk.properties配置路径 加载敏感信息加密证书
*/
private static void initRootCert() {
log.info("加载根证书==>" + SDKConfig.getConfig().getRootCertPath());
if (!SDKUtil.isEmpty(SDKConfig.getConfig().getRootCertPath())) {
rootCert = initCert(SDKConfig.getConfig().getRootCertPath());
log.info("Load RootCert Successful");
} else {
log.info("WARN: acpsdk.rootCert.path is empty");
}
}
/**
* 用配置文件acp_sdk.properties配置路径 加载银联公钥上级证书(中级证书)
*/
private static void initEncryptCert() {
log.info("加载敏感信息加密证书==>" + SDKConfig.getConfig().getEncryptCertPath());
if (!SDKUtil.isEmpty(SDKConfig.getConfig().getEncryptCertPath())) {
encryptCert = initCert(SDKConfig.getConfig().getEncryptCertPath());
log.info("Load EncryptCert Successful");
} else {
log.info("WARN: acpsdk.encryptCert.path is empty");
}
}
/**
* 用配置文件acp_sdk.properties配置路径 加载磁道公钥
*/
private static void initTrackKey() {
if (!SDKUtil.isEmpty(SDKConfig.getConfig().getEncryptTrackKeyModulus())
&& !SDKUtil.isEmpty(SDKConfig.getConfig().getEncryptTrackKeyExponent())) {
encryptTrackKey = getPublicKey(SDKConfig.getConfig().getEncryptTrackKeyModulus(),
SDKConfig.getConfig().getEncryptTrackKeyExponent());
log.info("LoadEncryptTrackKey Successful");
} else {
log.info("WARN: acpsdk.encryptTrackKey.modulus or acpsdk.encryptTrackKey.exponent is empty");
}
}
/**
* 用配置文件acp_sdk.properties配置路径 加载验证签名证书
*/
private static void initValidateCertFromDir() {
if (!"01".equals(SDKConfig.getConfig().getSignMethod())) {
log.info("非rsa签名方式,不加载验签证书。");
return;
}
certMap.clear();
String dir = SDKConfig.getConfig().getValidateCertDir();
log.info("加载验证签名证书目录==>" + dir + " 注:如果请求报文中version=5.1.0那么此验签证书目录使用不到,可以不需要设置(version=5.0.0必须设置)。");
if (SDKUtil.isEmpty(dir)) {
log.warn("WARN: acpsdk.validateCert.dir is empty");
return;
}
CertificateFactory cf;
FileInputStream in = null;
try {
cf = CertificateFactory.getInstance("X.509", "BC");
} catch (NoSuchProviderException e) {
log.error("LoadVerifyCert Error: No BC Provider", e);
return;
} catch (CertificateException e) {
log.error("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) {
log.error("Load verify cert error, " + file.getAbsolutePath() + " has error cert content.");
continue;
}
certMap.put(validateCert.getSerialNumber().toString(), validateCert);
// 打印证书加载信息,供测试阶段调试
log.info("[" + file.getAbsolutePath() + "][CertId=" + validateCert.getSerialNumber().toString() + "]");
} catch (CertificateException e) {
log.error("LoadVerifyCert Error", e);
} catch (FileNotFoundException e) {
log.error("LoadVerifyCert Error File Not Found", e);
} finally {
if (null != in) {
try {
in.close();
} catch (IOException e) {
log.error(e.toString());
}
}
}
}
log.info("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);
log.info("LoadRsaCert Successful");
} catch (IOException e) {
log.error("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);
// 打印证书加载信息,供测试阶段调试
log.info("[" + path + "][CertId=" + encryptCertTemp.getSerialNumber().toString() + "]");
} catch (CertificateException e) {
log.error("InitCert Error", e);
} catch (FileNotFoundException e) {
log.error("InitCert Error File Not Found", e);
} catch (NoSuchProviderException e) {
log.error("LoadVerifyCert Error No BC Provider", e);
} finally {
if (null != in) {
try {
in.close();
} catch (IOException e) {
log.error(e.toString());
}
}
}
return encryptCertTemp;
}
/**
* 通过keyStore 获取私钥签名证书PrivateKey对象
*
* @return
*/
public static PrivateKey getSignCertPrivateKey() {
try {
Enumeration<String> aliasenum = keyStore.aliases();
String keyAlias = null;
if (aliasenum.hasMoreElements()) {
keyAlias = aliasenum.nextElement();
}
PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, SDKConfig.getConfig().getSignCertPwd().toCharArray());
return privateKey;
} catch (KeyStoreException e) {
log.error("getSignCertPrivateKey Error", e);
return null;
} catch (UnrecoverableKeyException e) {
log.error("getSignCertPrivateKey Error", e);
return null;
} catch (NoSuchAlgorithmException e) {
log.error("getSignCertPrivateKey Error", e);
return null;
}
}
/**
* 通过指定路径的私钥证书 获取PrivateKey对象
*
* @return
*/
public static PrivateKey getSignCertPrivateKeyByStoreMap(String certPath, String certPwd) {
if (!keyStoreMap.containsKey(certPath)) {
loadSignCert(certPath, certPwd);
}
try {
Enumeration<String> 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) {
log.error("getSignCertPrivateKeyByStoreMap Error", e);
return null;
} catch (UnrecoverableKeyException e) {
log.error("getSignCertPrivateKeyByStoreMap Error", e);
return null;
} catch (NoSuchAlgorithmException e) {
log.error("getSignCertPrivateKeyByStoreMap Error", e);
return null;
}
}
/**
* 获取敏感信息加密证书PublicKey
*
* @return
*/
public static PublicKey getEncryptCertPublicKey() {
if (null == encryptCert) {
String path = SDKConfig.getConfig().getEncryptCertPath();
if (!SDKUtil.isEmpty(path)) {
encryptCert = initCert(path);
return encryptCert.getPublicKey();
} else {
log.error("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 {
log.error("缺少certId=[" + certId + "]对应的验签证书.");
return null;
}
}
}
/**
* 获取配置文件acp_sdk.properties中配置的签名私钥证书certId
*
* @return 证书的物理编号
*/
public static String getSignCertId() {
try {
Enumeration<String> aliasenum = keyStore.aliases();
String keyAlias = null;
if (aliasenum.hasMoreElements()) {
keyAlias = aliasenum.nextElement();
}
X509Certificate cert = (X509Certificate) keyStore.getCertificate(keyAlias);
return cert.getSerialNumber().toString();
} catch (Exception e) {
log.error("getSignCertId Error", e);
return null;
}
}
/**
* 获取敏感信息加密证书的certId
*
* @return
*/
public static String getEncryptCertId() {
if (null == encryptCert) {
String path = SDKConfig.getConfig().getEncryptCertPath();
if (!SDKUtil.isEmpty(path)) {
encryptCert = initCert(path);
return encryptCert.getSerialNumber().toString();
} else {
log.error("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 {
log.info("加载签名证书==>" + pfxkeyfile);
FileInputStream fis = null;
try {
KeyStore ks = KeyStore.getInstance(type, "BC");
log.info("Load RSA CertPath=[" + pfxkeyfile + "],Pwd=[" + keypwd + "],type=[" + type + "]");
fis = new FileInputStream(pfxkeyfile);
char[] nPassword;
nPassword = null == keypwd || "".equals(keypwd.trim()) ? null : keypwd.toCharArray();
if (null != ks) {
ks.load(fis, nPassword);
}
return ks;
} catch (Exception e) {
log.error("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<String> aliasenum = null;
try {
aliasenum = keyStore.aliases();
String keyAlias = null;
if (aliasenum.hasMoreElements()) {
keyAlias = aliasenum.nextElement();
}
X509Certificate cert = (X509Certificate) keyStore.getCertificate(keyAlias);
return cert.getSerialNumber().toString();
} catch (KeyStoreException e) {
log.error("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) {
log.error("构造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) {
log.error("gen certificate error", e);
}
return x509Cert;
}
/**
* 从配置文件acp_sdk.properties中获取验签公钥使用的中级证书
*
* @return
*/
public static X509Certificate getMiddleCert() {
if (null == middleCert) {
String path = SDKConfig.getConfig().getMiddleCertPath();
if (!SDKUtil.isEmpty(path)) {
initMiddleCert();
} else {
log.error(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 (!SDKUtil.isEmpty(path)) {
initRootCert();
} else {
log.error(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) {
log.error("cert must Not null");
return false;
}
X509Certificate middleCert = CertUtil.getMiddleCert();
if (null == middleCert) {
log.error("middleCert must Not null");
return false;
}
X509Certificate rootCert = CertUtil.getRootCert();
if (null == rootCert) {
log.error("rootCert or cert must Not null");
return false;
}
try {
X509CertSelector selector = new X509CertSelector();
selector.setCertificate(cert);
Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>();
trustAnchors.add(new TrustAnchor(rootCert, null));
PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(
trustAnchors, selector);
Set<X509Certificate> intermediateCerts = new HashSet<X509Certificate>();
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);
log.info("verify certificate chain succeed.");
return true;
} catch (java.security.cert.CertPathBuilderException e) {
log.error("verify certificate chain fail.", e);
} catch (Exception e) {
log.error("verify certificate chain exception: ", e);
}
return false;
}
/**
* 检查证书链
*
* @param cert 待验证的证书
* @return
*/
public static boolean verifyCertificate(X509Certificate cert) {
if (null == cert) {
log.error("cert must Not null");
return false;
}
try {
cert.checkValidity();//验证有效期
// cert.verify(middleCert.getPublicKey());
if (!verifyCertificateChain(cert)) {
return false;
}
} catch (Exception e) {
log.error("verifyCertificate fail", e);
return false;
}
if (SDKConfig.getConfig().isIfValidateCNName()) {
// 验证公钥是否属于银联
if (!SDKConstants.UNIONPAY_CNNAME.equals(CertUtil.getIdentitiesFromCertficate(cert))) {
log.error("cer owner is not CUP:" + CertUtil.getIdentitiesFromCertficate(cert));
return false;
}
} else {
// 验证公钥是否属于银联
if (!SDKConstants.UNIONPAY_CNNAME.equals(CertUtil.getIdentitiesFromCertficate(cert))
&& !"00040000:SIGN".equals(CertUtil.getIdentitiesFromCertficate(cert))) {
log.error("cer owner is not CUP:" + CertUtil.getIdentitiesFromCertficate(cert));
return false;
}
}
return true;
}
/**
* 打印系统环境信息
*/
private static void printSysInfo() {
if (log.isDebugEnabled()) {
log.debug("================= SYS INFO begin====================");
log.debug("os_name:" + System.getProperty("os.name"));
log.debug("os_arch:" + System.getProperty("os.arch"));
log.debug("os_version:" + System.getProperty("os.version"));
log.debug("java_vm_specification_version:" + System.getProperty("java.vm.specification.version"));
log.debug("java_vm_specification_vendor:" + System.getProperty("java.vm.specification.vendor"));
log.debug("java_vm_specification_name:" + System.getProperty("java.vm.specification.name"));
log.debug("java_vm_version:" + System.getProperty("java.vm.version"));
log.debug("java_vm_name:" + System.getProperty("java.vm.name"));
log.debug("java.version:" + System.getProperty("java.version"));
log.debug("java.vm.vendor=[" + System.getProperty("java.vm.vendor") + "]");
log.debug("java.version=[" + System.getProperty("java.version") + "]");
printProviders();
log.debug("================= SYS INFO end=====================");
}
}
/**
* 打jre中印算法提供者列表
*/
private static void printProviders() {
log.debug("Providers List:");
Provider[] providers = Security.getProviders();
for (int i = 0; i < providers.length; i++) {
log.debug(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;
}
}
@Override
public boolean accept(File dir, String name) {
return isCer(name);
}
}
}
package com.esv.freight.customer.common.unionpay;
import com.esv.freight.customer.common.constants.SDKConstants;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* @description: demo中用到的方法
* @project: freight-customer-service
* @name: com.esv.freight.customer.common.unionpay.DemoBase
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/05/30 14:38
* @version:1.0
*/
@Slf4j
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("yyyyMMddHHmmssSSS").format(new Date());
}
/**
* 组装请求,返回报文字符串用于显示
*
* @param data
* @return
*/
public static String genHtmlResult(Map<String, String> data) {
TreeMap<String, String> tree = new TreeMap<String, String>();
Iterator<Entry<String, String>> it = data.entrySet().iterator();
while (it.hasNext()) {
Entry<String, String> en = it.next();
tree.put(en.getKey(), en.getValue());
}
it = tree.entrySet().iterator();
StringBuffer sf = new StringBuffer();
while (it.hasNext()) {
Entry<String, String> en = it.next();
String key = en.getKey();
String value = en.getValue();
if ("respCode".equals(key)) {
sf.append("<b>" + key + SDKConstants.EQUAL + value + "</br></b>");
} else {
sf.append(key + SDKConstants.EQUAL + value + "</br>");
}
}
return sf.toString();
}
/**
* 功能:解析全渠道商户对账文件中的ZM文件并以List<Map>方式返回
* 适用交易:对账文件下载后对文件的查看
*
* @param filePath ZM文件全路径
* @return 包含每一笔交易中 序列号 和 值 的map序列
*/
public static List<Map> parseZMFile(String filePath) {
int lengthArray[] = {3, 11, 11, 6, 10, 19, 12, 4, 2, 21, 2, 32, 2, 6, 10, 13, 13, 4, 15, 2, 2, 6, 2, 4, 32, 1, 21, 15, 1, 15, 32, 13, 13, 8, 32, 13, 13, 12, 2, 1, 32, 98};
return parseFile(filePath, lengthArray);
}
/**
* 功能:解析全渠道商户对账文件中的ZME文件并以List<Map>方式返回
* 适用交易:对账文件下载后对文件的查看
*
* @param filePath ZME文件全路径
* @return 包含每一笔交易中 序列号 和 值 的map序列
*/
public static List<Map> 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<Map> parseFile(String filePath, int lengthArray[]) {
List<Map> ZmDataList = new ArrayList<Map>();
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<Integer, String> ZmDataMap = new LinkedHashMap<Integer, String>();
//左侧游标
int leftIndex = 0;
//右侧游标
int rightIndex = 0;
for (int i = 0; i < lengthArray.length; i++) {
rightIndex = leftIndex + lengthArray[i];
String filed = new String(Arrays.copyOfRange(bs, leftIndex, rightIndex), encoding);
leftIndex = rightIndex + 1;
ZmDataMap.put(i, filed);
}
ZmDataList.add(ZmDataMap);
}
read.close();
} else {
System.out.println("找不到指定的文件");
}
} catch (Exception e) {
System.out.println("读取文件内容出错");
e.printStackTrace();
}
return ZmDataList;
}
public static String getFileContentTable(List<Map> dataList, String file) {
StringBuffer tableSb = new StringBuffer("对账文件的规范参考 https://open.unionpay.com/ajweb/help/file/ 产品接口规范->平台接口规范:文件接口</br> 文件【" + file + "】解析后内容如下:");
tableSb.append("<table border=\"1\">");
if (dataList.size() > 0) {
Map<Integer, String> dataMapTmp = dataList.get(0);
tableSb.append("<tr>");
for (Iterator<Integer> it = dataMapTmp.keySet().iterator(); it.hasNext(); ) {
Integer key = it.next();
String value = dataMapTmp.get(key);
System.out.println("序号:" + (key + 1) + " 值: '" + value + "'");
tableSb.append("<td>序号" + (key + 1) + "</td>");
}
tableSb.append("</tr>");
}
for (int i = 0; i < dataList.size(); i++) {
System.out.println("行数: " + (i + 1));
Map<Integer, String> dataMapTmp = dataList.get(i);
tableSb.append("<tr>");
for (Iterator<Integer> it = dataMapTmp.keySet().iterator(); it.hasNext(); ) {
Integer key = it.next();
String value = dataMapTmp.get(key);
System.out.println("序号:" + (key + 1) + " 值: '" + value + "'");
tableSb.append("<td>" + value + "</td>");
}
tableSb.append("</tr>");
}
tableSb.append("</table>");
return tableSb.toString();
}
public static List<String> unzip(String zipFilePath, String outPutDirectory) {
List<String> fileList = new ArrayList<String>();
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;
}
}
package com.esv.freight.customer.common.unionpay;
import com.esv.freight.customer.common.unionpay.BaseHttpSSLSocketFactory.TrustAnyHostnameVerifier;
import lombok.extern.slf4j.Slf4j;
import javax.net.ssl.HttpsURLConnection;
import java.io.*;
import java.net.*;
import java.util.Map;
import java.util.Map.Entry;
/**
* @description: acpsdk发送后台http请求类
* @project: freight-customer-service
* @name: com.esv.freight.customer.common.unionpay.HttpClient
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/05/30 14:43
* @version:1.0
*/
@Slf4j
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) {
log.error(e.getMessage(), e);
}
}
/**
* 发送信息到服务端
*
* @param data
* @param encoding
* @return
* @throws Exception
*/
public int send(Map<String, String> data, String encoding) throws Exception {
try {
HttpURLConnection httpURLConnection = createConnection(encoding);
if (null == httpURLConnection) {
throw new Exception("Create httpURLConnection Failure");
}
String sendData = this.getRequestParamString(data, encoding);
log.info("请求报文(对每个报文域的值均已做url编码):[" + sendData + "]");
this.requestServer(httpURLConnection, sendData, encoding);
this.result = this.response(httpURLConnection, encoding);
log.info("Response message:[" + result + "]");
return httpURLConnection.getResponseCode();
} catch (Exception e) {
throw e;
}
}
/**
* 发送信息到服务端 GET方式
*
* @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);
log.info("同步返回报文:[" + result + "]");
return httpURLConnection.getResponseCode();
} catch (Exception e) {
throw e;
}
}
/**
* HTTP Post发送消息
*
* @param connection
* @param message
* @throws IOException
*/
private void requestServer(final URLConnection connection, String message, String encoder) throws Exception {
PrintStream out = null;
try {
connection.connect();
out = new PrintStream(connection.getOutputStream(), false, encoder);
out.print(message);
out.flush();
} catch (Exception e) {
throw e;
} finally {
if (null != out) {
out.close();
}
}
}
/**
* 显示Response消息
*
* @param connection
* @param encoding
* @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));
}
log.info("HTTP Return Status-Code:[" + connection.getResponseCode() + "]");
return sb.toString();
} catch (Exception e) {
throw e;
} finally {
if (null != br) {
br.close();
}
if (null != in) {
in.close();
}
if (null != connection) {
connection.disconnect();
}
}
}
public static byte[] read(InputStream in) throws IOException {
byte[] buf = new byte[1024];
int length = 0;
ByteArrayOutputStream bout = new ByteArrayOutputStream();
while ((length = in.read(buf, 0, buf.length)) > 0) {
bout.write(buf, 0, length);
}
bout.flush();
return bout.toByteArray();
}
/**
* 创建连接
*
* @return
* @throws ProtocolException
*/
private HttpURLConnection createConnection(String encoding) throws ProtocolException {
HttpURLConnection httpURLConnection = null;
try {
httpURLConnection = (HttpURLConnection) url.openConnection();
} catch (IOException e) {
log.error(e.getMessage(), e);
return null;
}
httpURLConnection.setConnectTimeout(this.connectionTimeout);// 连接超时时间
httpURLConnection.setReadTimeout(this.readTimeOut);// 读取结果超时时间
httpURLConnection.setDoInput(true); // 可读
httpURLConnection.setDoOutput(true); // 可写
httpURLConnection.setUseCaches(false);// 取消缓存
httpURLConnection.setRequestProperty("Content-type",
"application/x-www-form-urlencoded;charset=" + encoding);
httpURLConnection.setRequestMethod("POST");
if ("https".equalsIgnoreCase(url.getProtocol())) {
HttpsURLConnection husn = (HttpsURLConnection) httpURLConnection;
//是否验证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) {
log.error(e.getMessage(), e);
return null;
}
httpURLConnection.setConnectTimeout(this.connectionTimeout);// 连接超时时间
httpURLConnection.setReadTimeout(this.readTimeOut);// 读取结果超时时间
httpURLConnection.setUseCaches(false);// 取消缓存
httpURLConnection.setRequestProperty("Content-type",
"application/x-www-form-urlencoded;charset=" + encoding);
httpURLConnection.setRequestMethod("GET");
if ("https".equalsIgnoreCase(url.getProtocol())) {
HttpsURLConnection husn = (HttpsURLConnection) httpURLConnection;
//是否验证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<String, String> requestParam, String coder) {
if (null == coder || "".equals(coder)) {
coder = "UTF-8";
}
StringBuffer sf = new StringBuffer("");
String reqstr = "";
if (null != requestParam && 0 != requestParam.size()) {
for (Entry<String, String> en : requestParam.entrySet()) {
try {
sf.append(en.getKey()
+ "="
+ (null == en.getValue() || "".equals(en.getValue()) ? "" : URLEncoder
.encode(en.getValue(), coder)) + "&");
} catch (UnsupportedEncodingException e) {
log.error(e.getMessage(), e);
return "";
}
}
reqstr = sf.substring(0, sf.length() - 1);
}
log.info("Request Message:[" + reqstr + "]");
return reqstr;
}
}
package com.esv.freight.customer.common.unionpay;
import com.esv.freight.customer.common.constants.SDKConstants;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.util.Properties;
/**
* @description: acpsdk配置文件acp_sdk.properties配置信息类
* @project: freight-customer-service
* @name: com.esv.freight.customer.common.unionpay.SDKConfig
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/05/30 14:25
* @version:1.0
*/
@Slf4j
public class SDKConfig {
public static final String FILE_NAME = "acp_sdk_dev.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())) {
log.info("从路径读取配置文件: " + rootPath);
File file = new File(rootPath);
InputStream in = null;
if (file.exists()) {
try {
in = new FileInputStream(file);
properties = new Properties();
properties.load(in);
loadProperties(properties);
} catch (FileNotFoundException e) {
log.error(e.getMessage(), e);
} catch (IOException e) {
log.error(e.getMessage(), e);
} finally {
if (null != in) {
try {
in.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
} else {
// 由于此时可能还没有完成LOG的加载,因此采用标准输出来打印日志信息
log.error(rootPath + FILE_NAME + "不存在,加载参数失败");
}
} else {
loadPropertiesFromSrc();
}
}
/**
* 从classpath路径下加载配置参数
*/
public void loadPropertiesFromSrc() {
InputStream in = null;
try {
log.info("从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 {
log.error(FILE_NAME + "属性文件未能在classpath指定的目录下 " + SDKConfig.class.getClassLoader().getResource("").getPath() + " 找到!");
return;
}
loadProperties(properties);
} catch (IOException e) {
log.error(e.getMessage(), e);
} finally {
if (null != in) {
try {
in.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
}
/**
* 根据传入的 {Properties}对象设置配置参数
*
* @param pro
*/
public void loadProperties(Properties pro) {
log.info("开始从属性文件中加载配置项");
String value = null;
value = pro.getProperty(SDK_SIGNCERT_PATH);
if (!SDKUtil.isEmpty(value)) {
this.signCertPath = value.trim();
log.info("配置项:私钥签名证书路径==>" + SDK_SIGNCERT_PATH + "==>" + value + " 已加载");
}
value = pro.getProperty(SDK_SIGNCERT_PWD);
if (!SDKUtil.isEmpty(value)) {
this.signCertPwd = value.trim();
log.info("配置项:私钥签名证书密码==>" + SDK_SIGNCERT_PWD + " 已加载");
}
value = pro.getProperty(SDK_SIGNCERT_TYPE);
if (!SDKUtil.isEmpty(value)) {
this.signCertType = value.trim();
log.info("配置项:私钥签名证书类型==>" + SDK_SIGNCERT_TYPE + "==>" + value + " 已加载");
}
value = pro.getProperty(SDK_ENCRYPTCERT_PATH);
if (!SDKUtil.isEmpty(value)) {
this.encryptCertPath = value.trim();
log.info("配置项:敏感信息加密证书==>" + SDK_ENCRYPTCERT_PATH + "==>" + value + " 已加载");
}
value = pro.getProperty(SDK_VALIDATECERT_DIR);
if (!SDKUtil.isEmpty(value)) {
this.validateCertDir = value.trim();
log.info("配置项:验证签名证书路径(这里配置的是目录,不要指定到公钥文件)==>" + SDK_VALIDATECERT_DIR + "==>" + value + " 已加载");
}
value = pro.getProperty(SDK_FRONT_URL);
if (!SDKUtil.isEmpty(value)) {
this.frontRequestUrl = value.trim();
}
value = pro.getProperty(SDK_BACK_URL);
if (!SDKUtil.isEmpty(value)) {
this.backRequestUrl = value.trim();
}
value = pro.getProperty(SDK_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;
}
}
package com.esv.freight.customer.common.unionpay;
import com.esv.freight.customer.common.constants.SDKConstants;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.Map.Entry;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
/**
* @description: 银联支付:acpsdk工具类
* @project: freight-customer-service
* @name: com.esv.freight.customer.common.unionpay.SDKUtil
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/05/30 14:11
* @version:1.0
*/
@Slf4j
public class SDKUtil {
/**
* 根据signMethod的值,提供三种计算签名的方法
*
* @param data 待签名数据Map键值对形式
* @param encoding 编码
* @return 签名是否成功
*/
public static boolean sign(Map<String, String> data, String encoding) {
if (isEmpty(encoding)) {
encoding = "UTF-8";
}
String signMethod = data.get(SDKConstants.param_signMethod);
String version = data.get(SDKConstants.param_version);
if (!SDKConstants.VERSION_1_0_0.equals(version) && !SDKConstants.VERSION_5_0_1.equals(version) && isEmpty(signMethod)) {
log.error("signMethod must Not null");
return false;
}
if (isEmpty(version)) {
log.error("version must Not null");
return false;
}
if (SDKConstants.SIGNMETHOD_RSA.equals(signMethod) || SDKConstants.VERSION_1_0_0.equals(version) || SDKConstants.VERSION_5_0_1.equals(version)) {
if (SDKConstants.VERSION_5_0_0.equals(version) || SDKConstants.VERSION_1_0_0.equals(version) || SDKConstants.VERSION_5_0_1.equals(version)) {
// 设置签名证书序列号
data.put(SDKConstants.param_certId, CertUtil.getSignCertId());
// 将Map信息转换成key1=value1&key2=value2的形式
String stringData = coverMap2String(data);
log.info("打印排序后待签名请求报文串(交易返回11验证签名失败时可以用来同正确的进行比对):[" + stringData + "]");
byte[] byteSign;
String stringSign;
try {
// 通过SHA1进行摘要并转16进制
byte[] signDigest = SecureUtil
.sha1X16(stringData, encoding);
log.info("打印摘要(交易返回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) {
log.error("Sign Error", e);
return false;
}
} else if (SDKConstants.VERSION_5_1_0.equals(version)) {
// 设置签名证书序列号
data.put(SDKConstants.param_certId, CertUtil.getSignCertId());
// 将Map信息转换成key1=value1&key2=value2的形式
String stringData = coverMap2String(data);
log.info("打印待签名请求报文串(交易返回11验证签名失败时可以用来同正确的进行比对):[" + stringData + "]");
byte[] byteSign;
String stringSign;
try {
// 通过SHA256进行摘要并转16进制
byte[] signDigest = SecureUtil.sha256X16(stringData, encoding);
log.info("打印摘要(交易返回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) {
log.error("Sign Error", e);
return false;
}
}
} else if (SDKConstants.SIGNMETHOD_SHA256.equals(signMethod)) {
return signBySecureKey(data, SDKConfig.getConfig().getSecureKey(), encoding);
} else if (SDKConstants.SIGNMETHOD_SM3.equals(signMethod)) {
return signBySecureKey(data, SDKConfig.getConfig().getSecureKey(), encoding);
}
return false;
}
/**
* 通过传入的证书绝对路径和证书密码读取签名证书进行签名并返回签名值<br>
*
* @param data 待签名数据Map键值对形式
* @param secureKey
* @param encoding 编码
* @return 签名值
*/
public static boolean signBySecureKey(Map<String, String> data, String secureKey, String encoding) {
if (isEmpty(encoding)) {
encoding = "UTF-8";
}
if (isEmpty(secureKey)) {
log.error("secureKey is empty");
return false;
}
String signMethod = data.get(SDKConstants.param_signMethod);
if (isEmpty(signMethod)) {
log.error("signMethod must Not null");
return false;
}
if (SDKConstants.SIGNMETHOD_SHA256.equals(signMethod)) {
// 将Map信息转换成key1=value1&key2=value2的形式
String stringData = coverMap2String(data);
log.info("待签名请求报文串:[" + 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 (SDKConstants.SIGNMETHOD_SM3.equals(signMethod)) {
String stringData = coverMap2String(data);
log.info("待签名请求报文串:[" + 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;
}
/**
* 通过传入的签名密钥进行签名并返回签名值<br>
*
* @param data 待签名数据Map键值对形式
* @param encoding 编码
* @param certPath 证书绝对路径
* @param certPwd 证书密码
* @return 签名值
*/
public static boolean signByCertInfo(Map<String, String> data, String certPath, String certPwd, String encoding) {
if (isEmpty(encoding)) {
encoding = "UTF-8";
}
if (isEmpty(certPath) || isEmpty(certPwd)) {
log.error("CertPath or CertPwd is empty");
return false;
}
String signMethod = data.get(SDKConstants.param_signMethod);
String version = data.get(SDKConstants.param_version);
if (!SDKConstants.VERSION_1_0_0.equals(version) && !SDKConstants.VERSION_5_0_1.equals(version) && isEmpty(signMethod)) {
log.error("signMethod must Not null");
return false;
}
if (isEmpty(version)) {
log.error("version must Not null");
return false;
}
if (SDKConstants.SIGNMETHOD_RSA.equals(signMethod) || SDKConstants.VERSION_1_0_0.equals(version) || SDKConstants.VERSION_5_0_1.equals(version)) {
if (SDKConstants.VERSION_5_0_0.equals(version) || SDKConstants.VERSION_1_0_0.equals(version) || SDKConstants.VERSION_5_0_1.equals(version)) {
// 设置签名证书序列号
data.put(SDKConstants.param_certId, CertUtil.getCertIdByKeyStoreMap(certPath, certPwd));
// 将Map信息转换成key1=value1&key2=value2的形式
String stringData = coverMap2String(data);
log.info("待签名请求报文串:[" + 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) {
log.error("Sign Error", e);
return false;
}
} else if (SDKConstants.VERSION_5_1_0.equals(version)) {
// 设置签名证书序列号
data.put(SDKConstants.param_certId, CertUtil.getCertIdByKeyStoreMap(certPath, certPwd));
// 将Map信息转换成key1=value1&key2=value2的形式
String stringData = coverMap2String(data);
log.info("待签名请求报文串:[" + stringData + "]");
byte[] byteSign;
String stringSign;
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) {
log.error("Sign Error", e);
return false;
}
}
}
return false;
}
/**
* 验证签名
*
* @param resData 返回报文数据
* @param encoding 编码格式
* @return
*/
public static boolean validateBySecureKey(Map<String, String> resData, String secureKey, String encoding) {
log.info("验签处理开始");
if (isEmpty(encoding)) {
encoding = "UTF-8";
}
String signMethod = resData.get(SDKConstants.param_signMethod);
if (SDKConstants.SIGNMETHOD_SHA256.equals(signMethod)) {
// 1.进行SHA256验证
String stringSign = resData.get(SDKConstants.param_signature);
log.info("签名原文:[" + stringSign + "]");
// 将Map信息转换成key1=value1&key2=value2的形式
String stringData = coverMap2String(resData);
log.info("待验签返回报文串:[" + stringData + "]");
String strBeforeSha256 = stringData
+ SDKConstants.AMPERSAND
+ SecureUtil.sha256X16Str(secureKey, encoding);
String strAfterSha256 = SecureUtil.sha256X16Str(strBeforeSha256,
encoding);
return stringSign.equals(strAfterSha256);
} else if (SDKConstants.SIGNMETHOD_SM3.equals(signMethod)) {
// 1.进行SM3验证
String stringSign = resData.get(SDKConstants.param_signature);
log.info("签名原文:[" + stringSign + "]");
// 将Map信息转换成key1=value1&key2=value2的形式
String stringData = coverMap2String(resData);
log.info("待验签返回报文串:[" + 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<String, String> resData, String encoding) {
log.info("验签处理开始");
if (isEmpty(encoding)) {
encoding = "UTF-8";
}
String signMethod = resData.get(SDKConstants.param_signMethod);
String version = resData.get(SDKConstants.param_version);
if (SDKConstants.SIGNMETHOD_RSA.equals(signMethod) || SDKConstants.VERSION_1_0_0.equals(version) || SDKConstants.VERSION_5_0_1.equals(version)) {
// 获取返回报文的版本号
if (SDKConstants.VERSION_5_0_0.equals(version) || SDKConstants.VERSION_1_0_0.equals(version) || SDKConstants.VERSION_5_0_1.equals(version)) {
String stringSign = resData.get(SDKConstants.param_signature);
log.info("签名原文:[" + stringSign + "]");
// 从返回报文中获取certId ,然后去证书静态Map中查询对应验签证书对象
String certId = resData.get(SDKConstants.param_certId);
log.info("对返回报文串验签使用的验签公钥序列号:[" + certId + "]");
// 将Map信息转换成key1=value1&key2=value2的形式
String stringData = coverMap2String(resData);
log.info("待验签返回报文串:[" + stringData + "]");
try {
// 验证签名需要用银联发给商户的公钥证书.
return SecureUtil.validateSignBySoft(CertUtil.getValidatePublicKey(certId), SecureUtil
.base64Decode(stringSign.getBytes(encoding)),
SecureUtil.sha1X16(stringData, encoding));
} catch (UnsupportedEncodingException e) {
log.error(e.getMessage(), e);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
} else if (SDKConstants.VERSION_5_1_0.equals(version)) {
// 1.从返回报文中获取公钥信息转换成公钥对象
String strCert = resData.get(SDKConstants.param_signPubKeyCert);
// log.info("验签公钥证书:["+strCert+"]");
X509Certificate x509Cert = CertUtil.genCertificateByStr(strCert);
if (x509Cert == null) {
log.error("convert signPubKeyCert failed");
return false;
}
// 2.验证证书链
if (!CertUtil.verifyCertificate(x509Cert)) {
log.error("验证公钥证书失败,证书信息:[" + strCert + "]");
return false;
}
// 3.验签
String stringSign = resData.get(SDKConstants.param_signature);
log.info("签名原文:[" + stringSign + "]");
// 将Map信息转换成key1=value1&key2=value2的形式
String stringData = coverMap2String(resData);
log.info("待验签返回报文串:[" + stringData + "]");
try {
// 验证签名需要用银联发给商户的公钥证书.
boolean result = SecureUtil.validateSignBySoft256(x509Cert.getPublicKey(),
SecureUtil.base64Decode(stringSign.getBytes(encoding)),
SecureUtil.sha256X16(stringData, encoding));
log.info("验证签名" + (result ? "成功" : "失败"));
return result;
} catch (UnsupportedEncodingException e) {
log.error(e.getMessage(), e);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
} else if (SDKConstants.SIGNMETHOD_SHA256.equals(signMethod)) {
// 1.进行SHA256验证
String stringSign = resData.get(SDKConstants.param_signature);
log.info("签名原文:[" + stringSign + "]");
// 将Map信息转换成key1=value1&key2=value2的形式
String stringData = coverMap2String(resData);
log.info("待验签返回报文串:[" + stringData + "]");
String strBeforeSha256 = stringData
+ SDKConstants.AMPERSAND
+ SecureUtil.sha256X16Str(SDKConfig.getConfig()
.getSecureKey(), encoding);
String strAfterSha256 = SecureUtil.sha256X16Str(strBeforeSha256, encoding);
boolean result = stringSign.equals(strAfterSha256);
log.info("验证签名" + (result ? "成功" : "失败"));
return result;
} else if (SDKConstants.SIGNMETHOD_SM3.equals(signMethod)) {
// 1.进行SM3验证
String stringSign = resData.get(SDKConstants.param_signature);
log.info("签名原文:[" + stringSign + "]");
// 将Map信息转换成key1=value1&key2=value2的形式
String stringData = coverMap2String(resData);
log.info("待验签返回报文串:[" + stringData + "]");
String strBeforeSM3 = stringData + SDKConstants.AMPERSAND
+ SecureUtil.sm3X16Str(SDKConfig.getConfig().getSecureKey(), encoding);
String strAfterSM3 = SecureUtil .sm3X16Str(strBeforeSM3, encoding);
boolean result = stringSign.equals(strAfterSM3);
log.info("验证签名" + (result ? "成功" : "失败"));
return result;
}
return false;
}
/**
* 将Map中的数据转换成key1=value1&key2=value2的形式 不包含签名域signature
*
* @param data 待拼接的Map数据
* @return 拼接好后的字符串
*/
public static String coverMap2String(Map<String, String> data) {
TreeMap<String, String> tree = new TreeMap<>();
Iterator<Entry<String, String>> it = data.entrySet().iterator();
while (it.hasNext()) {
Entry<String, String> en = it.next();
if (SDKConstants.param_signature.equals(en.getKey().trim())) {
continue;
}
tree.put(en.getKey(), en.getValue());
}
it = tree.entrySet().iterator();
StringBuffer sf = new StringBuffer();
while (it.hasNext()) {
Entry<String, String> en = it.next();
sf.append(en.getKey() + SDKConstants.EQUAL + en.getValue() + SDKConstants.AMPERSAND);
}
return sf.substring(0, sf.length() - 1);
}
/**
* 兼容老方法 将形如key=value&key=value的字符串转换为相应的Map对象
*
* @param result
* @return
*/
public static Map<String, String> coverResultString2Map(String result) {
return convertResultStringToMap(result);
}
/**
* 将形如key=value&key=value的字符串转换为相应的Map对象
*
* @param result
* @return
*/
public static Map<String, String> convertResultStringToMap(String result) {
Map<String, String> map = null;
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<String, String> parseQString(String str) {
Map<String, String> map = new HashMap<String, String>();
int len = str.length();
StringBuilder temp = new StringBuilder();
char curChar;
String key = null;
boolean isKey = true;
boolean isOpen = false;//值里有嵌套
char openName = 0;
if (len > 0) {
for (int i = 0; i < len; i++) {// 遍历整个带解析的字符串
curChar = str.charAt(i);// 取当前字符
if (isKey) {// 如果当前生成的是key
if (curChar == '=') {// 如果读取到=分隔符
key = temp.toString();
temp.setLength(0);
isKey = false;
} else {
temp.append(curChar);
}
} else {// 如果当前生成的是value
if (isOpen) {
if (curChar == openName) {
isOpen = false;
}
} else {//如果没开启嵌套
if (curChar == '{') {//如果碰到,就开启嵌套
isOpen = true;
openName = '}';
}
if (curChar == '[') {
isOpen = true;
openName = ']';
}
}
if (curChar == '&' && !isOpen) {// 如果读取到&分割符,同时这个分割符不是值域,这时将map里添加
putKeyValueToMap(temp, isKey, key, map);
temp.setLength(0);
isKey = true;
} else {
temp.append(curChar);
}
}
}
putKeyValueToMap(temp, isKey, key, map);
}
return map;
}
private static void putKeyValueToMap(StringBuilder temp, boolean isKey,
String key, Map<String, String> map) {
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());
}
}
/**
* 获取应答报文中的加密公钥证书,并存储到本地,并备份原始证书<br>
* 更新成功则返回1,无更新返回0,失败异常返回-1。
*
* @param resData
* @param encoding
* @return
*/
public static int getEncryptCert(Map<String, String> 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 (SDKConstants.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;
}
log.info("save new encryptPubKeyCert success");
CertUtil.resetEncryptCertPublicKey();
return 1;
} else {
return 0;
}
} else if (SDKConstants.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;
// log.info("save new encryptPubKeyCert success");
// CertUtil.resetEncryptTrackCertPublicKey();
// return 1;
// }else {
return 0;
// }
} else {
log.info("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) {
log.error("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) {
log.error("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) {
log.error("WriteFile fail", e);
return false;
} catch (IOException ex) {
log.error("WriteFile fail", ex);
return false;
} finally {
try {
if (null != fout) {
fout.close();
}
if (null != fcout) {
fcout.close();
}
} catch (IOException ex) {
log.error("Releases any system resources fail", ex);
return false;
}
}
return true;
}
/**
* 将传入的文件名(xxx)改名 <br>
* 结果为: xxx_backup.cer
*
* @param fileName
* @return
*/
public static String genBackupName(String fileName) {
if (isEmpty(fileName)) {
return "";
}
int i = fileName.lastIndexOf(SDKConstants.POINT);
String leftFileName = fileName.substring(0, i);
String rightFileName = fileName.substring(i + 1);
String newFileName = leftFileName + "_backup" + SDKConstants.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) {
log.error(e.getMessage(), e);
return null;
} finally {
try {
if (null != fc) {
fc.close();
}
if (null != in) {
in.close();
}
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
}
}
/**
* 过滤请求报文中的空字符串或者空字符串
*
* @param contentData
* @return
*/
public static Map<String, String> filterBlank(Map<String, String> contentData) {
Map<String, String> submitFromData = new HashMap<>();
Set<String> keySet = contentData.keySet();
for (String key : keySet) {
String value = contentData.get(key);
if (value != null && !"".equals(value.trim())) {
// 对value值进行去除前后空处理
submitFromData.put(key, value.trim());
}
}
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) {
log.error("Data format error!", ex);
} 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());
}
}
package com.esv.freight.customer.common.unionpay;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.crypto.digests.SM3Digest;
import javax.crypto.Cipher;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
/**
* @description: 银联支付:acpsdk安全算法工具类
* @project: freight-customer-service
* @name: com.esv.freight.customer.common.unionpay.SecureUtil
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/05/30 14:07
* @version:1.0
*/
@Slf4j
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) {
log.error(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) {
log.error(e.getMessage(), e);
return null;
}
}
/**
* sha1计算.
*
* @param data 待计算的数据
* @return 计算结果
*/
private static byte[] sha1(byte[] data) {
MessageDigest md;
try {
md = MessageDigest.getInstance(ALGORITHM_SHA1);
md.reset();
md.update(data);
return md.digest();
} catch (Exception e) {
log.error("SHA1计算失败", e);
return null;
}
}
/**
* sha256计算.
*
* @param data 待计算的数据
* @return 计算结果
*/
private static byte[] sha256(byte[] data) {
MessageDigest md;
try {
md = MessageDigest.getInstance(ALGORITHM_SHA256);
md.reset();
md.update(data);
return md.digest();
} catch (Exception e) {
log.error("SHA256计算失败", e);
return null;
}
}
/**
* SM3计算.
*
* @param data 待计算的数据
* @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) {
log.error("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) {
log.error("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) {
log.error("SM3计算失败", e);
return null;
}
}
/**
* @param privateKey
* @param data
* @return
* @throws Exception
*/
public static byte[] signBySoft(PrivateKey privateKey, byte[] data) throws Exception {
byte[] result;
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;
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;
try {
data = encryptData(key, dataString.getBytes(encoding));
return new String(SecureUtil.base64Encode(data), encoding);
} catch (Exception e) {
log.error(e.getMessage(), e);
return "";
}
}
/**
* 对数据通过公钥进行加密,并进行base64计算
*
* @param accNo 待处理数据
* @param pin pin
* @param encoding 字符编码
* @param key 公钥
* @return
*/
public static String encryptPin(String accNo, String pin, String encoding, PublicKey key) {
/** 使用公钥对密码加密 **/
byte[] data;
try {
data = pin2PinBlockWithCardNO(pin, accNo);
data = encryptData(key, data);
return new String(SecureUtil.base64Encode(data), encoding);
} catch (Exception e) {
log.error(e.getMessage(), e);
return "";
}
}
/**
* 通过私钥解密
*
* @param dataString base64过的数据
* @param encoding 编码
* @param key 私钥
* @return 解密后的数据
*/
public static String decryptData(String dataString, String encoding, PrivateKey key) {
byte[] data;
try {
data = SecureUtil.base64Decode(dataString.getBytes(encoding));
data = decryptData(key, data);
return new String(data, encoding);
} catch (Exception e) {
log.error(e.getMessage(), e);
return "";
}
}
/**
* BASE64解码
*
* @param inputByte 待解码数据
* @return 解码后的数据
* @throws IOException
*/
public static byte[] base64Decode(byte[] inputByte) throws IOException {
return Base64.decodeBase64(inputByte);
}
/**
* BASE64编码
*
* @param inputByte 待编码数据
* @return 解码后的数据
* @throws IOException
*/
public static byte[] base64Encode(byte[] inputByte) throws IOException {
return Base64.encodeBase64(inputByte);
}
/**
* 加密除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 data
* @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) {
log.error("解密失败", e);
}
return null;
}
/**
* @param aPin
* @return
*/
private static byte[] pin2PinBlock(String aPin) {
int tTemp = 1;
int tPinLen = aPin.length();
byte[] tByte = new byte[8];
try {
/*******************************************************************
* if (tPinLen > 9) { tByte[0] = (byte) Integer.parseInt(new
* Integer(tPinLen) .toString(), 16); } else { tByte[0] = (byte)
* Integer.parseInt(new Integer(tPinLen) .toString(), 10); }
******************************************************************/
// tByte[0] = (byte) Integer.parseInt(new Integer(tPinLen).toString(),
// 10);
tByte[0] = (byte) Integer.parseInt(Integer.toString(tPinLen), 10);
if (tPinLen % 2 == 0) {
for (int i = 0; i < tPinLen; ) {
String a = aPin.substring(i, i + 2);
tByte[tTemp] = (byte) Integer.parseInt(a, 16);
if (i == (tPinLen - 2)) {
if (tTemp < 7) {
for (int x = (tTemp + 1); x < 8; x++) {
tByte[x] = (byte) 0xff;
}
}
}
tTemp++;
i = i + 2;
}
} else {
for (int i = 0; i < tPinLen - 1; ) {
String a;
a = aPin.substring(i, i + 2);
tByte[tTemp] = (byte) Integer.parseInt(a, 16);
if (i == (tPinLen - 3)) {
String b = aPin.substring(tPinLen - 1) + "F";
tByte[tTemp + 1] = (byte) Integer.parseInt(b, 16);
if ((tTemp + 1) < 7) {
for (int x = (tTemp + 2); x < 8; x++) {
tByte[x] = (byte) 0xff;
}
}
}
tTemp++;
i = i + 2;
}
}
} catch (Exception e) {
}
return tByte;
}
/**
* @param aPan
* @return
*/
private static byte[] formatPan(String aPan) {
int tPanLen = aPan.length();
byte[] tByte = new byte[8];
;
int temp = tPanLen - 13;
try {
tByte[0] = (byte) 0x00;
tByte[1] = (byte) 0x00;
for (int i = 2; i < 8; i++) {
String a = aPan.substring(temp, temp + 2);
tByte[i] = (byte) Integer.parseInt(a, 16);
temp = temp + 2;
}
} catch (Exception e) {
}
return tByte;
}
/**
* @param aPin
* @param aCardNO
* @return
*/
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;
}
}
package com.esv.freight.customer.common.unionpay;
import com.esv.freight.customer.common.exception.EException;
import com.esv.freight.customer.common.pojo.AppUnionPayOrderReq;
import com.esv.freight.customer.common.pojo.AppUnionPayOrderRes;
import com.esv.freight.customer.common.response.ECode;
import com.esv.freight.customer.common.util.DateUtils;
import com.esv.freight.customer.common.util.MapUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* @description: 银联开放平台组件
* @project: freight-customer-service
* @name: com.esv.freight.customer.common.unionpay.UnionPayComponent
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/05/29 9:29
* @version:1.0
*/
@Slf4j
@Component
public class UnionPayComponent {
/**
* description 创建手机APP支付订单
* param [appUnionPayOrder]
* return AppUnionPayOrderRes
* author HuangChaobin
* createTime 2020/05/30 13:51
**/
public AppUnionPayOrderRes createAppUnionPayOrder(AppUnionPayOrderReq appUnionPayOrderReq) throws EException {
Map<String, String> contentData = new HashMap<>(128);
/***银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改***/
//版本号 全渠道默认值
contentData.put("version", DemoBase.version);
//字符集编码 可以使用UTF-8,GBK两种方式
contentData.put("encoding", DemoBase.encoding);
//签名方法
contentData.put("signMethod", SDKConfig.getConfig().getSignMethod());
//交易类型 01:消费
contentData.put("txnType", "01");
//交易子类 01:消费
contentData.put("txnSubType", "01");
//填写000201
contentData.put("bizType", "000201");
//渠道类型 08手机
contentData.put("channelType", "08");
/***商户接入参数***/
//商户号码,请改成自己申请的商户号或者open上注册得来的777商户号测试
contentData.put("merId", appUnionPayOrderReq.getMerId());
//接入类型,商户接入填0 ,不需修改(0:直连商户, 1: 收单机构 2:平台商户)
contentData.put("accessType", "0");
//商户订单号,8-40位数字字母,不能含“-”或“_”,可以自行定制规则
contentData.put("orderId", appUnionPayOrderReq.getBillId());
//订单描述
contentData.put("orderDesc", appUnionPayOrderReq.getBillDesc());
//订单发送时间,取系统时间,格式为yyyyMMddHHmmss,必须取当前时间,否则会报txnTime无效
contentData.put("txnTime", appUnionPayOrderReq.getTxnTime());
//账号类型 01:银行卡02:存折03:IC卡帐号类型(卡介质)
contentData.put("accType", "01");
//交易金额 单位为分,不能带小数点
contentData.put("txnAmt", String.valueOf(appUnionPayOrderReq.getTxnAmt()));
//境内商户固定 156 人民币
contentData.put("currencyCode", "156");
// 请求方保留域,
// 透传字段,查询、通知、对账文件中均会原样出现,如有需要请启用并修改自己希望透传的数据。
// 出现部分特殊字符时可能影响解析,请按下面建议的方式填写:
// 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);
// 前台返回商户结果时使用,前台类交易需上送
contentData.put("frontUrl", DemoBase.frontUrl);
/**对请求参数进行签名并发送http post请求,接收同步应答报文**/
//报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。
Map<String, String> reqData = AcpService.sign(contentData, DemoBase.encoding);
String reqDataJsonStr = MapUtils.map2Json(reqData).toJSONString();
log.info("[银联开放平台]请求银联参数:{}", reqDataJsonStr);
//交易请求url从配置文件读取对应属性文件acp_sdk.properties中的 acpsdk.backTransUrl
String requestAppUrl = SDKConfig.getConfig().getAppRequestUrl();
//发送请求报文并接受同步应答(默认连接超时时间30秒,读取返回结果超时时间30秒);这里调用signData之后,调用submitUrl之前不能对submitFromData中的键值对做任何修改,如果修改会导致验签不通过
Map<String, String> rspData = AcpService.post(reqData, requestAppUrl, DemoBase.encoding);
String resDataJsonStr = MapUtils.map2Json(rspData).toJSONString();
log.info("[银联开放平台]银联响应参数:{}", resDataJsonStr);
/**对应答码的处理,请根据您的业务逻辑来编写程序,以下应答码处理逻辑仅供参考------------->**/
//应答码规范参考open.unionpay.com帮助中心 下载 产品接口规范 《平台接入接口规范-第5部分-附录》
String tn;
if (!rspData.isEmpty()) {
if (AcpService.validate(rspData, DemoBase.encoding)) {
log.info("[银联开放平台]验证签名成功");
String respCode = rspData.get("respCode");
if (("00").equals(respCode)) {
//成功,获取tn号
tn = rspData.get("tn");
} else {
//其他应答码为失败请排查原因或做失败处理
throw new EException(ECode.THIRD_PARTY_ERROR.code(), "未定义异常");
}
} else {
log.error("验证签名失败");
throw new EException(ECode.THIRD_PARTY_ERROR.code(), "创建银联受理订单号失败");
}
} else {
//未返回正确的http状态
log.error("[银联开放平台]未获取到返回报文或返回http状态码非200");
throw new EException(ECode.THIRD_PARTY_ERROR.code(), "[银联开放平台]创建银联受理订单号失败");
}
AppUnionPayOrderRes appUnionPayOrderRes = new AppUnionPayOrderRes();
appUnionPayOrderRes.setReqMessage(reqDataJsonStr);
appUnionPayOrderRes.setRspMessage(resDataJsonStr);
appUnionPayOrderRes.setTn(tn);
return appUnionPayOrderRes;
}
/**
* description 查询交易
* param [appUnionPayOrderReq]
* return java.util.Map<java.lang.String,java.lang.String>
* author HuangChaobin
* createTime 2020/06/03 10:44
**/
public Map<String, String> queryUnionPayOrder(AppUnionPayOrderReq appUnionPayOrderReq) throws EException {
Map<String, String> data = new HashMap<>(32);
/***银联全渠道系统,产品参数,除了encoding自行选择外其他不需修改***/
//版本号 全渠道默认值
data.put("version", DemoBase.version);
//字符集编码 可以使用UTF-8,GBK两种方式
data.put("encoding", DemoBase.encoding);
//签名方法
data.put("signMethod", SDKConfig.getConfig().getSignMethod());
//交易类型 00-默认
data.put("txnType", "00");
//交易子类型 默认00
data.put("txnSubType", "00");
//业务类型
data.put("bizType", "000201");
/***商户接入参数***/
//商户号码,请改成自己申请的商户号或者open上注册得来的777商户号测试
data.put("merId", appUnionPayOrderReq.getMerId());
//接入类型,商户接入固定填0,不需修改
data.put("accessType", "0");
/***要调通交易以下字段必须修改***/
//****商户订单号,每次发交易测试需修改为被查询的交易的订单号
data.put("orderId", appUnionPayOrderReq.getBillId());
//****订单发送时间,每次发交易测试需修改为被查询的交易的订单发送时间
data.put("txnTime", appUnionPayOrderReq.getTxnTime());
/**请求参数设置完毕,以下对请求参数进行签名并发送http post请求,接收同步应答报文------------->**/
//报文中certId,signature的值是在signData方法中获取并自动赋值的,只要证书配置正确即可。
Map<String, String> reqData = AcpService.sign(data, DemoBase.encoding);
//交易请求url从配置文件读取对应属性文件acp_sdk.properties中的 acpsdk.singleQueryUrl
String url = SDKConfig.getConfig().getSingleQueryUrl();
//发送请求报文并接受同步应答(默认连接超时时间30秒,读取返回结果超时时间30秒);这里调用signData之后,调用submitUrl之前不能对submitFromData中的键值对做任何修改,如果修改会导致验签不通过
Map<String, String> rspData = AcpService.post(reqData, url, DemoBase.encoding);
/**对应答码的处理,请根据您的业务逻辑来编写程序,以下应答码处理逻辑仅供参考------------->**/
//应答码规范参考open.unionpay.com帮助中心 下载 产品接口规范 《平台接入接口规范-第5部分-附录》
if (!rspData.isEmpty()) {
if (AcpService.validate(rspData, DemoBase.encoding)) {
log.info("[银联开放平台]验证签名成功");
} else {
log.error("[银联开放平台]验证签名失败");
throw new EException(ECode.THIRD_PARTY_ERROR.code(), "[银联开放平台]验证签名失败");
}
} else {
//未返回正确的http状态
log.error("[银联开放平台]未获取到返回报文或返回http状态码非200");
throw new EException(ECode.THIRD_PARTY_ERROR.code(), "[银联开放平台]银联响应异常");
}
return rspData;
}
}
package com.esv.freight.customer.common.util;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import java.util.Iterator;
import java.util.Map;
/**
* @description: Map工具类
* @project: freight-customer-service
* @name: com.esv.freight.customer.common.util.MapUtils
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/06/02 16:04
* @version:1.0
*/
@Slf4j
public class MapUtils {
/**
* description Map转Json
* param [map]
* return com.alibaba.fastjson.JSONObject
* author HuangChaobin
* createTime 2020/06/02 16:04
**/
public static JSONObject map2Json(Map<String, String> map) {
JSONObject json = new JSONObject();
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> en = it.next();
json.put(en.getKey(), en.getValue());
}
return json;
}
}
...@@ -11,6 +11,13 @@ import org.springframework.web.context.request.RequestContextHolder; ...@@ -11,6 +11,13 @@ import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
/** /**
* @description:Http请求工具类 * @description:Http请求工具类
...@@ -106,4 +113,63 @@ public class ReqUtils { ...@@ -106,4 +113,63 @@ public class ReqUtils {
} }
} }
/**
* description 获取Post请求Body
* param [req]
* return java.lang.String
* author HuangChaobin
* createTime 2020/06/02 19:45
**/
public static String getPostBody(HttpServletRequest req) {
String reqBody = null;
String method = req.getMethod();
if (!CommonConstants.HTTP_REQUEST_METHOD_POST.equalsIgnoreCase(method)) {
return reqBody;
}
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = req.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName(CommonConstants.DEFAULT_CHARACTER_ENCODING)));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
if (0 == sb.length()) {
Map<String, String> bodyMap = new HashMap<>();
Map<String, String[]> parameterMap = req.getParameterMap();
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
for (String value : entry.getValue()) {
bodyMap.put(entry.getKey(), value);
}
}
reqBody = bodyMap.toString();
} else {
reqBody = sb.toString();
}
} catch (IOException e) {
log.error("解析post参数时发生错误:{}", e.getMessage(), e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
return reqBody;
}
} }
package com.esv.freight.customer.common.util;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
/**
* @description: Http响应工具类
* @project: freight-customer-service
* @name: com.esv.freight.customer.common.util.ResUtils
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/06/02 19:26
* @version:1.0
*/
@Slf4j
public class ResUtils {
/**
* description Http响应
* param [response, status, contentType, resContent]
* return void
* author HuangChaobin
* createTime 2020/06/02 19:27
**/
public static void response(HttpServletResponse response, int status, String contentType, String resContent) {
response.setCharacterEncoding("UTF-8");
response.setContentType(contentType);
response.setStatus(status);
OutputStreamWriter osw = null;
PrintWriter writer = null;
try {
osw = new OutputStreamWriter(response.getOutputStream(), "UTF-8");
writer = new PrintWriter(osw, true);
writer.write(resContent);
writer.flush();
osw.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
} finally {
if (null != writer) {
writer.close();
}
if (null != osw) {
try {
osw.close();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
}
}
package com.esv.freight.customer.config;
import com.esv.freight.customer.module.servlet.AppFrontRcvServlet;
import com.esv.freight.customer.module.servlet.UnionpayBackRcvServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @description: 注册Servlet
* @project: freight-customer-service
* @name: com.esv.freight.customer.config.ServletConfig
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/06/02 18:59
* @version:1.0
*/
@Configuration
public class ServletConfig {
@Bean
public AppFrontRcvServlet getAppFrontRcvServlet() {
return new AppFrontRcvServlet();
}
@Bean
public ServletRegistrationBean appFrontRcvServletRegistrationBean() {
ServletRegistrationBean registrationBean = new ServletRegistrationBean(getAppFrontRcvServlet(), "/pay/frontRcvResponse");
return registrationBean;
}
@Bean
public UnionpayBackRcvServlet getUnionpayBackRcvServlet() {
return new UnionpayBackRcvServlet();
}
@Bean
public ServletRegistrationBean unionpayBackRcvServletRegistrationBean() {
ServletRegistrationBean registrationBean = new ServletRegistrationBean(getUnionpayBackRcvServlet(), "/pay/backRcvResponse");
return registrationBean;
}
}
package com.esv.freight.customer.feign;
import com.alibaba.fastjson.JSONObject;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
/**
* @description:
* @project: freight-customer-service
* @name: com.esv.freight.customer.feign.FreightBillService
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/06/04 9:48
* @version:1.0
*/
@FeignClient(value = "freight-bill-service")
public interface FreightBillService {
/**
* description 更改应收账单支付状态
* param [bodyJson]
* return com.alibaba.fastjson.JSONObject
* author HuangChaobin
* createTime 2020/06/04 9:49
**/
@PostMapping(value = "/bill/verify/recvbill/updatePayState")
JSONObject updatePayState(JSONObject bodyJson);
}
package com.esv.freight.customer.module.pay;
/**
* @description:
* @project: freight-customer-service
* @name: com.esv.freight.customer.module.pay.PayConstants
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/06/01 19:47
* @version:1.0
*/
public class PayConstants {
/**
* 支付方式:10-银联
*/
public static final Integer PAY_TYPE_UNIONPAY = 10;
/**
* 交易状态查询次数,最多6次
*/
public static final Integer UNIONPAY_QUERY_MAX_COUNT = 6;
/**
* 交易状态查询时间间隔(30分钟),单位:毫秒
*/
public static final Long UNIONPAY_QUERY_MAX_TIME = 1800000L;
/**
* 订单状态:1-支付中、2-支付结果确认中、3-支付成功、4-支付失败
*/
public static final Integer ORDER_STATUS_PAYING = 1;
public static final Integer ORDER_STATUS_CONFIRM = 2;
public static final Integer ORDER_STATUS_SUCCESS = 3;
public static final Integer ORDER_STATUS_FAILURE = 4;
}
package com.esv.freight.customer.module.pay.controller;
import com.alibaba.fastjson.JSONObject;
import com.esv.freight.customer.common.exception.EException;
import com.esv.freight.customer.common.response.ECode;
import com.esv.freight.customer.common.response.EResponse;
import com.esv.freight.customer.module.pay.form.CustomerPayOrderForm;
import com.esv.freight.customer.module.pay.form.UnionPayOrderQueryForm;
import com.esv.freight.customer.module.pay.service.UnionpayOrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @description: 支付Controller
* @project: freight-customer-service
* @name: com.esv.freight.customer.module.pay.controller.PayController
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/06/01 16:44
* @version:1.0
*/
@Slf4j
@RestController
@RequestMapping("/goodsowner/pay")
@Validated
public class PayController {
private UnionpayOrderService unionpayOrderService;
@Autowired
public PayController(UnionpayOrderService unionpayOrderService) {
this.unionpayOrderService = unionpayOrderService;
}
/**
* description 创建支付订单
* param [form]
* return com.esv.freight.customer.common.response.EResponse
* author HuangChaobin
* createTime 2020/06/02 9:13
**/
@PostMapping("/createPayOrder")
public EResponse createPayOrder(@RequestBody @Validated CustomerPayOrderForm form) {
String tn = unionpayOrderService.createCustomerPayOrder(form);
JSONObject data = new JSONObject();
data.put("tn", tn);
return EResponse.ok(data);
}
/**
* description 通过流水号查询订单状态
* param [form]
* return com.esv.freight.customer.common.response.EResponse
* author HuangChaobin
* createTime 2020/06/03 13:34
**/
@PostMapping("/getOrderStatusByTn")
public EResponse getOrderStatusByTn(@RequestBody @Validated UnionPayOrderQueryForm form) {
if (StringUtils.isBlank(form.getTn())) {
throw new EException(ECode.PARAM_ERROR.code(), "参数tn不能为空");
}
Integer orderStatus = unionpayOrderService.getOrderStatusByTn(form.getTn());
JSONObject data = new JSONObject();
data.put("orderStatus", orderStatus);
return EResponse.ok(data);
}
/**
* description 通过账单号查询订单状态
* param [form]
* return com.esv.freight.customer.common.response.EResponse
* author HuangChaobin
* createTime 2020/06/03 13:34
**/
@PostMapping("/getOrderStatusByBillId")
public EResponse getOrderStatusByBillId(@RequestBody @Validated UnionPayOrderQueryForm form) {
if (StringUtils.isBlank(form.getBillId())) {
throw new EException(ECode.PARAM_ERROR.code(), "参数orderId不能为空");
}
Integer orderStatus = unionpayOrderService.getOrderStatusByBillId(form.getBillId());
JSONObject data = new JSONObject();
data.put("orderStatus", orderStatus);
return EResponse.ok(data);
}
}
package com.esv.freight.customer.module.pay.dao; package com.esv.freight.customer.module.pay.dao;
import com.esv.freight.customer.module.pay.entity.GoodsOwnerPayOrderEntity;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.esv.freight.customer.module.pay.entity.CustomerUnionpayOrderEntity;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/** /**
* 货主支付订单表 * 客户银联支付订单表
* *
* @author 黄朝斌 * @author 黄朝斌
* @email huangchaobin@esvtek.com * @email huangchaobin@esvtek.com
* @date 2020-05-30 13:41:51 * @date 2020-06-03 14:45:31
*/ */
@Mapper @Mapper
public interface GoodsOwnerPayOrderDao extends BaseMapper<GoodsOwnerPayOrderEntity> { public interface CustomerUnionpayOrderDao extends BaseMapper<CustomerUnionpayOrderEntity> {
/**
* description 通过账单号查询支付订单记录
* param [billId]
* return List<CustomerPayOrderEntity>
* author HuangChaobin
* createTime 2020/06/03 9:27
**/
List<CustomerUnionpayOrderEntity> selectByBillId(String billId);
} }
...@@ -4,21 +4,21 @@ import com.baomidou.mybatisplus.annotation.FieldFill; ...@@ -4,21 +4,21 @@ import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
import lombok.Data;
/** /**
* 货主支付订单表 * 客户银联支付订单表
* *
* @author 黄朝斌 * @author 黄朝斌
* @email huangchaobin@esvtek.com * @email huangchaobin@esvtek.com
* @date 2020-05-30 13:41:51 * @date 2020-05-30 13:41:51
*/ */
@Data @Data
@TableName("goods_owner_pay_order") @TableName("customer_unionpay_order")
public class GoodsOwnerPayOrderEntity implements Serializable { public class CustomerUnionpayOrderEntity implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** /**
...@@ -37,17 +37,21 @@ public class GoodsOwnerPayOrderEntity implements Serializable { ...@@ -37,17 +37,21 @@ public class GoodsOwnerPayOrderEntity implements Serializable {
@TableField(fill = FieldFill.INSERT) @TableField(fill = FieldFill.INSERT)
private Long departmentId; private Long departmentId;
/** /**
* 支付方式:10-银联 * 客户ID
*/ */
private Integer payType; private Long customerId;
/** /**
* 商户代码(网络货运平台在支付平台的商户号) * 商户代码(网络货运平台在支付平台的商户号)
*/ */
private String merId; private String merId;
/** /**
* 业务订单号 * 单号
*/ */
private String orderId; private String billId;
/**
* 账单描述
*/
private String billDesc;
/** /**
* 请求支付平台订单发送时间 * 请求支付平台订单发送时间
*/ */
...@@ -56,22 +60,22 @@ public class GoodsOwnerPayOrderEntity implements Serializable { ...@@ -56,22 +60,22 @@ public class GoodsOwnerPayOrderEntity implements Serializable {
* 交易金额(单位为分) * 交易金额(单位为分)
*/ */
private Long txnAmt; private Long txnAmt;
/**
* 订单描述
*/
private String orderDesc;
/** /**
* 支付平台受理订单号 * 支付平台受理订单号
*/ */
private String tn; private String tn;
/** /**
* 订单状态:1-支付中、2-支付成功、3-支付失败 * 订单状态:1-支付中、2-支付结果确认中、3-支付成功、4-支付失败
*/
private Integer orderStatus;
/**
* 请求银联查询次数,最多6次
*/ */
private Long orderStatus; private Integer queryCount;
/** /**
* 查询流水号(消费交易的流水号,供后续查询用) * 银联交易状态
*/ */
private String queryId; private String origRespCode;
/** /**
* 创建时间 * 创建时间
*/ */
...@@ -80,10 +84,7 @@ public class GoodsOwnerPayOrderEntity implements Serializable { ...@@ -80,10 +84,7 @@ public class GoodsOwnerPayOrderEntity implements Serializable {
/** /**
* 修改时间 * 修改时间
*/ */
@TableField(fill = FieldFill.UPDATE)
private Date updateTime; private Date updateTime;
/**
* 支付平台回调时间
*/
private Date callbackTime;
} }
package com.esv.freight.customer.module.pay.form;
import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* @description:
* @project: freight-customer-service
* @name: com.esv.freight.customer.module.pay.form.CustomerPayOrderForm
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/06/01 17:15
* @version:1.0
*/
@Data
public class CustomerPayOrderForm {
/**
* 客户ID
*/
@NotNull(message = "参数customerId不能为空")
private Long customerId;
/**
* 账单号
*/
@NotBlank(message = "参数billId不能为空")
@Length(min = 8, max = 40, message = "参数billId长度不合法")
private String billId;
/**
* 账单描述
*/
@NotBlank(message = "参数billDesc不能为空")
@Length(max = 32, message = "参数billDesc长度不合法")
private String billDesc;
/**
* 交易金额(单位为分)
*/
@NotNull(message = "参数txnAmt不能为空")
private Long txnAmt;
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}
}
package com.esv.freight.customer.module.pay.form;
import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.validator.constraints.Length;
/**
* @description:
* @project: freight-customer-service
* @name: com.esv.freight.customer.module.pay.form.UnionPayOrderQueryForm
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/06/03 13:29
* @version:1.0
*/
@Data
public class UnionPayOrderQueryForm {
/**
* 订单流水号
*/
@Length(max = 50, message = "参数tn长度不合法")
private String tn;
/**
* 订单号
*/
@Length(max = 50, message = "参数billId长度不合法")
private String billId;
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}
}
package com.esv.freight.customer.module.pay.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.esv.freight.customer.module.pay.entity.GoodsOwnerPayOrderEntity;
/**
* 货主支付订单表
*
* @author 黄朝斌
* @email huangchaobin@esvtek.com
* @date 2020-05-30 13:41:51
*/
public interface GoodsOwnerPayOrderService extends IService<GoodsOwnerPayOrderEntity> {
}
package com.esv.freight.customer.module.pay.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.esv.freight.customer.module.pay.entity.CustomerUnionpayOrderEntity;
import com.esv.freight.customer.module.pay.form.CustomerPayOrderForm;
import java.util.List;
/**
* 货主支付订单表
*
* @author 黄朝斌
* @email huangchaobin@esvtek.com
* @date 2020-05-30 13:41:51
*/
public interface UnionpayOrderService extends IService<CustomerUnionpayOrderEntity> {
/**
* description 通过账单号查询记录
* param [billId]
* return com.esv.freight.customer.module.pay.entity.GoodsOwnerPayOrderEntity
* author HuangChaobin
* createTime 2020/06/02 15:48
**/
CustomerUnionpayOrderEntity getRecordByBillId(String billId);
/**
* description 通过订单流水号查询记录
* param [tn]
* return com.esv.freight.customer.module.pay.entity.CustomerUnionpayOrderEntity
* author HuangChaobin
* createTime 2020/06/03 19:38
**/
CustomerUnionpayOrderEntity getRecordByTn(String tn);
/**
* description 创建客户支付订单
* param [form]
* return java.lang.String
* author HuangChaobin
* createTime 2020/06/03 9:38
**/
String createCustomerPayOrder(CustomerPayOrderForm form);
/**
* description 通过订单流水号查询订单支付状态
* param [tn]
* return java.lang.Integer
* author HuangChaobin
* createTime 2020/06/03 10:58
**/
Integer getOrderStatusByTn(String tn);
/**
* description 通过账单号查询订单支付状态
* param [billId]
* return java.lang.Integer
* author HuangChaobin
* createTime 2020/06/03 11:01
**/
Integer getOrderStatusByBillId(String billId);
/**
* description 请求银联查询订单支付状态
* param [customerUnionPayOrderEntity]
* return java.lang.Integer
* author HuangChaobin
* createTime 2020/06/04 9:44
**/
Integer queryOrderStatus2Unionpay(CustomerUnionpayOrderEntity customerUnionPayOrderEntity);
}
package com.esv.freight.customer.module.pay.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.esv.freight.customer.module.pay.dao.GoodsOwnerPayOrderDao;
import com.esv.freight.customer.module.pay.entity.GoodsOwnerPayOrderEntity;
import com.esv.freight.customer.module.pay.service.GoodsOwnerPayOrderService;
import org.springframework.stereotype.Service;
@Service("goodsOwnerPayOrderService")
public class GoodsOwnerPayOrderServiceImpl extends ServiceImpl<GoodsOwnerPayOrderDao, GoodsOwnerPayOrderEntity> implements GoodsOwnerPayOrderService {
}
\ No newline at end of file
package com.esv.freight.customer.module.pay.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.esv.freight.customer.common.component.ErrorMessageComponent;
import com.esv.freight.customer.common.exception.EException;
import com.esv.freight.customer.common.pojo.AppUnionPayOrderReq;
import com.esv.freight.customer.common.pojo.AppUnionPayOrderRes;
import com.esv.freight.customer.common.unionpay.UnionPayComponent;
import com.esv.freight.customer.common.util.DateUtils;
import com.esv.freight.customer.feign.FreightBillService;
import com.esv.freight.customer.module.pay.PayConstants;
import com.esv.freight.customer.module.pay.dao.CustomerUnionpayOrderDao;
import com.esv.freight.customer.module.pay.entity.CustomerUnionpayOrderEntity;
import com.esv.freight.customer.module.pay.form.CustomerPayOrderForm;
import com.esv.freight.customer.module.pay.service.UnionpayOrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service("unionpayOrderService")
@Slf4j
public class UnionpayOrderServiceImpl extends ServiceImpl<CustomerUnionpayOrderDao, CustomerUnionpayOrderEntity> implements UnionpayOrderService {
@Value("${unionpay.mer-id}")
private String unionPayMerId;
private ErrorMessageComponent errorMessageComponent;
private UnionPayComponent unionPayComponent;
private FreightBillService freightBillService;
@Autowired
public UnionpayOrderServiceImpl(ErrorMessageComponent errorMessageComponent, UnionPayComponent unionPayComponent,
FreightBillService freightBillService) {
this.errorMessageComponent = errorMessageComponent;
this.unionPayComponent = unionPayComponent;
this.freightBillService = freightBillService;
}
@Override
public CustomerUnionpayOrderEntity getRecordByBillId(String billId) {
return this.baseMapper.selectOne(new LambdaQueryWrapper<CustomerUnionpayOrderEntity>().eq(CustomerUnionpayOrderEntity::getBillId, billId));
}
@Override
public CustomerUnionpayOrderEntity getRecordByTn(String tn) {
return this.baseMapper.selectOne(new LambdaQueryWrapper<CustomerUnionpayOrderEntity>().eq(CustomerUnionpayOrderEntity::getTn, tn));
}
@Override
public String createCustomerPayOrder(CustomerPayOrderForm form) {
// 判断订单是否存在
CustomerUnionpayOrderEntity unionpayOrderEntity = this.getRecordByBillId(form.getBillId());
if (null != unionpayOrderEntity) {
if (PayConstants.ORDER_STATUS_SUCCESS.equals(unionpayOrderEntity.getOrderStatus())) {
throw new EException(1001, errorMessageComponent.getPayCustomerOrderCreate1001());
} else {
// TODO 请求银联查询订单支付状态
}
}
// 请求支付平台获取支付订单号
AppUnionPayOrderReq appUnionPayOrderReq = new AppUnionPayOrderReq();
appUnionPayOrderReq.setMerId(unionPayMerId);
appUnionPayOrderReq.setBillId(form.getBillId());
appUnionPayOrderReq.setBillDesc(form.getBillDesc());
appUnionPayOrderReq.setTxnAmt(form.getTxnAmt());
appUnionPayOrderReq.setTxnTime(DateUtils.format(DateUtils.getSysdate(), DateUtils.DATE_FORMAT4));
AppUnionPayOrderRes appUnionPayOrderRes = unionPayComponent.createAppUnionPayOrder(appUnionPayOrderReq);
String tn = appUnionPayOrderRes.getTn();
// 新增或更新订单
CustomerUnionpayOrderEntity customerUnionPayOrderEntity = new CustomerUnionpayOrderEntity();
if (null == unionpayOrderEntity) {
// 新增订单信息
customerUnionPayOrderEntity.setCustomerId(form.getCustomerId());
customerUnionPayOrderEntity.setMerId(unionPayMerId);
customerUnionPayOrderEntity.setBillId(form.getBillId());
customerUnionPayOrderEntity.setBillDesc(form.getBillDesc());
customerUnionPayOrderEntity.setTxnTime(appUnionPayOrderReq.getTxnTime());
customerUnionPayOrderEntity.setTxnAmt(form.getTxnAmt());
customerUnionPayOrderEntity.setTn(tn);
customerUnionPayOrderEntity.setOrderStatus(PayConstants.ORDER_STATUS_PAYING);
this.getBaseMapper().insert(customerUnionPayOrderEntity);
} else {
// 更新订单信息
customerUnionPayOrderEntity.setId(unionpayOrderEntity.getId());
customerUnionPayOrderEntity.setTn(tn);
customerUnionPayOrderEntity.setTxnTime(appUnionPayOrderReq.getTxnTime());
customerUnionPayOrderEntity.setBillDesc(form.getBillDesc());
customerUnionPayOrderEntity.setTxnAmt(form.getTxnAmt());
this.baseMapper.updateById(customerUnionPayOrderEntity);
}
return tn;
}
@Override
public Integer getOrderStatusByTn(String tn) {
CustomerUnionpayOrderEntity customerUnionPayOrderEntity = this.getRecordByTn(tn);
if (null == customerUnionPayOrderEntity) {
throw new EException(1001, "该支付订单流水号[" + tn + "]不存在");
}
return this.queryOrderStatus2Unionpay(customerUnionPayOrderEntity);
}
@Override
public Integer getOrderStatusByBillId(String billId) {
CustomerUnionpayOrderEntity customerUnionPayOrderEntity = this.getRecordByBillId(billId);
if (null == customerUnionPayOrderEntity) {
throw new EException(1001, "该账单号[" + billId + "]不存在");
}
return this.queryOrderStatus2Unionpay(customerUnionPayOrderEntity);
}
/**
* description 请求银联查询订单支付状态
* param [customerUnionPayOrderEntity]
* return java.lang.Integer
* author HuangChaobin
* createTime 2020/06/03 11:08
**/
@Override
public Integer queryOrderStatus2Unionpay(CustomerUnionpayOrderEntity customerUnionPayOrderEntity) {
Integer orderStatus;
// 判断订单是否支付完成
orderStatus = customerUnionPayOrderEntity.getOrderStatus();
if (PayConstants.ORDER_STATUS_SUCCESS.equals(orderStatus)) {
return orderStatus;
} else {
// 判断订单是否超过30分钟,暂时取消这个判断逻辑
// Long current = System.currentTimeMillis();
// Long createTime = customerUnionPayOrderEntity.getCreateTime().getTime();
// if (PayConstants.UNIONPAY_QUERY_MAX_TIME < (current - createTime)) {
// return orderStatus;
// }
}
// 请求银联查询订单支付状态
AppUnionPayOrderReq appUnionPayOrderReq = new AppUnionPayOrderReq();
appUnionPayOrderReq.setMerId(unionPayMerId);
appUnionPayOrderReq.setBillId(customerUnionPayOrderEntity.getBillId());
appUnionPayOrderReq.setTxnTime(customerUnionPayOrderEntity.getTxnTime());
Map<String, String> queryResultMap = unionPayComponent.queryUnionPayOrder(appUnionPayOrderReq);
if ("00".equals(queryResultMap.get("respCode"))) {
String origRespCode = queryResultMap.get("origRespCode");
if (("00").equals(origRespCode)) {
//交易成功,更新商户订单状态
orderStatus = PayConstants.ORDER_STATUS_SUCCESS;
} else if (("03").equals(origRespCode) ||
("04").equals(origRespCode) ||
("05").equals(origRespCode)) {
//订单处理中或交易状态未明,需稍后发起交易状态查询交易 【如果最终尚未确定交易是否成功请以对账文件为准】
orderStatus = PayConstants.ORDER_STATUS_CONFIRM;
} else {
//其他应答码为交易失败
orderStatus = PayConstants.ORDER_STATUS_FAILURE;
}
// 更新订单支付状态
CustomerUnionpayOrderEntity orderEntity = new CustomerUnionpayOrderEntity();
orderEntity.setId(customerUnionPayOrderEntity.getId());
orderEntity.setQueryCount(customerUnionPayOrderEntity.getQueryCount() + 1);
orderEntity.setOrigRespCode(origRespCode);
orderEntity.setOrderStatus(orderStatus);
this.baseMapper.updateById(orderEntity);
// 通知账单服务:更改应收账单支付状态
JSONObject feignReqJson = new JSONObject();
feignReqJson.put("billNo", customerUnionPayOrderEntity.getBillId());
feignReqJson.put("payState", orderStatus);
try {
log.info("Feign请求[账单服务]传参:{}", feignReqJson.toJSONString());
JSONObject feignResJson = freightBillService.updatePayState(feignReqJson);
log.info("Feign请求[账单服务]返回:{}", feignResJson.toJSONString());
} catch (Exception e) {
log.error("Feign请求[账单服务]失败:{}", e.getMessage(), e);
}
}
return orderStatus;
}
}
\ No newline at end of file
package com.esv.freight.customer.module.servlet;
import com.esv.freight.customer.common.util.ResUtils;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* @description:
* @project: freight-customer-service
* @name: com.esv.freight.customer.module.servlet.AppFrontRcvServlet
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/06/02 18:57
* @version:1.0
*/
@Slf4j
public class AppFrontRcvServlet extends HttpServlet {
/**
* description 前端APP支付成功通过银联支付控件调用(frontUrl)
* param [req, resp]
* return void
* author HuangChaobin
* createTime 2020/06/02 19:28
**/
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
Map<String, String> reqParam = this.getAllRequestParam(req);
log.info(reqParam.toString());
//返回给银联服务器http 200 状态码
ResUtils.response(resp, HttpServletResponse.SC_OK, "text/plain;charset=utf-8", "ok");
}
/**
* 获取请求参数中所有的信息
*
* @param request
* @return
*/
private Map<String, String> getAllRequestParam(final HttpServletRequest request) {
Map<String, String> res = new HashMap<>(32);
Enumeration<?> temp = request.getParameterNames();
if (null != temp) {
while (temp.hasMoreElements()) {
String en = (String) temp.nextElement();
String value = request.getParameter(en);
res.put(en, value);
// 在报文上送时,如果字段的值为空,则不上送<下面的处理为在获取所有参数数据时,判断若值为空,则删除这个字段>
if (null == res.get(en) || "".equals(res.get(en))) {
res.remove(en);
}
}
}
return res;
}
}
package com.esv.freight.customer.module.servlet;
import com.esv.freight.customer.common.constants.SDKConstants;
import com.esv.freight.customer.common.unionpay.AcpService;
import com.esv.freight.customer.common.unionpay.DemoBase;
import com.esv.freight.customer.common.util.ResUtils;
import com.esv.freight.customer.module.pay.entity.CustomerUnionpayOrderEntity;
import com.esv.freight.customer.module.pay.service.UnionpayOrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
/**
* @description:
* @project: freight-customer-service
* @name: com.esv.freight.customer.module.servlet.UnionpayBackRcvServlet
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/06/02 19:35
* @version:1.0
*/
@Slf4j
public class UnionpayBackRcvServlet extends HttpServlet {
@Autowired
private UnionpayOrderService unionpayOrderService;
/**
* description 银联异步通知支付结果
* param [req, resp]
* return void
* author HuangChaobin
* createTime 2020/06/02 19:40
**/
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
log.info("[银联开放平台]接收后台通知开始");
// 获取银联通知服务器发送的后台通知参数
Map<String, String> reqParam = getAllRequestParamStream(req);
//重要!验证签名前不要修改reqParam中的键值对的内容,否则会验签不过
boolean validateResult = AcpService.validate(reqParam, reqParam.get(SDKConstants.param_encoding));
if (!validateResult) {
log.error("[银联开放平台]验证签名结果:失败");
} else {
log.info("[银联开放平台]验证签名结果:成功");
//判断respCode=00、A6后,对涉及资金类的交易,请再发起查询接口查询,确定交易成功后更新数据库。
String respCode = reqParam.get("respCode");
if ("00".equals(respCode) || "A6".equals(respCode)) {
String orderId = reqParam.get("orderId");
CustomerUnionpayOrderEntity customerUnionPayOrderEntity = unionpayOrderService.getRecordByBillId(orderId);
if (null == customerUnionPayOrderEntity) {
log.error("[银联开放平台]该账单号[" + orderId + "]不存在");
} else {
unionpayOrderService.queryOrderStatus2Unionpay(customerUnionPayOrderEntity);
}
}
}
log.info("[银联开放平台]接收后台通知结束");
//返回给银联服务器http 200 状态码
ResUtils.response(resp, HttpServletResponse.SC_OK, "text/plain;charset=utf-8", "ok");
}
/**
* 获取请求参数中所有的信息。
* 非struts可以改用此方法获取,好处是可以过滤掉request.getParameter方法过滤不掉的url中的参数。
* struts可能对某些content-type会提前读取参数导致从inputstream读不到信息,所以可能用不了这个方法。理论应该可以调整struts配置使不影响,但请自己去研究。
* 调用本方法之前不能调用req.getParameter("key");这种方法,否则会导致request取不到输入流。
* @param request
* @return
*/
private Map<String, String> getAllRequestParamStream(final HttpServletRequest request) {
Map<String, String> res = new HashMap<>(32);
try {
String notifyStr = new String(IOUtils.toByteArray(request.getInputStream()), DemoBase.encoding);
log.info("[银联开放平台]收到通知报文:" + notifyStr);
String[] kvs = notifyStr.split("&");
for (String kv : kvs) {
String[] tmp = kv.split("=");
if (tmp.length >= 2) {
String key = tmp[0];
String value = URLDecoder.decode(tmp[1], DemoBase.encoding);
res.put(key, value);
}
}
} catch (Exception e) {
log.error("[银联开放平台]报文解析出错: {}", e.getMessage(), e);
}
return res;
}
}
##############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.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://esvsy.wicp.vip:8082/customer/pay/backRcvResponse
#前台通知地址,填写处理银联前台通知的地址,必须外网能访问
acpsdk.frontUrl=http://esvsy.wicp.vip:8082/customer/pay/frontRcvResponse
#########################入网测试环境签名证书配置 ################################
# 多证书的情况证书路径为代码指定,可不对此块做配置。
# 签名证书路径,必须使用绝对路径,如果不想使用绝对路径,可以自行实现相对路径获取证书的方法;测试证书所有商户共用开发包中的测试签名证书,生产环境请从cfca下载得到。
# windows样例:
acpsdk.signCert.path=/home/esv/app/tomcat/freight/customer-service/certs/acp_test_sign.pfx
# 签名证书密码,测试环境固定000000,生产环境请修改为从cfca下载的正式证书的密码,正式环境证书密码位数需小于等于6位,否则上传到商户服务网站会失败
acpsdk.signCert.pwd=000000
# 签名证书类型,固定不需要修改
acpsdk.signCert.type=PKCS12
##########################加密证书配置################################
# 敏感信息加密证书路径(商户号开通了商户对敏感信息加密的权限,需要对 卡号accNo,pin和phoneNo,cvn2,expired加密(如果这些上送的话),对敏感信息加密使用)
acpsdk.encryptCert.path=/home/esv/app/tomcat/freight/customer-service/certs/acp_test_enc.cer
##########################验签证书配置################################
# 验签中级证书路径(银联提供)
acpsdk.middleCert.path=/home/esv/app/tomcat/freight/customer-service/certs/acp_test_middle.cer
# 验签根证书路径(银联提供)
acpsdk.rootCert.path=/home/esv/app/tomcat/freight/customer-service/certs/acp_test_root.cer
##############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.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://esvsy.wicp.vip:8082/customer/pay/backRcvResponse
#前台通知地址,填写处理银联前台通知的地址,必须外网能访问
acpsdk.frontUrl=http://esvsy.wicp.vip:8082/customer/pay/frontRcvResponse
#########################入网测试环境签名证书配置 ################################
# 多证书的情况证书路径为代码指定,可不对此块做配置。
# 签名证书路径,必须使用绝对路径,如果不想使用绝对路径,可以自行实现相对路径获取证书的方法;测试证书所有商户共用开发包中的测试签名证书,生产环境请从cfca下载得到。
# windows样例:
acpsdk.signCert.path=D:/certs/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
...@@ -63,10 +63,16 @@ hystrix: ...@@ -63,10 +63,16 @@ hystrix:
ribbon: ribbon:
eager-load: eager-load:
enabled: true enabled: true
clients: freight-base-service,freight-file-service,freight-app-service clients: freight-base-service,freight-file-service,freight-app-service,freight-bill-service
font: font:
windows: windows:
dir: /usr/share/fonts/windows dir: /usr/share/fonts/windows
#商户代码(网络货运平台在支付平台的商户号)
unionpay:
mer-id: 777290058182031
acp-sdk:
properties:
path: /home/esv/app/tomcat/freight/customer-service/config/acp_sdk.properties
error-message: error-message:
goodsowner: goodsowner:
account: account:
...@@ -272,3 +278,8 @@ error-message: ...@@ -272,3 +278,8 @@ error-message:
1001: 无效的合同编号 1001: 无效的合同编号
get-by-number: get-by-number:
1001: 无效的合同编号 1001: 无效的合同编号
pay:
customer:
order:
create:
1001: 订单已支付成功
\ No newline at end of file
...@@ -63,10 +63,16 @@ hystrix: ...@@ -63,10 +63,16 @@ hystrix:
ribbon: ribbon:
eager-load: eager-load:
enabled: true enabled: true
clients: freight-base-service,freight-file-service,freight-app-service clients: freight-base-service,freight-file-service,freight-app-service,freight-bill-service
font: font:
windows: windows:
dir: /usr/share/fonts/windows dir: /usr/share/fonts/windows
#商户代码(网络货运平台在支付平台的商户号)
unionpay:
mer-id: 777290058182031
acp-sdk:
properties:
path: /home/esv/app/tomcat/freight/customer-service/config/acp_sdk.properties
error-message: error-message:
goodsowner: goodsowner:
account: account:
...@@ -272,3 +278,8 @@ error-message: ...@@ -272,3 +278,8 @@ error-message:
1001: 无效的合同编号 1001: 无效的合同编号
get-by-number: get-by-number:
1001: 无效的合同编号 1001: 无效的合同编号
pay:
customer:
order:
create:
1001: 订单已支付成功
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.esv.freight.customer.module.pay.dao.GoodsOwnerPayOrderDao"> <mapper namespace="com.esv.freight.customer.module.pay.dao.CustomerUnionpayOrderDao">
<!-- 可根据自己的需求,是否要使用 --> <!-- 可根据自己的需求,是否要使用 -->
<resultMap type="com.esv.freight.customer.module.pay.entity.GoodsOwnerPayOrderEntity" id="goodsOwnerPayOrderMap"> <resultMap type="com.esv.freight.customer.module.pay.entity.CustomerUnionpayOrderEntity" id="customerUnionpayOrderMap">
<result property="id" column="id"/> <result property="id" column="id"/>
<result property="tenantId" column="tenant_id"/> <result property="tenantId" column="tenant_id"/>
<result property="departmentId" column="department_id"/> <result property="departmentId" column="department_id"/>
<result property="payType" column="pay_type"/> <result property="customerId" column="customer_id"/>
<result property="merId" column="mer_id"/> <result property="merId" column="mer_id"/>
<result property="orderId" column="order_id"/> <result property="billId" column="bill_id"/>
<result property="billDesc" column="bill_desc"/>
<result property="txnTime" column="txn_time"/> <result property="txnTime" column="txn_time"/>
<result property="txnAmt" column="txn_amt"/> <result property="txnAmt" column="txn_amt"/>
<result property="orderDesc" column="order_desc"/>
<result property="tn" column="tn"/> <result property="tn" column="tn"/>
<result property="orderStatus" column="order_status"/> <result property="orderStatus" column="order_status"/>
<result property="queryId" column="query_id"/> <result property="queryCount" column="query_count"/>
<result property="origRespCode" column="orig_resp_code"/>
<result property="createTime" column="create_time"/> <result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/> <result property="updateTime" column="update_time"/>
<result property="callbackTime" column="callback_time"/>
</resultMap> </resultMap>
<select id="selectByBillId" parameterType="java.lang.String" resultType="com.esv.freight.customer.module.pay.entity.CustomerUnionpayOrderEntity">
select *
from customer_unionpay_order
where bill_id = #{billId}
order by txn_time desc
</select>
</mapper> </mapper>
\ No newline at end of file
package com.esv.freight.customer.common.unionpay;
import com.esv.freight.customer.BaseTestController;
import com.esv.freight.customer.common.pojo.AppUnionPayOrderReq;
import lombok.extern.slf4j.Slf4j;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
/**
* @description:
* @project: freight-customer-service
* @name: com.esv.freight.customer.common.unionpay.UnionPayComponentTest
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/05/30 15:00
* @version:1.0
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Transactional
@Rollback(false)
public class UnionPayComponentTest extends BaseTestController {
@Autowired
UnionPayComponent unionPayComponent;
@Test
public void a1_createAppUnionPayOrder_test() throws Exception {
AppUnionPayOrderReq appUnionPayOrderReq = new AppUnionPayOrderReq();
appUnionPayOrderReq.setMerId("777290058182031");
appUnionPayOrderReq.setBillId("DD" + System.currentTimeMillis());
appUnionPayOrderReq.setTxnAmt(100L);
appUnionPayOrderReq.setBillDesc("测试订单");
unionPayComponent.createAppUnionPayOrder(appUnionPayOrderReq);
}
}
package com.esv.freight.customer.module.pay.controller;
import com.alibaba.fastjson.JSONObject;
import com.esv.freight.customer.BaseTestController;
import com.esv.freight.customer.common.response.ECode;
import com.esv.freight.customer.module.pay.form.CustomerPayOrderForm;
import com.esv.freight.customer.module.pay.form.UnionPayOrderQueryForm;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.transaction.annotation.Transactional;
/**
* @description:
* @project: freight-customer-service
* @name: com.esv.freight.customer.module.pay.controller.PayControllerTest
* @author: 黄朝斌
* @email: huangchaobin@esvtek.com
* @createTime: 2020/06/02 9:21
* @version:1.0
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Transactional
@Rollback(false)
public class PayControllerTest extends BaseTestController {
/**
* 创建支付订单
**/
@Test
public void a1_createPayOrder_success_test() throws Exception {
String url = "/goodsowner/pay/createPayOrder";
// 构造数据
CustomerPayOrderForm form = new CustomerPayOrderForm();
form.setCustomerId(1L);
form.setBillId("DD" + System.currentTimeMillis());
form.setBillDesc("测试支付订单");
form.setTxnAmt(1L);
MvcResult mvcResult = this.getMockMvc().perform(MockMvcRequestBuilders.post(url)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.headers(this.getDefaultHttpHeaders())
.content(form.toString()))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
String responseStr = mvcResult.getResponse().getContentAsString();
log.info(responseStr);
JSONObject result = JSONObject.parseObject(responseStr);
Assert.assertEquals(ECode.SUCCESS.code(), result.getIntValue("code"));
Assert.assertTrue(result.getJSONObject("data").containsKey("tn"));
}
/**
* 通过流水号查询订单状态
**/
@Test
public void b1_getOrderStatusByTn_success_test() throws Exception {
String url = "/goodsowner/pay/getOrderStatusByTn";
// 构造数据
UnionPayOrderQueryForm form = new UnionPayOrderQueryForm();
form.setTn("981455249750781438820");
MvcResult mvcResult = this.getMockMvc().perform(MockMvcRequestBuilders.post(url)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.headers(this.getDefaultHttpHeaders())
.content(form.toString()))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
String responseStr = mvcResult.getResponse().getContentAsString();
log.info(responseStr);
JSONObject result = JSONObject.parseObject(responseStr);
Assert.assertEquals(ECode.SUCCESS.code(), result.getIntValue("code"));
Assert.assertTrue(result.getJSONObject("data").containsKey("orderStatus"));
}
/**
* 通过流水号查询订单状态
**/
@Test
public void b1_getOrderStatusByOrderId_success_test() throws Exception {
String url = "/goodsowner/pay/getOrderStatusByOrderId";
// 构造数据
UnionPayOrderQueryForm form = new UnionPayOrderQueryForm();
form.setBillId("DD1591163252774");
MvcResult mvcResult = this.getMockMvc().perform(MockMvcRequestBuilders.post(url)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.headers(this.getDefaultHttpHeaders())
.content(form.toString()))
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
String responseStr = mvcResult.getResponse().getContentAsString();
log.info(responseStr);
JSONObject result = JSONObject.parseObject(responseStr);
Assert.assertEquals(ECode.SUCCESS.code(), result.getIntValue("code"));
Assert.assertTrue(result.getJSONObject("data").containsKey("orderStatus"));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment