美文网首页java学习之路better
java使用微信sdk实现支付,回调,退款

java使用微信sdk实现支付,回调,退款

作者: 唯有努力不欺人丶 | 来源:发表于2020-07-17 17:31 被阅读0次

其实微信支付这一块,真的是个翻来覆去讲烂了的话题,甚至我在去年也写过一个帖子用来记录。但是之所以还要写这篇文章,主要一来通过最近的使用对微信支付这块了解的更加深刻,其次也是这次用到了退款的功能,这个算是新鲜点,所以想来想去决定再从头整理一下。
结合实际说的话,我这次项目是小程序,所以支付是略有不同,我尽量一点点剥开了说。

第一步:看官网介绍

首先肯定是要从微信的开发文档说起,这里附上传送门:
微信支付开发文档——普通商户版

我个人是觉得微信的文档越来越好了,再也不是三年前一提起微信就脑壳痛的时候了。咱们继续这个页面说,虽然都是微信支付,但是也有不同的方式,我上面已经说了我在做的这个项目是小程序,所以毫不犹豫的选择小程序支付,其实各种支付方式大同小异,尤其是小程序支付走的类型也是JSAPI,所以两者没什么区别。而且有个小技巧(目前是2020/7/17,反正现在为止小程序付款是没有sdk的,但是别的付款方式是有的,所以可以把别的付款方式中的sdk下下来这里用)。

第二步:引入sdk

当然了,我习惯性的用maven直接引入依赖。
之前查了下资料,其实能直接引用的有好几种表现形式,我这里只列出我自己用的:

        <dependency>
            <groupId>com.github.wxpay</groupId>
            <artifactId>wxpay-sdk</artifactId>
            <version>0.0.3</version>
        </dependency>

说一下这个包,其实是这么引用还是把sdk中的几个工具类自己写出来我觉得都ok吧,我也特意和sdk中的代码对比了下,是一样的。

maven引入依赖中的代码
官网下载来的sdk中的代码
其实我看了一些网上的demo,我更愿意理解为时间太久了(有的哪怕是新发布的,但是可能代码年限很高了),居然自己写各种工具方法的占了一多半,挺费解的。所以我这里再次强调:微信已经有了非常好用的sdk,真没必要自己写!

第三步:逻辑流程

其实在有sdk的协助下,代码的实现变得很简单了。大概的步骤我简单说一下。主要也是要对这个支付,回调等流程心里有数。
微信的文档其实挺好的,就是有点复杂(可能是我有时候心烦意乱只想速成,所以没耐心一点点顺着图分析理解)。这里说一下JSAPI/小程序支付的大概流程:

  1. 预下单(有了下单的动作,计划好支付多少钱了。然后微信会照着已给的信息去生成那个支付页面,如下图)


    支付页面

我简单说下,这个最上面的支付0.01元的页面,就是你先告诉微信,openId是XXX(你自己)的用户想给XXXX企业支付0.01元钱。你先把所有的信息告诉微信,微信觉得没问题了(其中检查你的openid,企业的情况啥的)就会生成这个弹窗页面。
所以说预下单其实可以理解为信息的提交与校验,没问题了就跳到微信页面(ps:这个页面就不属于前端的控制范围了,但是微信也提供一些接口可以关闭啥的。不过我们没做这个操作)。

  1. 回调
    刚刚已经说了预下单之后,会弹出一个微信提供的页面,但是到底用户支付没支付要怎么判断呢?这里就用到了回调。
    官网也有关于回调地址的详细要求,其实本质上就是提供一个接口,微信会把支付的情况告诉你,但是这个接口必须是外网的,必须是https的,这个回调地址可以在微信设置。这两个要求不复杂,但是让本地没办法测试了。反正代码简单,所以仔细点可以一次过。

  2. 退款
    这个功能就是某单的钱到了商家的微信账户里,根据订单号或者微信流水号就可以退款了。当然也有一些限制的,比如微信账户要有钱,比如订单不能超过一年。当然了,因为我们这个业务需求都不会涉及这些,所以直接用订单号退款了。这个差不多就是秒退,如果有问题直接接口会报错,所以更简单了。一会儿我会附上代码。

