《独立开发者的商业指南·合同、应收与风险》4.2 催收升级与法律途径的成本收益

催收不是态度问题,而是期望值问题。本节把催收拆成提醒、正式函、停止服务、第三方催收、仲裁或诉讼五级,用内部工时、第三方佣金和仲裁费用算出每一级的期望回收净值,并以 Nord 的 11,264 应收和 3,000 美元尾款为例,说明为什么小额欠款即使胜诉也不值得打两年官司。

以下情境为教学假设,人物与数字不代表经核实的真实案例。

催收最容易变成情绪劳动:对方不回邮件,就越想发一封更严厉的函;再不行就找律师,仿佛升级本身就是进展。但从财务上看,每一级催收都是一笔投资:投入内部工时、固定费用或第三方佣金,换取一个概率更高的回收。是否升级,取决于期望净回收,而不是取决于有多生气。

催收的每一步都要算期望值

把一级催收看成一个项目,它的期望净回收是:

期望回收   = 应收 × 回收概率
期望净回收 = 期望回收 − 内部工时成本 − 固定费用 − 第三方佣金

内部工时成本按机会成本算:Nord 的咨询费率是 85 美元/小时,所以 2 小时催收的内部成本是 170 美元。这个数字不是工资,而是「同样的时间本可以拿去交付客户」的机会成本。如果创始人的时间没有替代用途,用工资口径也可以,但要在全表里保持一致。

每一级都要单独算,不能把五级叠加。叠加会重复计算同一笔应收:提醒拿回的 30% 和仲裁拿回的 70% 不是两笔钱,而是同一笔钱在不同路径下的概率。表格里的每一行,都是「走到这一级就停下,不再升级」的期望值。

五级阶梯:成本、概率与 Nord 的期望值

成本从哪里来

把催收拆成五级,每级的成本和回收概率如下。所有概率、佣金比例和固定费用都是教学假设,不是统计结果:

阶梯内部工时内部成本 @ $85/小时固定费用第三方佣金回收概率(教学假设)
提醒2 小时$170$0030%
正式函4 小时$340$30050%
停止服务8 小时$680$0060%
第三方催收2 小时$170$0回收额的 20%70%
仲裁/诉讼12 小时$1,020$8,500070%

固定费用 8,500 = 仲裁费 2,500 + 律师与准备 6,000,是教学假设;真实报价因辖区、金额和争议复杂度差异很大。第三方佣金取回收额的 20%,也是教学假设;市场上常见 25%–35%,但本书不引用真实报价。

回收概率是档位,不是统计结果:30%、50%、60%、70%、70% 只用于比较路径,不能拿去预测某个具体客户。为什么概率递增?因为每一级都在增加压力或增加专业性:提醒利用日常关系,正式函留下书面记录,停止服务影响对方的使用,第三方催收有专门的追讨流程,仲裁或诉讼有强制力。

但递增不是无限的。仲裁的 70% 已经包含「胜诉但执行不到」的情形,不能按 100% 胜诉率算。把胜诉率当成回收概率,是小额官司最常见的算错方式。

把 Nord 的 11,264 代进去

Nord Studio 的应收是 11,264 美元。把每一级代进公式:

阶梯期望回收内部成本佣金固定费用期望净回收
提醒$3,379$170$0$0$3,209
正式函$5,632$340$0$30$5,262
停止服务$6,758$680$0$0$6,078
第三方催收$7,885$170$1,577$0$6,138
仲裁/诉讼$7,885$1,020$0$8,500−$1,635

这里有三点值得注意。第一,提醒和正式函的成本很低,几乎没有理由不做;它们的问题不是「值不值」,而是「有没有做」。第二,停止服务和第三方催收的期望净回收接近,只差 60 美元;如果停止服务会损失未来收入,第三方催收可能更划算。第三,仲裁在 11,264 的金额上是负的,即使回收概率 70% 也不值得。

第三方催收的 6,138 = 7,885 − 1,577 − 170。佣金是按期望回收算的,不是按本金算的;如果第三方最终只收回一半,佣金也会按实际回收调整,但期望值仍然按概率加权。把 20% 乘在 11,264 上会得到 2,253,那会高估佣金 676 美元。

