《独立开发者的商业指南·合同、应收与风险》8.2 版税结构与保底

保底不是预付款,而是一份把下行风险从授权方转给被授权方的下限。本节用 Tidecast 的 60,000 美元年保底加 8% 净收入,算出当前销量下 8% 只有 11,054 美元、有效费率高达 43.4%,并反推保底对应的最低销量与阶梯费率的分界点。

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

版税看起来只是一个百分比,实际却包含三个独立决定:按什么基数算、按什么单位收、有没有下限。基数决定分成是毛收入还是净收入,单位决定收入随销量还是随价格变化,下限决定销售不及预期时谁承担后果。三个决定组合起来,才是一份完整的版税条款。

Tidecast 的授权报价是保底 60,000 美元一年,再加 8% 净收入,期限 3 年,最低保证 180,000 美元。这组数字里,60,000 和 8% 不是简单的“取大”,而是两种完全不同的现金流:保底是固定的下限,分成是浮动的上限。这一节把两者拆开,算清楚在当前销量下谁付多少、保底对应多高的销量、以及阶梯费率在什么位置才开始起作用。

三种版税结构

版税结构常见的有三种:按净收入比例、按销量、按阶梯费率。它们的区别不在百分比高低,而在风险落在谁身上。

结构计算基数授权方的上行被授权方的风险管理成本
按净收入比例扣除退款、平台费、支付费后的收入随售价和销量一起涨收入下滑时少付低,按报表算
按销量每份固定金额只随销量涨,不随售价涨降价促销时相对更痛低,按数量算
阶梯费率分档后的净收入超过门槛后分成更高增长越快,边际成本越高中,要核对分档

按净收入比例最常用,因为它自动跟随实际到账的钱。Tidecast 的净收入 138,171 美元,是标价收入 191,904 美元扣掉退款 7,676 美元、平台分成 44,215 美元和支付费 1,842 美元之后的余额。8% 算在这个净额上,是 11,054 美元;如果算在标价收入上,是 15,352 美元,差了 4,298 美元。基数写错,百分比再精确也没有意义。

按销量适合单价稳定、渠道折扣少的产品。它把“卖了多少份”和“收了多少钱”分开:授权方拿到的是每份固定金额,被授权方做降价促销时,版税不会跟着降价。对授权方来说,这在下行周期更稳;对被授权方来说,这在上行周期更贵,因为涨价的好处全部留在自己手里。

阶梯费率是前两种的混合。它在低档给一个较低的比例,鼓励被授权方把销量做起来;超过门槛后提高比例,让授权方分享增长。门槛可以按年净收入、累计销量或单款产品设定。阶梯的关键不是档位数量,而是“按什么周期累计”。按年累计,被授权方可以在每年初重新从低档开始;按合同期累计,门槛只会被跨过一次。两种算法的现金流差别很大。

保底是一份下限,不是预付款

保底的常见写法是:每个授权年度,被授权方支付“保底金额”与“按净收入计算的版税”两者中的较大值。它的效果是给授权方一个下限,同时给被授权方一个固定成本。

年度版税保底被授权方实付差额性质
$11,054$60,000$60,000保底高于分成,补足 $48,946
$60,000$60,000$60,000两者相等,按保底付
$85,000$60,000$85,000分成高于保底,按分成付

这张表就是保底的全部逻辑:分成低于保底时,被授权方补到保底;分成高于保底时,按分成付,保底不再额外收取。它不是“保底 + 分成”,而是“保底与分成取大”。如果合同写成“保底 + 分成”,那是另一种交易,价格和风险都要重算。

保底对现金流的影响,可以换一个角度看。对被授权方,保底是一笔固定支出:无论卖多少,60,000 美元都要付。对授权方,保底是一笔固定收入:无论卖多少,60,000 美元都能收到。固定支出加浮动分成,会让被授权方的利润在销量下滑时收缩得更快;固定收入加浮动上限,会让授权方的收入在销量爆发时分享得更少。

这也是保底谈判的核心:授权方要的是“最坏情况下也能收回多少”,被授权方要的是“最好情况下不用多付多少”。两边争的不是 8% 这个数字,而是 60,000 美元这个下限。下限越高,授权方的下行保护越强,被授权方的固定成本越高,交易越难谈成。

保底还有一种变体:可抵扣预付款。被授权方先付一笔钱,之后用应得版税逐期抵扣,抵完为止。案例写的是“保底 60,000 美元一年 + 8% 净收入,3 年,最低保证 180,000 美元”,更接近逐年取大的下限,而不是一次性预付款。两种结构的现金流完全不同:预付款是期初一次性到账,下限是每年结算时补足。读合同时要先分清是哪一种。

