Chiriri's blog Chiriri's blog
首页
  • Java

    • JavaSE
    • JavaEE
    • 设计模式
  • Python

    • Python
    • Python模块
    • 机器学习
  • Golang

    • Golang
    • gRPC
  • 服务器

    • Linux
    • MySQL
    • NoSQL
    • Kubernetes
  • 项目

    • 传智健康
    • 畅购商城
  • Hadoop生态

    • Hadoop
    • Zookeeper
    • Hive
    • Flume
    • Kafka
    • Azkaban
    • Hbase
    • Scala
    • Spark
    • Flink
  • 大数据项目

    • 离线数仓
  • 青训营

    • 第四届青训营
  • HTML

    • HTML
    • JavaScript
  • Vue

    • Vue2
    • TypeScript
    • Vue3
    • Uni-APP
  • 数据结构与算法
  • C语言
  • 考研数据结构
  • 计算机组成原理
  • 计算机操作系统
  • Java基础

    • Java基础
    • Java集合
    • JUC
    • JVM
  • 框架

    • Spring
    • Dubbo
    • Spring Cloud
  • 数据库

    • MySQL
    • Redis
    • Elasticesearch
  • 消息队列

    • RabbitMQ
    • RocketMQ
  • 408

    • 计算机网络
    • 操作系统
    • 算法
  • 分类
  • 标签
  • 归档
  • 导航站
GitHub (opens new window)

Iekr

苦逼后端开发
首页
  • Java

    • JavaSE
    • JavaEE
    • 设计模式
  • Python

    • Python
    • Python模块
    • 机器学习
  • Golang

    • Golang
    • gRPC
  • 服务器

    • Linux
    • MySQL
    • NoSQL
    • Kubernetes
  • 项目

    • 传智健康
    • 畅购商城
  • Hadoop生态

    • Hadoop
    • Zookeeper
    • Hive
    • Flume
    • Kafka
    • Azkaban
    • Hbase
    • Scala
    • Spark
    • Flink
  • 大数据项目

    • 离线数仓
  • 青训营

    • 第四届青训营
  • HTML

    • HTML
    • JavaScript
  • Vue

    • Vue2
    • TypeScript
    • Vue3
    • Uni-APP
  • 数据结构与算法
  • C语言
  • 考研数据结构
  • 计算机组成原理
  • 计算机操作系统
  • Java基础

    • Java基础
    • Java集合
    • JUC
    • JVM
  • 框架

    • Spring
    • Dubbo
    • Spring Cloud
  • 数据库

    • MySQL
    • Redis
    • Elasticesearch
  • 消息队列

    • RabbitMQ
    • RocketMQ
  • 408

    • 计算机网络
    • 操作系统
    • 算法
  • 分类
  • 标签
  • 归档
  • 导航站
GitHub (opens new window)
  • JavaSE

  • JavaEE

  • Linux

  • MySQL

  • NoSQL

  • Python

  • Python模块

  • 机器学习

  • 设计模式

  • 传智健康

  • 畅购商城

    • Day01 项目搭建
    • Day02 FastDFS
    • Day03 微服务鉴权
    • Day04 新增和修改商品
    • Day05 广告缓存
    • Day06 监听数据库更新广告缓存
    • Day07 ES搜索
    • Day08 Thymeleaf
    • Day09 Oauth2
    • Day10 购物车渲染
    • Day11 订单结算
    • Day12 分布式事务解决方案
    • Day13 微信支付
      • 微信支付快速入门
        • 申请微信支付
        • 入门案例
        • 二维码JS插件- QRcode.js
      • 微信支付二维码
        • 提交订单跳转支付页
        • 支付微服务-下单
        • 页面对接支付微服务
      • 支付回调逻辑处理
        • 回调消息接收并转换
        • 查询订单验证通知
        • 接收订单验证通知
      • 推送支付通知
        • RabbitMQ Web STOMP 插件
        • STOMP协议
        • 插件安装
        • 消息推送测试
      • 代码实现
    • Day14 订单处理
    • Day15 秒杀前端
    • Day16 秒杀后端
  • 博客项目

  • JVM

  • JUC

  • Golang

  • Kubernetes

  • 硅谷课堂

  • C

  • 源码

  • 神领物流

  • RocketMQ

  • 短链平台

  • 后端
  • 畅购商城
Iekr
2022-02-21
目录

Day13 微信支付

# Day13 微信支付

# 微信支付快速入门

# 申请微信支付

申请微信支付需要提交相关资料审核,比较麻烦

