SpringBoot实现微信支付流程+RabbitMQ消息推送

微信支付

整个流程使用到的组件代码:
链接:https://pan.baidu.com/s/1v5415tEtetxdsp4o7HMy5A
提取码:ys87

二维码创建

利用qrious制作二维码插件。

qrious是一款基于HTML5 Canvas的纯JS二维码生成插件。通过qrious.js可以快速生成各种二维码,你可以控制二维码的尺寸颜色,还可以将生成的二维码进行Base64编码。

qrious.js二维码插件的可用配置参数如下:

参数类型默认值描述
backgroundString“white”二维码的背景颜色。
foregroundString“black”二维码的前景颜色。
levelString“L”二维码的误差校正级别(L, M, Q, H)。
mimeString“image/png”二维码输出为图片时的MIME类型。
sizeNumber100二维码的尺寸,单位像素。
valueString“”需要编码为二维码的值,一般是跳转url

下面的代码即可生成一张二维码

<html>
<head>
<title>二维码入门小demo</title>
</head>
<body>
<img id="qrious">
<script src="qrious.js"></script>
<script>
 var qr = new QRious({
	    element:document.getElementById('qrious'),
	    size:250, 	   
     	level:'H',	   
     	value:'http://www.itheima.com'
	});
</script>
</body>
</html>

运行效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UeK0QqoQ-1619103521443)(..\images\1549706445665.png)]

微信扫码支付

微信扫码支付申请

微信扫码支付是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于PC网站支付、实体店单品或订单支付、媒体广告支付等场景。

申请步骤:

第一步:注册公众号(类型须为:服务号)

请根据营业执照类型选择以下主体注册:个体工商户| 企业/公司| 政府| 媒体| 其他类型

需要企业,才可微信支付。

第二步:认证公众号

公众号认证后才可申请微信支付,认证费:300元/次。

第三步:提交资料申请微信支付

登录公众平台,点击左侧菜单【微信支付】,开始填写资料等待审核,审核时间为1-5个工作日内。

第四步:开户成功,登录商户平台进行验证

资料审核通过后,请登录联系人邮箱查收商户号和密码,并登录商户平台填写财付通备付金打的小额资金数额,完成账户验证。

第五步:在线签署协议

本协议为线上电子协议,签署后方可进行交易及资金结算,签署完立即生效。

有“传智播客”的微信支付账号,学员无需申请。

开发文档

微信支付接口调用的整体思路:

按API要求组装参数,以XML方式发送POST)给微信支付接口(URL),微信支付接口也是以XML方式给予响应。程序根据返回的结果(其中包括支付URL)生成二维码或判断订单状态。

在线微信支付开发文档:

https://pay.weixin.qq.com/wiki/doc/api/index.html

Native二维码扫码支付。

”统一下单”和”查询订单”两组API

1. appid:微信公众账号或开放平台APP的唯一标识
2. mch_id:商户号  (配置文件中的partner)
3. partnerkey:商户密钥
4. sign:数字签名, 根据微信官方提供的密钥和一套算法生成的一个加密信息, 就是为了保证交易的安全性

微信扫码支付模式介绍

模式一

商家二维码不过时。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E5DwxYuF-1619103521445)(..\images\1558448158371.png)]

业务流程说明:

1.商户后台系统根据微信支付规定格式生成二维码(规则见下文),展示给用户扫码。
2.用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
3.微信支付系统收到客户端请求,发起对商户后台系统支付回调URL的调用。调用请求将带productid和用户的openid等参数,并要求商户系统返回交数据包,详细请见"本节3.1回调数据输入参数"
4.商户后台系统收到微信支付系统的回调请求,根据productid生成商户系统的订单。
5.商户系统调用微信支付【统一下单API】请求下单,获取交易会话标识(prepay_id)
6.微信支付系统根据商户系统的请求生成预支付交易,并返回交易会话标识(prepay_id)。
7.商户后台系统得到交易会话标识prepay_id(2小时内有效)。
8.商户后台系统将prepay_id返回给微信支付系统。返回数据见"本节3.2回调数据输出参数"
9.微信支付系统根据交易会话标识,发起用户端授权支付流程。
10.用户在微信客户端输入密码,确认支付后,微信客户端提交支付授权。
11.微信支付系统验证后扣款,完成支付交易。
12.微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
13.微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
14.未收到支付通知的情况,商户后台系统调用【查询订单API】。
15.商户确认订单已支付后给用户发货。
模式二