仲裁的盈亏平衡点是 13,600:

盈亏平衡应收 = (1,020 + 8,500) ÷ 70% = 13,600

11,264 低于 13,600,所以期望净回收为负。这个结论不依赖对方是否「有理」;它只依赖金额、成本和概率。如果应收是 20,000,仲裁的期望净回收就是 20,000 × 70% − 9,520 = 4,480,结论会反过来。

3,000 美元的官司:为什么不该打

假设一笔 3,000 美元的尾款,其他参数不变:

期望回收   = 3,000 × 70% = 2,100
期望净回收 = 2,100 − 1,020 − 8,500 = −7,420

即使胜诉率 100%,期望净回收也是 3,000 − 9,520 = −6,520。也就是说,官司还没开始,成本已经超过本金。这就是「为了 3,000 美元打两年官司」的反例:它把「赢」当成目标,而不是把「净回收」当成目标。

两年是另一个成本。仲裁和诉讼通常不是「立案就回款」,而是排期、开庭、裁决、执行,两年并不夸张。按 10% 折现率,两年后的 3,000 只值:

3,000 ÷ 1.10² = 2,479

而 8,500 的仲裁与律师费现在就要付。时间价值让这笔账更差。

更现实的问题是执行:赢了裁决不等于拿到钱。如果对方没有可执行资产,回收概率不是 70%,而是接近 0。仲裁的 70% 已经是「胜诉且能执行」的混合概率;如果对方已经失联或资不抵债,这个概率还要下调。失联与破产的回收顺序见 4.3 对方破产或失联:能拿回多少 。

更理性的选择是:按 4.1 坏账准备:计提、核销与转回 核销,或者接受一个折扣和解。比如现在拿 1,500 和解,虽然只有本金的 50%,但比期望净回收 −7,420 好得多。

和解的折扣也有下限。如果对方只能给 500,而继续催收的期望净回收是 3,209(提醒档),那就继续提醒;和解不是无条件接受第一个报价。

停止服务、第三方催收与和解的边界

停止服务是五级里最容易被情绪驱动的一级。它的成本不只是 8 小时内部工时,还包括未来收入损失、数据交接和关系修复。如果这个客户本来还会续约,停服可能把 6,078 的期望净回收再打掉一部分。表里的 6,078 没有扣未来收入,是因为案例没有给续约金额;使用时要自己补上。

第三方催收适合「金额中等、自己没有时间、对方还有一定还款能力」的区间。20% 的佣金看起来高,但它把催收工作外包,并且通常只在收回时收费;对于 11,264 这样的金额,6,138 的期望净回收高于自己停服的 6,078。如果金额只有 3,000,第三方可能不愿接,或者佣金比例更高,这时它的优势会消失。

和解适合「对方有支付意愿但暂时没有全额现金」的情形。50% 的折扣是否接受,要和下一级的期望净回收比,而不是和账面全额比。账面全额是 11,264,但如果继续打官司的期望净回收是负的,账面全额只是一个参照点,不是可实现的现金。

这一节不讨论具体的催收话术、函件模板或程序步骤;那些属于操作层。这里只回答一个问题:在这一级停下,期望净回收是多少。停止服务前是否被合同允许、和解协议怎么写,属于合同与争议解决条款,见 5.2 争议解决:管辖、仲裁与调解 ;本节只做财务估算,不构成法律意见。

写成代码:collection.ts

下面的 collection.ts 把每一级催收写成 CollectionStage,用 evaluateStage 算期望回收、佣金、内部成本、固定费用和期望净回收,用 evaluateLadder 一次算完五级,用 bestStage 选出期望净回收最高的一级。breakEvenReceivableCents 直接给出这一级的盈亏平衡应收。

// file: src/contract/collection.ts
// tier: L1
// verify: node --test test/contract/collection.test.ts

import { type Cents, add, sub } from "../core/money.ts";
import { type Bps, applyRate } from "../core/rate.ts";
import { type Result, fail, ok } from "../core/result.ts";

