文旅系统搭建方案,景点套餐分销功能实现
景点套餐分销是智慧文旅系统里重要的拓客模块,套餐一般包含多景点联票、门票+观光车、门票+民宿等组合产品,支持旅行社、本地达人、私域团长推广裂变。和普通商品分销不同,套餐分销需要和分时预约库存、实名入园、退票核销深度联动。很多项目开发后容易出现套餐佣金配置不独立、退票无法冲销佣金、分销关系被覆盖、套餐库存和单票库存冲突、对账困难等问题。本文从业务痛点出发,梳理整套套餐分销实现方案,附带Java核心代码,方便源码开发和二次集成。
业务痛点分析
第一,套餐和单品佣金规则混用。不同套餐利润空间不一样,统一佣金比例会出现部分套餐亏损、高利润套餐激励不足。 第二,分销关系绑定不稳定。游客多次点击不同推广链接,上级推广人被替换,推广员收益错乱,打击达人推广积极性。 第三,退票、过期订单佣金处理缺失。套餐下单后生成佣金,游客退票或者套餐过期失效,佣金没有逆向冲销,造成资金损失。 第四,套餐库存管控割裂。分销下单独立扣库存,没有接入景区分时承载,分销爆单后出现时段超约,无法入园。 第五,分销权限和数据没有隔离。多个旅行社共用一套数据,A旅行社能看到B旅行社的订单和佣金,存在数据泄露风险。 第六,缺少分销风控。黑产批量注册分销账号,囤套餐票、批量退票套取佣金,平台损耗严重。
模块整体流程设计
套餐分销模块拆分为套餐分销配置、推广关系绑定、订单分佣计算、佣金冻结解冻、逆向冲销、分销看板几个子单元,和预约订单、时段库存、核销模块共用一套资源。 整体流程:后台上架景点套餐,单独配置套餐分销比例、分销开关、可推广人群;推广员生成带推广ID的分享海报、链接;游客点击链接进入小程序,首次访问绑定上下级关系;游客购买套餐,支付成功后触发分佣计算,佣金进入冻结池;套餐核销完成且超过退票窗口期,佣金解冻到可提现余额;游客发起退票或者套餐过期,执行佣金作废或者冲销;推广员在工作台查看订单明细、佣金流水,发起提现。 技术栈使用SpringBoot、MyBatisPlus、Redis、MQ,Redis缓存套餐分销配置、推广关系;MQ异步处理分佣、冲销消息,避免支付回调超时。
核心数据表设计要点
核心四张表:套餐分销配置表、用户推广关系表、套餐佣金流水表、分销员账户表。 套餐分销配置表绑定套餐ID,记录该套餐是否开启分销、各级佣金比例、退票是否扣回佣金;用户推广关系表记录用户和上级推广人,用户只绑定一次上级;佣金流水表以套餐订单号作为唯一索引,记录每一笔分佣、冲销记录;分销员账户表区分冻结佣金、可提现佣金,独立统计。 所有佣金变更都留存流水日志,不和订单主表直接修改,满足财务对账和追溯需求。
套餐下单分佣核心代码实现
订单支付回调成功之后触发分佣逻辑,先读取套餐独立分销配置,获取推广链路,使用分布式锁做幂等控制,防止同一套餐订单重复分佣。
@Service public class PackageCommissionService { @Autowired private PackageDistributeConfigMapper distributeConfigMapper; @Autowired private UserPromoteRelationMapper promoteRelationMapper; @Autowired private CommissionRecordMapper commissionRecordMapper; @Autowired private RedissonClient redissonClient; private static final String COMMISSION_LOCK_PREFIX = "tour:package:commission:"; @Transactional(rollbackFor = Exception.class) public void calcPackageCommission(String orderNo, Long packageId, Long buyUserId, BigDecimal payAmount){ RLock lock = redissonClient.getLock(COMMISSION_LOCK_PREFIX + orderNo); try { boolean lockFlag = lock.tryLock(2, 8, TimeUnit.SECONDS); if(!lockFlag){ return; } // 幂等校验,避免重复分佣 CommissionRecord existRecord = commissionRecordMapper.selectOne(Wrappers.lambdaQuery() .eq(CommissionRecord::getOrderNo, orderNo)); if(existRecord != null){ return; } // 查询套餐独立分销配置 PackageDistributeConfig config = distributeConfigMapper.selectOne(Wrappers.lambdaQuery() .eq(PackageDistributeConfig::getPackageId, packageId) .eq(PackageDistributeConfig::getEnable, 1)); if(config == null){ return; } // 获取推广链路 List<UserPromoteRelation> relationChain = getPromoteChain(buyUserId); for(UserPromoteRelation item : relationChain){ BigDecimal rate = getRateByLevel(config, item.getLevel()); BigDecimal commission = payAmount.multiply(rate); CommissionRecord record = new CommissionRecord(); record.setOrderNo(orderNo); record.setPackageId(packageId); record.setPromoterId(item.getPromoterId()); record.setBuyUserId(buyUserId); record.setCommissionAmount(commission); record.setStatus(0); //0冻结待结算 record.setCreateTime(LocalDateTime.now()); commissionRecordMapper.insert(record); } }catch (Exception e){ log.error("套餐分佣异常,订单号:{}",orderNo,e); throw e; }finally { if(lock.isHeldByCurrentThread()){ lock.unlock(); } } } }佣金解冻、退票逆向冲销逻辑
佣金不能在支付完成直接提现,需要等待套餐核销、退票窗口期结束,定时任务批量解冻冻结佣金。 分两种退票场景: 佣金处于冻结状态:退票成功后,直接标记佣金记录作废,不会计入分销员可提现余额,风险最低。 佣金已经解冻提现:生成负向冲销流水,优先从该分销员后续套餐佣金中抵扣,无法抵扣的订单进入后台人工复核。 后台支持配置:部分特价套餐可以设置退票不扣回佣金,适配景区营销活动。
分销员工作台与权限隔离
分销员分为普通达人、签约旅行社两种类型,旅行社账号可以开通子账号,给导游分配推广权限。每个分销员只能查看自己推广产生的套餐订单、佣金明细,看不到其他分销员数据和景区基础营收。 工作台功能包含推广海报生成、套餐预览、订单列表、佣金明细、提现记录,海报链接携带分销员ID参数,扫码后自动绑定推广关系。 后台可以单独对套餐关闭分销,也可以单独禁用某个分销员推广权限,快速管控违规推广账号。
套餐分销和库存核销联动设计
分销下单和普通购票走同一个预约库存中心,不管是分销渠道、官方小程序下单,统一扣减分时时段名额。套餐内包含多个景点的,核销时按套餐子项单独核验,只要套餐订单状态正常,分销佣金逻辑不受单景点核销顺序影响。 核销系统回调订单服务后,更新订单核销状态,作为佣金解冻的前置条件,未核销套餐订单,不会自动结算佣金。
风控、看板和落地优化要点
风控重点监控:同一分销账号短时间大量出单、下单后快速退票、同一个实名信息多次通过不同分销链接购买套餐,命中风控的分销账号暂停结算佣金,转入人工审核。 分销数据看板统计每个套餐的推广订单、核销数量、退票率、佣金支出,按分销员、渠道分组统计,评估套餐分销ROI。 套餐分销配置缓存到Redis,修改配置实时刷新,不需要发布版本;佣金流水表按月分表存储,减少大数据量查询压力;预留对外接口,支持OTA渠道、本地生活平台接入套餐分销。 分销层级严格按照合规要求配置,后台可以一键开关分销功能,限制推广文案,规避违规宣传风险。
这套方案把景点套餐分销和景区预约、核销、库存体系打通,既可以用来旅行社批量拓客,也适合达人私域裂变,和之前的预约订单、线上营销模块可以无缝组合,适配智慧文旅项目长期迭代。