微信退款开发 --Java

一、下载证书并导入到系统

        微信支付接口中,涉及资金回滚的接口会使用到商户证书,包括退款、撤销接口。商家在申请微信支付成功后,可以按照以下路径下载:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->证书下载 。

原文:https://blog.csdn.net/fenghuibian/article/details/52459699


  下载的时候需要手机验证及登录密码。下载后找到apiclient_cert.p12这个证书,双击导入,导入的时候提示输入密码,这个密码就是商户ID,且必须是在自己的商户平台下载的证书。否则会出现密码错误的提示:


导入正确的提示:



二、编写代码

        首先初始化退款接口中的请求参数,如微信订单号transaction_id(和商户订单号只需要知道一个)、订单金额total_fee等;其次调用MobiMessage中的RefundResData2xml方法解析成需要的类型;最后调用RefundRequest类的httpsRequest方法触发请求。

  /**
     * 处理退款请求
     * @param request
     * @return
     * @throws Exception
     */
    @RequestMapping("/refund")
    @ResponseBody
    public JsonApi refund(HttpServletRequest request) throws Exception {
        //获得当前目录
        String path = request.getSession().getServletContext().getRealPath("/");
        LogUtils.trace(path);

        Date now = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");//可以方便地修改日期格式
        String outRefundNo = "NO" + dateFormat.format( now );

        //获得退款的传入参数
        String transactionID = "4008202001201609012791655620";
        String outTradeNo = "20160901141024";
        Integer totalFee = 1;
        Integer refundFee = totalFee;

        RefundReqData refundReqData = new RefundReqData(transactionID,outTradeNo,outRefundNo,totalFee,refundFee);

        String info = MobiMessage.RefundReqData2xml(refundReqData).replaceAll("__", "_");
        LogUtils.trace(info);

        try {
            RefundRequest refundRequest = new RefundRequest();
            String result = refundRequest.httpsRequest(WxConfigure.REFUND_API, info, path);
            LogUtils.trace(result);

            Map<String, String> getMap = MobiMessage.parseXml(new String(result.toString().getBytes(), "utf-8"));
            if("SUCCESS".equals(getMap.get("return_code")) && "SUCCESS".equals(getMap.get("return_msg"))){
                return new JsonApi();
            }else{
                //返回错误描述
                return new JsonApi(getMap.get("err_code_des"));
            }
        }catch(Exception e){
            e.printStackTrace();
            return new JsonApi();
        }
    }
初始化退款接口需要的数据,隐藏了get和set方法。
public class RefundReqData {

    //每个字段具体的意思请查看API文档
    private String appid = "";
    private String mch_id = "";
    private String nonce_str = "";
    private String sign = "";
    private String transaction_id = "";
    private String out_trade_no = "";
    private String out_refund_no = "";
    private int total_fee = 0;
    private int refund_fee = 0;
    private String op_user_id = "";

    /**
     * 请求退款服务
     * @param transactionID 是微信系统为每一笔支付交易分配的订单号,通过这个订单号可以标识这笔交易,它由支付订单API支付成功时返回的数据里面获取到。建议优先使用
     * @param outTradeNo 商户系统内部的订单号,transaction_id 、out_trade_no 二选一,如果同时存在优先级:transaction_id>out_trade_no
     * @param outRefundNo 商户系统内部的退款单号,商户系统内部唯一,同一退款单号多次请求只退一笔
     * @param totalFee 订单总金额,单位为分
     * @param refundFee 退款总金额,单位为分
     */
    public RefundReqData(String transactionID,String outTradeNo,String outRefundNo,int totalFee,int refundFee){

        //微信分配的公众号ID(开通公众号之后可以获取到)
        setAppid(WxConfigure.AppId);

        //微信支付分配的商户号ID(开通公众号的微信支付功能之后可以获取到)
        setMch_id(WxConfigure.Mch_id);

        //transaction_id是微信系统为每一笔支付交易分配的订单号,通过这个订单号可以标识这笔交易,它由支付订单API支付成功时返回的数据里面获取到。
        setTransaction_id(transactionID);

        //商户系统自己生成的唯一的订单号
        setOut_trade_no(outTradeNo);

        setOut_refund_no(outRefundNo);

        setTotal_fee(totalFee);

        setRefund_fee(refundFee);

        setOp_user_id(WxConfigure.Mch_id);

        //随机字符串,不长于32 位
        setNonce_str(StringUtil.generateRandomString(16));


        //根据API给的签名规则进行签名
        SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
        parameters.put("appid", appid);
        parameters.put("mch_id", mch_id);
        parameters.put("nonce_str", nonce_str);
        parameters.put("transaction_id", transaction_id);
        parameters.put("out_trade_no", out_trade_no);
        parameters.put("out_refund_no", out_refund_no);
        parameters.put("total_fee", total_fee);
        parameters.put("refund_fee", refund_fee);
        parameters.put("op_user_id", op_user_id);


        String sign = DictionarySort.createSign(parameters);
        setSign(sign);  //把签名数据设置到Sign这个属性中

    }
 MobiMessage实现json数据类型和xml数据之间的转换。
public class MobiMessage {

    public static Map<String,String> xml2map(HttpServletRequest request) throws IOException, DocumentException {
        Map<String,String> map = new HashMap<String, String>();
        SAXReader reader = new SAXReader();
        InputStream inputStream = request.getInputStream();
        Document document = reader.read(inputStream);
        Element root = document.getRootElement();
        List<Element> list = root.elements();
        for(Element e:list){
            map.put(e.getName(), e.getText());
        }
        inputStream.close();
        return map;
    }


