微信支付之原路退款

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渠道获取微信的appid及appSecret
    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));

}

签名方法:

 

/**
 * @Description:sign签名
 * @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;
}  

创作不易,莫要白嫖,您的关注及点赞是对于我创作的最大动力与源泉。

 

老张家的独苗 CSDN认证博客专家 java golang linux
微信搜索「老张家的独苗」,回复关键字「资料」获取海量学习资源。我是张星,CSDN博客专家,CSDN内容合伙人,"老张家的独苗"公号作者,GitChat付费专栏作者,拥有5+年的技术TL的项目管理经验,1000+面试经验。
<p style="color:#2F2F2F;"> 老规矩,先看本节效果图 </p> <div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img src="https://upload-images.jianshu.io/upload_images/6273713-fce2f4ffa8f92d99.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/418/format/webp" alt="" /></div> </div> </div> <br /><p style="color:#2F2F2F;"> 我们实现这个支付功能完全是借助小程序云开发实现的,不用搭建自己的服务器,不用买域名,不用备案域名,不用支持https。只需要一个简单的云函数,就可以轻松的实现微信小程序支付功能。<br /> 核心代码就下面这些 </p> <br /><div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img src="https://upload-images.jianshu.io/upload_images/6273713-7433fba3b792bb28.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/922/format/webp" alt="" /></div> </div> </div> 一,创建一个云开发小程序 <p style="color:#2F2F2F;"> 关于如何创建云开发小程序,这里我就不再做具体讲解。不知道怎么创建云开发小程序的同学,可以去翻看我之前的文章,或者看下我录制的视频:<a href="https://links.jianshu.com/go?to=https%3A%2F%2Fedu.csdn.net%2Fcourse%2Fplay%2F9604%2F204528">https://edu.csdn.net/course/play/9604/204528</a> </p> 创建云开发小程序有几点注意的 <p style="color:#2F2F2F;"> 1,一定不要忘记在app.js里初始化云开发环境。 </p> <br /><div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img src="https://upload-images.jianshu.io/upload_images/6273713-c436567c3368ac74.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp" alt="" /></div> </div> </div> <br /><p style="color:#2F2F2F;"> 2,创建完云函数后,一定要记得上传 </p> 二, 创建支付的云函数 <p style="color:#2F2F2F;"> 1,创建云函数pay </p> <br /><div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img src="https://upload-images.jianshu.io/upload_images/6273713-32302ade305b8a18.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/564/format/webp" alt="" /></div> </div> </div> <div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img src="https://upload-images.jianshu.io/upload_images/6273713-8ea47ffa0b4cffca.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/708/format/webp" alt="" /></div> </div> </div> 三,引入三方依赖tenpay <p style="color:#2F2F2F;"> 我们这里引入三方依赖的目的,是创建我们支付时需要的一些参数。我们安装依赖是使用里npm 而npm必须安装node,关于如何安装node,我这里不做讲解,百度一下,网上一大堆。 </p> 1,首先右键pay,然后选择在终端中打开 <div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img src="https://upload-images.jianshu.io/upload_images/6273713-8881030499ebe5ce.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp" alt="" /></div> </div> </div> 2,我们使用npm来安装这个依赖。 <p style="color:#2F2F2F;"> 在命令行里执行 npm i tenpay </p> <br /><div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img src="https://upload-images.jianshu.io/upload_images/6273713-c61cb1cb5880c475.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/684/format/webp" alt="" /></div> </div> </div> <br /><div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img src="https://upload-images.jianshu.io/upload_images/6273713-cd34c63e39e6427f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp" alt="" /></div> </div> </div> <br /><div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img src="https://upload-images.jianshu.io/upload_images/6273713-768712337485bf67.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp" alt="" /></div> </div> </div> <br /><p style="color:#2F2F2F;"> 安装完成后,我们的pay云函数会多出一个package.json 文件 </p> <br /><div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img src="https://upload-images.jianshu.io/upload_images/6273713-7e9236d8983ebb21.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/682/format/webp" alt="" /></div> </div> </div> <br /><span style="color:#2F2F2F;">到这里我们的tenpay依赖就安装好了。</span> 四,编写云函数pay <div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img src="https://upload-images.jianshu.io/upload_images/6273713-cd36f9084fada492.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp" alt="" /></div> </div> </div> <br /><p style="color:#2F2F2F;"> 完整代码如下 </p> <span style="color:#929292;">//云开发实现支付</span> <span style="color:#C678DD;">const</span> cloud = <span style="color:#E6C07B;">require</span>(<span style="color:#98C379;">'wx-server-sdk'</span>) cloud.init() <span style="color:#929292;">//1,引入支付的三方依赖</span> <span style="color:#C678DD;">const</span> tenpay = <span style="color:#E6C07B;">require</span>(<span style="color:#98C379;">'tenpay'</span>); <span style="color:#929292;">//2,配置支付信息</span> <span style="color:#C678DD;">const</span> config = { <span style="color:#D19A66;">appid</span>: <span style="color:#98C379;">'你的小程序appid'</span>, <span style="color:#D19A66;">mchid</span>: <span style="color:#98C379;">'你的微信商户号'</span>, <span style="color:#D19A66;">partnerKey</span>: <span style="color:#98C379;">'微信支付安全密钥'</span>, <span style="color:#D19A66;">notify_url</span>: <span style="color:#98C379;">'支付回调网址,这里可以先随意填一个网址'</span>, <span style="color:#D19A66;">spbill_create_ip</span>: <span style="color:#98C379;">'127.0.0.1'</span> <span style="color:#929292;">//这里填这个就可以</span> }; exports.main = <span style="color:#C678DD;">async</span>(event, context) => { <span style="color:#C678DD;">const</span> wxContext = cloud.getWXContext() <span style="color:#C678DD;">let</span> { orderid, money } = event; <span style="color:#929292;">//3,初始化支付</span> <span style="color:#C678DD;">const</span> api = tenpay.init(config); <span style="color:#C678DD;">let</span> result = <span style="color:#C678DD;">await</span> api.getPayParams({ <span style="color:#D19A66;">out_trade_no</span>: orderid, <span style="color:#D19A66;">body</span>: <span style="color:#98C379;">'商品简单描述'</span>, <span style="color:#D19A66;">total_fee</span>: money, <span style="color:#929292;">//订单金额(分),</span> openid: wxContext.OPENID <span style="color:#929292;">//付款用户的openid</span> }); <span style="color:#C678DD;">return</span> result; } 一定要注意把appid,mchid,partnerKey换成你自己的。 <p style="color:#2F2F2F;"> 到这里我们获取小程序支付所需参数的云函数代码就编写完成了。<br /> 不要忘记上传这个云函数。 </p> <br /><div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img src="https://upload-images.jianshu.io/upload_images/6273713-ba99ca6fe33401ec.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/992/format/webp" alt="" /></div> </div> </div> <br /><p style="color:#2F2F2F;"> 出现下图就代表上传成功 </p> <br /><div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img src="https://upload-images.jianshu.io/upload_images/6273713-6133d61bc300dac4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/626/format/webp" alt="" /></div> </div> </div> 五,写一个简单的页面,用来提交订单,调用pay云函数。 <div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img src="https://upload-images.jianshu.io/upload_images/6273713-ee974aecada48f7c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp" alt="" /></div> </div> </div> <br /><p style="color:#2F2F2F;"> 这个页面很简单,<br /> 1,自己随便编写一个订单号(这个订单号要大于6位)<br /> 2,自己随便填写一个订单价(单位是分)<br /> 3,点击按钮,调用pay云函数。获取支付所需参数。 </p> <p style="color:#2F2F2F;"> 下图是官方支付api所需要的一些必须参数。 </p> <br /><div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img src="https://upload-images.jianshu.io/upload_images/6273713-2708b7475409199b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp" alt="" /></div> </div> </div> <br /><p style="color:#2F2F2F;"> 下图是我们调用pay云函数获取的参数,和上图所需要的是不是一样。 </p> <br /><div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img src="https://upload-images.jianshu.io/upload_images/6273713-d94c566dd744f128.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp" alt="" /></div> </div> </div> 六,调用wx.requestPayment实现支付 <p style="color:#2F2F2F;"> 下图是官方的示例代码 </p> <br /><div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img src="https://upload-images.jianshu.io/upload_images/6273713-00e9315590e4e14c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp" alt="" /></div> </div> </div> <br /><p style="color:#2F2F2F;"> 这里不在做具体讲解了,完整的可以看视频。 </p> 实现效果 1,调起支付键盘 <div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img src="https://upload-images.jianshu.io/upload_images/6273713-b20becb49e6fd26e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/234/format/webp" alt="" /></div> </div> </div> 2,支付完成 <div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img src="https://upload-images.jianshu.io/upload_images/6273713-b2a8266fdc83edc3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/270/format/webp" alt="" /></div> </div> </div> 3,log日志,可以看出不同支付状态的回调 <div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img src="https://upload-images.jianshu.io/upload_images/6273713-3a1fca73b650742e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp" alt="" /></div> </div> </div> <br /><p style="color:#2F2F2F;"> 上图是支付成功的回调,我们可以在支付成功回调时,改变订单支付状态。 </p> <p style="color:#2F2F2F;"> 下图是支付失败的回调, </p> <br /><div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img src="https://upload-images.jianshu.io/upload_images/6273713-1b306a9b35b292e0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp" alt="" /></div> </div> </div> <p style="color:#2F2F2F;"> 下图是支付完成的状态。 </p> <br /><div style="text-align:center;color:#2F2F2F;"> <div style="background-color:transparent;"> <div> </div> <div> <img src="https://upload-images.jianshu.io/upload_images/6273713-906f64407be62c4c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp" alt="" /></div> </div> </div> <p style="color:#2F2F2F;"> 到这里我们就轻松的实现了微信小程序的支付功能了。是不是很简单啊,完整的讲解可以看视频。 </p>
相关推荐
©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页
实付 19.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值