支付模块后端设计
现在很多 App 都有应用内购买项目,因此这些 App 都需要一个支付模块。
一般地,我们都是接入第三方支付,如支付宝、微信、网银等。支付的通用流程如下:
- 用户在 App 内发起支付请求;
- App 通过自己的服务端创建订单;
- App 将订单号和应付金额等信息,传递给第三方支付的 SDK,随即显示支付界面;
- 用户在第三方支付界面完成支付;
- 返回 App 界面,查看支付结果。
下面是一个使用支付宝支付的流程图
App 这边主要维护一个订单表,当然需要自己的服务器,订单数据保存在服务器上。
订单表:
- 订单号:需要生成一个全局唯一的订单号
- 用户ID:标明该订单是哪个用户创建的
- 商品名称
- 商品描述
- 商品价格:这是打折前的价格
- 应付金额:用户实付金额,传递给第三方支付 SDK
- 创建时间
- 更新时间:该条记录更新的时间
- 支付状态:NEW 新创建,PAID 支付成功,FAILURE 支付失败,DONE 订单完成
核心问题
1. 创建订单
关键是生成全局唯一的订单号。最简单的办法是直接使用数据库的自增字段。
一般我们会设计一个有意义的订单号:订单标识 + 时间标识(YYYYMMDDHHMMSS 或者时间戳) + 序号。
订单标识,是预定义好的,作为订单号的前缀,可用于区别不同的业务。
序号,可以是递增的序号,也可以是无意义的随机值,但要保证在同一秒内不重复。递增的序号,可以使用NoSQL 数据库生成,例如 redis 的 incr 操作。
2. 支付回调
上面的通用流程中,显示的支付结果,是同步结果(由第三方支付 SDK 返回的结果)。这可以明确告诉用户,支付已经成功。但是对订单的发货操作,需要等待服务端订单的更新。这就是服务端的异步回调,由第三方支付的服务器向 App 的服务器发送支付结果通知(即上图中的第 13 步),然后 App 的服务端更新订单,执行发货操作。
一般回调请求的参数是带签名或者加密的,App 服务端需要验证签名,以确保通知的合法性。
这里关键是并发的问题,如果同一个订单有多个支付通知同时发过来,而更新订单的逻辑里包括发货操作,且不是原子性的。这就是导致重复发货。对于虚拟商品,如游戏中的金币,就会多次给用户发放金币。所以更新订单的逻辑必须加锁处理,或者采用队列的方式。