export type CollectionStage = {
  name: string;
  recoveryBps: Bps;
  internalHours: number;
  hourlyCostCents: Cents;
  fixedCostCents: Cents;
  commissionBps: Bps;
};

export type StageResult = {
  name: string;
  expectedRecoveryCents: Cents;
  internalCostCents: Cents;
  commissionCents: Cents;
  fixedCostCents: Cents;
  expectedNetCents: Cents;
  breakEvenReceivableCents: Cents;
};

function checkAmount(value: Cents, label: string): Result<true> {
  if (!Number.isInteger(value) || value < 0) {
    return fail(`${label} must be a non-negative integer amount in cents`);
  }
  return ok(true);
}

function checkRate(value: Bps, label: string): Result<true> {
  if (!Number.isInteger(value) || value < 0 || value > 10000) {
    return fail(`${label} must be between 0 and 10000 bps`);
  }
  return ok(true);
}

/**
 * Expected net recovery is probability-weighted cash minus the cost of that
 * stage:
 *   expected recovery = receivable x recovery probability
 *   commission        = expected recovery x third-party rate
 *   net               = expected recovery - internal - fixed - commission
 * The break-even receivable is the balance at which net equals zero, so a
 * small invoice can be rejected without arguing about the merits.
 */
export function evaluateStage(
  receivableCents: Cents,
  stage: CollectionStage,
): Result<StageResult> {
  const receivable = checkAmount(receivableCents, "receivable");
  if (!receivable.ok) {
    return receivable;
  }
  if (stage.name.trim() === "") {
    return fail("stage name cannot be empty");
  }
  const recovery = checkRate(stage.recoveryBps, `${stage.name} recovery rate`);
  if (!recovery.ok) {
    return recovery;
  }
  if (stage.recoveryBps === 0) {
    return fail("recovery probability must be greater than zero");
  }
  const commission = checkRate(stage.commissionBps, `${stage.name} commission`);
  if (!commission.ok) {
    return commission;
  }
  if (!Number.isInteger(stage.internalHours) || stage.internalHours < 0) {
    return fail("internal hours must be a non-negative integer");
  }
  const hourly = checkAmount(stage.hourlyCostCents, "hourly cost");
  if (!hourly.ok) {
    return hourly;
  }
  const fixed = checkAmount(stage.fixedCostCents, "fixed cost");
  if (!fixed.ok) {
    return fixed;
  }

  const expectedRecoveryCents = applyRate(receivableCents, stage.recoveryBps);
  const internalCostCents = Math.round(stage.internalHours * stage.hourlyCostCents);
  const commissionCents = applyRate(expectedRecoveryCents, stage.commissionBps);
  const expectedNetCents = sub(
    expectedRecoveryCents,
    add(add(internalCostCents, stage.fixedCostCents), commissionCents),
  );
  const netRateBps = Math.round(
    (stage.recoveryBps * (10000 - stage.commissionBps)) / 10000,
  );
  const breakEvenReceivableCents =
    netRateBps === 0
      ? 0
      : Math.ceil(
          ((internalCostCents + stage.fixedCostCents) * 10000) / netRateBps,
        );
  return ok({
    name: stage.name,
    expectedRecoveryCents,
    internalCostCents,
    commissionCents,
    fixedCostCents: stage.fixedCostCents,
    expectedNetCents,
    breakEvenReceivableCents,
  });
}

/**
 * Evaluate each rung as a standalone path: "stop here, do not escalate".
 * Summing the rows would double count the same receivable.
 */
export function evaluateLadder(
  receivableCents: Cents,
  stages: CollectionStage[],
): Result<StageResult[]> {
  if (stages.length === 0) {
    return fail("at least one collection stage is required");
  }
  const results: StageResult[] = [];
  for (const stage of stages) {
    const evaluated = evaluateStage(receivableCents, stage);
    if (!evaluated.ok) {
      return evaluated;
    }
    results.push(evaluated.value);
  }
  return ok(results);
}

/**
 * Pick the highest expected net. A tie keeps the earlier, cheaper rung so the
 * ladder never escalates without a positive gain.
 */
