微信支付之原路退款

1.场景还原

   最近项目要求上线微信支付原路退款功能,今天笔者就微信支付原路退款的流程梳理下,方便有需要的伙伴借阅

2.准备工作

①获取微信支付的相关配置

WECHATPAY_PARTNER = "150xxxxxxx"; //商户号
WECHATPAY_PARTNERKEY = "Yunjunxxxxxxxxxxxxxyyyyyyyy"; //商户秘钥

②获取微信支付API证书

微信支付后台管理--》API安全--》下载证书

③阅读微信官方申请退款文档,确保请求参数完整


④将证书.p12文件放置工程的resources下

InputStream instream = PropertiesUtil.class.getResourceAsStream("/yiwei/apiclient_cert.p12")

3.实现方案

代码如下:

@Override
public String doRefoundByWX(Map<String,Object> map) throws Exception {
    if (ObjectUtil.isNull(map.get("refund_money"),map.get("transaction_id"),map.get("channel"),map.get("sum_money"))) {
        throw new RequestException();
    }

    String refund_money = map.get("refund_money").toString(); //退款金额
    String out_trade_no = map.get("transaction_id").toString();//微信订单号
    String channel = map.get("channel").toString();
    String sumMoney = map.get("sum_money").toString(); //订单总价

    String result = "";
    //根据app渠道获取微信的appidappSecret
    WxpayUtil.loadWxAppIdAndSecret(Integer.valueOf(channel));
    String mch_id = WxpayUtil.WECHATPAY_PARTNER;
    String appid = WxpayUtil.WECHATPAY_APPID;
    String partnerkey = WxpayUtil.WECHATPAY_PARTNERKEY;
    String nonce_str = WeiXinUtil.CreateNoncestr();//随机字符串
    String out_refund_no = WeiXinUtil.generatePayNO();//商户退款单号
    Double total_fee = 0d;
    try {
        total_fee = StringUtil.getDouble(sumMoney);
    } catch (NumberFormatException e) {
        e.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    }
    //总金额以分为单位
    long totalAmount = new BigDecimal(total_fee * 100d).longValue();
    Double refund_fee = Double.parseDouble(refund_money);
    //退款金额以分为单位
    long Amount = new BigDecimal(refund_fee * 100d).longValue();
   

    //签名算法
    SortedMap<Object, Object> SortedMap = new TreeMap<Object, Object>();
    SortedMap.put("appid", appid);
    SortedMap.put("mch_id", mch_id);
    SortedMap.put("nonce_str", nonce_str);
    SortedMap.put("out_trade_no", out_trade_no);
    SortedMap.put("out_refund_no", out_refund_no);
    SortedMap.put("total_fee", String.valueOf(totalAmount));
    SortedMap.put("refund_fee", String.valueOf(Amount));


    String sign = WeiXinUtil.createSign("UTF-8",partnerkey,SortedMap);
    //获取最终待发送的数据
    String requestXml = "<xml>" +
            "<appid>" + appid + "</appid>" +
            "<mch_id>" + mch_id + "</mch_id>" +
            "<nonce_str>" + nonce_str + "</nonce_str>" +
            "<out_trade_no>" + out_trade_no + "</out_trade_no>" +
            "<out_refund_no>" + out_refund_no + "</out_refund_no>" +
            "<total_fee>" + String.valueOf(totalAmount) + "</total_fee>" +
            "<refund_fee>" + String.valueOf(Amount) + "</refund_fee>" +
            "<sign>" + sign + "</sign>" +
            "</xml>";
    //建立连接并发送数据
    HashMap<String, Object> resultMap = null;
    try {
        result = WeiXinUtil.WeixinSendPost(requestXml,mch_id,channel);
        //解析返回的xml
        resultMap = new HashMap<String, Object>(XMLParse.parseXmlToList2(result));
    }catch (Exception e){
        e.printStackTrace();
    }

    //退款返回标志码
    String return_code = resultMap.get("return_code").toString();
    String result_code = resultMap.get("result_code").toString();
    String msg = "";
    if(return_code.equals("SUCCESS") && result_code.equals("SUCCESS")){
       String userId = payorderMapper.getUserIdByOutTradeOrder(out_trade_no);
        //减少用户余额
        Integer updateAmount = this.accountMapper.subtractAmount(userId, refund_money);
        // 生成我的钱包流水
        Double remainAmount = this.accountMapper.findAmountByUserId(userId);
        AccountFlow flow = AccountFlow.newBuilder().setUserId(userId).setOrderCode(out_trade_no).setBody("微信原路退款扣除金额")
                .setIsInflow("0").setTotal_amount(refund_money).setRemainAmount(remainAmount.toString()).build();
        Integer insertAccountFlow = this.accountFlowMapper.insert(flow.toMap());

        msg = "微信原路返款成功!";
        if(updateAmount != 1 || insertAccountFlow != 1) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return JsonUtil.toJson(new ResultBean(true, "微信原路返款失败", null));
        }
    }else if(return_code.equals("SUCCESS") && result_code.equals("FAIL")){
        msg = "微信原路返款失败!";
    }else{
        msg = "微信原路返款未知错误!";
    }
    return JsonUtil.toJson(new ResultBean(true, msg, null));

}

