銀聯退款接口開發


退款流程:

發送退款請求->同步告訴你請求是否成功->異步告訴你退款是否成功

首先說一下一些請求的參數.銀聯退款是需要證書的,所以要先將證書加載下來.怎么下載證書我就不多說了,因為我沒有做過,在支付的時候就已經下載好了

  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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM