畅够商城第12天项目总结(微信支付)

191 阅读14分钟

微信支付开发文档:pay.weixin.qq.com/wiki/doc/ap…


微信支付时序图:模式二

在这里插入图片描述

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

相关配置属性介绍

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

1. 引入相关依赖

<!--httpclient支持-->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>
<!--微信支付SDK-->
<dependency>
    <groupId>com.github.wxpay</groupId>
    <artifactId>wxpay-sdk</artifactId>
    <version>0.0.3</version>
</dependency>

微信支付SDK相关方法

  • 获取随机字符串:WXPayUtil.generateNonceStr()
  • MAP转换为XML字符串(自动添加签名):WXPayUtil.generateSignedXml(param, partnerkey)
  • XML字符串转换为MAP:WXPayUtil.xmlToMap(result)

HttpClient工具类

HttpClient工具类代码:

/**
 * @Auther: csp1999
 * @Date: 2021/01/31/17:04
 * @Description: HttpClient g工具类
 */
public class HttpClientUtil {

    /**
     * 请求url
     */
    private String url;

    /**
     * 请求参数Map集合
     */
    private Map<String, String> param;

    /**
     * 状态码
     */
    private int statusCode;

    /**
     * 请求内容
     */
    private String content;

    /**
     * xml参数
     */
    private String xmlParam;

    /**
     * 是否是https
     */
    private boolean isHttps;

    public boolean isHttps() {
        return isHttps;
    }

    public void setHttps(boolean isHttps) {
        this.isHttps = isHttps;
    }

    public String getXmlParam() {
        return xmlParam;
    }

    public void setXmlParam(String xmlParam) {
        this.xmlParam = xmlParam;
    }

    public HttpClientUtil(String url, Map<String, String> param) {
        this.url = url;
        this.param = param;
    }

    public HttpClientUtil(String url) {
        this.url = url;
    }

    public void setParameter(Map<String, String> map) {
        param = map;
    }

    public void addParameter(String key, String value) {
        if (param == null)
            param = new HashMap<String, String>();
        param.put(key, value);
    }

    public void post() throws ClientProtocolException, IOException {
        HttpPost http = new HttpPost(url);
        setEntity(http);
        execute(http);
    }

    public void put() throws ClientProtocolException, IOException {
        HttpPut http = new HttpPut(url);
        setEntity(http);
        execute(http);
    }

    public void get() throws ClientProtocolException, IOException {
        if (param != null) {
            StringBuilder url = new StringBuilder(this.url);
            boolean isFirst = true;
            for (String key : param.keySet()) {
                if (isFirst) {
                    url.append("?");
                } else {
                    url.append("&");
                }
                url.append(key).append("=").append(param.get(key));
            }
            this.url = url.toString();
        }
        HttpGet http = new HttpGet(url);
        execute(http);
    }

    /**
     * set http post,put param
     */
    private void setEntity(HttpEntityEnclosingRequestBase http) {
        if (param != null) {
            List<NameValuePair> nvps = new LinkedList<NameValuePair>();
            for (String key : param.keySet()) {
                nvps.add(new BasicNameValuePair(key, param.get(key))); // 参数
            }
            http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 设置参数
        }
        if (xmlParam != null) {
            http.setEntity(new StringEntity(xmlParam, Consts.UTF_8));
        }
    }

    private void execute(HttpUriRequest http) throws ClientProtocolException,
            IOException {
        CloseableHttpClient httpClient = null;
        try {
            if (isHttps) {
                SSLContext sslContext = new SSLContextBuilder()
                        .loadTrustMaterial(null, new TrustStrategy() {
                            // 信任所有
                            @Override
                            public boolean isTrusted(X509Certificate[] chain,
                                                     String authType)
                                    throws CertificateException {
                                return true;
                            }
                        }).build();
                SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                        sslContext);
                httpClient = HttpClients.custom().setSSLSocketFactory(sslsf)
                        .build();
            } else {
                httpClient = HttpClients.createDefault();
            }
            CloseableHttpResponse response = httpClient.execute(http);
            try {
                if (response != null) {
                    if (response.getStatusLine() != null) {
                        statusCode = response.getStatusLine().getStatusCode();
                    }
                    HttpEntity entity = response.getEntity();
                    // 响应内容
                    content = EntityUtils.toString(entity, Consts.UTF_8);
                }
            } finally {
                response.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            httpClient.close();
        }
    }