export function bestStage(results: StageResult[]): StageResult | null {
  if (results.length === 0) {
    return null;
  }
  let best = results[0];
  for (const result of results) {
    if (result.expectedNetCents > best.expectedNetCents) {
      best = result;
    }
  }
  return best;
}

evaluateStage 的输入是应收美分和一级催收的参数。Nord 的 11,264 美元是 1,126,400 美分;提醒档的内部工时 2 小时、小时成本 8,500 美分,期望回收 337,920 美分,净回收 320,920 美分。仲裁档的固定费用 850,000 美分,期望净回收 −163,520 美分,盈亏平衡应收 1,360,000 美分。

evaluateLadder 不会把五级相加,它只是把每一级单独算一遍。bestStage 在五级里选期望净回收最高的;Nord 的结果是第三方催收 613,784 美分。如果某一级的期望净回收为负,它仍然会出现在结果里,方便看到「为什么不该升级」。

代码不处理时间价值、执行风险和未来收入损失;它只覆盖本节表格里的口径。时间价值可以在调用前把应收折现,执行风险可以下调回收概率,未来收入损失可以加进固定费用。三种调整都不改变公式,只改变输入。

小结

  • 每一级催收都是一笔投资:期望净回收 = 应收 × 回收概率 − 内部成本 − 固定费用 − 佣金。
  • Nord 的 11,264 应收:提醒 3,209、正式函 5,262、停止服务 6,078、第三方催收 6,138、仲裁 −1,635。
  • 仲裁的盈亏平衡点是 13,600;11,264 低于它,所以即使 70% 回收概率也不值得。
  • 3,000 美元的尾款打官司,期望净回收是 −7,420;即使 100% 胜诉,也是 −6,520。
  • 两年后的 3,000 按 10% 折现只值 2,479,而 8,500 的成本现在就要付。
  • 和解要和下一级的期望净回收比,不要和账面全额比;50% 的 3,000 是 1,500,好过负的期望值。

阅读导航:上一节:4.1 坏账准备:计提、核销与转回 · 下一节:4.3 对方破产或失联:能拿回多少 。

常见错误与边界

  1. 把升级当进展。 发更严厉的函、找律师,都是成本;如果期望净回收为负,升级只是把亏损做大。
  2. 把五级期望值相加。 五级是同一笔应收的不同路径,不是五笔独立收入;相加会重复计算。
  3. 用工资而不是机会成本。 如果创始人的时间本可以交付客户,内部成本应按 85 美元/小时算;用工资会低估催收成本。
  4. 忽略未来收入损失。 停止服务的 6,078 没有扣未来续约收入;如果客户本来会续约,这个数字会下降。
  5. 用胜诉率代替回收概率。 胜诉不等于拿到钱;仲裁的 70% 应理解为「胜诉且可执行」的混合概率。
  6. 忽略执行风险。 对方没有可执行资产时,裁决只是一张纸;失联或资不抵债要把回收概率下调。
  7. 把佣金按本金算。 20% 的佣金乘的是期望回收,不是应收全额;乘错会高估成本。
  8. 用账面全额和和解折扣比。 账面全额不是可实现的现金;应和继续催收的期望净回收比。
  9. 不做盈亏平衡。 13,600 是这一级的门槛;低于门槛的应收,不应该进入仲裁。
  10. 把教学概率当统计结果。 30%、50%、60%、70% 是档位,用于比较路径,不是对某个客户的预测。

本节的边界是「催收阶梯的期望值比较」:只覆盖内部工时、固定费用、第三方佣金和回收概率,不展开具体法律程序、证据规则、执行措施和跨境管辖。所有概率与费率均为教学假设;具体法律选择因司法辖区而异,本节不构成法律意见。坏账的账务处理见第 4.1 节,破产与失联的回收顺序见第 4.3 节。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「books」更多文章

  1. 《移动游戏产品的架构·商业化与发行工程》附录 C 商业化与发行评审清单
  2. 《移动游戏产品的架构·商业化与发行工程》附录 B 双案例商业化事实表
  3. 《移动游戏产品的架构·商业化与发行工程》附录 A 商业化与发行术语速查