签名方法:

/**
 * @Descriptionsign签名
 * @param characterEncoding
 *            编码格式
 * @param parameters
 * @param  secretKey
 *            请求参数
 * @return
 */
public static String createSign(String characterEncoding,String secretKey, SortedMap<Object, Object> parameters) {
   StringBuffer sb = new StringBuffer();
   Set<Map.Entry<Object, Object>> es = parameters.entrySet();
   Iterator<Map.Entry<Object, Object>> it = es.iterator();
   while (it.hasNext()) {
      Map.Entry<Object, Object> entry = (Map.Entry<Object, Object>) it.next();
      String k = (String) entry.getKey();
      Object v = entry.getValue();
      if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
         sb.append(k + "=" + v + "&");
      }
   }
   sb.append("key=" + secretKey);
   String sign = MD5Utils4WX.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
   parameters.put("sign", sign);
   return sign;
}

这里的secretKey指的是微信商户秘钥

执行退款逻辑

public static String WeixinSendPost(Object xmlObj,String mch_id,String channel) throws IOException, KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyManagementException {

   String result = "";
   InputStream instream = PropertiesUtil.class.getResourceAsStream("/yiwei/apiclient_cert.p12");
   KeyStore keyStore = KeyStore.getInstance("PKCS12");
   try {
      keyStore.load(instream, mch_id.toCharArray());
   } catch (CertificateException e) {
      e.printStackTrace();
   } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
   } finally {
      instream.close();
   }

   SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mch_id.toCharArray()).build();

   SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
   CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
   try {

      HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/secapi/pay/refund");
      @SuppressWarnings("deprecation")
      HttpEntity xmlData = new StringEntity((String) xmlObj, "text/xml", "iso-8859-1");
      httpPost.setEntity(xmlData);

      System.out.println("executing request" + httpPost.getRequestLine());

      CloseableHttpResponse response = httpclient.execute(httpPost);
      try {
         HttpEntity entity = response.getEntity();
         result = EntityUtils.toString(entity, "UTF-8");
         System.out.println(response.getStatusLine());
         EntityUtils.consume(entity);
      } finally {
         response.close();
      }
   } finally {
      httpclient.close();
   }
   //去除空格
   return result.replaceAll(" ", "");
}

执行完之后再进行是否成功判断

result = WeiXinUtil.WeixinSendPost(requestXml,mch_id,channel);
//解析返回的xml
resultMap = new HashMap<String, Object>(XMLParse.parseXmlToList2(result));

将xml字符串解析map方法

public static Map parseXmlToList2(String xml) {
   Map retMap = new HashMap();
   try {
      StringReader read = new StringReader(xml);
      // 创建新的输入源SAX 解析器将使用 InputSource 对象来确定如何读取 XML 输入
      InputSource source = new InputSource(read);
      // 创建一个新的SAXBuilder
      SAXBuilder sb = new SAXBuilder();
      // 通过输入源构造一个Document
      Document doc =  sb.build(source);
      Element root = (Element) doc.getRootElement();// 指向根节点
      List<Element> es = root.getChildren();
      if (es != null && es.size() != 0) {
         for (Element element : es) {
            retMap.put(element.getName(), element.getValue());
         }
      }
   } catch (Exception e) {
      e.printStackTrace();
   }
   return retMap;
}  

好了,我是张星,欢迎加入博主技术交流群,群号:313145288;需要源码的伙伴,请私信博主



猜你喜欢

转载自blog.csdn.net/zhangxing52077/article/details/80269999