第四步:代码实现

终于到了最重要的时候了,代码的实现。

如果你下载了微信官方的sdk,其实是有完成的代码实现的。我这里截图说明: 官方下载下来的sdk
sdk解压后

而这个demo代码在readme里:


使用demo

然后其实照着这个来做就行。简单说第一步:配置参数:
需要的参数有证书,appId,key,mchId。下面是我的配置类:

package com.dsyl.util;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.Map.Entry;

import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConfig;

public class PayConfig implements WXPayConfig{

    private byte[] certData;

    public PayConfig() throws Exception {
        File file = new File("C:\\Program Files\\apache-tomcat-8.5.38\\cert\\apiclient_cert.p12");
        InputStream certStream = new FileInputStream(file);
        this.certData = new byte[(int) file.length()];
        certStream.read(this.certData);
        certStream.close();
    }

    public String getAppID() {
        return "wx8888888888888";
    }

    public String getMchID() {
        return "XXXXXXXXX";
    }

    public String getKey() {
        return "xxxxxxxxxxxxxxxxxxxxxxxx";
    }

    public InputStream getCertStream() {
        ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
        return certBis;
    }

    public int getHttpConnectTimeoutMs() {
        return 8000;
    }

    public int getHttpReadTimeoutMs() {
        return 10000;
    }
}

第二步就是调用了。首先说支付的调用(其实说真的,差不多就是readme百分之八十复制粘贴):

    public R pay(String openId,Integer money){
        try {
            String no = (openId + "-" + money + "-" + System.currentTimeMillis()+"abcdefghigklmn").substring(0,32);
            PayConfig config = new PayConfig();
            WXPay wxpay = new WXPay(config);
            Map<String, String> data = new HashMap<String, String>();
            data.put("body", "支付");
            data.put("out_trade_no", no);
            data.put("openid", openId);
            data.put("total_fee", (money*100)+"");
            data.put("spbill_create_ip", "127.1.1.1");
            data.put("notify_url", "https://www.hongchunyan.com/crowd-funding/wxpay/notify");
            data.put("trade_type", "JSAPI");
            Map<String, String> resp = wxpay.unifiedOrder(data);
            String returnCode = resp.get("return_code");
            String resultCode = resp.get("result_code");
            String prepay_id = resp.get("prepay_id");
            if (!"SUCCESS".equals(returnCode) || !"SUCCESS".equals(resultCode) || prepay_id == null) {
                return R.error().put("code", 300).put("data", resp);
            }
            String packages = "prepay_id=" + prepay_id;
            Map<String, String> wxPayMap = new HashMap<String, String>();
            wxPayMap.put("appId", config.getAppID());
            wxPayMap.put("timeStamp", String.valueOf(System.currentTimeMillis()).substring(0, 10));
            wxPayMap.put("nonceStr", WXPayUtil.generateNonceStr());
            wxPayMap.put("package", packages);
            wxPayMap.put("signType", "MD5");
            // 加密串中包括 appId timeStamp nonceStr package signType 5个参数, 通过sdk WXPayUtil类加密,
            // 注意, 此处使用 MD5加密 方式
            String sign = WXPayUtil.generateSignature(wxPayMap, config.getKey());
            Map<String, Object> result = new HashMap<String, Object>();
            result.put("prepay_id", prepay_id);
            result.put("paySign", sign);
            result.putAll(wxPayMap);
            return R.ok().put("data", result);
        } catch (Exception e) {
            e.printStackTrace();
            return R.error();
        }
    }

简单的说几点:

  • 这里的out_trade_no要求是唯一的,其实用uuid也ok,我这里因为想用它存一些业务逻辑,所以是自己想要存的东西的拼接(用了时间戳保证唯一性)。
  • 只有当支付方式是JSAPI(小程序支付也这么填)的时候才需要openId。不同的支付方式参数略有不同。这个稍微注意一下就行了。
  • 我返回的这个R其实就是自己封装的一个返回对象,本质就是键值对。这个可以自由点。
  • 这其中生成sign的方法是sdk里的,网上好多人自己写了,我比较喜欢拿现成的。