Tidecast 的 60,000 与 8%

把 Tidecast 的净收入 138,171 美元代入 8%:

8% 版税 = 138,171 × 8% = 11,053.68 美元 ≈ 11,054 美元

保底是 60,000 美元,高于 11,054 美元,所以被授权方当年实付 60,000 美元,补足差额:

补足额 = 60,000 − 11,054 = 48,946 美元

如果只看“8%”这个数字,会以为授权方一年只拿 11,054 美元;实际拿的是 60,000 美元。把实付除以净收入,得到有效费率:

有效费率 = 60,000 ÷ 138,171 = 43.4%

43.4% 不是版税率,而是保底在当前销量下的等效费率。它说明:保底越高、销量越低,有效费率越接近“保底 ÷ 净收入”;只有销量足够高,有效费率才会回落到 8%。

3 年期的账同样要按“取大”算。如果销量维持当前水平,每年版税 11,054 美元,3 年合计 33,162 美元;保底 3 年合计 180,000 美元,授权方多收 146,838 美元。如果销量增长到每年净收入 750,000 美元,8% 正好是 60,000 美元,保底与分成相等;再往上,分成开始超过保底,3 年总额随销量上升。

保底对应的最低销量门槛,可以按每份净得反推。Tidecast 年销 9,600 份,净收入 138,171 美元,每份净得:

每份净得 = 138,171 ÷ 9,600 = 14.39 美元 ≈ 1,439 美分

8% 的每份版税是 115 美分。要凑够 60,000 美元保底:

最低销量 = 60,000 ÷ 1.15 = 52,174 份

52,174 份是当前 9,600 份的 5.4 倍。换句话说,保底 60,000 美元隐含的假设是:被授权方能把销量做到当前水平的 5 倍以上,否则保底永远高于分成,授权方拿的一直是固定下限。这个门槛也可以用净收入表达:60,000 ÷ 8% = 750,000 美元净收入,是当前 138,171 美元的 5.4 倍,两种口径一致。

指标当前保底平衡点倍数
年销量9,600 份52,174 份5.4×
净收入$138,171$750,0005.4×
8% 版税$11,054$60,0005.4×
有效费率43.4%8.0%—

这张表把“保底”翻译成了被授权方必须达到的销售目标。它不是精确预测,而是一条谈判线:如果被授权方自己都不相信能卖到 5.4 倍,60,000 美元的保底就是一份定价过高的固定成本;如果它相信能卖到 10 倍,保底只是第一年的下限,后面的分成才是主要收入。

阶梯费率在什么位置起作用

阶梯费率的常见写法是:净收入低于某个门槛时按 8%,超过部分按 10%。门槛设在哪里,决定了阶梯是“增长分享”还是“变相涨价”。

年净收入固定 8%阶梯 8%/10%(门槛 $750,000)保底应付
$138,171$11,054$11,054$60,000
$750,000$60,000$60,000$60,000
$1,000,000$80,000$85,000$85,000

门槛设在 750,000 美元时,阶梯的第一个拐点正好是保底平衡点。低于这个点,阶梯和固定 8% 没有区别,保底仍然主导;高于这个点,阶梯才开始多收。门槛设得越低,授权方越早分享增长,被授权方的边际成本越早上升;门槛设得越高,阶梯越像一份“只有大幅增长才触发”的奖励。

阶梯还有一个容易忽略的细节:分档是按“超过门槛的部分”还是按“全部收入”适用高费率。前者是边际阶梯,后者是追溯阶梯。以上表为例,净收入 1,000,000 美元时,边际阶梯是 60,000 + 250,000 × 10% = 85,000 美元;追溯阶梯是 1,000,000 × 10% = 100,000 美元。两者差 15,000 美元。合同里只写“超过 750,000 部分按 10%”还不够,要写清是边际还是追溯。

把保底与分成写成代码

下面的 royalty.ts 把三种结构写成可复算的函数:royaltyOnNet 算净收入比例,royaltyPerUnit 算按销量,tieredRoyalty 算阶梯,settleGuarantee 算保底与分成的取大,minimumUnits 把保底反推成销量门槛。

// file: src/contract/royalty.ts
// tier: L1
// verify: node --test test/contract/royalty.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 RoyaltyTier = {
  upToCents: Cents | null;
  rateBps: Bps;
};

export type GuaranteeSettlement = {
  royaltyCents: Cents;
  guaranteeCents: Cents;
  payableCents: Cents;
  shortfallCents: Cents;
  effectiveRateBps: Bps;
};

