记得上下班打卡 | git大法好,push需谨慎

Commit 70db7894 authored by anjiabin's avatar anjiabin

提交unionpay代码

parent a3ef7eb2
package com.liquidnet.service.dragon.channel.unionpay.sdk;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import java.io.IOException;
import java.security.PublicKey;
import java.util.Map;
/**
* @ClassName Acp6Service
* @Description 全渠道6.0接口服务类,接入商户集成请可以直接参考使用本类中的方法
* @date 2020/03
*/
@Slf4j
public class Acp6Service {
/**
* 请求报文签名(使用配置文件中配置的私钥证书或者对称密钥签名)<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) {
return signByCertInfo(reqData, SDKConfig.getConfig().getSignCertPath(), SDKConfig.getConfig().getSignCertPwd(), encoding);
}
/**
* 多证书签名(通过传入私钥证书路径和密码签名)<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) {
Map<String, String> data = SDKUtil.filterBlank(reqData);
if (SDKUtil.isEmpty(encoding)) {
encoding = "UTF-8";
}
if (SDKUtil.isEmpty(certPath) || SDKUtil.isEmpty(certPwd)) {
log.error("CertPath or CertPwd is empty");
return data;
}
try {
data.put(SDKConstants.param_certId, CertUtil.getCertIdByKeyStoreMap(certPath, certPwd));
data.put(SDKConstants.param_signature, SDKUtil.signRsa2(data, certPath, certPwd, encoding));
return data;
} catch (Exception e) {
log.error("Sign Error", e);
return data;
}
}
/**
* 验证签名<br>
* @param data 返回报文数据<br>
* @param encoding 上送请求报文域encoding字段的值<br>
* @return true 通过 false 未通过<br>
*/
public static boolean validate(Map<String, String> data, String encoding) {
log.info("验签处理开始");
if (SDKUtil.isEmpty(encoding)) {
encoding = "UTF-8";
}
String certId = data.get(SDKConstants.param_certId);
log.info("对返回报文串验签使用的验签公钥序列号:[" + certId + "]");
PublicKey verifyKey = CertUtil.getValidatePublicKey(certId);
if(verifyKey == null) {
log.error("未找到此序列号证书。");
return false;
}
try {
boolean result = SDKUtil.verifyRsa2(data, verifyKey, encoding);
log.info("验签" + (result ? "成功" : "失败") + "。");
return result;
} catch (Exception e) {
log.error(e.getMessage(), e);
return false;
}
}
/**
* 获取应答报文中的加密公钥证书,并存储到本地,备份原始证书,并自动替换证书<br>
* 更新成功则返回1,无更新返回0,失败异常返回-1<br>
* @return
*/
public static int updateEncryptCert(String strCert, String certType) {
return SDKUtil.updateEncryptCert(strCert, certType);
}
/**
* 密码加密并做base64<br>
* @param accNo 卡号<br>
* @param pin 密码<br>
* @param encoding<br>
* @return 加密的内容<br>
*/
public static String encryptPin(String accNo, String pin, String encoding) {
byte[] pinblock = SecureUtil.pinblock(accNo, pin);
return Base64.encodeBase64String(SecureUtil.encrypt(CertUtil.getPinEncryptCert().pubKey, pinblock));
}
// /**
// * 密码加密并做base64<br>
// * @param accNo 卡号<br>
// * @param pin 密码<br>
// * @param encoding<br>
// * @return 加密的内容<br>
// */
// public static String encryptPin(String pin, String encoding) {
// byte[] pinblock = SecureUtil.pinblock(pin);
// return Base64.encodeBase64String(SecureUtil.encrypt(CertUtil.getPinEncryptCert().pubKey, pinblock));
// }
/**
* 敏感信息加密并做base64(卡号,手机号,cvn2,有效期)<br>
* @param data 送 phoneNo,cvn2,有效期<br>
* @param encoding<br>
* @return 加密的密文<br>
*/
public static String encryptData(String data, String encoding) {
return AcpService.encryptData(data, encoding);
}
/**
* @param data 明文<br>
* @return 加密的密文<br>
*/
public static String encryptData(byte[] data) {
try {
return Base64.encodeBase64String(SecureUtil.encrypt(CertUtil.getEncryptCert().pubKey, data));
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
}
/**
* @param data 明文<br>
* @return 加密的密文<br>
*/
public static String tripleDesEncryptECBPKCS5Padding(byte[] key, byte[] data) {
try {
return Base64.encodeBase64String(SecureUtil.tripleDesEncryptECBPKCS5Padding(key, SecureUtil.rightPadZero(data, 8)));
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
}
/**
* 敏感信息解密,使用配置文件acp_sdk.properties解密<br>
* @param base64EncryptedInfo 加密信息<br>
* @param encoding<br>
* @return 解密后的明文<br>
*/
public static String decryptData(String base64EncryptedInfo, String encoding) {
return AcpService.decryptData(base64EncryptedInfo, encoding);
}
/**
* 敏感信息解密,通过传入的私钥解密<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 AcpService.decryptData(base64EncryptedInfo, certPath, certPwd, encoding);
}
/**
* 获取敏感信息加密证书的物理序列号<br>
* @return
*/
public static String getEncryptCertId(){
return CertUtil.getEncryptCert().certId;
}
/**
* 获取敏感信息加密证书的物理序列号<br>
* @return
*/
public static String getPinEncryptCertId(){
return CertUtil.getPinEncryptCert().certId;
}
/**
* 功能:后台交易提交请求报文并接收同步应答报文<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) {
if(reqData == null || reqUrl == null) {
log.error("null input");
return null;
}
log.info("请求银联地址:" + reqUrl + ",请求参数:" + reqData.toString());
if(reqUrl.startsWith("https://") && !SDKConfig.getConfig().isIfValidateRemoteCert()) {
reqUrl = "u" + reqUrl;
}
try{
byte[] respBytes = HttpsUtil.post(reqUrl, SDKUtil.createLinkString(reqData, false, true, encoding).getBytes(encoding));
if(respBytes == null) {
log.error("post失败");
return null;
}
Map<String,String> result = SDKUtil.parseQString(new String(respBytes, encoding), encoding);
log.info("应答参数:" + result);
return result;
} catch (Exception e) {
log.error("post失败:" + e.getMessage(), e);
return null;
}
}
/**
* 功能:后台交易提交请求报文并接收同步应答报文<br>
* @param reqData 请求报文<br>
* @param reqUrl 请求地址<br>
* @param encoding<br>
* @return 应答http 200返回true ,其他false<br>
*/
public static String postNotice(Map<String, String> reqData, String reqUrl,String encoding) {
if(reqData == null || reqUrl == null) {
log.error("null input");
return null;
}
log.info("请求银联地址:" + reqUrl + ",请求参数:" + reqData.toString());
if(reqUrl.startsWith("https://") && !SDKConfig.getConfig().isIfValidateRemoteCert()) {
reqUrl = "u" + reqUrl;
}
try{
byte[] respBytes = HttpsUtil.post(reqUrl, SDKUtil.createLinkString(reqData, false, true, encoding).getBytes(encoding));
if(respBytes == null) {
log.error("post失败");
return null;
}
String result = new String(respBytes, encoding);
log.info("应答体:" + result);
return result;
} catch (Exception e) {
log.error("post失败:" + e.getMessage(), e);
return null;
}
}
/**
* 对字符串做base64<br>
* @param rawStr<br>
* @param encoding<br>
* @return<br>
* @throws IOException
*/
public static String base64Encode(String rawStr, String encoding){
return AcpService.base64Encode(rawStr, encoding);
}
/**
* 对字符串做base64<br>
* @param base64Str<br>
* @param encoding<br>
* @return<br>
* @throws IOException
*/
public static String base64Decode(String base64Str, String encoding){
return AcpService.base64Decode(base64Str, encoding);
}
}
/**
*
* Licensed Property to China UnionPay Co., Ltd.
*
* (C) Copyright of China UnionPay Co., Ltd. 2010
* All Rights Reserved.
*
*
* Modification History:
* =============================================================================
* Author Date Description
* ------------ ---------- ---------------------------------------------------
* xshu 2014-05-28 报文加密解密等操作的工具类
* =============================================================================
*/
package com.liquidnet.service.dragon.channel.unionpay.sdk;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.crypto.digests.SM3Digest;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import java.security.*;
import java.util.Arrays;
/**
*
* @ClassName SecureUtil
* @Description acpsdk安全算法工具类
* @date 2016-7-22 下午4:08:32
*/
@Slf4j
public class SecureUtil {
/**
* @param bytes
* @return
*/
public static byte[] sha1(byte[] bytes) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
messageDigest.update(bytes);
return messageDigest.digest();
} catch (NoSuchAlgorithmException e) {
log.error("SHA1计算失败", e);
return null;
}
}
/**
* @param bytes
* @return
*/
public static byte[] sha256(byte[] bytes) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(bytes);
return messageDigest.digest();
} catch (Exception e) {
log.error("SHA256计算失败", e);
return null;
}
}
/**
* @param bytes
* @return
*/
public static byte[] sm3(byte[] bytes) {
SM3Digest sm3 = new SM3Digest();
sm3.update(bytes, 0, bytes.length);
byte[] result = new byte[sm3.getDigestSize()];
sm3.doFinal(result, 0);
return result;
}
public static byte[] getSignature(PrivateKey priKey, byte[] digest) {
byte[] mesDigest;
Signature sig;
try {
sig = Signature.getInstance("SHA1withRSA");
sig.initSign(priKey);
sig.update(digest);
mesDigest = sig.sign();
return mesDigest;
} catch (Exception e) {
log.error("签名计算失败", e);
return null;
}
}
public static byte[] getSignatureSHA256(PrivateKey priKey, byte[] digest) {
byte[] mesDigest;
Signature sig;
try {
sig = Signature.getInstance("SHA256withRSA");
sig.initSign(priKey);
sig.update(digest);
mesDigest = sig.sign();
return mesDigest;
} catch (Exception e) {
log.error("签名计算失败", e);
return null;
}
}
public static boolean verifySignature(PublicKey pubKey, byte[] digest, byte[] signature) {
try {
Signature sig = Signature.getInstance("SHA1withRSA");
sig.initVerify(pubKey);
sig.update(digest);
boolean ok = sig.verify(signature);
return ok;
} catch (Exception e) {
log.error("验签异常", e);
return false;
}
}
public static boolean verifySignatureSHA256(PublicKey pubKey, byte[] digest, byte[] signature) {
if (pubKey == null || digest == null || signature == null) {
if(pubKey == null){
log.error("验签时pubKey传入了空值,验签失败");
} else if (digest == null){
log.error("验签时digest传入了空值,验签失败");
} else if (signature == null){
log.error("验签时signature传入了空值,验签失败");
} else {
log.error("验签时传入了空值,验签失败");
}
return false;
}
try {
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify(pubKey);
sig.update(digest);
boolean ok = sig.verify(signature);
return ok;
} catch (Exception e) {
log.error("验签异常", e);
return false;
}
}
public static byte[] encrypt(Key key, byte[] data) {
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(data);
} catch (Exception e) {
log.error("加密失败", e);
return null;
}
}
public static byte[] decrypt(Key Key, byte[] data) {
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, Key);
return cipher.doFinal(data);
} catch (Exception e) {
log.error("解密失败", e);
return null;
}
}
/**
* ANSIX9.8格式(带主账号信息)pinblock
* @param pan 卡号
* @param pin
* @return
*/
public static byte[] pinblock(String pan, String pin){
if(SDKUtil.isEmpty(pan) || SDKUtil.isEmpty(pin)){
log.error("卡号或pin为空,无法算pinblock。");
return null;
}
pan = pan.trim();
pin = pin.trim();
if (!pan.matches("^[0-9]{13,19}$")) {
log.error("卡号格式不对,无法算pinblock。");
return null;
}
if (!pin.matches("^[0-9]{4,6}$")) {
log.error("pin格式不对,无法算pinblock。");
return null;
}
pan = ("0000") + pan.substring(pan.length() - 13, pan.length() - 1);
int blockLen = 8;
try {
pin = "0" + pin.length() + pin;
byte[] pinbyte = Arrays.copyOf(Hex.decodeHex(pin.toCharArray()), blockLen);
Arrays.fill(pinbyte, pin.length()/2, blockLen, (byte)0xff);
byte[] panbyte = Hex.decodeHex(pan.toCharArray());
byte[] tempPin = new byte[blockLen];
for (int i = 0; i < blockLen; i++) {
tempPin[i] = (byte) (pinbyte[i] ^ panbyte[i]);
}
return tempPin;
} catch (Exception e){
log.error("pinblock计算异常啦……", e);
return null;
}
}
// /**
// * ANSI X9.8格式(不带主账号信息)pinblock
// * @param pin
// * @return
// */
// public static byte[] pinblock(String pin){
//
// if(SDKUtil.isEmpty(pin)){
// log.error("卡号或pin为空,无法算pinblock。");
// return null;
// }
// pin = pin.trim();
// if (!pin.matches("^[0-9]{4,6}$")) {
// log.error("pin格式不对,无法算pinblock。");
// return null;
// }
// int blockLen = 8;
// try {
// pin = "0" + pin.length() + pin;
// byte[] pinbyte = Arrays.copyOf(Hex.decodeHex(pin.toCharArray()), blockLen);
// Arrays.fill(pinbyte, pin.length()/2, blockLen, (byte)0xff);
// return pinbyte;
// } catch (Exception e){
// log.error("pinblock计算异常啦……", e);
// return null;
// }
// }
public static byte[] tripleDesEncryptECBPKCS5Padding(byte[] key, byte[] data) {
try {
if(data == null || data.length % 8 != 0)
throw new IllegalArgumentException("data is null or error data length.");
SecretKey sk = getTripleDesKey(key);
Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, sk);
return cipher.doFinal(data);
} catch (Exception e) {
log.error("加密失败", e);
return null;
}
}
/**
* 后补0到位数为unitLength的整数倍
* @param value
* @return
*/
public static byte[] rightPadZero(byte[] value, final int unitLength){
if (value.length % unitLength == 0)
return value;
int len = (value.length/unitLength + 1) * unitLength;
return Arrays.copyOf(value, len);
}
/**
* 通过byte数组得到SecretKey类型的密钥
* @param key
* @return
* @throws IllegalArgumentException
*/
private static SecretKey getTripleDesKey(byte[] key) {
if (key == null || !(key.length== 8||key.length== 16||key.length== 24))
throw new IllegalArgumentException("key is null or error key length.");
byte[] specKey = new byte[24];
try {
switch (key.length) {
case 16:
System.arraycopy(key, 0, specKey, 0, 16);
System.arraycopy(key, 0, specKey, 16, 8);
break;
case 8:
System.arraycopy(key, 0, specKey, 0, 8);
System.arraycopy(key, 0, specKey, 8, 8);
System.arraycopy(key, 0, specKey, 16, 8);
break;
case 24:
System.arraycopy(key, 0, specKey, 0, 24);
break;
default:
throw new IllegalArgumentException("error key length.");
}
DESedeKeySpec ks = new DESedeKeySpec(specKey);
SecretKey sk = SecretKeyFactory.getInstance("DESede")
.generateSecret(ks);
return sk;
} catch (Exception e) {
e.printStackTrace();
throw new SecurityException("exception in 3des-ecb encryption", e);
}
}
}
【哪个sdk新就用哪个哦】【不完全向下兼容注意】
2020/9/29:
增加应答只需要关心200的post。
2020/6/23:
规范大改了,请参考最新规范进行修改。
1. 应答新增returnMsg,调整了应答码和交易状态相关字段。具体的值见规范。
目前使用的:
1)returnCode:代表此次交易请求的业务结果,查询交易表示查询操作的业务结果,具体交易结果,以交易应答码、交易状态码为准。
2)respCode:交易结果应答码。
3)xxxStatus:各类交易的状态
① transferStatus-转账状态,仅转账交易出现
② billStatus-账单状态,仅缴费交易用
③ entryStatus-入账状态,入账状态查询用
④ transStatus-消费/预授权交易的状态,仅消费和预授权查询和通知用
已删除的:resultCode、transCode、transMsg。
2. 应答新增merTransIndex,查询接口会原样应答,但目前并没有往发卡或者二维码的付款方送,大概没别的作用。
3. 应答新增preAuthId,没什么用,如果需要收单手工帮你处理预授权进行撤销或完成时可用提供他们,可方便他们处理。卡号+商户号+preAuthId在预授权超时或结束(完成或撤销)前是唯一的。
4. 新增respCode、transStatus取值TRANS_PRE_AUTH_COMPLETED,表示已被预授权完成。
5. 查询应答新增transStatus字段表示被查询的原交易的状态。
6. respCode和respMsg下沉到bizContent中.
7. traceNo和traceTime替换成清算主键settleKey,settleKey在收单和发卡的清算文件内是唯一的,可用于和收单对账,以及找银联和发卡查交易。
8. merCertId、cupCertId改为certId。
9. accessId:填写商户号。
10. accessType填写0。
11. 订单号、交易时间、商户代码从公共参数下沉到bizContent中。
12. 增加入账状态状态查询接口。
13. 转账交易termId从必填M改成可选O。
14. 两方转账也返回transferStatus。
2020/4/29:
6.0接口地址修正最终版,注意获取地址方式改getTransUrl,配置文件增加acpsdk.transUrl=https://gateway.test.95516.com/api/trans.do。
2020/4/4:
不完全向下兼容,注意对应修改,修改后的样例可参考assets/sdk测试类.
修改后:
全渠道5.0、5.1用AcpService。
全渠道6.0用Acp6Service。
二维码用QrcService。。
LogUtil删除,请改成直接调log4j或slf4j打印。
原二维码DemoBase中:
DemoBase.getAddnCond->QrcService.getKVBase64Field
DemoBase.formInfoBase64->QrcService.getKVBase64Field
DemoBase.getPayeeInfo->QrcService.getKVBase64Field
DemoBase.getPayeeInfoWithEncrpyt->QrcService.getKVEncBase64Field
DemoBase.getPayerInfo->QrcService.getKVBase64Field
DemoBase.getPayerInfoWithEncrpyt->QrcService.getKVEncBase64Field
\ No newline at end of file
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