用户支付
我们系统需要与小程序支付接口对接有:
下单接口:系统后端调用 微信支付的下单接口。支付通知:微信支付调用在线教育系统后端接口。商户订单号查询:系统后端调用 微信支付的商户订单号查询接口。
支付渠道表,交易单表,退款记录表
介绍:实现与第三方支付平台的对接,用户在进行支付时选择支付方式。我们公司已经准备好了相关的基础代码,支付功能的代码是固定的,只需要进行修改调用即可。
1.支付:用户点击支付按钮,前端请求订单服务,订单服务请求支付服务,最后支付服务请求微信的下单接口。订单管理服务携带订单号等信息请求支付服务生成支付二维码,拿到交易单号将其和支付渠道更新到订单表。最后订单管理服务将交易单信息及二维码返回给前端。请求支付服务生成支付二维码成功将交易单号和支付渠道更新到订单表中。图:
2.查询支付结果:在用户支付后用户点击“完成支付”此时前端请求订单服务的查询支付结果接口,如果支付成功则跳转到支付成功界面。对于支付中的订单最终由支付服务调用微信查询支付结果。订单管理服务查询到支付结果后更新订单的支付状态。拿到支付结果更新订单表的支付结果。查询支付结果需要做两部分的工作:调用支付服务的查询支付结果接口获取支付结果,将支付结果更新至订单表的支付状态字段。
传入参数:本接口要调用支付服务的支付结果查询接口,根据支付服务的支付结果查询接口的传入参数分析本接口的传入参数。支付服务的支付结果查询接口需要传入交易单号。交易单号在订单表已经保存所以前端传入订单号即可拿到交易单号。传入参数:订单号
接收支付通知:用户支付成功后支付服务获取支付结果后会通知业务系统,业务系统收到支付结果只处理属于自己的支付通知,根据支付结果更新订单表的支付状态,实现订单管理服务接收支付通知更新支付状态功能。订单管理服务接收支付通知的交互流程:支付服务将支付结果发送到MQ,订单管理服务监听MQ,收到支付结果,更新订单表的支付状态。
支付服务如何将属于每个收费订单的支付结果通知给它们:1.首先在请求支付服务支付接口中需要传入product_app_id,它表示请求支付业务系统的应用标识,此应用标识会存储到支付服务的交易单表。2.支付服务通知支付结果时将交易单中的product_app_id一起发给各个监听MQ的微服务(绑定交换机的有多个队列,每个队列是不同的收费订单支付通知队列。当支付服务向交换机发送一条支付通知消息,所有绑定此交换机的队列且都会收到支付通知。业务系统收到支付结果后解析出id,判断是否属于自己的支付结果通知,如果是则进行处理。
3.取消订单:订单的不同状态下去取消订单其执行的逻辑
3.1退款流程:订单为已支付状态时,取消订单后进行自动退款,此时需要调用支付服务的申请退款接口。取消订单执行:1、更新订单状态,待支付状态下取消订单后更新订单状态为“已取消”,派单中状态下取消订单后更新订单状态为“已关闭”。2、保存取消订单记录,记录取消的原因等信息。3、远程调用支付服务的退款接口申请退款。
取消派单中的订单存在如下问题:远程调用退款接口操作不放在事务方法中,避免影响数据库性能。如果远程调用退款接口失败了将无法退款,这个怎么处理?以上问题采用异步退款的方式来解决。
取消订单执行如下操作:1、使用数据库事务控制,保存以下数据:更新订单状态。保存取消订单记录表,记录取消订单的原因等信息。保存退款记录表。2、定时任务扫描退款记录表,对未退款的记录请求支付服务进行退款,退款成功更新订单的退款状态,并删除退款记录。说明:由定时任务去更新退款的状态,因为调用了退款接口只是申请退款了,退款结果可能还没有拿到,通过定时任务再次请求支付服务的退款接口,拿到退款结果。
3.2定义取消派单中的订单:当订单状态为派单中,取消此类订单需要进行退款操作,根据退款流程,需要做以下操作:1.添加取消订单记录。2.更新订单状态为“已关闭”。3.更新订单退款状态为退款中。4.添加退款记录。
取消派单中的订单进行自动退款,通过定时任务请求退款接口。
用户支付问题
这里有个问题是:支付服务作为项目的公共支付服务,对接支付服务的可能不止在线教育订单还可能有其它收费订单,比如:体检项目订单服务等等,支付服务如何将属于每个收费订单的支付结果通知给它们呢?
1.首先在请求支付服务支付接口中需要传入product_app_id,它表示请求支付业务系统的应用标识, 此应用标识会存储到支付服务的交易单表2.支付服务通知支付结果时将交易单中的product_app_id一起发给各个监听MQ的微服务。具体的方法是:
支付服务向jzo2o.exchange.topic.trade交换机发送消息,Routing Key=UPDATE_STATUS
绑定此交换机的有多个队列,每个队列是不同的收费订单支付通知队列。当支付服务向jzo2o.exchange.topic.trade交换机发送一条支付通知消息,所有绑定此交换机的队列且Routing Key=UPDATE_STATUS都会收到支付通知。业务系统收到支付结果后解析出product_app_id,判断是否属于自己的支付结果通知,如果是则进行处理。
2.你们对接的哪个支付接口?怎么对接的?
用户端是微信小程序,我们对接的小程序支付接口。
我们使用的SDK是微信支付的httpclient程序(wechatpay-apache-httpclient )。
首先用户发起支付会调用小程序的下单接口,微信返回一个下单标识。
小程序前端程序使用该下单标识调用小程序的方法唤起支付窗口,用户进行支付。
支付成功,微信通过通知接口调用服务端接口。
服务端程序也会调用微信的支付结果查询接口查询支付结果,根据支付结果更新订单的支付状态。
用户退款接口我们对接小程序的退款接口,用户发起退款申请,通过退款结果查询接口查询退款状态。
3.项目的支付服务有哪些接口?
项目的支付服务对接了微信、支付宝的Native、jsapi等常用支付方法。
支付服务提供以下接口:
支付接口,此接口请求第三方支付平台的下单接口,下单成功生成支付二维码返回给业务系统,如果已生成支付二维码会将二维码返回给业务系统。
支付结果查询接口,根据支付服务的交易单查询支付结果,支付服务会请求第三方的支付结果查询接口查询支付结果。
退款接口,此接口请求第三方支付平台的退款接口,如果已退款此接口会返回退款记录给业务系统。
支付通知接口,支付服务获取支付结果通过MQ通知业务系统。
4.支付服务设计了几张表?
核心的表有支付渠道表、交易单表、退款记录表。
支付接口:收到支付请求后请求第三方支付的下单接口,并向交易单表新增记录。
查询交易结果接口:请求第三方支付的查询支付结果并更新交易单表的支付状态。
接收第三方通过支付结果:更新交易单表的支付状态。
退款接口:新增退款记录
更新退款状态:请求第三方退款结果查询接口查询退款状态,并更新退款状态。
5.如何防止重复支付?
重复支付是一个订单客户支付多次,造成重复支付。
我们项目实现的是扫码支付,可能存在重复支付的问题,通过以下方式去避免重复支付:
1、同一个订单同一个支付渠道只生成一个支付二维码。
2、在请求第三方支付下单使用分布式锁控制不会重复请求第三方下单。
3、切换支付渠道时先关闭原渠道的交易单再生成新渠道的交易单。
4、使用定时任务每天扫描交易单表,如果存在多个支付成功的交易单则进行自动退款。
6.支付接口是怎么开发的?
项目有统一的支付服务与第三方支付平台对接,业务系统对接支付服务完成支付流程。
首先请求通过支付服务请求第三方支付平台的支付下单接口,如果是小程序支付下单成功会返回一个会话标识,前端通过该会话标识调起支付窗口,如果是扫码支付下单成功会返回二维码URL,生成二维码返回给前端。
用户支付成功,获取支付结果更新订单表的订单状态字段。
获取支付结果有两种方法:
调用 支付服务的查询支付结果接口查询支付结果。
通过监听MQ,支付服务将支付结果通知给业务系统。
支付通知
订单服务作为通用服务在订单支付成功后需要将支付结果异步通知给其它微服务。
下图使用了消息队列完成支付结果通知:
学习中心服务:对收费课程选课需要支付,与订单服务对接完成支付。
学习资源服务:对收费的学习资料需要购买后下载,与订单服务对接完成支付。
订单服务完成支付后将支付结果发给每一个与订单服务对接的微服务,订单服务将消息发给交换机,由交换机广播消息,每个订阅消息的微服务都可以接收到支付结果.
微服务收到支付结果根据订单的类型去更新自己的业务数据。
技术方案
使用消息队列进行异步通知需要保证消息的可靠性,即生产端将消息成功通知到消费端。
消息从生产端发送到消费端经历了如下过程:
1、消息发送到交换机
2、消息由交换机发送到队列
3、消息者收到消息进行处理
保证消息的可靠性需要保证以上过程的可靠性,本项目使用RabbitMQ可以通过如下方面保证消息的可靠性。
1、生产者确认机制
发送消息前使用数据库事务将消息保证到数据库表中
成功发送到交换机将消息从数据库中删除
2、mq持久化
mq收到消息进行持久化,当mq重启即使消息没有消费完也不会丢失。
需要配置交换机持久化、队列持久化、发送消息时设置持久化。
3、消费者确认机制
消费者消费成功自动发送ack,否则重试消费。
发送通知
订单服务通过消息队列将支付结果发给学习中心服务,消息队列采用发布订阅模式。
1、订单服务创建支付结果通知交换机。
2、学习中心服务绑定队列到交换机。
mq面试题
## RabbitMQ支持的消息类型有哪些
RabbitMQ主要支持两大类消息类型,分别是点对点和发布订阅
- 点对点指的是生成者发送的同一条消息只能被一个消费者所消费
- 发布订阅指的是生成发送的同一条消息可以被多个消费者所消费
在点对点这种模型下,根据消费者的数量又分为Simple和Work两种类型:Simple指的是一个消费者,Work指的是多个消费者
在发布订阅这种模型下,根据交换机类型的不同分为Fanout、Direct、Topic三种模式
- Fanout类型的交换机会将接收到的消息转发到所有与之绑定的队列上
- Direct类型的交换机会根据消息的routingKey与队列绑定时声明的bindingKey比对,比对成功再转发消息
- Topic类型就是在Direct类型的基础上支持了bindingKey的模糊写法,通常使用*表示一个单词,使用#表示任意个单词
## 你了解的消息中间件有哪些
消息中间件在项目中通过作为解耦合的一种异步消息传送媒介所存在,我了解主要有下面几个
- 第一个是RabbitMQ,它性能和安全性都很好,延迟也低,适用于一般的企业级项目
- 第二个是RocketMQ,它一般是电商项目的首选
- 第三个是Kafka,它的主要优点是吞吐量比较大,一般应用在大数据领域
- 还有一个就是ActiveMQ,这是一个非常老牌的消息中间件,对比其它三个来讲,性能比较低,近几年来已经很少使用了
## 项目中哪里用到了RabbitMQ
RabbitMQ是我们项目中服务通信的主要方式之一 , 我们项目中服务通信主要有二种方式实现 :
1. 通过Feign实现服务的同步调用
2. 通过MQ实现服务的异步通信
基本上除了查询请求之外, 大部分的服务调用都采用的是MQ实现的异步调用 , 例如 :
1. 发布内容的异步审核
2. 验证码的异步发送
3. 用户行为数据的异步采集入库
4. 搜索历史记录的异步保存
5. 用户信息修改的异步通知(用户修改信息之后, 同步修改其他服务中冗余/缓存的用户信息)
6. 静态化页面的异步生成
7. MYSQL和Redis , ES之间的数据同步
8. .....
## 使用RabbitMQ如何保证消息不丢失
消息从生产者发送到消费者接收会经历多个过程 , 其中的每一步都可能导致消息丢失 ,大体可以分为这样几种情况:
1. 消息在发送到mq过程中丢失
2. 消息在mq中丢失
3. 消费者消费消息失败
针对每一步,RabbitMQ分别给出了解决方案:
1. 为了解决消息在发送到mq过程中丢失,MQ提供了生产者重连机制和生产者确认机制
生产者重连机制指的是当由于网络抖动导致生产者暂时无法连接mq时,会进行自动重连
生产者确认机制指的是消息发送到mq的过程中如果出现问题,会触发失败回调方法,通知生产者失败原因
2. 为了解决消息在mq中丢失,MQ提供了持久化和惰性队列
MQ允许开启交换机持久化、队列持久化、消息持久化,以保证消息在传输过程中不会丢失
MQ还支持开启惰性队列,以提高消息的存储量
3. 为了解决消费者无法消费消息,MQ提供了消费者确认机制和消息重试机制
消费者确认机制指的是只有当消费者一方确认消息消费成功了,mq才删除消息,否则就会重新发送消息
但是这种重试机制,因此我们一般会使用Spring提供的本地重试,当然重试也要有一定的次数
如果超过了指定的重试次数,我们需要将消息转到一个指定的交换机和队列中,后期人工介入
## 消息的重复消费问题如何解决的
在使用RabbitMQ进行消息收发的时候,如果发送失败或者消费失败会自动进行重试,那么就有可能会导致消息的重复消费
解决方案可以使用:唯一消息ID+Redis
具体做法就是:当消费者接收到一条消息之后,首先使用redis的setnx命令尝试向redis中插入消息的唯一id
如果保存成功,说明此条消息没有被消费过,可以正常消费
如果保存失败,说明这条消息已经执行过消费逻辑,则不能进行再次消费
## RabbitMQ的集群有哪些
RabbitMQ天然支持集群模式,它的集群有两种模式:
- 普通集群:是一种分布式集群,将队列分散到集群的各个节点,从而提高整个集群的并发能力
这种集群会在集群的各个节点间共享部分数据,包括:交换机、队列元信息。不包含队列中的消息。
当访问集群某节点时,如果队列不在该节点,会从数据所在节点传递到当前节点并返回
如果队列所在节点宕机,队列中的消息就会丢失
- 镜像集群:是一种主从集群,普通集群的基础上,添加了主从备份功能,提高集群的数据可用性。
这种集群模式下,交换机、队列、队列中的消息会在各个mq的镜像节点之间同步备份
创建队列的节点被称为该队列的主节点,备份到的其它节点叫做该队列的镜像节点。
一个队列的主节点可能是另一个队列的镜像节点
所有操作都是主节点完成,然后同步给镜像节点
主宕机后,镜像节点会替代成新的主
## RabbitMQ如何设置消息过期
RabbitMQ设置消息过期的方式有两种 :
1. 在队列上设置过期时间,所有进到这个队列的消息就会具有统一的过期时间
2. 为消息单独设置过期时间
如果队列过期和消息过期同时存在 , 会以时间短的时间为准