Java微信退款开发

 更新时间:2018年09月29日 10:14:10   作者:thinkhui  
这篇文章主要为大家详细介绍了Java微信退款开发的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

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

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

下载的时候需要手机验证及登录密码。下载后找到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;
 }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • 详解Elasticsearch如何把一个索引变为只读

    详解Elasticsearch如何把一个索引变为只读

    这篇文章主要为大家介绍了详解Elasticsearch如何把一个索引变为只读示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-02-02
  • SpringBoot实现License认证(只校验有效期)的详细过程

    SpringBoot实现License认证(只校验有效期)的详细过程

    License也就是版权许可证书,一般用于收费软件给付费用户提供的访问许可证明,这篇文章主要介绍了SpringBoot实现License认证(只校验有效期),需要的朋友可以参考下
    2024-04-04
  • Intellij 下 mybatis 插件 MyBatisCodeHelperPro破解步骤详解

    Intellij 下 mybatis 插件 MyBatisCodeHelperPro破解步骤详解

    这篇文章主要介绍了Intellij 下 mybatis 插件 MyBatisCodeHelperPro破解步骤,本文分步骤给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • 在IntelliJ IDEA中.idea文件是什么可以删除吗

    在IntelliJ IDEA中.idea文件是什么可以删除吗

    相信有很多小伙伴,在用idea写java代码的时候,创建工程总是会出现.idea文件,该文件也从来没去打开使用过,那么它在我们项目里面,扮演什么角色,到底能不能删除它呢?这篇文章主要介绍了在IntelliJ IDEA中.idea文件是什么可以删除吗,需要的朋友可以参考下
    2024-01-01
  • IDEA中配置多个版本的JDK的实现示例

    IDEA中配置多个版本的JDK的实现示例

    IDEA可以配置多个JDK,根据需要使用不同版本的,本文就来介绍一下IDEA中配置多个版本的JDK的实现示例,具有一定的参考价值,感兴趣的可以了解一下
    2024-03-03
  • SpringSessionRedis配置及发现的问题讲解

    SpringSessionRedis配置及发现的问题讲解

    今天小编就为大家分享一篇关于SpringSessionRedis配置及发现的问题讲解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-03-03
  • Java ConcurrentModificationException异常解决案例详解

    Java ConcurrentModificationException异常解决案例详解

    这篇文章主要介绍了Java ConcurrentModificationException异常解决案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-09-09
  • Spring AOP结合注解实现接口层操作日志记录

    Spring AOP结合注解实现接口层操作日志记录

    在项目开发中我们需要记录接口的操作日志:包含请求参数、响应参数、接口所属模块、接口功能描述、请求地址、ip地址等信息;实现思路很简单就是基于注解和aop的方式去记录日志,主要的难点在于日志表结构、注解的设计已经aop实现的一些比较好的实现方式的借鉴
    2022-08-08
  • java并发编程之ThreadLocal详解

    java并发编程之ThreadLocal详解

    在锁的使用中会导致运行效率降低,ThreadLocal的使用彻底避免对共享资源的竞争,同时又可以不影响效率。本文详细讲解了ThreadLocal,需要了解的小伙伴可以看一看这篇文章
    2021-08-08
  • java8 Math新增方法介绍

    java8 Math新增方法介绍

    这篇文章主要介绍了java8 Math新增方法介绍,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08

最新评论