《独立开发者的商业指南·收入与财务工程》9.3 跨境预提、转让定价与资金回流

合同金额不等于到账金额。本节拆解预提税的毛额与净额、协定税率、跨境收款走廊的中间行费用与汇差,以及工资、服务费、特许权使用费、股息和借款在资金回流时的税负差异;最后把预提与通道成本写成一个可复算的 TypeScript 模型,并与三案例的示例所得税接上。

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

跨境收款最反直觉的地方,是发票金额、合同金额和银行到账金额可以三个都不一样。客户按合同付毛额,付款方在源头扣掉预提税,中间行和收款行再扣费用与汇差,最后到账的是净额。很多独立开发者按合同金额做预算,到了月底才发现少了 15% 到 30%,而少掉的部分既不是坏账,也不是退款,是税和通道成本。

这一节只算财务口径:预提税怎样作用在毛额上、协定税率怎样改变净额、收款走廊的费用怎样叠加、资金以什么名义回流时税负落在哪里。申报和协定申请的步骤不在本节展开,操作层见 税务的最低认知 。

预提税是毛额上的税

预提税(withholding tax)由付款方在付款时扣缴,按毛额乘税率,剩下的净额付给收款方。它和所得税的区别在于征收位置:所得税在收款方年度申报时结算,预提税在付款那一刻就离开现金流。对收款方来说,预提税可能只是「预缴」,年度申报时可以抵免;也可能因为协定、居民身份或费用性质,变成最终税负。

以一笔 $1,000 的跨境服务费为例。未适用协定、教学示例税率 30% 时,付款方扣 $300,收款方到账 $700;适用协定、教学示例税率 15% 时,扣 $150,到账 $850。两个税率都是示例,不是现行税率,也不代表任何具体协定的适用结果。

项目未适用协定适用协定
合同毛额$1,000$1,000
预提税率(教学示例)30%15%
源头扣缴$300$150
收款方到账$700$850
与毛额的差额$300$150

预提税对现金流的影响是即时的。$1,000 的合同,如果预算按毛额安排了下个月的服务器和外包付款,实际到账 $700 就会留下 $300 的缺口;这个缺口要等年度申报抵免后才可能补回,而抵免可能有时滞、上限和资料要求。对 runway 较短的独立开发者,预提税不是「年底再说」的科目,而是从第一笔跨境收款就要预留的现金。

如果合同约定「净额到手」,收款方要用 gross-up 把目标净额倒推成毛额。目标净额 $850、预提税率 15% 时,毛额是 $1,000;目标净额 $850、税率 30% 时,毛额约 $1,214。gross-up 不是把税「变没」,而是把税负前移到报价里,让付款方承担更高的合同金额。报价时是否 gross-up、客户是否接受,取决于议价能力,也取决于合同里有没有写明税负承担方。

协定税率与文档:同一笔钱的两种净额

双边税收协定通过降低预提税率、分配征税权来避免双重征税。协定不是自动适用的:收款方通常要证明自己是协定居民、是受益所有人、所得类型符合协定条款,付款方才会按协定税率扣缴。所得类型很关键:技术服务费、特许权使用费、股息、利息在协定里往往有不同的税率和门槛,同一笔付款被认定为「服务费」还是「特许权使用费」,净额可能不同。

所得类型常见扣缴位置协定通常处理财务关注点
服务费付款方所在国可能降低或免除,取决于协定有无服务条款是否构成常设机构、是否需要当地申报
特许权使用费付款方所在国通常保留较低税率软件授权与服务的定性差异
股息分配方所在国按持股比例和协定分档公司税后分配,预提后才是到手
利息付款方所在国通常保留较低税率关联方借款还受资本弱化规则限制
工资雇主所在国通常按个人居民身份处理社保、个税和常设机构可能同时触发

LedgerLite 的客户如果要求把软件订阅拆成「许可费 + 服务费」,收款方要先判断哪一部分可能被当地认定为特许权使用费、哪一部分是服务费。定性不同,预提税率不同,现金到账也不同。这并不是鼓励改变合同名称,而是提醒:合同条款、发票描述和实际交付必须一致,否则税务定性会按实质而不是按标题。

