《独立开发者的商业指南·合同、应收与风险》8.3 授权审计与终止

审计权不是每年查一次账,而是一道用成本换确定性的选择题。本节用 8,000 美元审计成本、6% 少报比例和 5% 成本转移门槛,算清一次审计的净收益与盈亏平衡点,并把终止后的剩余保底、替代授权和过渡期换算成收入缺口,给出什么时候查、什么时候退的判断线。

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

授权签完不等于钱会自动到账。被授权方报多少净收入、扣哪些费用、有没有超范围使用、有没有把权利转给别人,授权方都不会自动知道。审计权就是把这些“不知道”变成“可以查”的条款;终止权则是查出来之后,或者对方根本不付钱时,授权方最后的退出按钮。

这一节不写法律程序,只算两笔账。第一笔是审计的成本收益:花 8,000 美元查一次账,能追回多少,什么时候值得查。第二笔是终止的收入缺口:合同提前结束,剩下的保底没了,替代授权要多久才能补上,中间的空洞有多大。两笔账都不复杂,但都需要先把“查什么”和“终止什么”定义清楚。

审计权查什么

审计权不是“看看账本”这么简单。它要能回答四个问题:净收入怎么算、销量怎么核、使用范围有没有超、再许可有没有发生。四个问题对应四类证据。

审计对象要核对的证据常见争议
净收入平台结算单、退款明细、支付费哪些费用可以从收入里扣
销量销售报表、激活记录、发货单赠送、促销、捆绑算不算销量
使用范围产品清单、平台列表、地域子品牌、关联产品算不算超范围
再许可下游合同、分成记录外包、发行商、渠道算不算再许可

四类证据里,净收入最容易出问题,因为“净”是一个定义,不是一个事实。Tidecast 的净收入 138,171 美元,是标价收入扣掉退款、平台分成和支付费之后的余额;如果被授权方把营销费、客服费也算进扣除项,净收入会变小,8% 的版税也跟着变小。审计权要能查到扣除项的定义,而不只是看到一个净额。

销量和净收入不一定同步。被授权方可能做“买一送一”,销量翻倍但净收入不涨;也可能做大幅折扣,销量涨了、净收入反而降。按净收入分成时,销量只是辅助证据;按销量分成时,销量就是主证据。审计权要写明按哪种口径核对,否则双方会各拿一张对自己有利的表。

使用范围和再许可,是审计里最容易被忽略的部分。授权写的是“一款游戏、一个平台、三年”,被授权方可能把音乐用在续作、DLC、周边或子品牌上。这些使用不一定体现在销售报表里,但会体现在产品清单和下游合同里。审计权如果只覆盖财务数据,不覆盖使用记录,超范围使用就查不出来。

审计窗口和频率也要写。窗口太短,历史数据拿不到;频率太高,被授权方会把审计成本转嫁到报价里。常见做法是每年一次、覆盖最近 12 到 24 个月,具体长度取决于结算周期和记录保存期限。这一节不展开法律细节,只把审计当成一笔可计算的支出。

一次审计的成本收益

先设一组教学假设:一次审计成本 8,000 美元,审计发现少报的比例是 6%,合同约定“少报超过已报版税的 5%,审计费由被授权方承担”。三个数字都不是市场报价,只用来演示算法。

Tidecast 当前年份的版税是 11,054 美元。按 6% 少报计算:

少报金额 = 11,054 × 6% = 663 美元

如果审计费由授权方承担,净收益是:

663 − 8,000 = −7,337 美元

如果少报比例超过 5% 的门槛,审计费转由被授权方承担,授权方的净收益变成:

663 − 0 = +663 美元

同一笔审计,因为成本转移条款不同,结果从 −7,337 美元变成 +663 美元。差别不在少报金额,而在谁付 8,000 美元的审计费。

情形少报比例少报金额审计费承担方授权方净收益被授权方总支出
低于门槛3%$331授权方−$7,669$331
达到门槛6%$663被授权方+$663$8,663
达到门槛10%$1,105被授权方+$1,105$9,105

这张表说明,审计条款的核心不是“能不能查”,而是“查到多少才由对方付钱”。门槛设得低,授权方更容易把审计成本转出去,但被授权方会在报价里加回这笔风险;门槛设得高,授权方查账的意愿降低,少报的激励上升。

如果审计费始终由授权方承担,盈亏平衡点在哪里?设少报比例为 6%,审计成本 8,000 美元:

盈亏平衡版税 = 8,000 ÷ 6% = 133,333 美元