商家二维码会过时。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nCBfveR9-1619103521447)(..\images\1558448510488.png)]

业务流程说明:

1.商户后台系统根据用户选购的商品生成订单。
2.用户确认支付后调用微信支付【统一下单API】生成预支付交易;
3.微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。
4.商户后台系统根据返回的code_url生成二维码。
5.用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
6.微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。
7.用户在微信客户端输入密码,确认支付后,微信客户端提交授权。
8.微信支付系统根据用户授权完成支付交易。
9.微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
10.微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
11.未收到支付通知的情况,商户后台系统调用【查询订单API】。
12.商户确认订单已支付后给用户发货。

微信支付SDK

微信支付提供了SDK, 下载后打开源码,install到本地仓库。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-erQZ5LaT-1619103521450)(..\images\1537902584152.png)]

安装SDK,jar包。

使用微信支付SDK(开发工具包), 在maven工程中引入依赖

<!-- 微信支付 -->
<wxpay.version>0.0.3</wxpay.version>
<!-- 微信支付 -->
<dependency>
    <groupId>com.github.wxpay</groupId>
    <artifactId>wxpay-sdk</artifactId>
    <version>${wxpay.version}</version>
</dependency>

我们主要会用到微信支付SDK的以下功能:

    @Test
    public void test() throws Exception {
        //生成随机字符串
        String s = WXPayUtil.generateNonceStr();
        System.out.println("随机字符串:"+s);

        //将map转成xml字符串
        Map<String, String> param = new HashMap<>();
        param.put("id","1");
        param.put("title","吴泽强");
        String mapToXml = WXPayUtil.mapToXml(param);
        System.out.println("map转成xml字符串:\n"+mapToXml);

        //将map转成字符串,并且生成签名
        String partnerKey = "wzq"; //私钥
        String generateSignedXml = WXPayUtil.generateSignedXml(param, partnerKey);
        System.out.println("xml字符串带有签名:\n"+generateSignedXml);

        //将XML字符串转成map
        Map<String,String> mapResult = WXPayUtil.xmlToMap(mapToXml);
        System.out.println("xml字符串转成map"+mapResult);
    }

项目实战

支付可单独是一个微服务。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v9409DXL-1619103521452)(..\images\46.png)]

依赖:

<!-- 微信支付 -->
<wxpay.version>0.0.3</wxpay.version>
<!-- 微信支付 -->
<dependency>
    <groupId>com.github.wxpay</groupId>
    <artifactId>wxpay-sdk</artifactId>
    <version>${wxpay.version}</version>
</dependency>

商户到腾讯官网申请才有的配置application.yml:

#微信支付信息配置,这里是黑马提供的
weixin:
  #应用id
  appid: wx8397f8696b538317
  #商户id
  partner: 1473426802
  #私钥
  partnerkey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
  #支付回调地址,自己本机模拟,花生壳
  notifyurl: http://c3009841m5.zicp.vip:动态端口/weixin/pay/notify/url

appid: 微信公众账号或开放平台APP的唯一标识

partner:财付通平台的商户账号

partnerkey:财付通平台的商户密钥

notifyurl: 回调地址

默认二维码2小时失效,具体官网文档有说。

统一下单api

在支付页面上生成支付二维码,并显示订单号和金额

用户拿出手机,打开微信扫描页面上的二维码,然后在微信中完成支付

官网文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1

二维码只能支付成功一次,成功后则失效,默认2小时未支付,二维码无效,具体修改无效时长看文档。结合HttpClient工具类实现。我们传给微信服务器和微信服务器传过来都是xml格式的,有工具类可以转。

dto:

参数dto和返回dto,看情况使用。

/**
 * Title:
 * Description:创建二维码需要的参数
 * @author WZQ
 * @version 1.0.0
 * @date 2020/3/13
 */
public class NativeParamDto implements Serializable {

    private static final long serialVersionUID = -2376427360982551378L;

    //客户端自定义订单编号
    private String outTradeNo;

    //交易金额(单位:分)
    private String totalFee;
    
    //set.get..
}