我们使用课程提供的微信支付账号,用于 demo 开发

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

mch_id:商户号 1473426802

key:商户密钥 T6m9iK73b0kn9g5v426MKfHQH7X8rKwb

# 入门案例

开发文档 V2 (opens new window)

开发文档 V3 (opens new window)

由于 V3 SDK 封装不是特别完善 此处使用的是 V2 进行开发

导入微信支付 api 依赖

 <dependency>
    <groupId>com.github.wxpay</groupId>
    <artifactId>wxpay-sdk</artifactId>
    <version>3.0.9</version>
</dependency>
1
2
3
4
5

创建 com.github.wxpay.sdk 包,包下创建 MyConfig 类 ,继承自抽象类 WXPayConfig

public class MyConfig extends WXPayConfig {
    String getAppID() {
        //微信公众账号或开放平台APP的唯一标识
        return "wx8397f8696b538317";
    }

    String getMchID() {
        //商户号
        return "1473426802";
    }

    String getKey() {
        //商户密钥
        return "T6m9iK73b0kn9g5v426MKfHQH7X8rKwb";
    }

    InputStream getCertStream() {
        return null;
    }

    IWXPayDomain getWXPayDomain() {
        return new IWXPayDomain() {
            public void report(String domain, long elapsedTimeMillis, Exception ex) {

            }
            public DomainInfo getDomain(WXPayConfig config) {
                //请求路径域名
                return new DomainInfo("api.mch.weixin.qq.com",true);
            }
        };
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

创建测试类,编写代码

MyConfig config=new MyConfig();
WXPay wxPay=new WXPay( config );

Map<String,String> map=new HashMap();
map.put("body","畅购");//商品描述
map.put("out_trade_no","55555211");//订单号
map.put("total_fee","1");//金额 以分为单位
map.put("spbill_create_ip","127.0.0.1");//终端IP
map.put("notify_url","http://www.baidu.com");//回调地址
map.put("trade_type","NATIVE");//交易类型

Map<String, String> result = wxPay.unifiedOrder( map );
System.out.println(result);
1
2
3
4
5
6
7
8
9
10
11
12
13

返回结果为

{nonce_str=fvMGIlLauUPNCtws, code_url=weixin://wxpay/bizpayurl?pr=I5sd2rc, appid=wx8397f8696b538317, sign=48B2938F70EDADC9CC235249BC085FD1D83456F67C46601FFD23B5AFBDA502D0, trade_type=NATIVE, return_msg=OK, result_code=SUCCESS, mch_id=1473426802, return_code=SUCCESS, prepay_id=wx17193859685440d561c4cef01259098400}
1

其中的 code_url 就是我们的支付 URl ,我们可以根据这个 URl 生成支付二维码

# 二维码 JS 插件 - QRcode.js

QRCode.js 是一个用于生成二维码的 JavaScript 库。主要是通过获取 DOM 的标签,再通过 HTML5 Canvas 绘制而成

<script src="js/plugins/qrcode.min.js" ></script>
<script type="text/javascript">
let qrcode = new QRCode(document.getElementById("qrcode"), {
    width : 200,
    height : 200
});
qrcode.makeCode("weixin://wxpay/bizpayurl?pr=I5sd2rc");
</script>
1
2
3
4
5
6
7
8

# 微信支付二维码

# 提交订单跳转支付页

更新 changgou_service_order 中 add () , 设置返回值为订单 Id

OrderServiceImpl

    /**
     * 增加
     *
     * @param order
     */
    @Override
    @GlobalTransactional(name = "order_add")
    public String add(Order order) {
        //1.获取购物车的数据 从redis中获取
        Map cartMap = cartService.list(order.getUsername());  //调用cartService的方法
        List<OrderItem> orderItemList = (List<OrderItem>) cartMap.get("orderItemList");
        //2.统计计算总金额 和总数量
        //3.填充订单数据并保存到tb_order中
        order.setTotalNum((Integer) cartMap.get("totalNum"));  //总数量
        order.setTotalMoney((Integer) cartMap.get("totalMoney"));  //总金额
        order.setPayMoney((Integer) cartMap.get("totalMoney")); //总支付金额
        order.setCreateTime(new Date());  //创建时间
        order.setUpdateTime(new Date()); //更新时间
        order.setBuyerRate("0"); //是否评价 0未评价 1已评价
        order.setSourceType("1"); //来源端 1 web端
        order.setOrderStatus("0"); //订单状态  0 未完成 1已完成 2已退货
        order.setPayStatus("0");  //支付状态  0未支付 1已支付
        order.setConsignStatus("0");  //发货状态  0未发货  1已发货
        String orderId = String.valueOf(idWorker.nextId());
        order.setId(orderId);  //订单id
        orderMapper.insert(order);
        //4.填充订单项数据并保存到tb_order_item
        for (OrderItem orderItem : orderItemList) {
            orderItem.setId(String.valueOf(idWorker.nextId()));
            orderItem.setIsReturn("0");  //是否退货 0未退货  1已退货
            orderItem.setOrderId(orderId); //该商品是依赖订单id
            orderItemMapper.insertSelective(orderItem); //向tb_order_item表 插入orderItem
        }


        //扣减库存并增加销量
        skuFeign.decrCount(order.getUsername());

//        int i = 1/0;
        //添加任务数据
        System.out.println("向订单数据库中的任务表添加数据");
        Task task = new Task();
        task.setCreateTime(new Date());
        task.setUpdateTime(new Date());
        task.setMqExchange(RabbitMQConfig.EX_BUYING_ADDPOINTUSER);
        task.setMqRoutingkey(RabbitMQConfig.CG_BUYING_ADDPOINT_KEY);
        Map map = new HashMap();
        map.put("username", order.getUsername());
        map.put("orderId", orderId);
        map.put("point", order.getPayMoney());
        task.setRequestBody(JSON.toJSONString(map));  //转为json字符串
        taskMapper.insertSelective(task); //写入库中


        //5.删除购物车在redis中的数据
        redisTemplate.delete("cart_" + order.getUsername());

        return orderId;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

OrderService 接口 修改 add 接口的返回值

/***
 * 新增
 * @param order
 */
String add(Order order);
1
2
3
4
5

OrderController 返回 result 携带 orderid

/***
 * 新增数据
 * @param order
 * @return
 */
@PostMapping
public Result add(@RequestBody Order order){
    //获取登陆人名称
    String username = tokenDecode.getUserInfo().get("username");
    order.setUsername(username);
    String orderId = orderService.add(order);
    return new Result(true,StatusCode.OK,"添加成功",orderId);
}
1
2
3
4
5
6
7
8
9
10
11
12
13

修改 changgou_web_order 静态页面 order 下的 add 方法

            add: function () {
                axios.post('/api/worder/add', this.order).then(function (res) {
                    if (res.data.flag) {
                        //添加订单成功
                        alert("添加订单成功")
                        let orderId = res.data.data //获取orderId
                        location.href = "/api/worder/toPayPage?orderId=" + orderId  // 跳转到选择支付方式并携带orderid
                    } else {
                        alert("添加订单失败")
                    }
                })
            }
1
2
3
4
5
6
7
8
9
10
11
12

将 fail pay paysuccess wxpay 这个 4 个页面放到 changgou_web_order 的 template 文件夹下

在 changgou_service_order_api 的 OrderFeign 新增接口定义

/***
     * 根据ID查询数据
     * @param id
     * @return
     */
@GetMapping("/order/{id}")
public Result<Order> findById(@PathVariable("id") String id);
1
2
3
4
5
6
7

changgou_order_web 中的 orderController 新增方法,跳转支付页

@GetMapping("/toPayPage")
public String toPayPage (String orderId,Model model){
    //获取订单信息
    Order order= (Order) orderFeign.findById(orderId).getData();
    model.addAttribute("orderId",order); //订单id
    model.addAttribute("payMoney",order.getPayMoney()); //支付金额
    return "pay";
}
1
2
3
4
5
6
7
8

# 支付微服务 - 下单

创建 changgou_service_pay (支付微服务), pom.xml 添加依赖

需要注意的是 maven 中央仓库上的 wxpay-sdk 3.0.9 版本已经没有了

  1. 下载 sdk 打包成 jar, 再用 maven 安装到本地仓库中
  2. 使用 red.htt 的 wxpay-sdk
    <dependencies>
        <dependency>
            <groupId>com.changgou</groupId>
            <artifactId>changgou_common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.github.wxpay</groupId>
            <artifactId>wxpay-sdk</artifactId>
            <version>3.0.9</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
    </dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

排除 log 包,否则会因为包冲突无法正常启动

application

server:
  port: 9010
spring:
  application:
    name: pay
  rabbitmq:
    host: 192.168.200.128
  main:
    allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:6868/eureka
  instance:
    prefer-ip-address: true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

创建 com.github.wxpay.sdk 包,包下创建 MyConfig 类

public class MyConfig extends WXPayConfig {
    String getAppID() {
        return "wx8397f8696b538317";
    }

    String getMchID() {
        return "1473426802";
    }

    String getKey() {
        return "T6m9iK73b0kn9g5v426MKfHQH7X8rKwb";
    }

    InputStream getCertStream() {
        return null;
    }

    //使用red.htt的wxpay-sdk不需要重写此方法
    IWXPayDomain getWXPayDomain() {
        return new IWXPayDomain() {
            public void report(String domain, long elapsedTimeMillis, Exception ex) {
            }
            public DomainInfo getDomain(WXPayConfig config) {
                return new DomainInfo("api.mch.weixin.qq.com",true);
            }
        };
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

创建 com.changgou 包,包下创建类 PayApplication

@SpringBootApplication
@EnableEurekaClient
public class PayApplication {

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

    @Bean
    public WXPay wxPay(){
        try {
            return new WXPay(new MyConfig());
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

创建 com.changgou.pay.service 包,包下创建接口 WxPayService

public interface WxPayService {

    /**
     * 生成支付二维码url
     * @param orderId
     * @param money
     * @return
     */
    Map nativePay(String orderId, Integer money);
}
1
2
3
4
5
6
7
8
9
10

创建 com.changgou.pay.service.impl 包,新增服务类 WxPayServiceImpl

@Service
public class WxPayServiceImpl implements WxPayService {

    @Autowired
    private WXPay wxPay;

    //统一下单接口调用
    @Override
    public Map nativePay(String orderId, Integer money) {

        try {
            //封装请求参数
            Map<String, String> map = new HashMap<>();
            map.put("body", "畅购商城");//商品描述
            map.put("out_trade_no", orderId);//订单号
            //map.put("total_fee",String.valueOf(money*100));//金额,以分为单位
            BigDecimal payMoney = new BigDecimal("0.01");
            BigDecimal fen = payMoney.multiply(new BigDecimal("100")); //1.00
            fen = fen.setScale(0, BigDecimal.ROUND_UP); // 1
            map.put("total_fee", String.valueOf(fen));

            map.put("spbill_create_ip", "127.0.0.1");//终端IP
            map.put("notify_url", "http://www.itcast.cn");//回调地址,先随便填一个
            map.put("trade_type", "NATIVE");//交易类型

            //通过wxpay 调用下单接口 并获取返回结果
            Map<String, String> result = wxPay.unifiedOrder(map);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

创建 com.changgou.pay.controller 包 ,新增 WxPayController

@RestController
@RequestMapping("/wxpay")
public class WxPayController {
    @Autowired
    private WxPayService wxPayService;

    //下单
    @GetMapping("/nativePay")
    public Result nativePay(@RequestParam("orderId") String orderId, @RequestParam("money") Integer money){
        Map map = wxPayService.nativePay(orderId, money);
        return new Result(true, StatusCode.OK,"",map);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

测试:地址栏输入 http://localhost:9010/wxpay/nativePay?orderId=990099&money=1

# 页面对接支付微服务

新增 changgou_service_pay_api 模块 ,并添加 common 工程依赖

    <dependencies>
        <dependency>
            <groupId>com.changgou</groupId>
            <artifactId>changgou_common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
1
2
3
4
5
6
7

新增 com.changgou.pay.feign 包,包下创建接口 PayFeign

@FeignClient(name = "pay")
public interface PayFeign {

    //下单
    @GetMapping("/wxpay/nativePay")
    public Result nativePay(@RequestParam("orderId") String orderId, @RequestParam("money") Integer money);
}
1
2
3
4
5
6
7

在 changgou_web_order 添加 pay_api feign 的依赖

        <dependency>
            <groupId>com.changgou</groupId>
            <artifactId>changgou_service_pay_api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
1
2
3
4
5

在 changgou_web_order 项目的启动类 @EnableFeignClients 注解添加 pay_api 包引用

@EnableFeignClients(basePackages = {"com.changgou.order.feign","com.changgou.user.feign","com.changgou.pay.feign"})
1

在 changgou_web_order 项目的 controller 创建 PayController 类

@Controller
@RequestMapping("/wxpay")
public class PayController {

    @Autowired
    private OrderFeign orderFeign;

    @Autowired
    private PayFeign payFeign;

    //跳转到微博支付二维码页面
    @GetMapping
    public String wxPay(String orderId, Model model) {
        //根据order查询订单 订单不存在 则跳转到错误页面
        Result orderResult = orderFeign.findById(orderId);
        if (orderResult.getData() == null) {
            return "fail";
        }

        //根据订单支付状态进行判断 如果不是未支付的 则跳转到错误页面
        Order order = (Order) orderResult.getData();
        if (!"0".equals(order.getPayStatus())) {
            return "fail";
        }
        //调用payFeign下单接口 获取二维码地址
        Result payResult = payFeign.nativePay(orderId, order.getPayMoney());
        if (payResult.getData() == null) {
            return "fail";
        }
        Map payMap = (Map) payResult.getData();
        payMap.put("orderId", orderId);
        payMap.put("payMoney", order.getPayMoney());
        model.addAllAttributes(payMap); //将map拆分再传递给model
        return "wxpay";
    }

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

将静态原型中 wxpay.html 拷贝到 changgou_web_order 的 templates 文件夹下作为模板,修改模板,部分代码如下:二维码地址渲染

let qrcode = new QRCode(document.getElementById("qrcode"), {
	width : 240,
	height : 240
});
qrcode.makeCode([[${code_url}]]);
1
2
3
4
5

回显示订单号与金额

第 51 行

					<h4 class="fl tit-txt"><span class="success-icon"></span><span  class="success-info" th:text="|订单提交成功,请您及时付款!订单号:${orderId}|"></span></h4>
                    <span class="fr"><em class="sui-lead">应付金额:</em><em  class="orange money" th:text="${payMoney}/100"></em>元</span>
1
2

pay.html 设置支付跳转链接

第 109 行

<li><a th:href="|/api/wxpay?orderId=${orderId}|"><img src="/img/_/pay3.jpg"></a></li>
1

更新网关地址过滤器。添加 wxpay 路径的拦截

changgou_gateway_web 下 URLFilter 添加 /api/wxpay,/api/wxpay/**

    public static String filterPath = "/api/worder/**,/api/wseckillorder,/api/seckill,/api/wxpay,/api/wxpay/**,/api/worder/**,/api/user/**,/api/address/**,/api/wcart/**,/api/cart/**,/api/categoryReport/**,/api/orderConfig/**,/api/order/**,/api/orderItem/**,/api/orderLog/**,/api/preferential/**,/api/returnCause/**,/api/returnOrder/**,/api/returnOrderItem/**";
1

同时更新网关服务的 application.yml。添加地址路由标识

image-20220221152107237

# 支付回调逻辑处理

在完成支付后,修改订单状态为已支付,并记录订单日志。

在请求统一下单接口时,有个参数 notify_url ,这个是回调地址,也就是在支付成功后微信支付会自动访问这个地址,通知业务系统支付完成。但这个地址必须是互联网可以访问的(也就是有域名的)。

在 WxPayController 新增 notifyLogic 方法

/**
 * 回调
 */
@RequestMapping("/notify")
public void notifyLogic(){
    System.out.println("支付成功回调。。。。");
}
1
2
3
4
5
6
7

添加公网地址 到支付微服务配置文件

wxpay:
  notify_url: http://weizhaohui.cross.echosite.cn/wxpay/notify #回调地址 必须为公网的自己域名接口
1
2

修改 WxPayServiceImpl ,引入

@Value( "${wxpay.notify_url}" )
private String notifyUrl;
1
2

并修改 WxPayServiceImpl 的 nativePay 方法

map.put("notify_url",notifyUrl);//回调地址
1

1、同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。

2、后台通知交互时,如果微信收到商户的应答不符合规范或超时,微信会判定本次通知失败,重新发送通知,直到成功为止(在通知一直不成功的情况下,微信总共会发起 10 次通知,通知频率为 15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m),但微信不保证通知最终一定能成功。

# 回调消息接收并转换

微信支付平台发送给回调地址的数据是二进制流,我们需要提取二进制流转换为字符串,这个字符串就是 xml 格式。

在 changgou_common 工程添加工具类 ConvertUtils

/**
 * 转换工具类
 */
public class ConvertUtils {
    /**
     * 输入流转换为xml字符串
     * @param inputStream
     * @return
     */
    public static String convertToString(InputStream inputStream) throws IOException {
        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inputStream.read(buffer)) != -1) {
            outSteam.write(buffer, 0, len);
        }
        outSteam.close();
        inputStream.close();
        String result  = new String(outSteam.toByteArray(), "utf-8");
        return result;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

修改 notifyLogic 方法

    /**
     * 回调
     */
    @RequestMapping("/notify")
    public void notifyLogic(HttpServletRequest request, HttpServletResponse response) {
        System.out.println("支付成功回调。。。。");
        try {
            //输入流转换为字符串
            String xml = ConvertUtils.convertToString(request.getInputStream());
            System.out.println(xml);

            //返回微信一个结果通知
            response.setContentType("text/xml");
            String data = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
            response.getWriter().write(data);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

测试后,在控制台看到输出的消息

<xml><appid><![CDATA[wx8397f8696b538317]]></appid>
<bank_type><![CDATA[CFT]]></bank_type>
<cash_fee><![CDATA[1]]></cash_fee>
<fee_type><![CDATA[CNY]]></fee_type>
<is_subscribe><![CDATA[N]]></is_subscribe>
<mch_id><![CDATA[1473426802]]></mch_id>
<nonce_str><![CDATA[c6bea293399a40e0a873df51e667f45a]]></nonce_str>
<openid><![CDATA[oNpSGwbtNBQROpN_dL8WUZG3wRkM]]></openid>
<out_trade_no><![CDATA[1553063775279]]></out_trade_no>
<result_code><![CDATA[SUCCESS]]></result_code>
<return_code><![CDATA[SUCCESS]]></return_code>
<sign><![CDATA[DD4E5DF5AF8D8D8061B0B8BF210127DE]]></sign>
<time_end><![CDATA[20190320143646]]></time_end>
<total_fee>1</total_fee>
<trade_type><![CDATA[NATIVE]]></trade_type>
<transaction_id><![CDATA[4200000248201903206581106357]]></transaction_id>
</xml>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 查询订单验证通知

img

WxPayService 新增方法定义

    //微信查询订单
    Map queryOrder(String orderId);
1
2

WxPayServiceImpl 实现方法

    @Override
    public Map queryOrder(String orderId) {
        try {
            Map<String,String> map = new HashMap();
            map.put("out_trade_no",orderId); //根据订单id查询
            Map<String, String> resultMap = wxPay.orderQuery(map);
            return resultMap;
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12

修改 notifyLogic 方法

生产者

在 changgou_service_pay 模块 com.changgou.pay 创建 config 包 并创建 RabbitMQconfig 类

@Configuration
public class RabbitMQConfig {

    public static final String ORDER_PAY="order_pay";

    @Bean
    public Queue queue(){
        return new Queue(ORDER_PAY);
    }


}
1
2
3
4
5
6
7
8
9
10
11
12

使用微信提供的工具类将 xml 转为 map 再发送到消息队列中

    /**
     * 回调
     */
    @RequestMapping("/notify")
    public void notifyLogic(HttpServletRequest request, HttpServletResponse response) {
        System.out.println("支付成功回调。。。。");
        try {
            //输入流转换为字符串
            String xml = ConvertUtils.convertToString(request.getInputStream());
            System.out.println(xml);

            //基于微信发送的通知内容,完成后续的业务
            Map<String, String> result = WXPayUtil.xmlToMap(xml);//将xml转为Map
            if (result.get("result_code").equals("SUCCESS")) {
                //查询订单
                Map map = wxPayService.queryOrder(result.get("out_trade_no"));
                System.out.println(map);
                if ("SUCCESS".equals(map.get("result_code"))) {
                    //查询成功 将订单的消息发送mq
                    Map message = new HashMap();
                    message.put("orderId", map.get("out_trade_no"));//订单号
                    message.put("transactionId", map.get("transaction_id")); //微信支付订单号

                    //发送到mq
                    rabbitTemplate.convertAndSend("", RabbitMQConfig.ORDER_PAY, JSON.toJSONString(message));

                } else {
                    //错误原因
                    System.out.println(result.get("err_code_des"));
                }
            } else {
                //错误原因
                System.out.println(result.get("err_code_des"));
            }

            //返回微信一个结果通知
            response.setContentType("text/xml");
            String data = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
            response.getWriter().write(data);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

# 接收订单验证通知

修改 changgou_service_order 项目 config 包下的 RabbitMQConfig 添加微信回调消息队列

    //微信完成消息队列
    public static final String ORDER_PAY="order_pay";

    @Bean
    public Queue queue(){
        return new Queue(ORDER_PAY);
    }
1
2
3
4
5
6
7

在 listener 包下新建 OrderPayListener 类

用于消费消息队列中的数据

@Component
public class OrderPayListener {

    @Autowired
    private OrderService orderService;

    @RabbitListener(queues = RabbitMQConfig.ORDER_PAY)
    public void receiverPayMessage(String message){
        System.out.println("接收到了订单支付的消息"+message);

        Map map = JSON.parseObject(message, Map.class);

        //调用业务层 完成订单数据库的修改
        orderService.updatePayStatus((String)map.get("orderId"),(String)map.get("transactionId"));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Service 层 OrderService 类添加 updatePayStatus 用于修改订单支付状态

    //修改订单的支付状态 并记录日志
    void updatePayStatus(String orderId, String transactionId);
1
2

Impl 实现接口方法

    @Autowired
    private OrderLogMapper orderLogMapper;

    @Override
    @Transactional
    public void updatePayStatus(String orderId, String transactionId) {
        //查询订单
        Order order = orderMapper.selectByPrimaryKey(orderId);
        if (order != null && "0".equals(order.getPayStatus())) {
            //修改订单的支付状态
            order.setPayStatus("1");
            order.setOrderStatus("1");
            order.setUpdateTime(new Date());
            order.setPayTime(new Date());
            order.setTransactionId(transactionId); //微信交易流水号
            orderMapper.updateByPrimaryKeySelective(order);

            //记录订单日志
            OrderLog orderLog = new OrderLog();
            orderLog.setId(idWorker.nextId()+"");
            orderLog.setOperater("system");
            orderLog.setOperateTime(new Date());
            orderLog.setOrderStatus("1");
            orderLog.setPayStatus("1");
            orderLog.setRemarks("交易流水号"+transactionId);
            orderLog.setOrderId(orderId);
            orderLogMapper.insert(orderLog);

        }


    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 推送支付通知

我们需要将支付的结果通知前端页面,其实就是我们通过所说的服务器端推送,主要有三种实现方案

(1)Ajax 短轮询 Ajax 轮询主要通过页面端的 JS 定时异步刷新任务来实现数据的加载

如果我们使用 ajax 短轮询方式,需要后端提供方法,通过调用微信支付接口实现根据订单号查询支付状态的方法(参见查询订单 API) 。 前端每间隔三秒查询一次,如果后端返回支付成功则执行页面跳转。

缺点:这种方式实时效果较差,而且对服务端的压力也较大。不建议使用

(2)长轮询

长轮询主要也是通过 Ajax 机制,但区别于传统的 Ajax 应用,长轮询的服务器端会在没有数据时阻塞请求直到有新的数据产生或者请求超时才返回,之后客户端再重新建立连接获取数据。

如果使用长轮询,也同样需要后端提供方法,通过调用微信支付接口实现根据订单号查询支付状态的方法,只不过循环是写在后端的。

缺点:长轮询服务端会长时间地占用资源,如果消息频繁发送的话会给服务端带来较大的压力。不建议使用

(3)WebSocket 双向通信 WebSocket 是 HTML5 中一种新的通信协议,能够实现浏览器与服务器之间全双工通信。如果浏览器和服务端都支持 WebSocket 协议的话,该方式实现的消息推送无疑是最高效、简洁的。并且最新版本的 IE、Firefox、Chrome 等浏览器都已经支持 WebSocket 协议,Apache Tomcat 7.0.27 以后的版本也开始支持 WebSocket。

# RabbitMQ Web STOMP 插件

借助于 RabbitMQ 的 Web STOMP 插件,实现浏览器与服务端的全双工通信。从本质上说,RabbitMQ 的 Web STOMP 插件也是利用 WebSocket 对 STOMP 协议进行了一次桥接,从而实现浏览器与服务端的双向通信。

img

# STOMP 协议

STOMP 即 Simple (or Streaming) Text Orientated Messaging Protocol,简单 (流) 文本定向消息协议。前身是 TTMP 协议(一个简单的基于文本的协议),专为消息中间件设计。它提供了一个可互操作的连接格式,允许 STOMP 客户端与任意 STOMP 消息代理(Broker)进行交互。STOMP 协议由于设计简单,易于开发客户端,因此在多种语言和多种平台上得到广泛地应用。

# 插件安装

我们进入 rabbitmq 容器,执行下面的命令开启 stomp 插件

docker exec -it tensquare_rabbitmq bash
rabbitmq-plugins enable rabbitmq_web_stomp rabbitmq_web_stomp_examples
exit
1
2
3

将当前的容器提交为新的镜像

docker commit tensquare_rabbitmq rabbitmq:stomp
docker stop tensquare_rabbitmq
1
2

根据新的镜像创建容器 此处只是暂时测试用 宿主机重启后此容器并不会自动启动

docker run -di --name=changgou_rabbitmq -p 5671:5617 -p 5672:5672 -p 4369:4369 -p 15671:15671 -p 15672:15672 -p 25672:25672 -p 15670:15670 -p 15674:15674 rabbitmq:stomp
1

启动 changgou_rabbitmq

docker start changgou_rabbitmq
1

访问 http://192.168.130.128:15670/ 可以看到 WEB-STOMP 已经开启

image-20220222202222345

# 消息推送测试

前端代码

let client = Stomp.client('ws://192.168.130.128:15674/ws');
let on_connect = function(x) {
    id = client.subscribe("/exchange/paynotify", function(d) {
        alert(d.body) //消息队列中消息
    });
};
let on_error =  function() {
    console.log('error');
};
client.connect('webguest', 'webguest', on_connect, on_error, '/');
1
2
3
4
5
6
7
8
9
10

destination 在 RabbitMQ Web STOM 中进行了相关的定义,根据使用场景的不同,主要有以下 4 种:

  • 1./exchange/

对于 SUBCRIBE frame,destination 一般为 /exchange//[/pattern] 的形式。该 destination 会创建一个唯一的、自动删除的、名为的 queue,并根据 pattern 将该 queue 绑定到所给的 exchange,实现对该队列的消息订阅。 对于 SEND frame,destination 一般为 /exchange//[/routingKey] 的形式。这种情况下消息就会被发送到定义的 exchange 中,并且指定了 routingKey。

  • 2./queue/ 对于 SUBCRIBE frame,destination 会定义的共享 queue,并且实现对该队列的消息订阅。 对于 SEND frame,destination 只会在第一次发送消息的时候会定义的共享 queue。该消息会被发送到默认的 exchange 中,routingKey 即为。
  • 3./amq/queue/ 这种情况下无论是 SUBCRIBE frame 还是 SEND frame 都不会产生 queue。但如果该 queue 不存在,SUBCRIBE frame 会报错。 对于 SUBCRIBE frame,destination 会实现对队列的消息订阅。 对于 SEND frame,消息会通过默认的 exhcange 直接被发送到队列中。
  • 4./topic/ 对于 SUBCRIBE frame,destination 创建出自动删除的、非持久的 queue 并根据 routingkey 为绑定到 amq.topic exchange 上,同时实现对该 queue 的订阅。 对于 SEND frame,消息会被发送到 amq.topic exchange 中,routingKey 为。

访问 MQ 管理页面创建 paynotify 交换机

http://192.168.130.128:15672/#/exchanges

image-20220222202705122

为了安全,我们在页面上不能用我们的 rabbitmq 的超级管理员用户 guest,所以我们需要在 rabbitmq 中新建一个普通用户 webguest(普通用户无法登录管理后台)

http://192.168.130.128:15672/#/users

img

img

http://192.168.130.128:15672/#/exchanges/%2F/paynotify

测试发送消息 查看网页端是否收到消息

# 代码实现

实现思路:后端在收到回调通知后发送订单号给 mq(paynotify 交换器),前端通过 stomp 连接到 mq 订阅 paynotify 交换器的消息,判断接收的订单号是不是当前页面的订单号,如果是则进行页面的跳转。

在 WxPayController 类的 notifyLogic 方法添加双向通信编码 (生产者)

//完成双向通信
                    rabbitTemplate.convertAndSend("paynotify","",result.get("out_trade_no"));
1
2

在 PayController 添加支付成功的页面跳转方法

//支付成功页面的跳转
@RequestMapping("/toPaySuccess")
public String toPaySuccess(Integer payMoney,Model model){
    model.addAttribute("payMoney",payMoney);
    return "paysuccess";
}
1
2
3
4
5
6

修改 wxpay 页面的 RabbitMQ Stomp

let client = Stomp.client('ws://192.168.130.128:15674/ws');
let on_connect = function(x) {
    id = client.subscribe("/exchange/paynotify", function(d) {
		//回调方法
		let orderId = [[${orderId}]]
		if (b.body == orderId){
			//跳转页面
			location.href="/api/wxpay/toPaySuccess?payMoney="+[[${payMoney}]]
		}
        // alert(d.body) //消息队列中消息
    });
};
let on_error =  function() {
    console.log('error');
};
client.connect('webguest', 'webguest', on_connect, on_error, '/');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
编辑 (opens new window)
上次更新: 2023/12/06, 01:31:48
Day12 分布式事务解决方案
Day14 订单处理

← Day12 分布式事务解决方案 Day14 订单处理→

最近更新
01
k8s
06-06
02
进程与线程
03-04
03
计算机操作系统概述
02-26
更多文章>
Theme by Vdoing | Copyright © 2022-2025 Iekr | Blog
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式