也就是说,只有当已报版税达到约 133,333 美元时,6% 的少报才刚好覆盖 8,000 美元审计费。按 8% 费率换算,对应净收入约 1,666,667 美元,是 Tidecast 当前净收入 138,171 美元的 12.1 倍。在当前销量下,授权方自掏腰包审计是明显亏钱的。

这个结论看起来反直觉,但和保底有关。Tidecast 当前实付 60,000 美元保底,版税只有 11,054 美元;被授权方少报净收入,最多把版税从 11,054 压到更低,但保底 60,000 美元仍然要付。保底把少报的损失吸收了一大半,所以授权方查账的动力很低。只有当年版税超过保底、分成成为主要收入时,少报才会直接伤害授权方,审计才变得值得。

年净收入8% 版税6% 少报审计净收益(授权方付费)是否值得
$138,171$11,054$663−$7,337否
$750,000$60,000$3,600−$4,400否
$1,666,667$133,333$8,000$0临界
$2,000,000$160,000$9,600+$1,600是

这张表把“什么时候查账”变成了一个可读的门槛:净收入低于约 166.7 万美元时,6% 的少报覆盖不了 8,000 美元审计费;高于这个点,审计才开始为正。若合同有成本转移条款,门槛会大幅下降,因为授权方不再承担 8,000 美元。

终止条件与收入缺口

终止条款通常围绕几类事件:未付版税超过补正期、超出授权范围使用、未经许可再许可、拒绝提供报表或阻碍审计、被授权方失去偿付能力。每一类都要写清“什么算触发”和“触发后多久生效”,否则终止权只是一句表态。

终止的财务后果,不是“少了一年的版税”,而是“剩下的保底没了,替代收入要重新找”。用 Tidecast 的 3 年授权做例子:假设第 1 年结束时终止,剩余 2 年;每年保底 60,000 美元;替代授权按普通授权 60,000 美元一年计算;找到替代方需要 6 个月;审计和法律成本 8,000 美元。

项计算金额
剩余保底损失60,000 × 2$120,000
替代授权收入60,000 × 1.5$90,000
收入缺口120,000 − 90,000$30,000
加审计与法律成本30,000 + 8,000$38,000

38,000 美元是“终止后 2 年内”的现金缺口。它的构成是:6 个月过渡期没有替代收入,损失 30,000 美元;再加上 8,000 美元的追缴成本。如果替代方一直找不到,缺口变成 120,000 + 8,000 = 128,000 美元。如果替代授权只能按更低的倍数成交,缺口还会扩大。

把 8.1 的倍数放进来,可以看到终止对高倍数授权的伤害更大。假设终止的是一份按 4 倍定价的独占授权,年费 240,000 美元;替代报价只有普通授权的 1 倍,60,000 美元一年。剩余 2 年:

剩余保底损失 = 240,000 × 2 = 480,000 美元

替代收入 = 60,000 × 1.5 = 90,000 美元

收入缺口 = 480,000 − 90,000 + 8,000 = 398,000 美元

同样是终止,普通授权的缺口是 38,000 美元,独占授权的缺口是 398,000 美元,差 10 倍以上。原因不是终止条款不同,而是被终止的权利价值不同。独占授权把授权方的自用权和再许可权都让出去了,一旦对方违约,授权方要重新建立一套替代安排,时间和价格都更不利。

被终止的授权年费剩余 2 年保底替代收入(1.5 年)加成本后缺口
普通 1×$60,000$120,000$90,000$38,000
排他 2.5×$150,000$300,000$90,000$218,000
独占 4×$240,000$480,000$90,000$398,000

这张表说明,终止条款的谈判要和授权类型一起看。普通授权的终止缺口可以用一年收入覆盖;独占授权的终止缺口接近两年的保底,必须靠担保、分期或提前收回权利来降低。终止权本身不产生现金,它只是把未来的应收变成一笔可能收不回来的债权。

把审计与终止写成代码

下面的 license-audit.ts 把两笔账写成可复算的函数:auditOutcome 算少报金额、成本转移和授权方净收益,auditBreakEvenRoyalty 算授权方自费审计的盈亏平衡版税,terminationGap 算剩余保底、替代收入和总缺口。

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

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

export type AuditInput = {
  reportedRoyaltyCents: Cents;
  underpaymentRateBps: Bps;
  auditCostCents: Cents;
  costShiftThresholdBps: Bps;
};

export type AuditOutcome = {
  underpaymentCents: Cents;
  auditCostCents: Cents;
  costShifted: boolean;
  licensorNetCents: Cents;
  licenseeTotalCents: Cents;
  recoveryRateBps: Bps;
};

export type TerminationGapInput = {
  remainingYears: number;
  annualGuaranteeCents: Cents;
  replacementAnnualCents: Cents;
  transitionMonths: number;
  auditCostCents: Cents;
};