export type UnitThreshold = {
  royaltyPerUnitCents: Cents;
  minimumUnits: number;
  minimumNetRevenueCents: 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;
}

export function royaltyOnNet(
  netRevenueCents: Cents,
  rateBps: Bps,
): Result<Cents> {
  const amountError = checkAmount(netRevenueCents, "net revenue");
  if (amountError) {
    return fail(amountError);
  }
  const rateError = checkBps(rateBps, "royalty rate");
  if (rateError) {
    return fail(rateError);
  }
  return ok(applyRate(netRevenueCents, rateBps));
}

export function royaltyPerUnit(
  units: number,
  ratePerUnitCents: Cents,
): Result<Cents> {
  if (!Number.isInteger(units) || units < 0) {
    return fail("units must be a non-negative integer");
  }
  const amountError = checkAmount(ratePerUnitCents, "per-unit royalty");
  if (amountError) {
    return fail(amountError);
  }
  return ok(units * ratePerUnitCents);
}

export function tieredRoyalty(
  netRevenueCents: Cents,
  tiers: RoyaltyTier[],
): Result<Cents> {
  const amountError = checkAmount(netRevenueCents, "net revenue");
  if (amountError) {
    return fail(amountError);
  }
  if (tiers.length === 0) {
    return fail("at least one tier is required");
  }
  let previous = 0;
  let total = 0;
  for (let index = 0; index < tiers.length; index += 1) {
    const tier = tiers[index];
    const rateError = checkBps(tier.rateBps, `tier ${index + 1} rate`);
    if (rateError) {
      return fail(rateError);
    }
    if (index < tiers.length - 1 && tier.upToCents === null) {
      return fail("only the last tier may be open-ended");
    }
    const cap = tier.upToCents === null ? netRevenueCents : tier.upToCents;
    if (!Number.isInteger(cap) || cap < previous) {
      return fail(`tier ${index + 1} cap must not decrease`);
    }
    const band = Math.min(netRevenueCents, cap) - previous;
    if (band > 0) {
      total += applyRate(band, tier.rateBps);
    }
    previous = cap;
    if (netRevenueCents <= cap) {
      break;
    }
  }
  return ok(total);
}

/**
 * A minimum guarantee is a floor, not an advance: the licensee pays the
 * larger of the guarantee and the earned royalty. The effective rate is
 * measured against net revenue, so a guarantee can look like a very high
 * royalty rate when sales are far below the guarantee.
 */
export function settleGuarantee(
  netRevenueCents: Cents,
  rateBps: Bps,
  guaranteeCents: Cents,
): Result<GuaranteeSettlement> {
  const royalty = royaltyOnNet(netRevenueCents, rateBps);
  if (!royalty.ok) {
    return royalty;
  }
  const amountError = checkAmount(guaranteeCents, "guarantee");
  if (amountError) {
    return fail(amountError);
  }
  const payableCents = Math.max(royalty.value, guaranteeCents);
  const shortfallCents = Math.max(0, guaranteeCents - royalty.value);
  return ok({
    royaltyCents: royalty.value,
    guaranteeCents,
    payableCents,
    shortfallCents,
    effectiveRateBps:
      netRevenueCents === 0 ? 0 : divide(payableCents, netRevenueCents),
  });
}

/**
 * Converts a guarantee into the sales volume that would make the earned
 * royalty equal the guarantee. The per-unit rate is rounded to cents first,
 * so the unit threshold is the number the licensee can actually count.
 */
export function minimumUnits(
  guaranteeCents: Cents,
  netPerUnitCents: Cents,
  rateBps: Bps,
): Result<UnitThreshold> {
  const guaranteeError = checkAmount(guaranteeCents, "guarantee");
  if (guaranteeError) {
    return fail(guaranteeError);
  }
  const unitError = checkAmount(netPerUnitCents, "net per unit");
  if (unitError) {
    return fail(unitError);
  }
  const rateError = checkBps(rateBps, "royalty rate");
  if (rateError) {
    return fail(rateError);
  }
  if (rateBps === 0) {
    return fail("royalty rate must be positive to compute a threshold");
  }
  const royaltyPerUnitCents = applyRate(netPerUnitCents, rateBps);
  if (royaltyPerUnitCents === 0) {
    return fail("per-unit royalty rounds to zero");
  }
  return ok({
    royaltyPerUnitCents,
    minimumUnits: Math.ceil(guaranteeCents / royaltyPerUnitCents),
    minimumNetRevenueCents: Math.ceil((guaranteeCents * 10000) / rateBps),
  });
}