/**
 * Title:
 * Description:创建二维码成功的结果
 * @author WZQ
 * @version 1.0.0
 * @date 2020/3/13
 */
public class NativeDto implements Serializable {

    private static final long serialVersionUID = -2376427360982551378L;

    //客户端自定义订单编号
    private String outTradeNo;

    //交易金额(单位:分)
    private String totalFee;
    
    //二维码地址
    private String codeUrl
    
    //set.get..
}

servie:

    /**
     * 创建二维码
     * @param paramMap 客户端自定义订单编号;交易金额,单位:分
     * @return
     */
     Map<String, String> createNative(Map<String, String> paramMap);
import com.changgou.commons.utils.HttpClient;
import com.changgou.service.pay.service.WeixinPayService;
import com.github.wxpay.sdk.WXPayUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class WeixinPayServiceImpl implements WeixinPayService {

    @Value("${weixin.appid}")
    private String appid;

    @Value("${weixin.partner}")
    private String partner;

    @Value("${weixin.partnerkey}")
    private String partnerkey;

    @Value("${weixin.notifyurl}")
    private String notifyurl;

    /**
     * 创建二维码
     * @param parameterMap 客户端自定义订单编号;交易金额(单位:分)
     * @return
     */
    @Override
    public Map<String,String> createNative(Map<String, String> parameterMap){

        //客户端自定义订单编号;交易金额(单位:分)
        String outTradeNo = parameterMap.get("no");
        String totalFee = parameterMap.get("money");

        try {
            //1、封装参数
            Map<String,String> paramMap = new HashMap<>();
            paramMap.put("appid", appid);                              //应用ID
            paramMap.put("mch_id", partner);                           //商户ID号
            paramMap.put("nonce_str", WXPayUtil.generateNonceStr());   //随机数
            paramMap.put("body", "畅购");                            	//订单描述
            paramMap.put("out_trade_no", outTradeNo);                   //商户订单号
            paramMap.put("total_fee", totalFee);                       //交易金额,单位分
            paramMap.put("spbill_create_ip", "127.0.0.1");            //终端IP,请求的地址
            paramMap.put("notify_url", notifyurl);                    //回调地址
            paramMap.put("trade_type", "NATIVE");                     //交易类型,扫码

            //2、将参数转成xml字符,并携带签名
            String paramXml = WXPayUtil.generateSignedXml(paramMap, partnerkey);

            ///3、执行请求,微信统一下单api
            HttpClient httpClient = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
            httpClient.setHttps(true);
            httpClient.setXmlParam(paramXml);
            httpClient.post(); //执行

            //4、获取参数
            String content = httpClient.getContent();
            Map<String, String> stringMap = WXPayUtil.xmlToMap(content);
            System.out.println("stringMap:"+stringMap);

            //5、获取部分页面所需参数
            Map<String,String> dataMap = new HashMap<String,String>();
            dataMap.put("code_url", stringMap.get("code_url"));
            dataMap.put("out_trade_no", outTradeNo);
            dataMap.put("total_fee", totalFee);

            return dataMap;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

controller:

@RestController
@RequestMapping(value = "/weixin/pay")
@CrossOrigin
public class WeixinPayController {

    @Autowired
    private WeixinPayService weixinPayService;

    /***
     * 创建二维码
     * 参数是订单号和金额,分为单位
     * @return
     */
    @RequestMapping(value = "/create/native")
    public ResponseResult<Map<String,String>> createNative(@RequestParam Map<String, String> paramMap){
        Map<String,String> resultMap = weixinPayService.createNative(paramMap);
        return new ResponseResult<Map<String,String>>(true, StatusCode.OK,"创建二维码预付订单成功!",resultMap);
    }
}

测试 http://localhost:18092/weixin/pay/create/native?no=1714080902133&money=1:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aatYVgTk-1619103521454)(..\images\41.png)]

这里是表单传参的,json的话也可以,自定义好dto即可。

把code_url的地址显示到qrious组件的前端页面,存放到value对应的值即是一个二维码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2kGKdfli-1619103521456)(..\images\42.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dYyeLRoB-1619103521457)(..\images\43.png)]

支付信息回调

接口分析

每次实现支付之后,微信支付都会将用户支付结果返回到指定路径,而指定路径是指创建二维码的时候填写的notifyurl参数,响应的数据以及相关文档参考一下地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7&index=8

返回参数分析

通知参数如下:

字段名变量名必填类型示例值描述
返回状态码return_codeString(16)SUCCESSSUCCESS
返回信息return_msgString(128)OKOK

以下字段在return_code为SUCCESS的时候有返回

字段名变量名必填类型示例值描述
公众账号IDappidString(32)wx8888888888888888微信分配的公众账号ID(企业号corpid即为此appId)
业务结果result_codeString(16)SUCCESSSUCCESS/FAIL
商户订单号out_trade_noString(32)1212321211201407033568112322商户系统内部订单号
微信支付订单号transaction_idString(32)1217752501201407033233368018微信支付订单号
响应分析

回调地址接收到数据后,需要响应信息给微信服务器,告知已经收到数据,不然微信服务器会再次发送4次请求推送支付信息。

字段名变量名必填类型示例值描述
返回状态码return_codeString(16)SUCCESS请按示例值填写
返回信息return_msgString(128)OK请按示例值填写

举例如下:

<xml>
  <return_code><![CDATA[SUCCESS]]></return_code>
  <return_msg><![CDATA[OK]]></return_msg>
</xml>
回调接收数据实现

WeixinPayController, 添加回调方法,代码如下:

/***
 * 支付回调,数据流数据
 * @param request
 * @return
 */
@RequestMapping(value = "/notify/url")
public String notifyUrl(HttpServletRequest request){
    InputStream inStream;
    try {
        //读取支付回调数据
        inStream = request.getInputStream();
        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024]; //缓存区
        int len = 0;
        while ((len = inStream.read(buffer)) != -1) {
            outSteam.write(buffer, 0, len);
        }
        outSteam.close();
        inStream.close();
        // 将支付回调数据转换成xml字符串
        String result = new String(outSteam.toByteArray(), "utf-8");
        //将xml字符串转换成Map结构
        Map<String, String> map = WXPayUtil.xmlToMap(result);

        //响应数据设置
        Map<String,String> respMap = new HashMap<>();
        respMap.put("return_code","SUCCESS");
        respMap.put("return_msg","OK");
        return WXPayUtil.mapToXml(respMap);
    } catch (Exception e) {
        e.printStackTrace();
        //记录错误日志
    }
    return null;
}