export type TerminationGap = {
  lostGuaranteeCents: Cents;
  replacementCents: Cents;
  replacementShareBps: Bps;
  revenueGapCents: Cents;
  totalGapCents: Cents;
};

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

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

/**
 * An audit clause rarely makes the licensee pay every time. The usual shape
 * is a materiality threshold: if the underpayment exceeds a stated share of
 * reported royalties, the licensee bears the audit cost; otherwise the
 * licensor does.
 */
export function auditOutcome(input: AuditInput): Result<AuditOutcome> {
  const reportedError = checkAmount(
    input.reportedRoyaltyCents,
    "reported royalty",
  );
  if (reportedError) {
    return fail(reportedError);
  }
  const costError = checkAmount(input.auditCostCents, "audit cost");
  if (costError) {
    return fail(costError);
  }
  for (const [value, label] of [
    [input.underpaymentRateBps, "underpayment rate"],
    [input.costShiftThresholdBps, "cost shift threshold"],
  ] as Array<[Bps, string]>) {
    const error = checkBps(value, label);
    if (error) {
      return fail(error);
    }
  }
  const underpaymentCents = applyRate(
    input.reportedRoyaltyCents,
    input.underpaymentRateBps,
  );
  const costShifted =
    input.underpaymentRateBps >= input.costShiftThresholdBps;
  return ok({
    underpaymentCents,
    auditCostCents: input.auditCostCents,
    costShifted,
    licensorNetCents:
      underpaymentCents - (costShifted ? 0 : input.auditCostCents),
    licenseeTotalCents:
      underpaymentCents + (costShifted ? input.auditCostCents : 0),
    recoveryRateBps:
      input.reportedRoyaltyCents === 0
        ? 0
        : divide(underpaymentCents, input.reportedRoyaltyCents),
  });
}

/**
 * The reported royalty at which the expected underpayment exactly pays the
 * audit cost. Above this line a licensor-funded audit is cash-positive;
 * below it, auditing is a loss unless the clause shifts the cost.
 */
export function auditBreakEvenRoyalty(
  auditCostCents: Cents,
  underpaymentRateBps: Bps,
): Result<Cents> {
  const costError = checkAmount(auditCostCents, "audit cost");
  if (costError) {
    return fail(costError);
  }
  const rateError = checkBps(underpaymentRateBps, "underpayment rate");
  if (rateError) {
    return fail(rateError);
  }
  if (underpaymentRateBps === 0) {
    return fail("underpayment rate must be positive");
  }
  return ok(Math.ceil((auditCostCents * 10000) / underpaymentRateBps));
}

/**
 * Termination does not just stop the royalty; it removes the remaining
 * guarantee. Replacement revenue arrives only after a transition gap, so the
 * cash hole is the lost guarantee minus what the replacement actually pays.
 */
export function terminationGap(
  input: TerminationGapInput,
): Result<TerminationGap> {
  if (!Number.isInteger(input.remainingYears) || input.remainingYears <= 0) {
    return fail("remaining years must be a positive integer");
  }
  for (const [value, label] of [
    [input.annualGuaranteeCents, "annual guarantee"],
    [input.replacementAnnualCents, "replacement annual fee"],
    [input.auditCostCents, "audit cost"],
  ] as Array<[Cents, string]>) {
    const error = checkAmount(value, label);
    if (error) {
      return fail(error);
    }
  }
  const totalMonths = input.remainingYears * 12;
  if (
    !Number.isInteger(input.transitionMonths) ||
    input.transitionMonths < 0 ||
    input.transitionMonths > totalMonths
  ) {
    return fail("transition months must fit inside the remaining term");
  }
  const lostGuaranteeCents = input.annualGuaranteeCents * input.remainingYears;
  const replacementMonths = totalMonths - input.transitionMonths;
  const replacementCents = Math.round(
    (input.replacementAnnualCents * replacementMonths) / 12,
  );
  return ok({
    lostGuaranteeCents,
    replacementCents,
    replacementShareBps:
      lostGuaranteeCents === 0
        ? 0
        : divide(replacementCents, lostGuaranteeCents),
    revenueGapCents: lostGuaranteeCents - replacementCents,
    totalGapCents:
      lostGuaranteeCents - replacementCents + input.auditCostCents,
  });
}

