银联退款接口开发

退款流程:

发送退款请求->同步告诉你请求是否成功->异步告诉你退款是否成功

首先说一下一些请求的参数.银联退款是需要证书的,所以要先将证书加载下来.怎么下载证书我就不多说了,因为我没有做过,在支付的时候就已经下载好了

  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

转载于:https://www.cnblogs.com/lxsxsy/p/10783493.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值