    public int getStatusCode() {
        return statusCode;
    }

    public String getContent() throws ParseException, IOException {
        return content;
    }
}

测试使用:

/**
 * @Auther: csp1999
 * @Date: 2021/01/31/17:08
 * @Description: HttpClient工具类使用测试
 */
public class HttpClientTest {

    public static void main(String[] args) throws IOException {

        // 请求地址url
        String url = "https://api.mch.weixin.qq.com/pay/orderquery";
        // 要发送的xml参数
        String xmlParam = "<xml><name>张三</name></xml>";

        // 实例化HttpClientUtil
        HttpClientUtil client=new HttpClientUtil(url);

        client.setHttps(true);// 是否是https协议
        client.setXmlParam(xmlParam);// 发送的xml数据
        client.post();// 执行post请求
        String result = client.getContent();// 获取响应结果

        System.out.println(result);
    }
}

测试结果:

在这里插入图片描述


2. 支付微服务搭建

创建changgou-service-pay 微服务工程

application.yml

创建application.yml,配置文件如下:

server:
  port: 8092
spring:
  application:
    name: changgou-pay
  main:
    allow-bean-definition-overriding: true
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka
  instance:
    prefer-ip-address: true
feign:
  hystrix:
    enabled: true
# hystrix 配置
hystrix:
  command:
    default:
      execution:
        timeout:
        # 如果enabled设置为false,则请求超时交给ribbon控制
          enabled: true
        isolation:
          strategy: SEMAPHORE

# 微信支付信息配置
weixin:
  appid: wx8397f8696b538317 # 微信公众账号或开放平台APP的唯一标识
  partner: 1473426802 # 财付通平台的商户账号
  partnerkey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb # 财付通平台的商户密钥
  notifyurl: http://www.itcast.cn # 回调地址

启动类

changgou-service-pay中创建com.changgou.WeixinPayApplication,代码如下:

@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
@EnableEurekaClient
public class WeixinPayApplication {

    public static void main(String[] args) {
        SpringApplication.run(WeixinPayApplication.class,args);
    }
}

3. 微信支付二维码生成

需求分析与实现思路

在支付页面上生成支付二维码,并显示订单号和金额,用户拿出手机,打开微信扫描页面上的二维码,然后在微信中完成支付:

在这里插入图片描述

实现思路

  • 通过HttpClient工具类实现对远程支付接口的调用。接口链接:api.mch.weixin.qq.com/pay/unified…
  • 具体参数参见“统一下单”API pay.weixin.qq.com/wiki/doc/ap…, 构建参数发送给统一下单的url ,返回的信息中有支付url,根据url生成二维码,显示的订单号和金额也在返回的信息中

代码实现

(1)业务层

新增com.changgou.service.WeixinPayService接口,代码如下:

/**
 * @Auther: csp1999
 * @Date: 2021/01/31/18:33
 * @Description: 微信支付service接口
 */
public interface WeixinPayService {

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

创建com.changgou.service.impl.WeixinPayServiceImpl类,并发送Post请求获取预支付信息,包含二维码扫码支付地址。代码如下:

/**
 * @Auther: csp1999
 * @Date: 2021/01/31/18:33
 * @Description: 微信支付service接口实现类
 */
@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;