差不多预下单就这样了。

然后上面有个notify_url就是回调的接口地址了,必须是外网可访问的,必须是https的。

下面是回调接口,他的本质就是一个对外的接口,所以路径要对得上:

@RestController
@RequestMapping("/wxpay")
public class WXController {
    @PostMapping("/notify")
    public String wxnotify(HttpServletRequest request,HttpServletResponse response) {
        String xmlBack = "";
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream(),"UTF-8"));
            String line = null;
            StringBuilder sb = new StringBuilder();
            while((line = br.readLine()) != null){
                sb.append(line);
            } 
            br.close();
            //sb为微信返回的xml,微信sdk提供转化成map的方法
            String notityXml = sb.toString();           
            System.out.println("接收到的报文:" + notityXml);
            Map<String, String> map =  WXPayUtil.xmlToMap(notityXml); 
            WXPay wxpay = new WXPay(new PayConfig());
            if (wxpay.isPayResultNotifySignatureValid(map)) {//验证成功                  
                String return_code = map.get("return_code");//状态
                String out_trade_no = map.get("out_trade_no");//订单号
                String result_code = map.get("result_code");
                   //其实这里验证两个code都是success就说明充值成功了。可以处理自己的业务逻辑了。
                if (!"SUCCESS".equals(return_code) || !"SUCCESS".equals(result_code)) {
                    System.out.println("充值失败!");
                    //支付失败的逻辑
                    xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
                    return xmlBack;
                }
                xmlBack = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[SUCCESS]]></return_msg>" + "</xml> ";
                return xmlBack;
            } else {
                System.out.println("<<<<<<<<<<<<<<<<<<<<<微信支付回调通知签名错误");
                xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
                return xmlBack;
            }
        } catch (Exception e) {
            System.err.println(e);
            System.out.println("微信支付回调通知失败");
            xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
            return xmlBack;
            }
    }
}

这个给微信的返回值是为了防止重复回调。当你给微信返回了,微信就知道关于这个订单的回调你收到了,也就不会再给你发了(上面代码中涉及到业务逻辑的代码我都删了,因为删删减减的,所以不确定是不是多个括号少个括号啥的,如果报错了检查下代码吧)。

最后还有一个退款,因为当时就有结果了,所以比较简单,我直接附上代码:

    public static Map<String, String> refund(String transactionId,Integer money) throws Exception{
        PayConfig config = new PayConfig();
        WXPay wxpay = new WXPay(config);
        Map<String, String> params = new HashMap<String, String>();
        params.put("appid", config.getAppID());
        params.put("mch_id", config.getMchID());
        params.put("nonce_str", System.currentTimeMillis()+"");
        params.put("transaction_id",transactionId);
        params.put("out_refund_no", System.currentTimeMillis()+"");
        params.put("total_fee", (money*100)+"");
        params.put("refund_fee", (money*100)+"");
        String sign = WXPayUtil.generateSignature(params, config.getKey());
        params.put("sign", sign);
        Map<String, String> refund = wxpay.refund(params);
        System.out.println(refund);
        return refund;
    }

注意因为我这里是退全款,所以两个fee是一样的。还有关于钱转化成分。我第一遍测试,因为一边乘100.一边忘记乘了,所以退款失败了,会报错金额不同。
微信退款页介绍
如果遇到什么退款失败可以去查查失败的原因,还是很容易理解的。

然后至此,我这两天这块关于微信支付的业务就写完了,比较简单,而且感觉对微信支付的了解也更深了一点。
本篇文章就记到这里,如果稍微帮到你了记得点个喜欢点个关注,如果有什么问题也欢迎私信或者留言交流。也祝大家工作顺顺利利!周末愉快!java技术交流群130031711,欢迎各位踊跃加入!

相关文章

网友评论

    本文标题:java使用微信sdk实现支付,回调,退款

    本文链接:https://www.haomeiwen.com/subject/mpughktx.html