把 Tidecast 的数字代入:净收入 13,817,100 美分、费率 800 基点,royaltyOnNet 返回 1,105,368 美分,即 11,053.68 美元;settleGuarantee 返回实付 6,000,000 美分、补足 4,894,632 美分、有效费率 4,342 基点,即 43.4%。把保底 6,000,000 美分、每份净得 1,439 美分、费率 800 基点代入 minimumUnits,得到每份版税 115 美分、最低销量 52,174 份、最低净收入 75,000,000 美分。

代码里有三个刻意的限制。第一,金额用整数美分,费率用基点,避免 0.08 和 2.5 在连续计算里漂移。第二,settleGuarantee 返回的是“取大”后的实付,不是“保底 + 分成”;如果合同真是叠加结构,要另写函数,不能复用这个结果。第三,minimumUnits 先把每份版税取整到美分,再算销量门槛;用未取整的每份版税会得到 52,109 份,差 65 份,这个差异来自取整口径,不是计算错误。

保底和分成确定之后,剩下的是执行:被授权方报的净收入是否真实、版税是否按约支付、超范围使用有没有被发现。这些问题的答案不在报价表里,而在审计权和终止条款里,这是 8.3 节的内容。

小结

  • 版税有三个独立决定:基数、单位、下限;百分比只是其中最显眼的一个。
  • 按净收入比例跟随实际到账,按销量跟随数量,阶梯费率在门槛以上分享增长;三者风险分配不同。
  • 保底是“保底与分成取大”,不是“保底 + 分成”;分成低于保底时补足,高于保底时按分成付。
  • Tidecast 当前净收入 138,171 美元,8% 版税 11,054 美元,低于 60,000 美元保底,实付 60,000 美元,补足 48,946 美元,有效费率 43.4%。
  • 保底平衡点是净收入 750,000 美元或销量 52,174 份,都是当前水平的 5.4 倍。
  • 3 年保底 180,000 美元;按当前销量,3 年版税只有 33,162 美元,保底比分成多 146,838 美元。
  • 阶梯费率要写清门槛、累计周期和边际/追溯口径;门槛设在保底平衡点时,阶梯在拐点之后才起作用。

阅读导航:上一节:8.1 授权类型:独占、排他、普通 · 下一节:8.3 授权审计与终止 。

常见错误与边界

  1. 把保底当成预付款。 逐年取大的保底是下限,不是期初一次性到账;预付款可以抵扣未来版税,两者的现金流完全不同。
  2. 把“保底 + 分成”当成“取大”。 叠加结构下,被授权方要付 60,000 + 11,054 = 71,054 美元;取大结构只付 60,000 美元,差 11,054 美元。
  3. 用标价收入算 8%。 Tidecast 的标价收入 191,904 美元,8% 是 15,352 美元;净收入 138,171 美元,8% 是 11,054 美元,基数不同,差 4,298 美元。
  4. 忽略保底的有效费率。 当前销量下有效费率是 43.4%,不是 8%;只看 8% 会低估被授权方的固定成本。
  5. 把 3 年保底当成 3 年分成。 180,000 美元是最低保证,不是按 8% 算出的预期版税;按当前销量,3 年版税只有 33,162 美元。
  6. 不反推销量门槛。 保底 60,000 美元对应 52,174 份或 750,000 美元净收入;不把这个数字摆上桌,谈判就只是在猜。
  7. 阶梯费率不写累计周期。 按年累计和按合同期累计,门槛被跨过的次数不同,3 年总额可能差出一整档。
  8. 把边际阶梯写成追溯阶梯。 净收入 1,000,000 美元时,两者差 15,000 美元;合同里只写“超过部分按 10%”不够,要写清计算方式。
  9. 用每份版税反推时忽略取整。 每份 115 美分得到 52,174 份,未取整得到 52,109 份;差 65 份,来自取整口径。
  10. 只算授权方收入,不算被授权方现金流。 保底是被授权方的固定支出,销量下滑时利润收缩更快;只从授权方角度看,会低估交易对手的违约概率。

本节的边界是“单一产品、单一渠道、按年结算”的静态模型。它假设净收入口径稳定、销量均匀发生、保底按年取大,不含跨年结转、不含预付款抵扣、不含汇率和税务预提。它足够回答“保底和分成怎么算”,但不足以回答“被授权方少报收入怎么办”。后者需要审计权和终止条款,这正是 8.3 节的内容。本节不构成法律意见,具体版税条款请由律师审阅。

继续阅读

探索更多技术文章

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

全部文章 返回首页

「books」更多文章

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