电商平台接入第三方支付时关于订单合并支付的数据表设计

业务背景

当引入了商家与购物车的模型后,无可避免地就会出现一下情况:

  • 用户在购物车选择了多个商家的不同商品选择购买后,会创建多条订单。
  • 多条订单创建完成之后, 不可能需要用户分开支付多次,一定是刷一次卡扫一次二维码输一次密码就支付完成
  • 用户可能放弃合并支付,然后又选择了支付其中某一笔订单

在此情况下,订单与支付不可能再像没有合并支付功能那样,属于同一条数据或者一对一关系。

数据操作

数据结构应该为算法服务,设计数据结构之前,先来尝试抽象总结需要满足的算法。

合并支付订单需要支持的数据操作:

  • 查询已付款订单的支付情况

    根据单个订单号查询已支付PayOrder

    PayOrder queryPaidByOrderIds(String orderId);

  • 为单个或多个订单创建微信PrePay之前先查询是否已经生成过二维码

    根据单个或多个订单号(且匹配所有订单号顺序:abc,bac,cba等)查询未支付PayOrder.codeUrl

    PayOrder/null queryNotPaidByOrderIds(String[] orderIds);

  • 处理第三方支付平台的已支付通知时,根据外部订单号查询内部订单号(一个或多个)

    String[] queryOrderIdsByOutTradeNo(String outTradeNo);

第三方支付平台的约束

  • 微信、支付宝对外部订单号的限制:32字符以内,必须保证唯一性,因此不能使用将内部订单号数组字符串连接的方式作为外部订单号
  • 微信将订单号是否重复的判断任务丢给了开发者,如果申请相同订单号的预支付订单,微信会直接返回错误而不是相同的订单信息
  • 外部订单都有自己的有效期规定,如果失效,不能再使用相同外部订单号去创建外部订单

表关联关系分析

由于用户可以选择不支付合并订单,重新支付其中单个订单,所以内部订单与外部支付订单之间形成了一个多对多关联

不管使用数据库主外键显式关联还是某种字符串匹配规则进行隐式关联,都无法避免业务模型中的这种关联。

但从业务逻辑上:

  • 订单模块不依赖于支付模块的实现方式,能否支持合并支付与此无关
  • 支付模块也不依赖于订单模块的任何细节,只依赖于一个按订单号标记已支付状态的接口

所以此处不希望引入任何模型实现层面的紧耦合,只需要依赖上面提供的三个主要的操作的抽象方法。

目前方案

此处只是记录方案,不是真实表、字段名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
支付订单表 
PayOrder {
id,
outTradeNo, 外部订单号
3ndPayType, 第三方平台类型
prepayIdForWechat, 微信预支付id
qrcode, 微信预支付二维码
expireDate, 过期时间
hasPaid, 是否已支付
totalFee 总价
}
订单支付关联表
PayToOrder {
payOrderId, 指向PayOrder.id,考虑使用外键方便Hibernate
innerOrderId 内部订单号,普通字符串,与Order表字符串相等,不设置外键
}

sql

  1. PayOrder queryPaidByOrderIds(String orderId);

    1
    2
    3
    4
    select p.* from PayOrder p 
    JOIN PayToOrder pto ON(p.id=pto.payOrderId)
    where pto.innerOrderId=?
    and p.hasPaid=true
  2. PayOrder/null queryNotPaidByOrderIds(String[] orderIds);

    1
    2
    3
    4
    SELECT p.id FROM PayOrder p
    JOIN PayToOrder pto1 ON ( p.id = pto1.PAY_ORDER_ID AND pto1.innerOrderId = '201608050005' )
    JOIN PayToOrder pto2 ON ( p.id = pto2.PAY_ORDER_ID AND pto2.innerOrderId = '201708090004' )
    WHERE p.isPaid = 0;
  3. String[] queryOrderIdsByOutTradeNo(String outTradeNo);

    1
    2
    3
    select pto.innerOrderId from PayToOrder pto
    JOIN PayOrder p ON(p.id=pto.payOrderId)
    where p.outTradeNo=?