本地机可以使用花生壳来模拟外网访问我们的本地机服务。花生壳服务器通过该软件和账号把一个域名和本地机的ip绑定在一起,实现内网穿透。通过随机域名可外网访问本地机。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mt1Vp79W-1619103521459)(..\images\45.png)]

外网访问:http://c3009841m5.zicp.vip:动态端口/weixin/pay/notify/url

查询订单api

官网文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_2

通过HttpClient工具类实现对远程支付接口的调用。具体参数参见“查询订单”API, 我们在controller方法中轮询调用查询订单(间隔3秒),当返回状态为SUCCESS时,我们会在controller方法返回结果。前端代码收到结果后跳转到成功页面。默认统一下单api是有回调地址notifyurl,我们自己设定的地址,一般微信服务器支付成功后,会回调该地址过来,默认5次,该回调带有订单状态。如果出事故,则需要我们自己用查询订单api来查询订单的状态。

service:

    /***
     * 查询订单状态
     * @param outTradeNo : 客户端自定义订单编号
     * @return
     */
    Map<String, String> queryPayStatus(String outTradeNo);

    /***
     * 查询订单状态
     * @param outTradeNo : 客户端自定义订单编号
     * @return
     */
    @Override
    public Map<String,String> queryPayStatus(String outTradeNo) {
        try {
            //1.封装参数
            Map<String,String> param = new HashMap<>();
            param.put("appid",appid);                            //应用ID
            param.put("mch_id",partner);                         //商户号
            param.put("out_trade_no",outTradeNo);              //商户订单编号
            param.put("nonce_str",WXPayUtil.generateNonceStr()); //随机字符

            //2、将参数转成xml字符,并携带签名
            String paramXml = WXPayUtil.generateSignedXml(param,partnerkey);

            //3、发送请求
            HttpClient httpClient = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery");
            httpClient.setHttps(true);
            httpClient.setXmlParam(paramXml);
            httpClient.post();

            //4、获取返回值,并将返回值转成Map
            String content = httpClient.getContent();
            return WXPayUtil.xmlToMap(content);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

controller:

    /***
     * 查询支付状态
     * @param outTradeNo 自定义订单id,不是微信的id
     * @return
     */
    @GetMapping(value = "/status/query")
    public ResponseResult<Map<String,String>> queryStatus(String outTradeNo){
        Map<String,String> resultMap = weixinPayService.queryPayStatus(outTradeNo);
        return new ResponseResult<Map<String,String>>(true,StatusCode.OK,"查询状态成功!",resultMap);
    }

测试:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TgOwLdYy-1619103521460)(..\images\44.png)]

