项目背景
最近因为项目业务缘由,须要为系统设计虚拟币的充值及消费功能。公司内已经有成熟的支付网关服务,因此重点变成了如何设计项目内虚拟币的充值流程,让整个充值流程都实现幂等,确保用户的虚拟币余额不会重复增长或扣减。数据库
商品购买及支付流程
- 用户购买商品,商户后台请求生成支付订单并返回相关信息到客户端。
- 客户端根据返回的信息唤起支付SDK,用户确认支付。
- 用户完成支付后,支付系统会异步通知商户后台支付结果。
- 商户后台接收支付回调,在回调接口内完成本身的业务逻辑。
- 客户端在支付完成后延时必定时间从商户后台查询支付结果,此时若还没有接收到支付回调,可主动同步支付结果(保底策略)。
支付宝支付流程和微信支付相似,此处省略。正常状况下支付回调都会在毫秒级别进行通知回调。安全
虚拟币充值流程
虚拟币充值流程会嵌套支付回调流程中。若虚拟币没有完成完整的业务流程,支付系统会进行重试。所以业务流程须要支持幂等。 bash
在实践过程遇到如下问题并最终获得解决:
- 如何支持订单按用户维度分表? 支付回调信息只包括订单ID信息,在这种状况下通常只能根据订单ID进行分表。考虑到订单会愈来愈多,咱们一开始就把订单按用户维度进行分表。通常状况下按用户维度的查询是不少的,而单纯按订单维度的查询会比较少。因此在预下单的时候把用户ID信息写入attach附加信息,支付回调时会携带上原先的附加信息,这样就能够知道用户及订单ID信息,完成后续操做。
在后来的优化中,订单ID生成时预留低位段存储用户订单表ID信息,这样彻底不依赖附加信息进行传递,在用户进行自动扣费受权时的回调通知也能够适用。微信
- 虚拟币如何作事务操做? 若只有用户虚拟币的数量信息,很容易会出现错误的重复操做。所以须要流水表配合进行事务操做,当流水表已经有相同的记录时说明当前操做是重复的,须要回滚虚拟币数值。经过数据库的单机事务便可实现虚拟币的正确变动,支持重入。
- 如何作虚拟币的版本控制? 咱们虚拟币每次变动都会对应一个版本号,在对虚拟币的并发操做时通常都是经过判断version是否符合预期时才进行数据变动。这个和乐观锁的控制相似,但是在这种状况下容易出现死锁,尤为是数据库性能差更容易触发。所以再也不严格判断version版本号,只须要变动后的虚拟币数量不小于0便可,全部符合这个条件的变动都视为有效变动。这个情景适合不用版本号,只更新是作数据安全校验,适合库存模型,性能更高。
update table_xxx set avai_amount=avai_amount+:deltaAmount where user_id=:userId and avai_amount+:deltaAmount >= 0
复制代码
大多数状况下只有虚拟币消费才会出现并发修改,所以咱们只须要严格控制虚拟币不出现余额不足以扣除的状况。并发
苹果内购虚拟币充值流程
用户在应用内购买商品时,客户端能够获取到用户ID、交易凭证receipt和交易ID等信息。总体购买流程和Android端差别比较大,由于对receipt验证流程参考Android下单流程作了拆解,更容易作到重入。异步
- 客户端获取到充值列表;
- 客户端支付成功后提交交易凭证receipt给服务端验证,服务端建立对应的凭证和订单记录,更新状态,完成充值;
苹果内购注意事项
- 如何避免receipt被重复使用? iOS客户端支付成功后能获取到transactionId和receipt信息,二者惟一对应。所以服务端在验证receipt有效后,可建立对应的transaction记录,根据transactionId进行分表,transactionId做为惟一键。这样可避免receipt被重复使用。
- transaction和订单记录的映射关系? 订单记录包含渠道transactionId信息,这样在建立transaction记录后也能够惟一绑定到订单信息,避免重复建立订单。
设计总结
在设计订单系统时幂等性是首要考虑的问题,须要严格保证金额的准确性,不能给用户多扣款或多打款。通常状况下咱们经过数据库单机事务和幂等重试等方式提升订单系统的健壮性。性能