把 Tidecast 的数字代入:已报版税 1,105,368 美分、少报 600 基点、审计成本 800,000 美分、门槛 500 基点,auditOutcome 返回少报 66,322 美分、成本转移为真、授权方净收益 66,322 美分、被授权方总支出 866,322 美分;把少报改成 300 基点,成本转移为假,授权方净收益变成 −766,839 美分。auditBreakEvenRoyalty 返回 13,333,334 美分,即约 133,333 美元。终止场景代入剩余 2 年、年保底 6,000,000 美分、替代年费 6,000,000 美分、过渡 6 个月、审计成本 800,000 美分,得到剩余保底 12,000,000 美分、替代收入 9,000,000 美分、收入缺口 3,000,000 美分、总缺口 3,800,000 美分。

代码里有三个刻意的限制。第一,成本转移用“少报比例 ≥ 门槛”判断,门槛相等时转由被授权方承担;如果合同写的是“超过”而不是“达到”,把比较符改掉即可,结论会差一个边界点。第二,terminationGap 把过渡期按月折算,替代收入只算剩余月份,不假设替代方一签约就立刻付款。第三,所有金额用整数美分,避免 6% 和 1.5 年在连续计算里产生分币误差。

审计和终止是授权交易的执行层。条款写得再细,最后都要落到两个动作:查账和退出。查账的成本收益决定授权方有没有动力执行,终止的收入缺口决定授权方敢不敢执行。两者合起来,才是一份授权合同的实际约束力。

小结

  • 审计权要覆盖四类证据:净收入、销量、使用范围、再许可;只查财务数据,查不出超范围使用。
  • 一次审计成本 8,000 美元、少报 6% 时,授权方自费审计的净收益是 −7,337 美元;若 5% 门槛触发成本转移,净收益变成 +663 美元。
  • 授权方自费审计的盈亏平衡版税是约 133,333 美元,对应净收入约 166.7 万美元,是 Tidecast 当前净收入的 12.1 倍。
  • 保底会吸收少报的损失:当前版税 11,054 美元低于保底 60,000 美元,少报最多把版税压低,保底仍要付,所以低销量时审计动力很弱。
  • 终止的缺口不是剩余版税,而是剩余保底减替代收入再加追缴成本;Tidecast 剩余 2 年的缺口是 38,000 美元。
  • 高倍数授权的终止缺口更大:独占 4× 的剩余 2 年缺口是 398,000 美元,普通 1× 只有 38,000 美元。
  • 终止权把未来应收变成可能收不回的债权;它不产生现金,只决定现金缺口有多大。

阅读导航:上一节:8.2 版税结构与保底 · 下一节:9.1 直销、代理、经销的选择 。

常见错误与边界

  1. 把审计权写成“可以查账”。 不写查什么、查多久、谁付钱,审计权在执行时几乎没有约束力;四类证据要分别落条款。
  2. 只查净收入,不查扣除项定义。 被授权方把营销费、客服费算进扣除项,净收入变小,8% 的版税跟着变小;审计要能看到扣除项明细。
  3. 忽略保底对审计动力的抑制。 当前版税 11,054 美元低于保底 60,000 美元,少报不会减少保底支付;低销量时审计是亏钱的。
  4. 用当前销量判断审计价值。 当前 6% 少报只追回 663 美元,审计成本 8,000 美元;自费审计要到版税约 133,333 美元才回本。
  5. 成本转移门槛设得太低。 门槛低,授权方容易转嫁审计费,但被授权方会在报价里加回风险;门槛高,少报的激励上升。
  6. 把终止当成“停止授权”就结束。 终止后剩余保底没了,替代授权要时间;Tidecast 剩余 2 年的现金缺口是 38,000 美元,不是 0。
  7. 只算剩余版税,不算剩余保底。 保底是固定收入,终止直接抹掉;按版税算会低估缺口,尤其在高保底、低销量的年份。
  8. 忽略过渡期。 替代方 6 个月才到位,第一年只收半年;不把过渡期放进模型,缺口会被低估一半。
  9. 用普通授权的缺口判断独占授权。 独占 4× 的剩余 2 年缺口是 398,000 美元,普通 1× 只有 38,000 美元;权利越宽,终止越贵。
  10. 把终止权当成收款保障。 终止只是把未来应收变成债权,债权能不能收回要看对方偿付能力;真正的保障是担保、分期和提前收回权利。

本节的边界是“单一授权、按年结算、替代授权可找到”的静态模型。它假设少报比例已知、审计一次就能查清、替代方在过渡期后按普通授权价格签约,不含诉讼周期、不含汇率和税务预提、不含被授权方破产后的受偿顺序。它足够回答“查不查、终止后亏多少”,但不足以回答“对方破产时能拿回多少”。后者需要把授权应收放进破产受偿顺位,和普通应收一起看,见 4.3 对方破产或失联:能拿回多少 。本节不构成法律意见,具体审计与终止条款请由律师审阅。

继续阅读

探索更多技术文章

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

全部文章 返回首页

「books」更多文章

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