关闭订单api

官网文档:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_3

用户如果一直未支付,或者取消订单,我们除了要对自定义数据库取消支付状态回滚库存外,还需要向微信服务器请求关闭订单api,让二维码失效(默认2小时失效)。

关闭支付订单,但在关闭之前,需要先关闭微信支付,防止中途用户支付。

修改支付微服务的WeixinPayService,添加关闭支付方法,代码如下:

/***
 * 关闭支付
 * @param orderId
 * @return
 */
Map<String,String> closePay(Long orderId) throws Exception;

修改WeixinPayServiceImpl,实现关闭微信支付方法,代码如下:

/***
 * 关闭微信支付
 * @param orderId
 * @return
 * @throws Exception
 */
@Override
public Map<String, String> closePay(Long orderId) throws Exception {
    //参数设置
    Map<String,String> paramMap = new HashMap<String,String>();
    paramMap.put("appid",appid); //应用ID
    paramMap.put("mch_id",partner);    //商户编号
    paramMap.put("nonce_str",WXPayUtil.generateNonceStr());//随机字符
    paramMap.put("out_trade_no",String.valueOf(orderId));   //商家的唯一编号

    //将Map数据转成XML字符
    String xmlParam = WXPayUtil.generateSignedXml(paramMap,partnerkey);

    //确定url
    String url = "https://api.mch.weixin.qq.com/pay/closeorder";

    //发送请求
    HttpClient httpClient = new HttpClient(url);
    //https
    httpClient.setHttps(true);
    //提交参数
    httpClient.setXmlParam(xmlParam);

    //提交
    httpClient.post();

    //获取返回数据
    String content = httpClient.getContent();

    //将返回数据解析成Map
    return  WXPayUtil.xmlToMap(content);
}

controller:

    /***
     * 关闭订单状态
     * @param outTradeNo 自定义订单id,不是微信订单的id
     * @return
     */
    @GetMapping(value = "/status/close")
    public ResponseResult<Map<String,String>> closePay(String outTradeNo){
        Map<String,String> resultMap = weixinPayService.closePay(outTradeNo);
        return new ResponseResult<Map<String,String>>(true,StatusCode.OK,"关闭订单成功!",resultMap);
    }

MQ处理支付回调状态

支付系统是独立于其他系统的服务,不做相关业务逻辑操作,只做支付处理,所以回调地址接收微信服务返回的支付状态后,立即将消息发送给RabbitMQ,订单系统再监听支付状态数据,根据状态数据做出修改订单状态或者删除订单操作。

发送MQ消息

支付微服务回调后发送消息

支付微服务集成RabbitMQ,添加依赖:

<!--加入ampq-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

在后台手动创建队列,并绑定队列(进入rabbitmq主页,手动创建)。如果使用程序创建队列,可以按照如下方式实现。

修改application.yml,配置支付队列和交换机信息,代码如下:

spring:
  rabbitmq:
    host: 192.168.169.140 #ip地址
    port: 5672 #端口
    username: guest #账号
    password: guest #密码
    
#设置order交换机和order队列名称 
mq:
  pay:
    exchange:
      order: exchange.order
    queue:
      order: queue.order
    routing:
      orderkey: queue.order

创建队列以及交换机并让队列和交换机绑定,添加到MQConfig,添加如下代码:

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

import javax.annotation.Resource;
import java.util.Objects;

/**
 * Title:rabbitmq配置
 * Description:创建队列和交换机
 * @author WZQ
 * @version 1.0.0
 * @date 2020/3/13
 */
@Configuration
public class MQConfig {

