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;
 }
}

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

(0)

相关推荐

  • java服务器端微信、支付宝支付和退款功能

    工作需要,写了服务器端的支付和退款功能,包含微信和支付宝,网上也有很多demo可以借鉴,我把我的代码放出来,写的比较简单,有问题的欢迎指正,大家一起学习. 微信支付需要调用微信的统一下单接口,而支付宝不用. 我写的时候微信和支付宝都单独写了一个工具类,来调用支付,给前端返回需要的数据. ps:支付是可以不需要服务器端的,不过为了安全一点点,所以前端需要调起支付的字段都直接从服务器端返回,前端拿到字段直接调起支付就可以了. Map<String,String> map = new HashMap

  • java实现微信点餐申请微信退款

    应用场景: 当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退给买家,微信支付将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家账号上. 注意事项: 1.交易时间超过一年的订单无法提交退款 2.微信支付退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单好和设置不同的退款单号.申请退款总金额不能超过订单金额.一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号. 3.请求频率限制,150qps,即每秒钟正常的申请退

  • Java开发SSM框架微信退款的实现

    这篇文章是Java微信退款的教程,退款之前用户需要先进行支付,支付之后才可以使用退款.做到退款的同学应该已经是完成了支付了,我写的退款和支付的流程很相似只是所需的参数有所不同. String outTradeNo = request.getParameter("outTradeNo");// 获取商户订单号 Integer totalFee = Integer.parseInt(request.getParameter("totalFee"));// 获取支付金额

  • java实现微信退款功能

    微信退款之前需要在常量中配置退款地址,退款的地址必须是可以直接访问的.(之前的申请商户平台及在开放平台申请账号不在描述)在调起之前需要下载商户平台上的证书将其放在项目src下. 微信退款回调url :微信官方建议在提交退款申请后进行退款回调url配置,便于通知退款的结果.配置在微信商户平台->交易中心->退款配置栏进行退款结果回调通知配置.配置的url必须为可以直接访问的类似付款成功回调url. 配置成功后在回调的url中处理退款后的结果.微信给返回的参数如下: 对于加密信息req_info的

  • java版微信和支付宝退款接口

    本文实例为大家分享了java微信退款接口和支付宝退款接口的具体代码,供大家参考,具体内容如下 1.微信退款接口  相对来说我感觉微信的退款接口还是比较好调用的,直接发送httppost请求即可: /** * * 微信退款 * @param transaction_id 微信支付订单号 * @param out_refund_no 商户订单号 * @param total_fee 总金额 * @param refund_fee 退款金额 * @param op_user_id 操作人 * @ret

  • Java微信退款开发

    一.下载证书并导入到系统 微信支付接口中,涉及资金回滚的接口会使用到商户证书,包括退款.撤销接口.商家在申请微信支付成功后,可以按照以下路径下载:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->证书下载. 下载的时候需要手机验证及登录密码.下载后找到apiclient_cert.p12这个证书,双击导入,导入的时候提示输入密码,这个密码就是商户ID,且必须是在自己的商户平台下载的证书.否则会出现密码错误的提示: 导入正确的提示: 二.编写代码 首先初

  • java微信企业号开发之发送消息(文本、图片、语音)

    上篇文章介绍了开启回调模式,开始回调模式后我们就要实现聊天功能了.平时使用微信聊天可以发送文本消息.语音.图片.视频等,这里只实现了其中的一些功能和大家分享. 一.与微信企业号建立连接 1.企业应用调用企业号提供的接口,管理或查询企业号后台所管理的资源.或给成员发送消息等,以下称主动调用模式. 2.企业号把用户发送的消息或用户触发的事件推送给企业应用,由企业应用处理,以下称回调模式. 3.用户在微信中阅读企业应用下发的H5页面,该页面可以调用微信提供的原生接口,使用微信开放的终端能力,以下称JS

  • java微信企业号开发之开发模式的开启

    首先说微信企业号的开发模式分为:编辑模式(普通模式)和开发模式(回调模式) ,在编辑模式下,只能做简单的自定义菜单和自动回复消息,要想实现其他功能还得开启开发者模式. 一.编辑模式和开发模式对消息的处理流程 1.编辑模式下,所有的业务流程都配置在微信服务器上,由它处理 2.开发模式,消息通过第三方服务器处理,最后经过微信服务器把消息发送给用户 开发模式能处理的消息比编辑模式多,所以要先开启开发模式才能开发更多功能. 二.开发模式的开启 在回调模式下,企业不仅可以主动调用企业号接口,还可以接收用户

  • java微信企业号开发之通讯录

    上篇文章中介绍了聊天功能,这里介绍通讯录是如何实现的.首先要加载公司的所有部门,树形结构,然后点击进入部门的人员列表,点击人员能查看详细信息. 一.界面 公司部门的树形结构: 部门成员列表: 个人详细信息: 二.代码实现 1.controller /** * 加载部门列表 */ @RequestMapping("/addressListDepartmentjsp.do") public void addressListDepartment(HttpServletRequest requ

  • Java后台实现微信支付和微信退款

    微信支付流程 都是我自己工作中开发的,亲测可用,不喜勿喷. controller中我是这么写的,你们需要根据自己的业务需求改动.ResponseBean是我自己封装的,你们可以改成你们想要的形式. /** * 微信统一下单接口 * @return */ @RequestMapping(value = "/doUnifiedOrder", method = RequestMethod.POST) public ResponseBean doUnifiedOrder(@RequestBod

  • java微信开发第二步 获取消息和回复消息

    接着上一篇java微信开发API第一步 服务器接入进行学习,下面介绍java微信开发第二步:获取消息和回复消息,具体内容如下 * 本示例根据微信开发文档:http://mp.weixin.qq.com/wiki/home/index.html最新版(4/3/2016 5:34:36 PM )进行开发演示. * 编辑平台:myeclipse10.7+win32+jdk1.7+tomcat7.0  * 服务器:阿里云 windows server 2008 64bits * 平台要求:servlet

  • Java微信公众平台开发(15) 微信JSSDK的使用

    在前面的文章中有介绍到我们在微信web开发过程中常常用到的 [微信JSSDK中Config配置] ,但是我们在真正的使用中我们不仅仅只是为了配置Config而已,而是要在我们的项目中真正去使用微信JS-SDK给我们带来便捷,那么这里我们就简述如何在微信web开发中使用必要的方法!微信的JS-SDk中为我们提供的方法很多,这里我有一个简单截图如下: 在上图的提供的所有口中我们可以按照接口实现的难易程度分成两个部分: 较易实现:基础接口.分享接口.设备信息接口.地理位置接口.界面操作接口.微信扫一扫

  • java微信开发API第一步 服务器接入

    微信开发API如何接入服务器,下面就为大家进行介绍 一.说明 * 本示例根据微信开发文档:http://mp.weixin.qq.com/wiki/home/index.html最新版(4/3/2016 5:34:36 PM )进行开发演示. * 编辑平台:myeclipse10.7+win32+jdk1.7+tomcat7.0  * 服务器:阿里云 windows server 2008 64bits * 平台要求:servlet使用注解方式,平台要求:j2ee6.0+.jdk6.0+.tom

  • java微信开发之上传下载多媒体文件

    回复图片.音频.视频消息都是需要media_id的,这个是需要将多媒体文件上传到微信服务器才有的. 将多媒体文件上传到微信服务器,以及从微信服务器下载文件,可以参考:http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件 上传下载多媒体文件的方法还是写到WeixinUtil.java中. 代码如下: import java.io.BufferedOutputStream; import java.io.BufferedReader; impo

随机推荐