    /**
     * 创建二维码
     * <p>
     * out_trade_no : 客户端自定义订单编号
     * total_fee    : 交易金额,单位:分
     *
     * @return
     */
    @Override
    public Map<String, String> createNative(Map<String, String> paramMap) {
        try {
            // 1、封装参数
            Map<String, String> param = new HashMap();
            param.put("appid", appid);                               // 应用appID
            param.put("mch_id", partner);                            // 商户ID号
            param.put("nonce_str", WXPayUtil.generateNonceStr());    // 随机数
            param.put("body", "畅购");                                // 订单描述
            param.put("out_trade_no", paramMap.get("out_trade_no"));                 // 商户订单号
            param.put("total_fee", paramMap.get("total_fee"));                       // 交易金额
            param.put("spbill_create_ip", "127.0.0.1");              // 终端IP
            param.put("notify_url", notifyurl);                      // 回调地址
            param.put("trade_type", "NATIVE");                       // 交易类型

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

            // 3、执行请求
            HttpClientUtil httpClient = new HttpClientUtil("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", paramMap.get("out_trade_no"));
            dataMap.put("total_fee", paramMap.get("total_fee"));

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

(2) 控制层

创建com.changgou.controller.WeixinPayController,主要调用WeixinPayService的方法获取创建二维码的信息,代码如下:

/**
 * @Auther: csp1999
 * @Date: 2021/01/31/19:26
 * @Description: 微信支付controller
 */
@CrossOrigin
@RestController
@RequestMapping(value = "/weixin/pay")
public class WeixinPayController {

    @Autowired
    private WeixinPayService weixinPayService;

    /**
     * 创建下单支付的二维码
     *
     * @return
     */
    @RequestMapping(value = "/create/native")
    public Result createNative(@RequestParam Map<String, String> paramMap) {
        Map<String, String> resultMap = weixinPayService.createNative(paramMap);
        
        return new Result(true, StatusCode.OK, "创建二维码预付订单成功!", resultMap);
    }
}

这里我们订单号通过随机数生成,金额暂时写死,后续开发我们再对接业务系统得到订单号和金额。

(3)测试

访问http://localhost:8092/weixin/pay/create/native?out_trade_no=No055550001&total_fee=1

在这里插入图片描述

打开支付页面 pay.html,修改value路径,然后打开,会出现二维码,可以扫码测试:

<html>
<head>
<title>二维码入门小demo</title>
<!--1.引入js  2. 创建一个img标签 用来存储显示二维码的图片 3.创建js对象 4.设置js对象的配置项-->
<script src="qrious.js"> </script>
</head>
<body>
<img id="myqrious" >
</body>

<script>
   var qrious = new QRious({
   		 element:document.getElementById("myqrious"),// 指定的是图片所在的DOM对象
   		 size:250,//指定图片的像素大小
		 level:'H',//指定二维码的容错级别(H:可以恢复30%的数据)
		 value:'weixin://wxpay/bizpayurl?pr=MRD74tfzz'//指定二维码图片代表的真正的值
   })
</script>
</html>

在这里插入图片描述

扫码测试:

在这里插入图片描述

4. 检测支付状态

需求分析

当用户支付成功后跳转到成功页面:

在这里插入图片描述

当用户支付成功后跳转到成功页面:

在这里插入图片描述

实现思路

  • 通过HttpClient工具类实现对远程支付接口的调用。接口链接:api.mch.weixin.qq.com/pay/orderqu…
  • 具体参数参见“查询订单”API pay.weixin.qq.com/wiki/doc/ap…, 我们在controller方法中轮询调用查询订单(间隔3秒),当返回状态为success时,我们会在controller方法返回结果。前端代码收到结果后跳转到成功页面。

代码实现

(1)业务层

修改com.changgou.service.WeixinPayService,新增方法定义:

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

在com.changgou.pay.service.impl.WeixinPayServiceImpl中增加实现方法:

/**
 * 查询订单状态
 *
 * @param out_trade_no: 客户端自定义订单编号
 * @return
 */
@Override
public Map<String,String> queryPayStatus(String out_trade_no) {
    try {
        // 1.封装参数
        Map param = new HashMap();
        param.put("appid", appid);                            // 应用appID
        param.put("mch_id", partner);                         // 商户号
        param.put("out_trade_no", out_trade_no);              // 商户订单编号
        param.put("nonce_str", WXPayUtil.generateNonceStr()); // 随机字符
        // 2、将参数转成xml字符,并携带签名
        String paramXml = WXPayUtil.generateSignedXml(param, partnerkey);
        // 3、发送请求
        HttpClientUtil httpClient = new HttpClientUtil("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;
}

(2)控制层

com.changgou.controller.WeixinPayController新增方法,用于查询支付状态,代码如下:

/**
 * 查询支付状态
 *
 * @param out_trade_no
 * @return
 */
@GetMapping(value = "/status/query")
public Result queryStatus(String out_trade_no) {
    Map<String, String> resultMap = weixinPayService.queryPayStatus(out_trade_no);
    
    return new Result(true, StatusCode.OK, "查询状态成功!", resultMap);
}

(3)测试

访问:http://localhost:8092/weixin/pay/status/query?out_trade_no=No055550001

在这里插入图片描述

5. 支付信息回调

每次实现支付之后,微信支付都会将用户支付结果返回到指定路径,而指定路径是指创建二维码的时候填写的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>

回调接收数据实现

修改changgou-service-pay微服务的com.changgou.pay.controller.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);
        System.out.println(map);
        // 响应数据设置
        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;
}

当用户扫码支付时,会触发回调!

application.yml中修改支付回调地址:

在这里插入图片描述

6.MQ处理支付回调状态

业务分析

在这里插入图片描述

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

集成RabbitMQ

修改支付微服务,集成RabbitMQ,添加如下依赖:

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

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

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

spring:
  application:
    name: changgou-pay
  main:
    allow-bean-definition-overriding: true
  # rabbitmq相关配置:
  rabbitmq:
    host: 8.xxx.xx.136
    port: 5672
    username: guest
    password: guest
    
# 位置支付交换机和队列
mq:
  pay:
    exchange:
      order: exchange.order
    queue:
      order: queue.order
    routing:
      key: queue.order

创建队列以及交换机并让队列和交换机绑定,修改com.changgou.WeixinPayApplication,添加如下代码:

/**
 * @Auther: csp1999
 * @Date: 2021/02/01/12:31
 * @Description: RabbitMq配置类
 */
@Configuration
public class RabbitMqConfig {

    /**
     * 读取配置文件中的信息对象
     */
    @Autowired
    private Environment environment;

    /**
     * 创建队列
     *
     * @return
     */
    @Bean
    public Queue orderQueue() {
        return new Queue(environment.getProperty("mq.pay.queue.order"), true);
    }

    /**
     * 创建交换机
     *
     * @return
     */
    @Bean
    public Exchange orderExchange() {
        return new DirectExchange(environment.getProperty("mq.pay.exchange.order"), true, false);
    }

    /**
     * 队列兵丁交换机
     *
     * @param orderQueue
     * @param orderExchange
     * @return
     */
    @Bean
    public Binding orderQueueExchange(Queue orderQueue, Exchange orderExchange) {
        return BindingBuilder
                .bind(orderQueue)
                .to(orderExchange)
                .with(environment.getProperty("mq.pay.routing.key"))
                .noargs();
    }
}

发送MQ消息

修改WeixinPayController中的回调方法,在接到支付信息后,立即将支付信息发送给RabbitMQ,如下:

在这里插入图片描述

代码如下:

/**
 * @Auther: csp1999
 * @Date: 2021/01/31/19:26
 * @Description: 微信支付controller
 */
@CrossOrigin
@RestController
@RequestMapping(value = "/weixin/pay")
public class WeixinPayController {

    @Autowired
    private WeixinPayService weixinPayService;

    @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> resultMap = WXPayUtil.xmlToMap(result);
            System.out.println(resultMap);

            /**
             * 发送支付结果给mq
             */
            rabbitTemplate.convertAndSend("exchange.order","queue.order", 
                                          JSON.toJSONString(resultMap));

            // 响应数据设置
            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;
    }
}

监听MQ消息处理订单

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

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

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

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

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

spring:
  application:
    name: changgou-pay
  main:
    allow-bean-definition-overriding: true
  # rabbitmq相关配置:
  rabbitmq:
    host: 8.xxx.xx.136
    port: 5672
    username: guest
    password: guest
    
# 位置支付交换机和队列
mq:
  pay:
    exchange:
      order: exchange.order
    queue:
      order: queue.order
    routing:
      key: queue.order

OrderMessageListener监听组件:

/**
 * @Auther: csp1999
 * @Date: 2021/02/01/12:57
 * @Description: 订单消息监听
 */
@Component
@RabbitListener(queues = {"${mq.pay.queue.order}"})
public class OrderMessageListener {

    @Autowired
    private OrderService orderService;

    /**
     * 支付结果监听处理
     *
     * @param message
     */
    @RabbitHandler
    public void getMessage(String message) {

        // 支付结果
        Map<String, String> resultMap = JSON.parseObject(message, Map.class);
        System.out.println("监听到的消息:" + resultMap);

        // 通信标识 return_code
        String return_code = resultMap.get("return_code");

        if (return_code.equals("SUCCESS")) {
            // 支付业务结果 result_code
            String result_code = resultMap.get("result_code");

            // 订单号 out_trade_no
            String out_trade_no = resultMap.get("out_trade_no");

            // 支付时间 time_end
            String time_end = resultMap.get("time_end");

            // 支付成功,修改订单状态
            if (result_code.equals("SUCCESS")) {
                // 微信支付交易流水号 transaction_id
                String transaction_id = resultMap.get("transaction_id");
                // 修改订单状态
                orderService.updateOrderStatus(out_trade_no,time_end,transaction_id);
            } else {
                // 支付失败,关闭支付,取消订单,回滚库存
                // 删除订单
                orderService.deleteOrder(out_trade_no);
            }
        }
    }
}

OrderService 中新增如下代码:

/***
 * 删除订单,回滚库存
 * @param out_trade_no
 */
void deleteOrder(String out_trade_no);

/***
 * 修改订单状态
 * 1.修改支付时间
 * 2.修改支付状态
 * @param out_trade_no
 * @param pay_time
 * @param transaction_id
 */
void updateOrderStatus(String out_trade_no,String pay_time,String transaction_id);

OrderServiceImpl 中新增如下代码:

/***
 * 删除订单
 * @param out_trade_no
 */
@Override
public void deleteOrder(String out_trade_no) {
    Order order = orderMapper.selectByPrimaryKey(out_trade_no);
    // 修改订单状态(假删除)
    order.setUpdateTime(new Date());
    order.setPayStatus("2");// 支付失败
    orderMapper.updateByPrimaryKeySelective(order);
    // 回滚库存 -> 调用goods微服务
}

/***
 * 修改订单状态
 * 1.修改支付时间
 * 2.修改支付状态
 * @param out_trade_no 订单号
 * @param pay_time 支付时间 格式:20210109
 * @param transaction_id 订单流水号
 */
@Override
public void updateOrderStatus(String out_trade_no, String pay_time, String transaction_id) {
    // 时间格式转换
    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
    Date payTime = null;
    try {
        // string类型:20210109 -> date类型
        payTime = sdf.parse(pay_time);
    } catch (ParseException e) {
        e.printStackTrace();
    }
    // 先查询订单信息
    Order order = orderMapper.selectByPrimaryKey(out_trade_no);
    // 再修改订单信息
    order.setPayTime(payTime);// 支付时间
    order.setTransactionId(transaction_id);// 修改流水号
    order.setPayType("1");// 修改支付状态:1-已支付
    orderMapper.updateByPrimaryKeySelective(order);
}

7. 延时队列(扩展)

QueueConfig类

在changgou-service-order微服务的config包下添加QueueConfig类:

/**
 * @Auther: csp1999
 * @Date: 2021/02/01/15:31
 * @Description: 延时队列配置
 */
public class QueueConfig {

    /***
     * 创建Queue1:延时队列,会过期,过期后将数据发给Queue2
     */
    @Bean
    public Queue orderDelayQueue() {
        return QueueBuilder
                .durable("orderDelayQueue")
                // 死信:过了一定时间仍未被读取的消息(即,被放弃读取的消息)
                .withArgument("x-dead-letter-exchange", "orderListenerExchange")// 死信交换机
                .withArgument("x-dead-letter-routing-key", "orderListenerQueue")
                .build();
    }

    /***
     * 创建Queue2
     */
    @Bean
    public Queue orderListenerQueue() {
        return new Queue("orderListenerQueue", true);
    }

    /***
     * 创建交换机
     */
    @Bean
    public Exchange orderListenerExchange() {
        return new DirectExchange("orderListenerExchange");
    }

    /***
     * 队列Queue2绑定交换机
     */
    @Bean
    public Binding orderListenerBinding(Queue orderListenerQueue, Exchange orderListenerExchange) {
        return BindingBuilder
                .bind(orderListenerQueue)
                .to(orderListenerExchange)
                .with("orderListenerQueue")
                .noargs();
    }
}

添加订单方法中增加mq消息发送

OrderServiceImpl类中的add方法添加如下代码:

/**
 * 增加Order订单
 *
 * @param order
 */
@Override
public int add(Order order) {
    ......
    
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    System.out.println("创建订单时间:"+sdf.format(new Date()));
    
    // 添加订单消息发送
    rabbitTemplate.convertAndSend("orderDelayQueue", (Object) order.getId(), new MessagePostProcessor() {
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            // 设置延时20s读取
            message.getMessageProperties().setExpiration("20000");
            return message;
        }
    });
    
    ...
}

添加过期消息监听

新建DelayMessageListener配置类

/**
 * @Auther: csp1999
 * @Date: 2021/02/01/15:58
 * @Description: 过期消息监听配置类
 */
@Component
@RabbitListener(queues = "orderListenerQueue")
public class DelayMessageListener {

    /**
     * 延时队列监听
     *
     * @param message
     */
    @RabbitHandler
    public void getDelayMessage(String message) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("监听消息的时间:" + sdf.format(new Date()));
        System.out.println(",监听到的消息内容:" + message);
    }
}