    //读取配置文件的内容
    @Resource
    private Environment env;

    /***
     * 创建DirectExchange交换机
     * @return
     */
    @Bean
    public DirectExchange basicExchange(){
        //true:是否实体化,false:是否自动删除
        return new DirectExchange(env.getProperty("mq.pay.exchange.order"), true,false);
    }

    /***
     * 创建队列
     * @return
     */
    @Bean(name = "queueOrder")
    public Queue queueOrder(){
        return new Queue(Objects.requireNonNull(env.getProperty("mq.pay.queue.order")), true);
    }

    /****
     * 队列绑定到交换机上
     * @return
     */
    @Bean
    public Binding basicBinding(){
        return BindingBuilder.bind(queueOrder()).to(basicExchange()).with(env.getProperty("mq.pay.routing.orderkey"));
    }
}

使用:

@Value("${mq.pay.exchange.order}")
private String exchange;
@Value("${mq.pay.queue.order}")
private String queue;
@Value("${mq.pay.routing.key}")
private String routing;

@Autowired
private RabbitTemplate rabbitTemplate;

/***
 * 支付回调
 * @param request
 * @return
 */
@RequestMapping(value = "/notify/url")
public String notifyUrl(HttpServletRequest request){
    InputStream inStream;
    try {
        //读取支付回调数据
        inStream = request.getInputStream();
        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inStream.read(buffer)) != -1) {
            outSteam.write(buffer, 0, len);
        }
        outSteam.close();
        inStream.close();
        // 将支付回调数据转换成xml字符串
        String result = new String(outSteam.toByteArray(), "utf-8");
        //将xml字符串转换成Map结构
        Map<String, String> map = WXPayUtil.xmlToMap(result);
        
        //将消息发送给RabbitMQ
        rabbitTemplate.convertAndSend(exchange,routing, JSON.toJSONString(map));

        //响应数据设置
        Map respMap = new HashMap();
        respMap.put("return_code","SUCCESS");
        respMap.put("return_msg","OK");
        return WXPayUtil.mapToXml(respMap);
    } catch (Exception e) {
        e.printStackTrace();
        //记录错误日志
    }
    return null;
}
监听MQ消息

处理订单状态。在订单微服务中,我们需要监听MQ支付状态消息,并实现订单数据操作。

订单微服务集成RabbitMQ,再监听队列消息。

在pom.xml中引入如下依赖:

<!--加入ampq-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

在application.yml中加入配置,代码如下:

spring:
  rabbitmq:
    host: 192.168.169.140 #ip地址
    port: 5672 #端口
    username: guest #账号
    password: guest #密码
    
#设置队列名称,跟发送消息的队列名字一致
mq:
  pay:
    queue:
      order: queue.order

监听消息修改订单

在订单微服务于中创建consumer.OrderPayMessageListener,在该类中consumeMessage方法,用于监听消息,并根据支付状态处理订单,代码如下:

@Component
@RabbitListener(queues = {"${mq.pay.queue.order}"})
public class OrderPayMessageListener {

    @Resource
    private OrderService orderService;

    /***
     * 接收消息
     */
    @RabbitHandler
    public void consumeMessage(String msg){
        //将数据转成Map
        Map<String,String> result = JSON.parseObject(msg,Map.class);

        //return_code=SUCCESS
        String return_code = result.get("return_code");
        
        //业务结果
        String result_code = result.get("result_code");

        //状态码 return_code=SUCCESS/FAIL,修改订单状态
        if(return_code.equalsIgnoreCase("success") ){
            //获取订单号
            String outTradeNo = result.get("out_trade_no");
            //业务结果,支付成功的
            if(result_code.equalsIgnoreCase("success")){
                if(outTradeNo!=null){
                    //修改订单状态根据out_trade_no,具体逻辑业务,看情况
                    //transaction_id微信支付的订单号,time_end是结算时间
                    orderService.updateStatus(outTradeNo, result.get("time_end"),result.get("transaction_id"));
                }
            }else{
                //需要调用微信服务器关闭订单
                
                //订单删除,具体逻辑业务,看情况
                orderService.deleteOrder(outTradeNo);
            }
        }
    }
}

其它微信api接口,详看:https://pay.weixin.qq.com/wiki/doc/api/native.php

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ToDYmGZW-1619103521462)(..\images\47.png)]

