业务系统中技术攻关可能只是一小部分工作,更多的还是对于用户需求和应用场景的深刻理解,而且这种理解需要随着时间的推移不断演进,否则就会出现“跟不上”的情况。如果能再有一些前瞻性的探索,始终使系统保持着一种不过度的超前设计,就可以平衡丝滑地迭代版本,而不是那种撕裂似的推倒重来。
业务背景米攸(Wechat Mini Program)是一个专注于年轻人的活动社交平台,里面有大量丰富有趣的活动,像桌游、剧本杀、电玩、聚餐、健身之类的活动都很受欢迎。用户参与活动需要提交报名信息、支付费用,俗称 买票;活动主办方需要统计报名信息、收取费用,俗称 卖票;因此,米攸需要提供一个服务于用户和活动主办方的票务系统。
票务系统 V1.0系统设计阶段,我们参考了不少资深活动领队的意见,票务系统支持以下票种(票价):
-
普通票
普通用户报名活动时的价格。 -
会员票
会员用户报名活动时的价格,相较于普通票价,享有九折优惠。 -
超级会员票
超级会员用户报名活动时的价格,相较于普通票价,享有七折优惠。 -
早鸟票
特定时间范围内(简称:早鸟时间)报名活动时的价格,价格低于普通票价,主要针对活动推广初期阶段报名的用户,即:报名越早,价格越低。 -
团购票
超过特定人数(简称:团购人数)报名活动时的价格,价格低于普通票价,主要针对同时多人报名的用户,即:人数越多,价格越低。
用户报名时,系统需要根据用户会员、报名时间和报名人数,自动计算出用户报名的票种、票价,以及需要支付的总额。此外,会员用户多人报名时,只有会员用户自己可以享受会员价格优惠;活动早鸟票和团购票为可选项(活动可以不支持早鸟票或团购票),活动早鸟票价或团购票价可以低于会员票价或超级会员票价。
用户单人报名时,票种仅需要考虑用户票种(普通票、会员票、超级会员票或早鸟票),票价仅需要考虑用户票种对应的票价,即:
票种 = 用户票种
票价 = 用户票价
总额 = 用户票价 * 1
用户多人报名时,票种需要考虑用户票种(普通票、会员票、超级会员票、早鸟票或团购票)和他人票种(除用户自己之外的其他人使用的票种,普通票、早鸟票或团购票),票价需要考虑用户票价(用户票种对应的票价)和他人票价(他人票种对应的票价),即:
票种 = 用户票种 + 他人票种
票价 = 用户票价 + 他人票价(系统仅记录用户票价)
总额 = 用户票价 * 1 + 他人票价 * (报名人数 - 1)
票种和票价是对应关系,后文描述仅涉及票种。
用户或他人票种的计算过程:
- 票种初始化为普通票种;
- 根据用户会员、报名时间和报名人数,尝试切换为价格更低的票种。
用户票种初始为 普通票;
如果活动支持早鸟票,用户于早鸟时间报名,则切换为票种:早鸟票。
用户票种初始为 普通票;用户是会员,切换为 会员票;
如果活动支持早鸟票,用户于早鸟时间报名且早鸟票价低于用户票价,则切换为 早鸟票。
用户票种初始为 普通票;用户是会员,切换为 超级会员票;
如果活动支持早鸟票,用户于早鸟时间报名且早鸟票价低于用户票价,则切换为 早鸟票。
综上所述,用户单人报名时,票种可能是以下四种之一:
- 普通票
- 会员票
- 超级会员票
- 早鸟票
用户票种初始为 普通票,他人票种初始为 普通票;
如果活动支持早鸟票,用户于早鸟时间报名,则用户票种切换为 早鸟票,他人票种切换为 早鸟票;
如果活动支付团购票,用户人数达到团购人数要求且团购票价低于用户票价,则用户票种切换为 团购票,他人票种切换为 团购票。
用户票种初始为 普通票,他人票种初始为 普通票;
用户是会员,用户票种切换为 会员票;
如果活动支持早鸟票,用户于早鸟时间报名且早鸟票价低于用户票价,用户票种切换为 早鸟票;
如果活动支持早鸟票,用户于早鸟时间报名,他人票种切换为 早鸟票;
如果活动支持团购票,用户人数达到团购人数要求且团购票价低于用户票价,则用户票种切换为 团购票;
如果活动支持团购票,用户人数达到团购人数要求且团购票价低于他人票价,则他人票种切换为 团购票。
用户票种初始为 普通票,他人票种初始为 普通票;用户是超级会员,用户票种切换为 超级会员票;
如果活动支持早鸟票,用户于早鸟时间报名且早鸟票价低于用户票价,用户票种切换为 早鸟票;
如果活动支持早鸟票,用户于早鸟时间报名,他人票种切换为 早鸟票;
如果活动支持团购票,用户人数达到团购人数要求且团购票价低于用户票价,则用户票种切换为 团购票;
如果活动支持团购票,用户人数达到团购人数要求且团购票种低于他人票价,则他人票种切换为 团购票。
综上所述,用户多人报名时,票种可能是以下之一:
- 普通票 + 普通票
- 早鸟票 + 早鸟票
- 团购票 + 团购票
- 会员票 + 普通票
- 会员票 + 早鸟票
- 会员票 + 团购票
- 超级会员票 + 普通票
- 超级会员票 + 早鸟票
- 超级会员票 + 团购票
经过这样严密逻辑的设计,我们认为自己考虑的已经足够全面,应该能够很好地覆盖绝大多数应用场景,系统运行初期确表现的也很好,直到我们开始上线 自驾、露营 之类的活动,系统的弊端开始突显。
自驾活动,有的用户是自己开车,有的用户需要拼车,我们需要为开车的用户减免一定的票价抵扣油费;也说是说,开车和拼车的用户需要支付的票价是不一样的,开车用户需要 自驾票,拼车用户需要 拼车票。
露营活动,有的用户是自带装备,有的用户需要租用装备,用户装备的需求差异可以还比较大;也说是说,不同装备需求的用户需要支付的票价也是不一样的,我们应该把不同装备组合设计为不同的套餐,每一个套餐对应着一个 套餐票,如:套餐1票、套餐2票等。
很遗憾,现在的票务系统完全不支持这种自定义票种的需求!
临时的解决方案是线上用户仍按现有票种报名支付,线下活动现场领队再根据每位用户的具体情况额外收取一次费用。用户具体情况需要依赖于活动报名备注或线上沟通交流,汇总统计不好做,领队工作难度大,效率很低,影响活动时间,用户体验也不好。
必须升级改造!
票务系统 V2.0升级改造的方向很明确:我们需要为每一个活动设定不同的票种(简称:自定义票种);同时,每一种票种仍然需要支持会员、早鸟、团购场景。
自定义票种前文中的票种称之为默认票种。
-
票种名称
票种名称,如:会员票、早鸟票、团购票、自驾票、拼车票、套餐1票,...。 -
票种价格
票种默认价格,类似于前文中的 普通票。 -
会员价格标识
标识票种是否支持使用会员价格。 -
会员价格
票种支持使用会员价格时,设置票种会员价格,类似于前文中的 会员票。 -
超级会员标识
标识票种是否支持使用超级会员价格。 -
超级会员价格
票种支持使用超级会员价格时,设置票种超级会员价格,类似于前文中的 超级会员票。 -
报名时间标识
标识票种是否限制用户报名时间,即:用户只能在指定的时间范围内使用该票种报名,类似于前文中的 早鸟票。 -
报名开始时间
票种限制用户报名时间时,设置报名开始时间。 -
报名截止时间
票种限制用户报名时间时,设置报名截止时间。 -
报名人数开关
标识票种是否限制用户报名人数,即:用户只能在指定的人数范围内使用该票种报名,类似于前文中的 团购票。 -
报名最少人数
票种限制用户报名人数时,设置报名最少人数。 -
报名最多人数
票种限制用户报名人数时,设置报名最多人数。 -
票种说明
票种详情。
每一个活动可以添加多个自定义票种,每一个票种都可以根据该票种的具体情况:
- 设置名称和价格,实现【普通票】功能
- 选择是否使用会员价格或超级会员价格,实现【会员票】或【超级会员票】功能
- 选择是否限制报名时间,实现【早鸟票】功能
- 选择是否限制报名人数,实现【团购票】功能
用户开始报名活动时,系统会假设用户报名人数为1人(仅为自己报名),自动根据用户会员、报名时间从活动的多个自定义票种中选择一个价格最低的票种供用户参考;用户实际报名活动时,可以根据自己实际的报名人数和活动具体需求选择合适自己的那一个票种。
用户多人报名时,自定义票种的会员优惠权益也仅限会员本人使用。
请注意,目前为止,自定义票种还只是一个想法,难的是想法的具体实现:如何在已有系统中同时兼容默认票种和自定义票种,实现系统升级的平滑迁移,这里仅谈几个关键点:
- 默认票种(组合)有 N 种,默认票种ID 使用 1 ~ N 之间的数值分别表示;
- 自定义票种使用单独的数据表进行存储,票种ID使用起始于 N + 1 的自增整数表示,使用活动ID进行关联,
- 加载活动时,使用活动ID关联查询活动自定义票种列表;如果票种列表不为空,判断为自定义票种活动;否则,判断为默认票种活动;
- 客户端根据活动类型(自定义票种活动或默认票种活动)的不同渲染不同的页面模块;
- 用户报名时,活动订单中的票种ID使用默认票种ID或自定义票种活动ID;
- 加载活动订单时,如果订单票种ID小于或等于 N,判断为默认票种订单;否则,判断为自定义票种订单,根据票种ID在自定义票种数据表中查询票种信息。
实现自定义票种之后,活动主办方可以灵活设置活动票种和价格,更好地为用户提供服务;用户可以根据自己的情况选择合适的票种,支付相应的费用;平台可以按票种统计每个活动的报名信息,一举三得。
结语看着简单的问题可能是因为我们想的不够复杂,看着复杂的方案一定是因为我们想的不够简单,Keep Simple Thing Simple。