ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

折扣卡CPS聚合系统开发架构讲解

折扣卡CPS聚合系统开发架构讲解

折扣卡CPS聚合系统开发架构讲解:多平台归一与分账引擎实战

折扣卡CPS聚合系统不是“把几个优惠券入口拼一起”,它的核心是把多平台转链、归因锁定、履约核销、佣金冲正做成一套可配置中台。业务表面是发卡领券,底层是资金信任工程:用户信返利到账、渠道信归因干净、财务信账能对平。

一、系统定位与分层架构

聚合系统面对的是异构供给:电商联盟、本地生活、话费充值、影票权益、折扣卡包。每个上游字段不同、回调时效不同、退款规则不同。

整体分四层:

  1. 接入层:各平台 Adapter,统一收敛成内部订单模型
  2. 中台服务层:归一、归因、计费、冲正、结算
  3. 业务装配层:折扣卡、外卖、影票、充值按插件挂载
  4. 终端层:用户小程序、推广员端、运营总控后台

技术栈:Spring Boot + MyBatis-Plus + MySQL 5.7(分表)+ Redis 7 + RabbitMQ + 分布式任务调度。

二、统一订单模型:先归一,再谈业务

各联盟返回结构天差地别:淘宝用 pid、京东用 subUnionId、拼多多用 customParameters。业务层不能写满 if (platform.equals(“xxx”))。

定义内部标准模型:
@Data
public class NormalizedOrder {
private String outOrderNo; // 外部订单号
private String platform; // taobao/jd/pdd/meituan/local
private String traceCode; // 归因追踪码
private BigDecimal payAmount;
private BigDecimal commission; // 平台基准佣
private Integer status; // 0下单 1支付 2完成 3退款
private LocalDateTime eventTime;
}

适配器只做字段映射:
public interface CpsAdapter {
NormalizedOrder normalize(String rawJson);
}

@Component
public class TaobaoAdapter implements CpsAdapter {
public NormalizedOrder normalize(String rawJson) {
JSONObject o = JSON.parseObject(rawJson);
NormalizedOrder order = new NormalizedOrder();
order.setOutOrderNo(o.getString(“tb_trade_parent_id”));
order.setTraceCode(o.getString(“adzone_id”));
order.setPayAmount(o.getBigDecimal(“pay_price”));
order.setCommission(o.getBigDecimal(“alimama_share_fee”));
return order;
}
}

工厂路由:
@Component
public class AdapterFactory {
private final Map<String, CpsAdapter> map = new ConcurrentHashMap<>();

public AdapterFactory(List<CpsAdapter> list) { list.forEach(a -> map.put(a.platform(), a)); } public CpsAdapter get(String platform) { return Optional.ofNullable(map.get(platform)) .orElseThrow(() -> new BizException("未接入平台:" + platform)); }

}

新增渠道只加一个实现类,核心分佣代码零改动。

三、归因引擎:跳转前落快照,回调时对齐

折扣卡场景的归因难点:用户领卡后可能隔几天才下单,链接参数会丢。

采用“预写归因 + 多策略匹配”:
public void preAttach(Long userId, String cardNo, String platform) {
TraceSnapshot snap = new TraceSnapshot();
snap.setUserId(userId);
snap.setCardNo(cardNo);
snap.setPlatform(platform);
snap.setTraceCode(genTraceCode());
// Redis 存 7 天,TTL 覆盖归因窗口
redisTemplate.opsForValue().set(
“trace:” + snap.getTraceCode(),
JSON.toJSONString(snap), 7, TimeUnit.DAYS);
}

归因策略可配:

• 首次归因:以第一次领卡为准

• 末次点击:以最后一次转链为准

• 卡包锁定:折扣卡激活后绑定,优先级最高

回调到达时反查:
public TraceSnapshot resolve(String traceCode) {
String val = redisTemplate.opsForValue().get(“trace:” + traceCode);
if (val != null) return JSON.parseObject(val, TraceSnapshot.class);
// 兜底查 DB 归因表
return traceMapper.selectByCode(traceCode);
}

匹配不到归属的订单,佣金归平台,不瞎分。

四、订单状态机与幂等消费

CPS 回调会重复推、乱序推,必须状态收敛:
public enum CpsOrderStatus {
CALLBACK, PAID, FINISH, REFUND, INVALID;

public boolean allowTransit(CpsOrderStatus next) { return switch (this) { case CALLBACK -> next == PAID || FINISH || REFUND; case PAID -> next == FINISH || REFUND; case FINISH -> next == REFUND; default -> false; }; }

}

