前言
最近一直在做公司的小程序对接停车场的需求。结果遇到了并发问题,出现一笔订单,用户支付两次的情况。现在对整个支付流程进行梳理。下面讨论的A用户和B用户都是对于同一订单处理的场景,对于不同订单,则没有并发问题。
一、查询停车费接口
业务流程
在停车缴费页面,可以手输车牌,因此可以多个用户查询同一个车牌的停车费。此时需要保证系统停车费只有一笔。
- 根据车牌调第三方停车场费用查询接口;
- 查询停车费账单表中同一车牌,同一入场时间,状态为查询完成,支付中,微信支付中的数据;
- 如果存在,则更新表数据中的费用;
- 如果不存在,则插入表数据。
流程图
思考
在上述查询账单表数据过程中,需要以车牌做key值,加上分布式锁。因为可能存在并发,同时有多个用户查询表数据,当表中没数据时,则会插入多条表数据。当A用户对A数据支付,B用户对B数据支付,则会支付两次。
二、余额支付接口
业务流程
在用户缴费时,当只选择使用账户余额付款时,则可以直接调标准的余额支付接口去付款。
- 调用标准余额支付接口进行余额支付;
- 当余额支付成功,更新停车费账单表数据为支付完成;
- 1至2操作成功,异步通知停车场缴费完成;
- 1至2操作失败,则进行事务回滚;
- 3操作失败则间隔1s进行重试,共重试3次,直至成功。
流程图
思考
余额支付接口,需要以停车费订单号为key值,加上分布式锁。如果存在多个用户同时进行费用支付,则会因为锁进行排队,无法一起支付。当其中一个用户支付完成后,更新订单状态,则排队的其他用户无法进行支付。
三、券与积分支付接口
业务流程
当用户缴费时,有选择券或积分,则可以使用券或积分去抵扣停车费用。
- 抵扣券或积分,校验券状态以及积分是否充足;
- 如果券和积分校验通过,进行券核销和积分扣除;
- 如果还需要支付,返回状态为支付中;
- 如果不需要继续支付,返回状态为支付完成,异步通知停车场缴费完成。
流程图
思考
券与积分支付接口需要以停车费订单号为key值,加上分布式锁。如果存在多个用户同时进行费用支付,则会因为锁进行排队,无法一起支付。当其中一个用户支付完成后,更新订单状态,则排队的其他用户无法进行券与积分支付。
四、微信支付流程
业务流程
当用户缴费时,仅选择微信支付时,则需要用标准的微信预下单接口在微信服务器下单。等前端调用wx.requestpayment组件支付完成,后端收到微信回调结果,则支付完成。
- 前端调用后台微信预下单接口,设置订单过期时间为60s;
- 下单成功,更新订单状态为微信支付中,等待用户在微信小程序支付;
- 创建70s后执行延迟任务,当用户过期未支付,更新订单状态为已过期;
- 前端调用wx.requestpayment进行支付,微信返回支付结果;
- 如果用户叉掉密码输入框,撤销支付,前端通知后台订单已取消,更新订单状态为已取消;
- 微信如果返回前端支付失败,前端通知后台订单支付失败,更新订单状态为微信支付失败;
- 如果支付成功,微信异步通知后台服务器支付成功,更新订单状态为支付完成;
流程图
思考
微信支付流程包含多个接口
- 后台微信预下单接口,进行微信预下单,绑定微信订单号,更新订单状态为微信支付中;
- 后台延迟任务,当订单过期未支付,更新订单状态为已过期;
- 前端wx.requestpayment组件进行微信支付,中途退出或支付失败调4;
- 后端更新订单状态为已取消或支付失败接口;
- 微信回调通知接口,接受微信支付成功通知,更新订单状态为支付完成。
其中微信预下单接口、后台延迟任务、后端更新订单接口,都需要以订单号为key值,加上分布式锁。
当多个用户同时预下单时,通过分布式锁,使其排队。当其中一个用户下单成功,更新订单状态为微信支付中,则排队的其他用户无法下单。
当多个用户同时调用更新订单状态接口,通过分布式锁使其排队。当其中一个用户执行成功,排队执行的其他用户查询订单状态是已取消或支付失败,则不会执行。
后台延迟任务同时触发,道理同上。
当A用户预下单,B用户同时更新订单状态。由于都持有以订单号为key的分布式锁,会形成排队。此时订单状态应该为微信支付中,因为B用户调更新订单状态接口,说明B用户取消微信支付或微信支付失败。
当在微信预下单后才会可以取消支付或微信支付失败。(1)当A用户先抢到锁,预下单失败。B用户后抢到锁,更新订单为已取消或支付失败。(2)当B用户先抢到锁,更新订单为已取消或支付失败。A用户后抢到锁,预下单失败,提示用户重新查询费用。
A用户预下单,B用户延迟任务使订单过期。由于都持有以订单号为key的分布式锁,会形成排队。因为有延迟任务,所以订单在此之前有进行微信预下单,订单状态为微信支付中或已取消或支付失败或支付成功。上述状态,A用户下单一定失败。B用户触发的延迟任务更新订单为已过期或不处理(状态为支付成功、已取消、支付失败)。
五、券与积分以及余额组合支付流程
业务流程
当用户支付时,可以选择优惠券核销和积分抵扣,以及余额付款。包含如下几个接口:
- 券与积分支付接口(一个接口处理优惠券核销与积分抵扣,更新订单状态为支付中)
- 余额付款接口
- 券与积分回滚接口(余额付款失败,回滚优惠券和积分,回滚订单状态为查询完成)
前端判断用户是否有使用券和积分,在用户点击余额付款时,调券与积分支付接口,后台判断如果足够抵扣全部费用,则更新订单状态为支付完成,如果不足够抵扣全部费用,则更新订单状态为支付中,前端调余额付款接口,进行余额付款。
流程图
思考
券与积分支付接口需要核销优惠券与积分,更新订单状态为支付中,需要增加事务,当其中一步失败就回滚。同时接口需要加上以订单号为key的分布式锁,当同时两个用户核销券或积分时,会排队处理。当其中一个用户核销券或积分成功后,订单状态为支付中。其他用户则不允许核销券或积分,提示用户订单支付中。
余额付款接口进行余额付款,需要添加事务以及以订单号为key值的分布式锁。当同时两个用户进行余额付款时,则会排队处理。当第一个用户支付成功,则更新订单状态为支付完成,其他用户无法进行余额支付。当第一个用户支付失败,还没调回滚券与积分及订单状态接口时,第二个用户不是同一用户支付,则余额支付失败。
当余额付款失败,调回滚券与积分以及订单状态接口。需要添加事务以及订单号为key值的分布式锁。防重放攻击。当回滚券积分后,更新订单状态为查询完成,则无法重复回滚券与积分。
当A用户进行券与积分支付,B用户进行余额支付。由于两个接口都是以订单号为key作为分布式锁。(1)A用户先抢到锁,则扣券与积分,并更新订单状态为支付中。B用户后持有锁,判断不是同一用户支付,则不会进行余额付款。(2)B用户先抢到锁,则扣除余额,并更新订单状态为支付完成。A用户无法进行券与积分支付。
当A用户进行券与积分支付,B用户调用券与积分回滚接口。由于两个接口都是以订单号为key作为分布式锁。(1)A用户先抢到锁,则判断订单为支付中,无法进行券与积分支付。因为B用户是余额付款失败,才会调用券与积分回滚接口。也就是B用户在此之前成功进行券与积分支付,订单被更新成了支付中。B用户后持有锁,可正常进行券与积分回滚,并更新订单状态为查询完成。(2)B用户先持有锁,可正常回滚券与积分,并更新订单状态为查询完成,A用户后持有锁,可正常进行余额付款,并更新订单状态为支付完成。
当A用户进行余额支付,B用户调用券与积分回滚接口。由于两个接口都是以订单号为key。(1)A用户先抢到锁,则判断支付不是同一用户或费用不等于券金额+积分抵现金额+支付余额,则不能进行余额支付,提示用户重新查询费用。B用户后持有锁,正常回滚券与积分以及订单状态。(2)B用户先抢到锁,可正常回滚券与积分以及订单状态。A用户后持有锁,如果金额正确,可正常进行余额支付。
当三个接口同时发生,则是由上述所说的情况可处理。
六、券与积分以及微信组合支付流程
业务流程
当用户支付时,可以选择优惠券核销和积分抵扣,以及微信支付。包含如下几个接口:
- 券与积分支付接口(一个接口处理优惠券核销与积分抵扣,更新订单状态为支付中)
- 微信预下单接口(60s订单过期,下单失败执行4,下单成功,执行5)
- 前端调wx.requestpayment组件进行微信支付(撤销支付,支付失败执行4)
- 券与积分回滚接口(微信下单失败时,回滚优惠券和积分,更新订单为支付失败。撤销微信支付时,回滚优惠券和积分,更新订单状态为已取消。微信支付失败,回滚优惠券和积分,更新订单状态为支付失败)
- 回滚券与积分延迟任务(预下单70s后,用户一直不支付,订单过期,未收到微信回调通知,查询微信订单状态,如果已支付,更新订单为支付完成,如果已过期,回滚券与积分,更新订单状态为已过期)
- 微信回调通知接口(微信支付成功,回调后端服务通知支付成功,更新订单状态为支付完成)
方案一流程图
方案二流程图![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/e2e044b14299c2e6a0fcac598304d65e.png)
思考
券与积分支付接口,微信预下单接口,券与积分回滚接口,延迟任务,微信回调通知接口都以订单号为key,添加分布式锁。
上述接口,当同一种接口发生并发会因为分布式锁进行排队。排队后执行的接口会因为订单状态修改而执行失败。
当两种接口发生并发的场景,【三、券与积分支付接口】和【四、微信支付流程】中已经分析大部分了。下面只阐述券与积分支付接口和微信支付流程中的接口的并发场景。
当A用户进行券与积分支付,B用户进行微信预下单。由于两个接口都是以订单号为key作为分布式锁。(1)A用户先抢到锁,当订单是查询完成状态时,A用户核销券,抵扣积分成功,更新订单状态为支付中。B用户后持有锁,进行微信预下单,不是同一用户支付,无法进行微信下单。当订单是支付中时,A用户先抢到锁,无法核销券与积分,B用户后持有锁,金额正确,则微信下单成功。(2)B用户先抢到锁,当订单是查询完成状态时,下单成功,更新订单状态为微信支付中。A用户后持有锁,因为状态为微信支付中,无法进行券与积分支付。当订单状态是支付中,B用户先抢到锁,如果金额正确,则正常微信预下单,更新订单状态为微信支付中。如果金额不正确,无法微信预下单。A用户后持有锁,由于状态是支付中或微信支付中状态,无法进行券和积分支付。
当A用户进行券与积分支付,B用户进行wx.requestpayment微信支付时。没有分布式锁,但可以根据状态(乐观锁)来锁住。B用户进行微信组件的支付,说明订单状态已经是微信支付中,因此A用户无法核销券和积分。B用户正常进行微信支付。
当A用户进行券和积分支付,B用户微信回调通知。在微信回调通知前,订单状态已经是微信支付中,因此A用户无法进行券和积分支付。
当A用户进行券和积分支付时,B用户执行延迟任务。此时订单为支付中以后的状态,无法进行券和积分支付,券和积分支付只能是查询完成的状态才能正常执行。
当A用户进行接口方式的券和积分回滚,B用户进行微信预下单。因为都是以订单为key作为分布式锁,两个排队执行。由于其中一个接口是券和积分回滚,因此订单状态是支付中或微信支付中。(1)当A用户先抢到锁,券和积分回滚成功,订单状态更新为支付失败,B用户后持有锁,微信预下单失败,提示用户重新查询费用。(2)当B用户抢到锁,如果是支付中,判断不是同一用户支付,则B用户微信预下单失败,A用户正常回滚券和积分。如果是微信支付中,B用户无法预下单,A用户正常回滚券和积分。
当A用户进行接口方式的券和积分回滚,B用户进行wx.requestpayment微信支付时。此场景不存在,因为这两个情况发生在A用户券和积分支付成功,B用户微信预下单成功的情况下。在上面已经分析不会同时成功。除非发生在模拟桩场景,总费用10元,券支付1元,积分抵现1元,通过模拟桩模拟微信预下单8元。此种情况要杜绝的话,需要在券和积分支付后,存用户id到表中,当其他用户进行支付时,判断是否是当前用户操作。
当A用户进行接口方式的券和积分回滚,B用户进行微信回调通知。此场景不存在,因为已经控制了两个用户不会同时进入支付中的状态。
当A用户进行延迟任务的券和积分回滚,B用户进行微信预下单。因为有延迟任务,此时订单为支付中、微信支付中、支付失败、已取消,支付成功的状态。当为支付中时,判断金额不正确,B用户微信预下单失败。当为微信支付中、支付失败、已取消、支付成功场景时,B用户微信预下单失败。
当A用户进行延迟任务的券和积分回滚,B用户进行wx.requestpayment微信支付时。
当A用户进行延迟任务的券和积分回滚,B用户进行微信回调通知接口。一般不会出现,如果出现,则在延迟任务中,判断订单为微信支付中时,调用微信服务器查询订单状态是已过期还是支付成功,做对应处理。