    //订单转换成xml
    public static String JsApiReqData2xml(JsApiReqData jsApiReqData){
        /*XStream xStream = new XStream();
        xStream.alias("xml",productInfo.getClass());
        return xStream.toXML(productInfo);*/
        MobiMessage.xstream.alias("xml",jsApiReqData.getClass());
        return MobiMessage.xstream.toXML(jsApiReqData);
    }

    public static String RefundReqData2xml(RefundReqData refundReqData){
        /*XStream xStream = new XStream();
        xStream.alias("xml",productInfo.getClass());
        return xStream.toXML(productInfo);*/
        MobiMessage.xstream.alias("xml",refundReqData.getClass());
        return MobiMessage.xstream.toXML(refundReqData);
    }

    public static String class2xml(Object object){

        return "";
    }
    public static Map<String, String> parseXml(String xml) throws Exception {
        Map<String, String> map = new HashMap<String, String>();
        Document document = DocumentHelper.parseText(xml);
        Element root = document.getRootElement();
        List<Element> elementList = root.elements();
        for (Element e : elementList)
            map.put(e.getName(), e.getText());
        return map;
    }

    //扩展xstream,使其支持CDATA块
    private static XStream xstream = new XStream(new XppDriver() {
        public HierarchicalStreamWriter createWriter(Writer out) {
            return new PrettyPrintWriter(out) {
                // 对所有xml节点的转换都增加CDATA标记
                boolean cdata = true;

                //@SuppressWarnings("unchecked")
                public void startNode(String name, Class clazz) {
                    super.startNode(name, clazz);
                }

                protected void writeText(QuickWriter writer, String text) {
                    if (cdata) {
                        writer.write("<![CDATA[");
                        writer.write(text);
                        writer.write("]]>");
                    } else {
                        writer.write(text);
                    }
                }
            };
        }
    });


}
   RefundRequest类中initCert方法加载证书到系统中,其中证书地址如下:
public static String certLocalPath = "/WEB-INF/cert/apiclient_cert.p12";  

  RefundRequest类中httpsRequest方法调用微信接口,触发请求。

/**
 * User: rizenguo
 * Date: 2014/10/29
 * Time: 14:36
 */
public class RefundRequest {

    //连接超时时间,默认10秒
    private int socketTimeout = 10000;

    //传输超时时间,默认30秒
    private int connectTimeout = 30000;

    //请求器的配置
    private RequestConfig requestConfig;

    //HTTP请求器
    private CloseableHttpClient httpClient;

    /**
     * 加载证书
     * @param path
     * @throws IOException
     * @throws KeyStoreException
     * @throws UnrecoverableKeyException
     * @throws NoSuchAlgorithmException
     * @throws KeyManagementException
     */
    private void initCert(String path) throws IOException, KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyManagementException {
        //拼接证书的路径
        path = path + WxConfigure.certLocalPath;
        KeyStore keyStore = KeyStore.getInstance("PKCS12");

        //加载本地的证书进行https加密传输
        FileInputStream instream = new FileInputStream(new File(path));
        try {
            keyStore.load(instream, WxConfigure.Mch_id.toCharArray());  //加载证书密码,默认为商户ID
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } finally {
            instream.close();
        }

        // Trust own CA and all self-signed certs
        SSLContext sslcontext = SSLContexts.custom()
                .loadKeyMaterial(keyStore, WxConfigure.Mch_id.toCharArray())       //加载证书密码,默认为商户ID
                .build();
        // Allow TLSv1 protocol only
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                sslcontext,
                new String[]{"TLSv1"},
                null,
                SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);

        httpClient = HttpClients.custom()
                .setSSLSocketFactory(sslsf)
                .build();

        //根据默认超时限制初始化requestConfig
        requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();

    }


    /**
     * 通过Https往API post xml数据
     * @param url   API地址
     * @param xmlObj   要提交的XML数据对象
     * @param path    当前目录,用于加载证书
     * @return
     * @throws IOException
     * @throws KeyStoreException
     * @throws UnrecoverableKeyException
     * @throws NoSuchAlgorithmException
     * @throws KeyManagementException
     */
    public String httpsRequest(String url, String xmlObj, String path) throws IOException, KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyManagementException {
        //加载证书
        initCert(path);

        String result = null;

        HttpPost httpPost = new HttpPost(url);

        //得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
        StringEntity postEntity = new StringEntity(xmlObj, "UTF-8");
        httpPost.addHeader("Content-Type", "text/xml");
        httpPost.setEntity(postEntity);

        //设置请求器的配置
        httpPost.setConfig(requestConfig);

        try {
            HttpResponse response = httpClient.execute(httpPost);

            HttpEntity entity = response.getEntity();

            result = EntityUtils.toString(entity, "UTF-8");

        } catch (ConnectionPoolTimeoutException e) {
            LogUtils.trace("http get throw ConnectionPoolTimeoutException(wait time out)");

        } catch (ConnectTimeoutException e) {
            LogUtils.trace("http get throw ConnectTimeoutException");

        } catch (SocketTimeoutException e) {
             LogUtils.trace("http get throw SocketTimeoutException");

        } catch (Exception e) {
             LogUtils.trace("http get throw Exception");

        } finally {
            httpPost.abort();
        }

        return result;
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值