现代的电商系统中,优惠活动种类繁多,比如 会员价,满减满折,现金券,折扣券和第二件半价,SKU级别的优惠等。还涉及会员权益的抵扣,积分,会员余额等等。优惠项目两两之间还有同享关系。例如使用了满减就不能享受折扣券折扣,使用了余额就不能再用现金券。
如何设计和实现一套优雅的得到默认最优支付方案的代码,是所有电商服务端研发人员都会面临的挑战。
设计目标
活动及优惠类型的可添加扩展
在众多的支付方案中,选出实付最少,优惠最多的支付方案。
运行效率高
复用性高
-
功能稳定
概要设计
优惠类目有固定的顺序,可以通过系统配置和用户现有权益,生成此次批价的所有可行支付方案。
-
同享关系
同享关系对可行支付路径的影响。
可行路径筛选
用户指定必须使用的优惠节点和不能使用的优惠节点,可以在生成支付路径后通过用户指定的信息,对可行路径进行过滤。即可行路径中必须包含用户指定的节点,并去掉含有用户关闭节点的路径。
价格计算树
-
Reduce Node
其所有子结点返回一个或者多个支付方案,结点本身对上级返回最优支付方案。上级传入的支付方案,分别传入每一个子结点。传入子结点的方案是上级传入方案的深度克隆。
-
Iteration Node
每个IterationNode代表一个可支付路径,把收到上级给入的支付方案,串行传入每一个子结点。就是把第一个子结点返回的支付方案传入第二个子结点。
-
Enum Node
子结点的处理及支付方案的传递和Reduce Node类似,不同的是其不对支付方案进行选择,因为支付方案的处理并未完成,其返回的支付方案的集合,会进入后续结点,继续处理。
-
Apply First Node
此结点也是把传入的方案,依次传递给子结点运行。每次子结点返回方案后,检测方案的实付是否与传入方案实付有变动,如果有变动,立即向上返回改方案,不再用后续子结点对方案进行处理。
-
Preferential Node
PreferentialNode 为批价树的叶子结点,其他四个结点,均不可为叶子结点。其主要功能有,
根据结点上配置对输入的sku集合进行分组。例如传入了10个不同的sku,其中2个sku可以参与一个满减活动,则将这2个sku进行包装,与另外8个sku组成一个不同于传入方案的新支付方案。
判断优惠触发条件。有部分优惠有触发条件,比如,10元抵扣券,满100元可用。能否适用当前结点的优惠 由其所持有的一个条件树结合输入方案中的信息和context中的信息 返回boolean值给出。上图中的and,or,及其子结点,均为条件树结点。条件树有无比优良的复用和扩展特性,其深入描述可以参见本人另外一篇博客,通用电商异步营销引擎。(知乎 https://zhuanlan.zhihu.com/p/31784295 ,CSDN http://blog.csdn.net/007Javaking )
在支付方案中产生优惠。此结点上的优惠适用传入的方案后,便会调用结点上持有的PreferentialGenerator与适用的sku或者sku分组一起,生成一个Preferential对象(包含优惠的金额和优惠信息),此对象与适用的单个sku或者sku分组直接关联。
有了以上的不同功能结点,我们可以根据需要随意组合此类结点来构建批价树,在根结点上调用一个方法,同时传入输入sku集合,得到最优的可行支付方案。
-
中间结果缓存
在可行路径上每一个独立的优惠项目(Iteration Node的直接子结点),开启缓存,其完成计算后的结果会缓存到当前的context中,路径更远或者有分叉的其他方案,可以复用中间结果,直接使用中间结果,往后传递,避免从头算起。例如:未优惠的支付方案经过A结点后,一定得到一个固定的优惠方案ResultA,其他的可行方案AB和AC,可以分别把ResultA传入B和C得到。
-
支付方案结构
3个PreferentialNode成功对一个批价方案处理前后对比。
输入及输出类设计
PricingContext 引擎输入参数
PricingSolution 引擎返回结果
PreferentialGenerator 三个子类分别是
打折
每满减-例如: 每满50元减5元
立减
可以扩展满送,包邮等优惠。
引擎树结点
条件树结点
上述批价引擎能在复杂的逻辑下,使用一行代码,即可得到最优的支付方案,完成了我们既定的设计目标。
引擎已经实现,通过测试验收,上线。
在此感谢参与其中的参与人员,你们都是最棒的:
前端小程序研发 范宜江
优惠买单测试 陶冶
SKU买单测试 张梦洁
产品 杜颖,成翔
其它服务端研发人员 孙竹洋,邢鹏龙,何伟
联系作者: [email protected]