消费侧双幂等:
@RabbitListener(queues = “cps.order.sync”)
public void onMessage(NormalizedOrder order) {
String idem = “idem:order:” + order.getOutOrderNo() + “:” + order.getStatus();
if (Boolean.TRUE.equals(redisTemplate.hasKey(idem))) return;

try { cpsOrderService.upsertWithStateMachine(order); redisTemplate.opsForValue().set(idem, "1", 30, TimeUnit.DAYS); } catch (DuplicateKeyException ignore) { // 唯一索引 uk_out_order_no 兜底 }

}

表结构核心字段:
CREATE TABLE cps_order (
id BIGINT PRIMARY KEY,
out_order_no VARCHAR(64) NOT NULL UNIQUE,
platform VARCHAR(16),
trace_code VARCHAR(64),
user_id BIGINT,
promoter_id BIGINT,
card_no VARCHAR(32),
pay_amount DECIMAL(10,2),
commission DECIMAL(10,2),
refund_amount DECIMAL(10,2) DEFAULT 0,
status TINYINT,
settle_status TINYINT DEFAULT 0 COMMENT ‘0冻结 1解冻 2已提现’,
version INT DEFAULT 0
);

五、分佣引擎:规则快照 + 多方分账

折扣卡系统分佣节点多:平台、租户、推广员、用户返利、上级代理。

规则配置化,不硬编码:
public List calc(NormalizedOrder order, TraceSnapshot trace) {
CommissionRule rule = ruleService.snapshot(order.getPlatform(), trace.getCardNo());
BigDecimal base = order.getCommission();

List<CommissionDetail> details = new ArrayList<>(); details.add(of("platform", base.multiply(rule.getPlatformRate()))); details.add(of("user", base.multiply(rule.getUserRebateRate()))); if (trace.getPromoterId() != null) { details.add(of("promoter", base.multiply(rule.getPromoterRate()))); } return details;

}

关键点:规则快照。订单创建时把 ruleVersion 存下来,后续改比例不影响历史单。

正向订单只进“冻结池”,不立即可提现:
accountService.freeze(trace.getUserId(), userRebate, order.getOutOrderNo());

六、核销后返利:折扣卡特有链路

很多折扣卡不是“下单即返”,而是到店核销、履约完成才发钱。提前发佣,用户退款就追不回。

状态流转:

待回调 → 待核销 → 核销成功(解冻) → 已结算
↘ 核销撤销/退款(逆向冲正)

核销接口异步化 + 幂等:
@PostMapping(“/verify”)
public Result verify(@RequestBody VerifyReq req) {
String lock = “verify:” + req.getCardNo();
if (!redisLock.tryLock(lock, 10)) return Result.fail(“处理中”);

try { String idem = "verify:idem:" + req.getVerifyNo(); if (redisTemplate.hasKey(idem)) return Result.success("已处理"); // 校验卡状态、门店、操作员 cardService.checkCanVerify(req); // 写核销流水 verifyLogMapper.insert(req.toLog()); // 发 MQ 触发返利计算 mqSender.sendVerifySuccess(req.getOutOrderNo()); redisTemplate.opsForValue().set(idem, "1", 7, TimeUnit.DAYS); return Result.success("核销成功"); } finally { redisLock.release(lock); }

}

七、退款冲正:最终一致,不靠人工追

联盟回调退款时,按原分账比例反向回滚:
public void onRefund(NormalizedOrder order) {
CpsOrder o = orderMapper.selectByOutNo(order.getOutOrderNo());
// 1. 回滚用户冻结返利
accountService.rollbackFreeze(o.getUserId(), o.getOutOrderNo());
// 2. 回滚推广员分润
if (o.getPromoterId() != null) {
accountService.rollbackFreeze(o.getPromoterId(), o.getOutOrderNo());
}
// 3. 写逆向流水(审计append-only)
auditLogMapper.insertReverse(o);
// 4. 状态机流转
o.setStatus(REFUND.code);
o.setRefundAmount(order.getRefundAmount());
orderMapper.updateById(o);
}

跨通道不做 XA 事务,用“本地事务表 + MQ 补偿”保最终一致。

八、漏单补偿与对账

别把回调当唯一数据源。定时任务主动拉联盟报表:
@Scheduled(cron = “0 */5 * * * ?”)
public void pullAndReconcile() {
if (!redisLock.tryLock(“job:pull:cps”, 60)) return;
try {
platformList.forEach(p -> {
List remote = adapterFactory.get§
.pullLatest();
remote.forEach(cpsOrderService::upsertWithStateMachine);
});
} finally {
redisLock.release(“job:pull:cps”);
}
}

日终对账:

• 本地订单 vs 联盟报表

• 识别漏单 / 金额不一致 / 状态漂移

• 差异写台账,自动或人工补偿

所有资金流水关联原始 out_order_no,可穿透溯源。

九、防刷与合规底线

• 转链接口限流:单用户每秒阈值内

• 同设备批量小号:风险单冻结返利

• 分销层级控制在二级以内,不碰多级返佣

• 资金不走私户池,提现直连持牌分账

• 回调验签 + HTTPS + IP 白名单

• 金额全用 BigDecimal,舍入统一 HALF_UP

十、小程序端关键模块

用户端:

• 折扣卡广场(按场景:外卖/影音/充值/到店)

• 领卡即绑定归因,生成专属转链

• 订单中心:预估收益、冻结中、已到账、已退款

  • 提现中心:可用余额 / 冻结余额分离

推广端:

• 专属海报、邀请码、团队订单榜

• 下级归因关系查看(防篡改绑定时间)

运营后台:

• 渠道适配管理、规则模板配置

• 差异台账、核销审计、异常单复核

十一、落地节奏

  1. MVP:单平台转链 + 归因 + 回调落单 + 预估收益展示
  2. 增强:多平台 Adapter、核销返利、退款冲正、冻结解冻
  3. 规模化:多租户隔离、规则灰度、自动对账、团队分销

别一上来就做“全品类聚合+自动提现+AI推荐”。先把归一、归因、冲正、对账四根柱子立住,系统才不会被大促和退款打穿。

折扣卡CPS聚合系统的专业度,不体现在界面多花哨,而体现在:转链不丢人、回调不重算、退款不资损、每笔钱都能反查到那张卡。把适配器、状态机、规则快照、逆向流水写进骨架,这套架构才真正能跑生产。

返回列表