一、为什么说结算引擎是分润体系的"心脏"

很多转卡码平台的代理分润,起步阶段靠人工算、月底用 Excel 对,代理一多就崩:算错比例、漏发佣金、退款后佣金没冲正、同一个人重复打款……每一条都是信任危机。分润规则设计得再好,结算环节出一次错,代理就会流失一片。

本文不重复讲分润体系怎么建模(那是上一篇文章的内容),而是聚焦"钱怎么算、怎么冻、怎么发、怎么对"这四个环节,从零实现一个生产级的分润结算引擎:多级分润与阶梯返佣算法、佣金冻结与售后解冻、结算批次与幂等打款、日终对账与异常告警,全部附完整 Python / MySQL 实战代码。这套引擎我们商城在售的转卡码系统 V3 版已经内置,你也可以照着本文自己实现。

二、分润规则建模:多级分润 + 阶梯返佣

先定规则。最常见的组合是:二级分润(一级 30%、二级 10%)叠加阶梯返佣(代理当月业绩越高,比例越高),既能激励直推,又能刺激冲业绩。规则不写死在代码里,而是放数据库,运营改比例不用发版:

-- 分润规则表:level 决定层级,min_achievement 决定阶梯门槛
CREATE TABLE `commission_rule` (
  `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  `level` TINYINT NOT NULL COMMENT '分润层级 1=一级 2=二级',
  `rate` DECIMAL(5,2) NOT NULL COMMENT '分润比例(%)',
  `min_achievement` DECIMAL(10,2) NOT NULL DEFAULT 0
      COMMENT '月度业绩门槛(元),0=无门槛',
  `status` TINYINT NOT NULL DEFAULT 1 COMMENT '1=启用 0=停用',
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  KEY `idx_level` (`level`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分润规则表';

查询时按 level + 业绩门槛 取满足条件且门槛最高的一条即可。注意金额一律用 DECIMAL,绝不能用 FLOAT——分润计算是乘法再取整,浮点误差会在对账时放大成"差一分钱"的灵异事件。

三、分润计算引擎:支付成功后的异步算账

分润计算必须在支付成功回调里触发,而且只能触发一次。标准做法:回调里投递消息队列,由独立的 worker 消费计算,这样即使计算逻辑挂了也不会阻塞发货,还能重试。

# worker: 消费支付成功事件,计算整条代理链的分润
def calc_commission(order_no, session):
    order = get_order(order_no)
    if order is None or order.status != 'PAID':
        # 幂等:只对已支付订单算一次
        return
    chain = get_agent_chain(order.buyer_id, max_level=2)  # 最多两级
    for level, agent in enumerate(chain, start=1):
        rule = get_active_rule(level, agent.monthly_achievement)
        amount = round(order.amount * rule.rate / 100, 2)
        if amount <= 0:
            continue
        session.add(CommissionFlow(
            order_no=order.order_no,
            agent_id=agent.id,
            level=level,
            rule_id=rule.id,
            amount=amount,
            status='FROZEN',          # 先冻结,不直接可提现
            frozen_until=now() + timedelta(days=7),  # 7天售后期
        ))
    session.commit()

关键细节:每笔分润流水都带上 order_no,这是后续退款冲正和日终对账的追溯锚点;流水先落 FROZEN 状态,杜绝"刚成交就提现、随后退款"的套利漏洞。

四、佣金冻结与解冻:别让退款把账算崩

冻结期的意义是给售后留出窗口。代理申请提现时,只统计 AVAILABLE 状态的流水;一旦订单退款,就把该订单对应的全部流水置为 VOID(冲正),而不是简单删除——流水要留痕。解冻由定时任务执行,必须加分布式锁防止多实例重复执行:

# 每日凌晨解冻到期佣金(Redis SETNX 分布式锁,防重复执行)
def unfreeze_due_commissions():
    if not redis.set('lock:unfreeze', 1, nx=True, ex=3600):
        return  # 已有实例在跑,直接退出
    try:
        flows = CommissionFlow.query.filter(
            CommissionFlow.status == 'FROZEN',
            CommissionFlow.frozen_until <= now(),
        ).all()
        for f in flows:
            f.status = 'AVAILABLE'
        db.session.commit()
        logger.info('unfrozen %d flows', len(flows))
    finally:
        redis.delete('lock:unfreeze')

对应的 crontab(每天 02:00 跑一次):

0 2 * * * cd /opt/card-qr && /usr/bin/python3 scripts/unfreeze.py >> logs/unfreeze.log 2>&1

五、自动结算与打款:批次 + 幂等,一分钱不多发

打款是最怕重复的操作。方案是"结算批次 + 幂等键":先把代理可提现佣金汇总成一张结算单,打款接口携带 idempotent_key,三方通道重复请求时直接返回上次结果。建表如下:

CREATE TABLE `settle_batch` (
  `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  `batch_no` VARCHAR(32) NOT NULL UNIQUE COMMENT '批次号 YYYYMMDD+序号',
  `agent_id` INT UNSIGNED NOT NULL,
  `amount` DECIMAL(10,2) NOT NULL,
  `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0待打款 1打款中 2成功 3失败',
  `idempotent_key` VARCHAR(64) NOT NULL UNIQUE
      COMMENT '幂等键:agent_id+结算周期',
  `paid_at` DATETIME NULL,
  KEY `idx_agent` (`agent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='结算批次表';
# 打款前先抢幂等键:抢到才允许发起
def create_settlement(agent_id, period):
    key = f"{agent_id}:{period}"
    try:
        db.session.execute(text(
            "INSERT INTO settle_batch (batch_no, agent_id, amount, status, idempotent_key) "
            "VALUES (:bn, :aid, :amt, 0, :key)"
        ), {"bn": gen_batch_no(), "aid": agent_id,
            "amt": available_balance(agent_id), "key": key})
        db.session.commit()
    except IntegrityError:
        # 幂等键冲突 = 该周期已结算,直接返回已有批次
        db.session.rollback()
        return get_batch_by_key(key)
    return pay_out(batch_no)  # 调通道打款,失败置 3 可重试

打款失败不要立刻重试,置为失败状态后由补偿任务按 1min/5min/30min/2h/6h 指数退避重试,超过 5 次转人工,同时告警到钉钉/企微。

六、日终对账:每一笔佣金都要对得上账

再好的引擎也要对账兜底。每天凌晨跑一次对账脚本:当日订单金额 = 各层级分润流水之和 + 平台留成,差额超过 0.01 元就告警,把问题挡在代理发现之前。

0 3 * * * cd /opt/card-qr && /usr/bin/python3 scripts/reconcile_commission.py >> logs/reconcile.log 2>&1
def reconcile(date_str):
    total_orders = db.session.execute(text(
        "SELECT COALESCE(SUM(amount),0) FROM orders "
        "WHERE DATE(paid_at)=:d AND status='PAID'"), {"d": date_str}).scalar()
    total_flows = db.session.execute(text(
        "SELECT COALESCE(SUM(amount),0) FROM commission_flow "
        "WHERE DATE(created_at)=:d AND status IN ('FROZEN','AVAILABLE')"),
        {"d": date_str}).scalar()
    if abs(total_orders - total_flows) > 0.01:
        alert_webhook.send(f"[分润对账异常] {date_str} 差额 "
                           f"{round(total_orders-total_flows,2)} 元")

七、避坑清单与小结

  • 幂等是底线:回调、计算、打款三个环节都要幂等,否则并发一上来就重复发货、重复打款。
  • 金额一律 Decimal:数据库用 DECIMAL,Python 用 decimal.Decimal,展示层再转字符串。
  • 冲正不删除:退款对应的分润流水置 VOID 留痕,方便审计与代理申诉。
  • 冻结期别省:建议 7 天,和你的售后政策对齐,防止"套利-退款"循环。
  • 每笔流水可追溯:order_no、rule_id、agent_id 全链路落库。

把规则入库、计算异步化、打款幂等化、对账自动化,四步做完,分润体系基本就能"算得清、发得准、对得上"。想省去自研成本,可以直接用我们商城的转卡码系统 V3(内置完整分润结算引擎)或V2 版;日常维护这些 Python 脚本,搭配 Codex Desktop 这类 AI 编程助手,效率能再翻一番。