文档是协定税率的前提。案例事实表里提到 W-8BEN 协定预提,这类表格的作用是向付款方声明税收居民身份和协定资格;没有有效文档,付款方通常按国内法较高税率扣缴。文档还会过期,居住地、主体类型和受益所有人变化都可能让原来的税率失效。文档的提交和更新属于操作层,本节只把它当作现金流假设:预算里的预提税率,应该是「有文档支持、当前有效」的税率,而不是「希望适用」的税率。

跨境收款走廊:净得不是毛额减预提

预提税之后,钱还要经过收款通道。中间行费用、收款行入账费、货币转换点差和平台提现费,都会在净额上再削一层。把这些成本放在一起,才能回答「合同金额到底有多少回到账上」。

仍以 $1,000 毛额、15% 教学预提税率为例,假设中间行费用 2%、汇差 1%,两者都按毛额计算,到账是 $1,000 减 $150 减 $20 减 $10,等于 $820,净得率 82%。如果预提税率是 30%,同样费用下到账 $670,净得率 67%。费用比例不变,预提税率每高 15 个百分点,净得率就低 15 个百分点。

项目协定情形未适用协定情形
合同毛额$1,000$1,000
预提税$150$300
中间行费用(2%)$20$20
汇差(1%)$10$10
到账净额$820$670
净得率82%67%

这张表解释了一个常见误判:把「预提税降了 15%」直接等同于「收入多了 15%」。实际上,费用按毛额计算时,预提税下降会全额转化为净额增加;但如果费用按净额计算,或者通道有最低收费,净额改善会小于税率差。做预算时要把每一项成本的作用基数写清楚,不能把百分比直接相加。

Tidecast 的 $138,171 净得已经扣过退款、Steam 分成 30% 和官网支付费 5%,但它不是「税后净得」:如果所有者与付款方不在同一税收辖区,这笔钱还可能叠加预提税和收款走廊成本。平台代收销售税解决了流转税,不等于解决了所得税和跨境预提。渠道净得、税前利润、税后现金是三层不同的数字,不能互相替代。渠道结构见 4.2 收入质量 ,现金排程见 8.1 13 周现金流表 。

转让定价与资金回流:利润放在哪个主体

当独立开发者有一个海外主体、一个境内主体,或者两者都有时,资金回流不是「把钱转回来」这么简单。不同名义对应不同的税务处理:工资在付款方通常可扣除、在收款方按个人所得纳税;服务费在付款方可能可扣除、在收款方按经营所得纳税,并可能触发预提;特许权使用费在付款方可能可扣除、在收款方按特许权使用费纳税,预提更常见;股息在付款方不可扣除,是税后分配;利息在付款方可扣除但受资本弱化规则限制;股东借款不是收入,但必须有真实借款关系、合理利率和还款安排。

回流方式付款方能否扣除收款方税负预提可能性现金流特征
工资通常可扣除个人所得可能定期、可预测
服务费需符合独立交易原则经营所得可能按合同和发票
特许权使用费通常可扣除特许权使用费所得较高按授权协议
股息不可扣除股息所得可能税后分配,金额受可分配利润限制
利息受资本弱化等规则限制利息所得可能按借款合同
股东借款不可扣除借款本身不征税通常不适用需要真实还款安排

转让定价是这张表的底层约束。关联方之间的服务费、授权费、利息和货物价格,必须符合独立交易原则(arm’s length principle);否则税务机关可以调整利润,补税加罚息。对两人或一人公司,最容易出问题的是「把利润全部做成服务费」或「把授权费定得过高/过低」:前者可能被认定为利润转移,后者可能被认定为人为压低纳税地利润。合理的做法是保留功能、风险、资产的分析,准备可比价格或成本加成依据,并让合同、发票、实际交付和定价逻辑一致。

资金回流的财务目标不是「税最低」,而是「净得最高且可持续」。一条路径税率低,但预提高、费用多、文档成本高,最终净得可能低于另一条税率稍高但通道顺的路径。比较时要把公司层税、预提税、通道费用、汇差、申报成本和滞纳风险放在同一张表里,而不是只比 headline rate。税费预留的做法见 税费预提 ,现金流分类见 3.2 现金流量表 。

把预提与走廊成本写成代码

