折扣卡CPS核销后返利触发逻辑开发
折扣卡CPS核销后返利触发逻辑开发在折扣卡CPS业务中很多权益不是下单即返利而是需要用户到店核销、线上服务履约完成之后才触发返利结算。如果沿用联盟回调直接生成分佣的模式会出现用户下单后退款、弃用权益但平台依旧发放佣金造成资金损失。本文围绕核销后返利的业务场景从痛点分析、状态流转、触发时机、数据模型、核销校验、异步返利、兜底补偿、防重复几个维度讲解逻辑开发方案附带Java核心代码。痛点分析第一CPS联盟回调就直接计算佣金履约前用户退款、过期未核销佣金已经发放追回难度大。第二核销和返利流程耦合在一起核销接口内同步执行分佣计算核销量大的时候接口超时。第三缺少幂等控制网络重试、前端重复提交核销导致一笔订单多次返利资金超付。第四核销状态和CPS订单状态脱节核销成功之后找不到对应的CPS订单返利无法触发。第五核销过期、核销驳回场景没有逆向回滚逻辑已经预冻结的佣金无法释放。第六没有链路记录核销成功但返利没生成排查时无法确认是核销消息丢失还是分佣引擎异常。第七区分不了联盟原生结算和核销履约结算两套模式两种返利规则混在一起配置维护混乱。整体设计思路系统新增履约核销模式开关在CPS渠道模板上配置分为两种模式回调即返利、核销后返利。核销后返利模式下联盟回调到达时只做订单入库、归因绑定不生成分佣、不冻结资金佣金预标记待履约。完整流程用户下单 → 联盟回调CPS订单状态置为【待核销】保存归因、卡批次、模板信息 → 用户到店/线上完成履约提交核销 → 核销服务校验卡密、订单有效性校验通过后发送核销成功事件 → 分佣引擎消费事件读取订单信息匹配分佣模板生成分佣明细并冻结佣金 → 后续联盟结算报表拉取完成再执行解冻到可提现余额。同时支持核销失败、过期核销、核销撤销逆向事件释放预冻结资金核销动作与返利异步解耦保证核销接口响应稳定。订单状态流转设计待回调联盟尚未推送订单待核销订单已入库等待用户履约核销暂不生成佣金核销成功履约完成触发返利计算佣金进入冻结池核销驳回核销校验失败不触发返利核销撤销已核销订单取消履约执行逆向扣佣已结算联盟对账完成佣金解冻可提现已退款联盟侧退款逆向扣减佣金只有状态从【待核销】流转为【核销成功】才会触发正向返利核销撤销、退款事件触发逆向扣佣逻辑。核心数据模型扩展在原有cps_order订单表增加履约相关字段is_need_verify是否需要核销后返利 0否 1是 verify_status核销状态 待核销/核销成功/核销驳回/核销撤销 verify_no核销单号 verify_time核销时间 verify_operator核销操作人核销记录表cps_verify_record每一次核销操作一条记录作为幂等依据id order_idCPS订单ID out_order_no联盟外部订单号 verify_type核销类型 verify_status核销结果 verify_time open_id用户openid batch_id卡密批次 verify_operator核销校验规则核销提交时后端做多层校验校验不通过直接返回驳回不发送返利事件CPS订单存在且状态为待核销用户使用的折扣卡和下单时绑定的卡批次一致核销时间在订单有效期内未过期该核销单号没有被处理过防止重复核销渠道模板配置为核销后返利模式。校验全部通过写入核销记录更新订单核销状态投递RocketMQ核销成功事件。核心代码实现核销接口异步解耦返利逻辑RestController RequestMapping(/cps/verify) public class CpsVerifyController { Autowired private CpsOrderService orderService; Autowired private RocketMQTemplate rocketMQTemplate; Transactional(rollbackFor Exception.class) PostMapping(/submit) public ResultDTOString submitVerify(RequestBody CpsVerifyDTO dto) { // 查询CPS订单 CpsOrder order orderService.getByOutOrderNo(dto.getOutOrderNo()); if(order null){ return ResultDTO.fail(CPS订单不存在); } if(!Objects.equals(order.getVerifyStatus(), WAIT_VERIFY)){ return ResultDTO.fail(订单状态不支持核销); } if(!Objects.equals(order.getIsNeedVerify(),1)){ return ResultDTO.fail(该渠道不需要核销返利); } // 幂等判断核销记录是否已经存在 boolean exist orderService.checkVerifyRecordExist(dto.getVerifyNo()); if(exist){ return ResultDTO.fail(该核销单已处理); } // 保存核销记录更新订单核销状态 orderService.saveVerifyRecord(order, dto); orderService.updateVerifySuccess(order.getId(), dto.getVerifyNo()); // 投递核销成功事件由分佣引擎异步消费触发返利 CpsVerifyEvent event new CpsVerifyEvent(); event.setOrderId(order.getId()); event.setOutOrderNo(order.getOutOrderNo()); event.setTenantId(order.getTenantId()); event.setChannelCode(order.getChannelCode()); event.setBatchId(order.getBatchId()); event.setOpenId(order.getOpenId()); rocketMQTemplate.syncSend(cps-verify-success-topic, MessageBuilder.withPayload(event).build()); return ResultDTO.success(核销成功); } }核销成功事件消费者触发返利计算Component RocketMQMessageListener(topic cps-verify-success-topic, consumerGroup cps-verify-rebate-consumer) public class CpsVerifyRebateConsumer implements RocketMQListenerCpsVerifyEvent { Autowired private CpsOrderService orderService; Autowired private CommissionEngineService commissionEngineService; Autowired private AttributionService attributionService; Override public void onMessage(CpsVerifyEvent event) { CpsOrder order orderService.getById(event.getOrderId()); if(!Objects.equals(order.getVerifyStatus(), VERIFY_SUCCESS)){ return; } // 幂等判断该订单是否已经生成分佣 boolean commissionExist commissionEngineService.checkCommissionExist(order.getOutOrderNo(), order.getChannelCode()); if(commissionExist){ return; } // 读取归因信息匹配模板执行返利分佣 AttributionDTO attribution attributionService.getAttributionByOrder(order); commissionEngineService.calcCommission(order, attribution); } }核销撤销逆向事件消费扣佣释放资金Component RocketMQMessageListener(topic cps-verify-cancel-topic, consumerGroup cps-verify-cancel-consumer) public class CpsVerifyCancelConsumer implements RocketMQListenerCpsVerifyEvent { Autowired private CommissionEngineService commissionEngineService; Override public void onMessage(CpsVerifyEvent event) { commissionEngineService.reverseCommission(event.getOutOrderNo(), event.getChannelCode()); } }消息可靠性与补偿机制消息投递使用事务消息保证订单核销状态更新成功才投递核销事件消费者处理失败时消息重试达到重试上限进入死信队列后台增加定时补偿任务定时扫描【核销成功但无分佣明细】的订单重新触发返利// 补偿任务伪代码示例 Scheduled(cron 0 0 2 * * ?) public void verifyRebateCompensateTask(){ ListCpsOrder needCompensateList orderService.listVerifySuccessWithoutCommission(); for(CpsOrder order : needCompensateList){ CpsVerifyEvent event new CpsVerifyEvent(); event.setOrderId(order.getId()); event.setOutOrderNo(order.getOutOrderNo()); rocketMQTemplate.send(cps-verify-success-topic, MessageBuilder.withPayload(event).build()); } }补偿任务增加告警批量补偿数量过大时推送通知。模板配置区分两种返利模式在分佣模板页面增加开关【核销后返利】。关闭联盟回调到达直接执行分佣开启联盟回调仅落单等待核销成功事件才触发分佣。引擎读取模板标识决定订单走哪一条链路一套引擎同时兼容两种业务模式。风控要点返利只看核销成功事件下单回调阶段不产生任何资金流水核销撤销、履约取消必须执行逆向扣佣如果资金已经解冻从可提现余额扣回核销记录永久留存每一次核销操作、返利生成都写入链路追踪日志不支持对历史已经结算的订单重新核销返利支持配置核销有效期超期未核销订单自动标记失效不会再触发返利。总结折扣卡CPS核销后返利触发逻辑开发的核心是下单预存订单、核销异步触发分佣、幂等防重复、逆向链路闭环、定时补偿兜底。把返利时机从联盟回调后置到履约核销完成解决用户下单后弃单退款、平台白白发放佣金的风险。这套逻辑和原有分佣引擎、链路追踪、卡密批次、渠道模板模块无缝兼容适合本地到店券、服务类权益、需要履约核验的CPS场景同时和直接回调返利模式共存运营可以按渠道灵活切换。