RabbitMQ延时消息队列

延时队列介绍

延时队列即放置在该队列里面的消息是不需要立即消费的,而是等待一段时间之后取出消费。
那么,为什么需要延迟消费呢?我们来看以下的场景:

网上商城下订单后30分钟后没有完成支付,取消订单(如:淘宝、去哪儿网);
系统创建了预约之后,需要在预约时间到达前一小时提醒被预约的双方参会;
系统中的业务失败之后,需要重试;
这些场景都非常常见,我们可以思考,比如第二个需求,系统创建了预约之后,需要在预约时间到达前一小时提醒被预约的双方参会。那么一天之中肯定是会有很多个预约的,时间也是不一定的,假设现在有1点 2点 3点 三个预约,如何让系统知道在当前时间等于0点 1点 2点给用户发送信息呢,是不是需要一个轮询,一直去查看所有的预约,比对当前的系统时间和预约提前一小时的时间是否相等呢?这样做非常浪费资源而且轮询的时间间隔不好控制。如果我们使用延时消息队列呢,我们在创建时把需要通知的预约放入消息中间件中,并且设置该消息的过期时间,等过期时间到达时再取出消费即可。

Rabbitmq实现延时队列一般而言有两种形式:
第一种方式:利用两个特性: Time To Live(TTL)、Dead Letter Exchanges(DLX)[A队列过期->转发给B队列]

第二种方式:利用rabbitmq中的插件x-delay-message

TTL DLX实现延时队列
TTL DLX介绍

TTL
RabbitMQ可以针对队列设置x-expires(则队列中所有的消息都有相同的过期时间)或者针对Message设置x-message-ttl(对消息进行单独设置,每条消息TTL可以不同),来控制消息的生存时间,如果超时(两者同时设置以最先到期的时间为准),则消息变为dead letter(死信)

Dead Letter Exchanges(DLX)
RabbitMQ的Queue可以配置x-dead-letter-exchange和x-dead-letter-routing-key(可选)两个参数,如果队列内出现了dead letter,则按照这两个参数重新路由转发到指定的队列。
x-dead-letter-exchange:出现dead letter之后将dead letter重新发送到指定exchange

x-dead-letter-routing-key:出现dead letter之后将dead letter重新按照指定的routing-key发送

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YPCb5ySg-1619103521463)(..\images\1557396863944.png)]

DLX延时队列实现

还是在订单微服务中,当用户生成订单支付状态后,如果半小时未支付,则取消支付,向微信服务器请求关闭订单api。

配置队列
#设置交换机和队列名称
mq:
  pay:
    exchange:
      order: exchange.order
      cancelOrder: dlx.exchange
    queue:
      order: queue.order
      cancelOrder: queue.listener
      cancelOrderDelay: queue.delay
    routing:
      orderkey: queue.order
      cancelOrderkey: queue.listener
队列创建

创建2个队列,用于接收消息的叫延时队列DelayQueue,用于转发消息的队列叫ListenerQueue,同时创建一个交换机,代码如下:

/**
 * Title:延时队列配置
 * Description:创建2个队列,利用rabbit队列超时机制
 * @author WZQ
 * @version 1.0.0
 * @date 2020/3/14
 */
@Configuration
public class DelayQueueConfig {
    
    //读取配置文件的内容
    @Resource
    private Environment env;