下面的 withholding.ts 把四个动作固化:withhold 按国内税率或协定税率计算毛额、扣缴和净额,compareTreaty 比较适用协定前后的节省,grossUp 从目标净额倒推合同毛额,crossBorderReceipt 把预提、中间行费用和汇差叠加成最终到账。所有金额用整数美分,所有税率用基点。

// file: src/tax/withholding.ts
// tier: L1
// verify: node --test test/tax/withholding.test.ts

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

function isCents(value: number): boolean {
  return Number.isInteger(value) && value >= 0;
}

function isValidRate(rate: Bps): boolean {
  return Number.isInteger(rate) && rate >= 0 && rate < 10000;
}

export type WithholdingInput = {
  gross: Cents;
  domesticRate: Bps;
  treatyRate: Bps;
  treatyApplied: boolean;
};

export type WithholdingResult = {
  gross: Cents;
  rate: Bps;
  withheld: Cents;
  net: Cents;
  effectiveRateBps: Bps;
};

/**
 * Withholds tax from a cross-border payment. The domestic rate applies when no
 * treaty relief is claimed; the treaty rate applies only when the payer has the
 * supporting documentation. Both rates are inputs, not assumptions.
 */
export function withhold(input: WithholdingInput): Result<WithholdingResult> {
  if (!isCents(input.gross)) {
    return fail("gross payment must be a non-negative integer in cents");
  }
  if (!isValidRate(input.domesticRate) || !isValidRate(input.treatyRate)) {
    return fail("withholding rates must be integers between 0 and 9999 bps");
  }
  const rate = input.treatyApplied ? input.treatyRate : input.domesticRate;
  const withheld = applyRate(input.gross, rate);
  return ok({
    gross: input.gross,
    rate,
    withheld,
    net: sub(input.gross, withheld),
    effectiveRateBps: divide(withheld, input.gross),
  });
}

export type TreatyComparison = {
  withoutTreaty: WithholdingResult;
  withTreaty: WithholdingResult;
  saving: Cents;
  savingBps: Bps;
};

/**
 * Compares the same gross payment with and without treaty relief. A negative
 * saving means the treaty rate is higher than the domestic rate, which is a
 * signal to check the applicable article rather than to force the treaty rate.
 */
export function compareTreaty(
  gross: Cents,
  domesticRate: Bps,
  treatyRate: Bps,
): Result<TreatyComparison> {
  const without = withhold({
    gross,
    domesticRate,
    treatyRate,
    treatyApplied: false,
  });
  if (!without.ok) {
    return without;
  }
  const withTreaty = withhold({
    gross,
    domesticRate,
    treatyRate,
    treatyApplied: true,
  });
  if (!withTreaty.ok) {
    return withTreaty;
  }
  const saving = sub(without.value.withheld, withTreaty.value.withheld);
  return ok({
    withoutTreaty: without.value,
    withTreaty: withTreaty.value,
    saving,
    savingBps: divide(saving, without.value.withheld),
  });
}

/**
 * Works back from a target net receipt to the gross invoice amount. This is
 * what a contractor needs to quote when the client withholds tax at source.
 */
export function grossUp(targetNet: Cents, rate: Bps): Result<Cents> {
  if (!isCents(targetNet)) {
    return fail("target net must be a non-negative integer in cents");
  }
  if (!isValidRate(rate)) {
    return fail("withholding rate must be an integer between 0 and 9999 bps");
  }
  return ok(Math.round((targetNet * 10000) / (10000 - rate)));
}

export type CorridorInput = {
  gross: Cents;
  withholdingRate: Bps;
  intermediaryFeeBps: Bps;
  fxSpreadBps: Bps;
};

export type CorridorResult = {
  gross: Cents;
  withheld: Cents;
  fee: Cents;
  fxCost: Cents;
  received: Cents;
  netRateBps: Bps;
};

/**
 * The full path from a gross invoice to cash in the home account: withholding
 * at source, intermediary fees and the foreign-exchange spread. Each cost is
 * applied to the gross amount so the four deductions stay comparable.
 */
