退款流程:
发送退款请求->同步告诉你请求是否成功->异步告诉你退款是否成功
首先说一下一些请求的参数.银联退款是需要证书的,所以要先将证书加载下来.怎么下载证书我就不多说了,因为我没有做过,在支付的时候就已经下载好了
Map
/*** 银联全渠道系统,产品参数 ***/
contentData.put("version", ‘’5.1.0”); // 版本号
contentData.put("encoding","UTF-8"); //字符集编码 可以使用UTF-8,GBK两种方式
contentData.put("signMethod", " 01"); // 签名方法
contentData.put("txnType", ''04"); // 交易类型 04:退款
contentData.put("txnSubType", "00"); //交易子类 00:默认
contentData.put("bizType","000301"); //产品/业务类型, 退款填写000301
contentData.put("channelType","08"); //渠道类型 08手机
/*** 商户接入参数 ***/
contentData.put("accessType","0"); //接入类型,商户接入填0(0:直连商户, 1: 收单机构 2:平台商户)
contentData.put("merId", merId); //merId:商户号
String orderId=getRandomUUID(); //生成退款订单号
contentData.put("orderId", orderId); //orderId:退货交易的订单号,有商户生成
contentData.put("origQryId", origQryId); //原始消费交易的queryId,这个是在银联支付时,银联异步回调返回来的消费交易流水号
contentData.put("txnTime", new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())); //订单时间
contentData.put("txnAmt", txnAmt); //退款金额
contentData.put("accType", PayConstant.UnionPay.ACC_TYPE); //接入类型 01:银行卡02:存折03:IC卡帐号类型(卡介质)
contentData.put("backUrl", po.getNotify()); //回调地址(一定是要能够请求到的地址)
//下面是对请求参数进行签名
//:首先对参数进行前后去空处理
contentData=filterBlank(contentData);
//去掉空字符串
public static Map
Map
Set
for (String key : keyset) {
String value = contentData.get(key);
if (StringUtils.isNotBlank(value)) {
// 对value值进行去除前后空处理
submitFromData.put(key, value.trim());
}
}
return submitFromData;
}
//添加证书id
contentData.put("certId", certId);证书id
// 将Map信息转换成key1=value1&key2=value2的形式
String stringData = coverMap2String(data);
// 通过SHA256进行摘要并转16进制
byte[] signDigest =sha256X16(stringData, “UTF-8”);
PrivateKey privateKey=null;
byte[] byteSign=.base64Encode(signBySoft256(privateKey, signDigest))
String stringSign=new String(byteSign);
// 设置签名域值
data.put("signature", stringSign);
// 发送请求报文并接受同步应答(默认连接超时时间30秒,读取返回结果超时时间30秒);这里调用signData之后,调用submitUrl之前不能对submitFromData中的键值对做任何修改,如果修改会导致验签不通过
String url="https://gateway.95516.com/gateway/api/backTransReq.do"; //退款请求地址
//发送请求并获得结果
Map
if(rspData.isEmpty()){
System.err.println("请求失败!");
}
//验证签名
validate(rspData, "UTF-8");
//获取响应码
String respCode = rspData.get("respCode");
if (“00”.equals(respCode) {
System.err.pringln("退款请求发送成功");
}
//异步回调
@Path("http:huidiao.com")//这里的路径是你在发请求的时候填写的backurl,注意一定要能够直接请求的地址.不能是内网地址
public void refundSuccess(Map
String respCode = params.get("respCode");
if ("00".equals(respCode)) {
System.err.pringln("退款成功");
}else{
System.err.pringln("退款失败");
}
//生成退款订单号方法
public String getRandomUUID() {
java.util.Date dateNow = new java.util.Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
String dateNowStr = dateFormat.format(dateNow);
StringBuffer sb = new StringBuffer(dateNowStr);
Random rd = new Random();
String n = "";
int rdGet;
do {
rdGet = Math.abs(rd.nextInt()) % 10 + 48;
char num1 = (char) rdGet;
String dd = Character.toString(num1);
n += dd;
} while (n.length() < 6);
sb.append(n);
return sb.toString();
}
/**
* 将Map中的数据转换成key1=value1&key2=value2的形式 不包含签名域signature
*
* @param data 待拼接的Map数据
* @return 拼接好后的字符串
*/
public static String coverMap2String(Map
TreeMap
Iterator
while (it.hasNext()) {
Entry
if (“signature”.equals(en.getKey().trim())) {
continue;
}
tree.put(en.getKey(), en.getValue());
}
it = tree.entrySet().iterator();
StringBuffer sf = new StringBuffer();
while (it.hasNext()) {
Entry
sf.append(en.getKey() +“=” + en.getValue() + “&”);
}
return sf.substring(0, sf.length() - 1);
}
/**
* 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) {
logger.error(e.getMessage(), 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) {
logger.error("SHA256计算失败", e);
return null;
}
}
private static byte[] sha256(byte[] data) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-256");
md.reset();
md.update(data);
return md.digest();
} catch (Exception e) {
logger.error("SHA256计算失败", e);
return null;
}
}
/**
* @param privateKey
* @param data
* @return
* @throws Exception
*/
public static byte[] signBySoft256(PrivateKey privateKey, byte[] data)
throws Exception {
byte[] result = null;
Signature st = Signature.getInstance(BC_PROV_ALGORITHM_SHA256RSA, "BC");
st.initSign(privateKey);
st.update(data);
result = st.sign();
return result;
}
/**
* 验证签名(SHA-1摘要算法)
*
* @param resData 返回报文数据
* @param encoding 上送请求报文域encoding字段的值
* @return true 通过 false 未通过
*/
public static boolean validate(Map
return validate(rspData, encoding);
}
/**
* 验证签名 银联返回给我们的报文也是像在我们上送给银联报文时做的工作(签名)一样,所以在银联返回给我们的参数resData中也是有一个key=signature的值的,
* 但是因为我们配置的银联参数signMethod=01 version5.1.0,银联那边规定如果这么配置的话不需要检验resData中的signature了,我们需要的是
* 检验key=signPubKeyCert的值,而signPubKeyCert就是银联的签名公钥证书
* 综上的意思是按照我们的配置,我们验签的时候只需要检查signPubKeyCert即可,而检查这个只需要用到验签根证书以及中级证书即可,
* 如果后续银联有涉及到其他的交易,那些交易有不同的signMethod及version的话,可能会依赖更多的公钥证书,以后扩展时要注意
*
* @param resData 返回报文数据
* @param encoding 编码格式
* @return
*/
public static boolean validate(Map
logger.info("[SDKUtil --> validate]msg:验签处理开始");
if (isEmpty(encoding)) {
encoding = "UTF-8";
}
String signMethod = resData.get("signMethod"); // 01
String version = resData.get("version"); // 5.1.0
// 获取返回报文的版本号
if ("5.1.0".equals(version) && "01".equals(signMethod)) {
// 1.从返回报文中获取公钥信息转换成公钥对象(key=signPubKeyCert对应的value即是公钥证书的字符串形式),意思就是获取到该报文的 签名公钥证书
String strCert = resData.get("signPubKeyCert");
// logger.info("[SDKUtil --> validate]msg:验签公钥证书(字符串形式)={}", strCert);
X509Certificate x509Cert = CertUtil.genCertificateByStr(strCert); // 把String转换为X509Certificate对象
if (x509Cert == null) {
logger.error("convert signPubKeyCert failed");
return false;
}
// 2.获取到银联的签名公钥证书后要进行验证,确保是银联发送过来的,其中有验证证书链(使用根证书与中级证书来验证)
if (!CertUtil.verifyCertificate(x509Cert)) {
logger.error("验证公钥证书失败,证书信息: ", strCert);
return false;
}
// 3.验签(验证signature对应的值,既验证签名)
String stringSign = resData.get("signature");
logger.info("[SDKUtil --> validate]msg:签名原文={}", stringSign);
// 将Map信息转换成key1=value1&key2=value2的形式
String stringData = coverMap2String(resData);
// logger.info("[SDKUtil --> validate]msg:待验签返回报文串={}", stringData);
try {
// 利用方法来验证
boolean result = validateSignBySoft256(x509Cert.getPublicKey(), base64Decode(stringSign.getBytes(encoding)), sha256X16(stringData, encoding));
logger.info("[SDKUtil --> validate]msg:验证签名={}", (result ? "成功" : "失败"));
return result;
} catch (UnsupportedEncodingException e) {
logger.error(e.getMessage(), e);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
} else {
logger.error("配置信息错误");
}
return false;
}
package cn.com.evlink.evcharge.utils.unionPayCer;
import static cn.com.evlink.evcharge.utils.unionPayCer.SDKConstants.UNIONPAY_CNNAME;
import static cn.com.evlink.evcharge.utils.unionPayCer.SDKUtil.isEmpty;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertPathBuilder;
import java.security.cert.CertStore;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.PKIXCertPathBuilderResult;
import java.security.cert.TrustAnchor;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import cn.com.evlink.evcharge.utils.Configuration;
/**
* @ClassName: CertUtil
* @Description: acpsdk证书工具类,主要用于对证书的加载和使用
*
*/
public class CertUtil {
static Logger logger = LoggerFactory.getLogger(CertUtil.class);
/**
* 添加签名,验签,加密算法提供者 暂时不知道这里是干什么的,但是如果去掉该方法会导致加载证书时报java.security.NoSuchProviderException
*/
private static void addProvider() {
if (Security.getProvider("BC") == null) {
logger.info("add BC provider");
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
} else {
Security.removeProvider("BC"); // 解决eclipse调试时tomcat自动重新加载时,BC存在不明原因异常的问题。
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
logger.info("re-add BC provider");
}
}
/**
* 用配置文件acp_sdk.properties配置路径 加载中级证书
*/
private static void initMiddleCert() {
logger.info("加载中级证书==>" + Configuration.getInstance().getConfig("acpsdk.middleCert.path"));
if (!isEmpty(Configuration.getInstance().getConfig("acpsdk.middleCert.path"))) {
middleCert = initCert(Configuration.getInstance().getConfig("acpsdk.middleCert.path"));
logger.info("Load MiddleCert Successful");
} else {
logger.info("WARN: acpsdk.middle.path is empty");
}
}
/**
* 用配置文件acp_sdk.properties配置路径 加载根证书
*/
private static void initRootCert() {
logger.info("加载根证书==>" + Configuration.getInstance().getConfig("acpsdk.rootCert.path"));
if (!isEmpty(Configuration.getInstance().getConfig("acpsdk.rootCert.path"))) {
rootCert = initCert(Configuration.getInstance().getConfig("acpsdk.rootCert.path"));
logger.info("Load RootCert Successful");
} else {
logger.info("WARN: acpsdk.rootCert.path is empty");
}
}
/**
*
* @param path
* @return
*/
private static X509Certificate initCert(String path) {
X509Certificate encryptCertTemp = null;
CertificateFactory cf = null;
// FileInputStream in = null;
InputStream in = null;
try {
cf = CertificateFactory.getInstance("X.509", "BC");
// in = new FileInputStream(path);
in = getInputStreamByUrl(path);
encryptCertTemp = (X509Certificate) cf.generateCertificate(in);
// 打印证书加载信息,供测试阶段调试
logger.info("[" + path + "][CertId=" + encryptCertTemp.getSerialNumber().toString() + "]");
} catch (CertificateException e) {
logger.error("InitCert Error", e);
} catch (NoSuchProviderException e) {
logger.error("LoadVerifyCert Error No BC Provider", e);
} finally {
if (null != in) {
try {
in.close();
} catch (IOException e) {
logger.error(e.toString());
}
}
}
return encryptCertTemp;
}
/**
* 通过指定路径的私钥证书 获取PrivateKey对象 多证书签名时使用该方法
*
* @return
*/
public static PrivateKey getSignCertPrivateKeyByStoreMap(String certPath, String certPwd) {
if (!keyStoreMap.containsKey(certPath)) {
// keyStoreMap中没有保存该证书的话就把该证书放入到keyStoreMap中
loadSignCert(certPath, certPwd);
}
try {
Enumeration
String keyAlias = null;
if (aliasenum.hasMoreElements()) {
keyAlias = aliasenum.nextElement();
}
PrivateKey privateKey = (PrivateKey) keyStoreMap.get(certPath).getKey(keyAlias, certPwd.toCharArray());
return privateKey;
} catch (KeyStoreException e) {
logger.error("getSignCertPrivateKeyByStoreMap Error", e);
return null;
} catch (UnrecoverableKeyException e) {
logger.error("getSignCertPrivateKeyByStoreMap Error", e);
return null;
} catch (NoSuchAlgorithmException e) {
logger.error("getSignCertPrivateKeyByStoreMap Error", e);
return null;
}
}
/**
* 通过签名私钥证书路径,密码获取私钥证书certId (多证书使用)
*
* @param certPath
* @param certPwd
* @return
*/
public static String getCertIdByKeyStoreMap(String certPath, String certPwd) {
if (!keyStoreMap.containsKey(certPath)) {
// 缓存中未查询到,则加载RSA证书,既缓存中没有的话就加载进去keyStoreMap
loadSignCert(certPath, certPwd);
}
return getCertIdIdByStore(keyStoreMap.get(certPath));
}
/**
* 通过keystore获取私钥证书的certId值 (多证书使用)
*
* @param keyStore
* @return
*/
private static String getCertIdIdByStore(KeyStore keyStore) {
Enumeration
try {
aliasenum = keyStore.aliases();
String keyAlias = null;
if (aliasenum.hasMoreElements()) {
keyAlias = aliasenum.nextElement();
}
X509Certificate cert = (X509Certificate) keyStore.getCertificate(keyAlias);
return cert.getSerialNumber().toString();
} catch (KeyStoreException e) {
logger.error("getCertIdIdByStore Error", e);
return null;
}
}
/**
* 将字符串转换为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) {
logger.error("gen certificate error", e);
}
return x509Cert;
}
/**
* 从配置文件acp_sdk.properties中获取验签公钥使用的中级证书
*
* @return
*/
public static X509Certificate getMiddleCert() {
if (null == middleCert) {
String path = Configuration.getInstance().getConfig("c://");
if (!isEmpty(path)) {
initMiddleCert();
} else {
logger.error("配置文件中没有设置验签中级证书路径");
return null;
}
}
return middleCert;
}
/**
* 从配置文件acp_sdk.properties中获取验签公钥使用的根证书
*
* @return
*/
public static X509Certificate getRootCert() {
if (null == rootCert) {
String path = Configuration.getInstance().getConfig("c://");
if (!isEmpty(path)) {
initRootCert();
} else {
logger.error("配置文件中没有设置验签根证书路径");
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;
}
/**
* 验证证书链,使用验签根证书与验签中级证书来验证传入的X509Certificate对象
*
* @param cert
* @return
*/
private static boolean verifyCertificateChain(X509Certificate cert) {
if (null == cert) {
logger.error("cert must Not null");
return false;
}
// 这里使用到了中级证书
X509Certificate middleCert = CertUtil.getMiddleCert();
if (null == middleCert) {
logger.error("middleCert must Not null");
return false;
}
// 这里用到了根证书
X509Certificate rootCert = CertUtil.getRootCert();
if (null == rootCert) {
logger.error("rootCert or cert must Not null");
return false;
}
try {
X509CertSelector selector = new X509CertSelector();
selector.setCertificate(cert);
Set
trustAnchors.add(new TrustAnchor(rootCert, null));
PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(trustAnchors, selector);
Set
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);
logger.info("verify certificate chain succeed.");
return true;
} catch (java.security.cert.CertPathBuilderException e) {
logger.error("verify certificate chain fail.", e);
} catch (Exception e) {
logger.error("verify certificate chain exception: ", e);
}
return false;
}
/**
* 检查证书链
*
* @param rootCerts 根证书
* @param cert 待验证的证书
* @return
*/
public static boolean verifyCertificate(X509Certificate cert) {
if (null == cert) {
logger.error("cert must Not null");
return false;
}
try {
cert.checkValidity();// 验证有效期
// cert.verify(middleCert.getPublicKey());
// 验证证书链
if (!verifyCertificateChain(cert)) {
return false;
}
} catch (Exception e) {
logger.error("verifyCertificate fail", e);
return false;
}
// 此处不验证证书CN
// 验证公钥是否属于银联
if (!UNIONPAY_CNNAME.equals(CertUtil.getIdentitiesFromCertficate(cert)) && !"00040000:SIGN".equals(CertUtil.getIdentitiesFromCertficate(cert))) {
logger.error("cer owner is not CUP:" + CertUtil.getIdentitiesFromCertficate(cert));
return false;
}
return true;
}
/**
* 把一个url连接转化为一个InputStream对象并返回
*
* @param path url链接
* @return InputStream对象
*/
public static InputStream getInputStreamByUrl(String path) {
InputStream input = null;
try {
logger.info("url转化为inoputStream: url={}", path);
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
input = conn.getInputStream();
return input;
} catch (Exception e) {
logger.error("url转为inputStream失败");
e.printStackTrace();
return null;
} finally {
// if (input != null) {
// try {
// input.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
}
}
}
/**
* BASE64解码
*
* @param inputByte
* 待解码数据
* @return 解码后的数据
* @throws IOException
*/
public static byte[] base64Decode(byte[] inputByte) throws IOException {
return Base64.decodeBase64(inputByte);
}
//银联开发地址https://open.unionpay.com/upload/download/%E6%89%8B%E6%9C%BA%E6%8E%A7%E4%BB%B6%E6%94%AF%E4%BB%98%E4%BA%A7%E5%93%81%E6%8E%A5%E5%8F%A3%E8%A7%84%E8%8C%83V2.2.pdf