退款流程:
發送退款請求->同步告訴你請求是否成功->異步告訴你退款是否成功
首先說一下一些請求的參數.銀聯退款是需要證書的,所以要先將證書加載下來.怎么下載證書我就不多說了,因為我沒有做過,在支付的時候就已經下載好了
Map<String, String> contentData = new HashMap<String, String>(); //參數對象
/*** 銀聯全渠道系統,產品參數 ***/
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<String, String> filterBlank(Map<String, String> contentData) {
Map<String, String> submitFromData = new HashMap<String, String>();
Set<String> keyset = contentData.keySet();
for (String key : keyset) {
String value = contentData.get(key);
if (StringUtils.isNotBlank(value)) {
// 對value值進行去除前后空處理
submitFromData.put(key, value.trim());
}
}
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<String, String> rspData = AcpService.post(reqData, url,"UTF-8");
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, String> params) {
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<String, String> data) {
TreeMap<String, String> tree = new TreeMap<String, String>();
Iterator<Entry<String, String>> it = data.entrySet().iterator();
while (it.hasNext()) {
Entry<String, String> en = it.next();
if (“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() +“=” + 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摘要算法)<br>
*
* @param resData 返回報文數據<br>
* @param encoding 上送請求報文域encoding字段的值<br>
* @return true 通過 false 未通過<br>
*/
public static boolean validate(Map<String, String> rspData, String encoding) {
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<String, String> resData, String encoding) {
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> 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) {
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<String> aliasenum = null;
try {
aliasenum = keyStore.aliases();
String keyAlias = null;
if (aliasenum.hasMoreElements()) {
keyAlias = aliasenum.nextElement();
}
X509Certificate cert = (X509Certificate) keyStore.getCertificate(keyAlias);
return cert.getSerialNumber().toString();
} catch (KeyStoreException e) {
logger.error("getCertIdIdByStore Error", e);
return null;
}
}
/**
* 將字符串轉換為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<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);
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