export function crossBorderReceipt(input: CorridorInput): Result<CorridorResult> {
  if (!isCents(input.gross)) {
    return fail("gross payment must be a non-negative integer in cents");
  }
  const rates = [input.withholdingRate, input.intermediaryFeeBps, input.fxSpreadBps];
  if (rates.some((rate) => !Number.isInteger(rate) || rate < 0 || rate > 10000)) {
    return fail("corridor rates must be integers between 0 and 10000 bps");
  }
  const withheld = applyRate(input.gross, input.withholdingRate);
  const fee = applyRate(input.gross, input.intermediaryFeeBps);
  const fxCost = applyRate(input.gross, input.fxSpreadBps);
  const received = sub(sub(sub(input.gross, withheld), fee), fxCost);
  return ok({
    gross: input.gross,
    withheld,
    fee,
    fxCost,
    received,
    netRateBps: divide(received, input.gross),
  });
}

代码里有三个边界。第一,withhold 把国内税率和协定税率都作为输入,treatyApplied 只决定用哪一个,不判断协定是否真的适用;协定资格、受益所有人和所得类型必须由人核对。第二,compareTreaty 的 saving 允许为负,因为协定税率不总是低于国内税率,负值提醒你回头检查条款而不是强行套用。第三,crossBorderReceipt 把三项成本都按毛额计算,这是为了横向可比;真实通道可能按净额收费、设最低收费或分段计价,使用时要把基数改成合同约定的口径。

把 $1,000、30%、15% 代入:withhold 得到 $300 和 $150 的扣缴、$700 和 $850 的净额;compareTreaty 得到 $150 的节省;grossUp 把目标净额 $850 在 15% 下倒推回 $1,000;crossBorderReceipt 在 2% 费用和 1% 汇差下得到 $820 和 67% 到 82% 的净得率。每个数字都能在测试里复算。

小结

  • 预提税按毛额在付款时扣缴,直接减少到账现金;$1,000 在 30% 下到账 $700,在 15% 下到账 $850,两个税率都是教学示例。
  • 协定税率不是自动适用;税收居民身份、受益所有人、所得类型和有效文档都会改变结果。
  • 跨境收款走廊会在预提之后再扣中间行费用和汇差;$1,000 在 15% 预提、2% 费用、1% 汇差下到账 $820。
  • gross-up 把目标净额倒推成合同毛额;目标 $850 在 15% 下需要 $1,000,在 30% 下约需 $1,214。
  • 资金回流的名义决定税负:工资、服务费、特许权使用费、股息、利息和股东借款各有不同的扣除、预提和文档要求。
  • 转让定价要求关联交易符合独立交易原则;利润转移式的定价可能被调整并补税。
  • LedgerLite 的所得税 $11,941、Nord 的 $8,580、Tidecast 的 $29,081 是所得税口径的示例;预提税是在这些税之后、资金跨境时可能再出现的一层。

阅读导航:上一节:9.2 所得税与常设机构 · 下一节:10.1 五个核心指标与口径 。

常见错误与边界

  1. 按合同毛额做预算。 预提税和通道费用会在到账前扣掉;$1,000 的合同可能只有 $670 到 $820 可用。
  2. 把预提税当最终税负。 预提可能只是预缴,年度申报可以抵免;也可能因为协定或定性变成最终税负,不能一概而论。
  3. 假设协定自动适用。 没有有效文档、居民身份或受益所有人证明,付款方通常按国内法较高税率扣缴。
  4. 忽略所得类型。 服务费、特许权使用费、股息和利息的协定待遇不同;合同名称与实际交付不一致会带来定性风险。
  5. 只看预提税率不看通道成本。 中间行费用、汇差和最低收费会改变净得率,百分比不能直接相加。
  6. 用资金回流方式做「税负优化」。 没有真实业务实质的 service fee 或 royalty 可能被转让定价调整,补税和罚息会吃掉节省。
  7. 把 Tidecast 的 $138,171 当税后现金。 那是扣除退款、平台分成和支付费后的渠道净得,所得税和跨境预提还在后面。
  8. 把教学税率当现行税率。 30% 和 15% 只是示例,实际税率、协定待遇和文档要求必须按当期法规核对。

税负讲完,下一章回到经营指标:把收入、毛利、现金流、留存和集中度放在同一张仪表盘上,判断生意是在变好还是只是变大。

继续阅读

探索更多技术文章

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

全部文章 返回首页

「books」更多文章

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