    /**
     * 创建queue1,没有被监听,延时队列
     * 消息过期了发送给死信队列,死信队列绑定queue2
     */
    @Bean
    public Queue orderDelayQueue(){
        return QueueBuilder
                .durable(Objects.requireNonNull(env.getProperty("mq.pay.queue.cancelOrderDelay"))
                // 延时队列过期的消息会进入给死信队列(没有被读的消息),死信队列的消息就发送到queue2中
                // 这里就需要死信队列的交换机和队列,规定是死信路由key的就路由到queue2
                .withArgument("x-dead-letter-exchange", env.getProperty("mq.pay.exchange.cancelOrder")) //死信交换机
                .withArgument("x-dead-letter-routing-key", Objects.requireNonNull(env.getProperty("mq.pay.queue.cancelOrder")) //交换机绑定路由消息到queue2
                .build();
    }

    /**
     * 创建queue2,普通有监听的队列
     */
    @Bean
    public Queue orderListenerQueue(){
        return new Queue(Objects.requireNonNull(env.getProperty("mq.pay.queue.cancelOrder"),true);
    }

    /**
     * 创建交换机
     */
    @Bean
    public Exchange orderListenerExchange(){
        return new DirectExchange(env.getProperty("mq.pay.exchange.cancelOrder"));
    }

    /**
     * 队列queue2绑定交换机
     */
    @Bean
    public Binding orderListenerBinding(){
        return BindingBuilder.bind(orderListenerQueue()).to(orderListenerExchange()).with(Objects.requireNonNull(env.getProperty("mq.pay.queue.cancelOrderkey")).noargs();
    }

}
启动类添加

@EnableRabbit

发送消息

在生成订单,获取支付状态的service中,加入发送消息代码

@Resource
private RabbitTemplate rabbitTemplate;

//读取配置文件的内容
@Resource
private Environment env;

//传订单id过去
rabbitTemplate.convertAndSend(env.getProperty("mq.pay.queue.cancelOrderkey"),
                              (Object) order.getId(), new MessagePostProcessor() {
                                  @Override
                                  public Message postProcessMessage(Message message) throws AmqpException {
                   //设置延时
                   message.getMessageProperties().setExpiration("1800000");//30*60*1000,半小时
                        return message;
                    }
                });

   

监听实现

订单微服务中监听订单支付状态

@Component
@RabbitListener(queues = DelayQueueConfig.LISTENER_QUEUE)
public class OrderDelayMessageListener {

    @Autowired
    private OrderService orderService;

    @Autowired
    private WeixinPayFeign weixinPayFeign;

    /***
     * 读取消息,消息是id
     * 关闭支付,再关闭订单
     * @param message
     */
    @RabbitHandler
    public void consumeMessage(String message){//@Payload Object message消息是实体类的json数据
        
        //读取消息,消息封装类,自定义,订单号
        //MessageStatus messageStatus = JSON.parseObject(message,MessageStatus.class); 

        //查询订单状态,确定未支付
        selectById(message);
        
        //如果订单未支付
        //关闭支付,feign调用支付微服务的关闭订单接口
        Result closeResult = weixinPayFeign.closePay(message);
        
        Map<String,String> closeMap = (Map<String, String>) closeResult.getData();

        if(closeMap!=null && closeMap.get("return_code").equalsIgnoreCase("success") &&
           closeMap.get("result_code").equalsIgnoreCase("success") ){
           //关闭订单
           OrderService.closeOrder(username);
           //库存回滚
           //...
        }
    }
}

  • 4
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
实现微信代扣教育续费通功能,你可以按照以下步骤进行操作: 1. 配置微信支付:首先,在微信支付平台上注册账号,并获取到相关的API密钥、商户号等信息。 2. 创建Spring Boot项目:使用Spring Initializr或其他方式创建一个新的Spring Boot项目。 3. 添加依赖:在项目的pom.xml文件中添加微信支付相关的依赖,比如`wechatpay-api`、`wechatpay-transport`等。 4. 实现代扣接口:创建一个Controller类,在其中定义一个处理教育续费代扣请求的接口,比如`/wechat/pay/withhold`。在这个接口中,你可以接收用户的续费请求并处理代扣逻辑。 5. 配置支付参数:将微信支付所需的参数配置到application.properties或application.yml文件中,包括API密钥、商户号等。 6. 发起代扣请求:在你的业务逻辑中,当用户需要进行续费时,调用微信支付接口,生成代扣链接或二维码,并将用户的续费信息传递给微信支付平台。 7. 处理代扣回调:当代扣操作完成后,微信会通过回调通知的方式将支付结果发给你的服务器。在之前定义的代扣回调接口中,你可以解析并处理这些回调通知,更新订单状态、发通知等操作。 8. 异常处理和安全性:在实现过程中,要考虑异常处理和安全性。例如,处理代扣过程中可能出现的异常情况,并确保代扣接口的安全性,防止恶意攻击和重复代扣等问题。 这只是一个简单的实现思路,具体的实现细节还需要根据你的具体需求和业务场景来定制。希望对你有所帮助!如果你还有其他问题,请继续提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wzq_55552

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值