《独立开发者的商业指南·资本与退出》7.1 资产收购与股权收购

资产收购买的是挑选过的资产,股权收购买的是整个主体。本节用 Tidecast 的四份 IP 转让与 LedgerLite 的 260 份订阅合同,算清两种结构各自要重新签署多少合同、留下多少负债,以及迁移成本如何从 248,708 与 768,768 美元的报价里扣掉。

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

同一笔交易,报价可以一样,卖方最终拿到的钱却可能差几万美元。差别不在谈判技巧,而在结构:买方是买走挑选过的资产,还是买走持有资产的公司。资产收购让买方把一个新主体摆到合同前面,股权收购让旧主体继续存在,只是换了股东。前者要重新签署合同、重新取得同意、重新设置账号;后者把这些动作省掉,但把主体的历史和负债一起交给买方。

这一节把两种结构拆成四个可比较的维度:承接什么、留下什么、税基怎么走、第三方同意要多少。然后用 Tidecast 的资产型交易和 LedgerLite 的股权型交易各算一遍迁移成本,最后把成本放回报价里,看卖方净得差多少。所有单位成本、同意费用和托管之外的数字都是教学假设,不构成法律或税务意见。

两种结构买的是不同的东西

维度资产收购股权收购
买方买到挑选过的资产与合同持有资产的主体(股份或成员权益)
合同需要重新签署或取得转让同意留在原主体内,通常只需控制权变更同意
负债卖方保留未明确承接的负债买方承接主体全部历史负债
税基买方通常可按资产重新计价通常延续原主体的历史税基
第三方同意反转让条款、平台条款、授权协议控制权变更条款、贷款与房东同意
客户关系要逐户通知与换约合同主体不变,客户通常无感
主体与历史新设或另行指定的主体原主体、原账套、原记录
适合场景资产边界清楚、合同少、负债可切分合同多、客户关系连续、主体干净

这张表里最容易被低估的是「合同」和「负债」两行。资产收购看起来干净,因为买方可以只挑自己想要的东西;但合同不会自动跟着资产走,尤其是带反转让条款的订阅协议、平台账号和授权协议。股权收购看起来省事,因为合同和客户都留在原主体里;但买方买到的是一段完整历史,包括没写进报价的应付、递延收入、税务申报和潜在索赔。

税基是第二个容易被忽略的维度。资产收购中,买方通常可以把价格分配到各类资产,并按分配后的价值重新计价;股权收购中,主体没变,历史税基通常延续。对卖方来说,两种结构的税负可能不同;对买方来说,税基影响未来的摊销和折旧。本节只把它当作价格谈判的一个变量,不展开税法条文,也不给税务建议。

第三方同意则是交割条件,不只是成本。资产收购要逐份确认合同能不能转、平台账号能不能换主体、授权方是否同意;股权收购要确认控制权变更是否触发同意权、贷款是否提前到期、房东是否接受新股东。一个关键同意拿不到,交易可能从「贵一点」变成「做不成」。

Tidecast:四份 IP 转让和一份音乐授权

Tidecast 的资产是代码、美术、音乐和商标组成的 IP 组合,加上 Steam 发行账号。它的收入来自买断制销售,没有 260 份客户合同,所以资产收购的主要工作量不在客户换约,而在 IP 转让和平台账号迁移。音乐是买断授权,不得转授权,这意味着音乐授权方必须同意转让;如果对方不同意,音乐要么从交易里剔除,要么重新谈一份授权。

按教学假设,代码、美术、商标三份转让各 1,500 美元,音乐转让 1,500 美元,音乐授权方同意 5,000 美元,Steam 账号迁移 2,500 美元,新主体设立 1,000 美元。资产收购的迁移成本是 14,500 美元,占 248,708 美元报价的 5.8%。

迁移项数量单价小计
代码、美术、商标转让31,5004,500
音乐授权转让11,5001,500
音乐授权方同意15,0005,000
Steam 账号迁移12,5002,500
新主体设立11,0001,000
合计14,500

如果改成股权收购,Tidecast 的 LLC 继续存在,四份 IP 转让和主体设立都不需要,只剩音乐授权方同意 5,000 美元和账号迁移 1,000 美元,合计 6,000 美元,占报价的 2.4%。两种结构相差 8,500 美元,约 3.4 个百分点。

这个差距不大,原因是 Tidecast 没有客户合同,资产边界也清楚。买方选择资产收购,往往不是为了省迁移成本,而是为了不继承 LLC 的历史负债和税务记录;卖方接受资产收购,往往是因为买方只想要游戏资产,不想接手主体。8,500 美元就是「干净壳」的价格。如果音乐授权根本不能转,差距就不再是钱,而是交易能不能成立。

LedgerLite:260 份订阅合同决定结构

LedgerLite 是另一个极端。它有 260 家付费客户、企业订阅协议、数据处理协议和新加坡主体。资产收购意味着买方用新主体重新签下这 260 份合同;其中 40 家是 enterprise 客户,合同里有控制权变更或转让同意条款。按教学假设,每份合同重新签署成本 120 美元,40 家 enterprise 客户每家额外 250 美元同意成本,账号迁移 8,000 美元,新主体设立 3,000 美元。

迁移项数量单价小计
标准订阅合同重签22012026,400
Enterprise 合同重签401204,800
Enterprise 转让同意4025010,000
支付、云、支持账号迁移18,0008,000
新主体设立13,0003,000
合计52,200

52,200 美元占 768,768 美元报价的 6.8%。更重的一项不在表里:如果买方不承接 22,000 美元的递延收入,这笔负债留在卖方主体里,卖方要么继续履约,要么退款。把迁移成本和留存负债一起扣掉,卖方净得是 768,768 − 52,200 − 22,000 = 694,568 美元。

股权收购则简单得多。260 份合同留在原主体里,不需要逐份重签;只有 40 家 enterprise 客户的控制权变更同意需要处理,按每家 250 美元算是 10,000 美元,账号迁移 2,000 美元,合计 12,000 美元,占报价的 1.6%。卖方净得 768,768 − 12,000 = 756,768 美元。两种结构相差 62,200 美元,约 8.1 个百分点。

这就是为什么 SaaS 交易更常见股权结构:合同数量越多,资产收购的迁移成本越像一笔按客户数计价的税。260 份合同乘以 120 美元只是纸面成本,真正的风险是重签过程中的流失、同意延迟和客户重新询价。股权收购把这些动作省掉,但买方会要求主体干净;如果账套、税务或合同有瑕疵,买方会把风险转成赔偿上限、托管或更低的报价。

把迁移成本算成价格

把两种结构放在同一张表里,差别就很清楚。公式只有一行:卖方净得 = 报价 − 迁移成本 − 卖方保留的负债。迁移成本 = 重签成本 + 同意成本 + 账号迁移 + 新主体设立。股权收购没有重签和新主体设立,但可能有控制权变更同意和承接的负债。

方案报价迁移成本留下负债卖方净得迁移占报价
Tidecast 资产收购248,70814,5000234,2085.8%
Tidecast 股权收购248,7086,0000242,7082.4%
LedgerLite 资产收购768,76852,20022,000694,5686.8%
LedgerLite 股权收购768,76812,0000756,7681.6%

这张表只算迁移成本和留存负债,没有算税。资产收购中,买方可能获得资产重新计价的好处,卖方可能面对不同的税务处理;股权收购中,历史税基延续,卖方通常按股权转让处理。这些差异会移动价格,但方向取决于主体所在地、资产构成和持有期限,不能用一个固定比例概括。本节把它们留作谈判变量,不给税务结论。

第三方同意也不能只用单价表示。Tidecast 的音乐授权如果不同意转让,交易可能要把音乐剔除;LedgerLite 的 40 家 enterprise 客户如果有 5 家拒绝同意,买方要么接受 35 家的合同基础,要么要求价格下调。同意成本是「每份 250 美元」,同意风险是「一份不批就可能改变交易边界」。前者可以算进表里,后者只能写成交割条件。

下面的 deal.ts 把两种结构写成同一组输入:合同组、账号迁移、主体设立、承接负债和留存负债。资产收购会对每个合同组计算重签成本,股权收购只对需要同意的合同组计算同意成本;股权收购不允许留存负债,也不允许新主体设立成本,因为这两个字段在概念上不属于股权结构。

// file: src/capital/deal.ts
// tier: L1
// verify: node --test test/capital/deal.test.ts

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

export type DealStructure = "asset" | "stock";

export const STRUCTURE_ORDER: DealStructure[] = ["asset", "stock"];

export type ContractGroup = {
  count: number;
  reSignCostCents: Cents;
  consentRequired: boolean;
  consentCostCents: Cents;
};

export type StructureInput = {
  structure: DealStructure;
  priceCents: Cents;
  contractGroups: ContractGroup[];
  accountTransferCostCents: Cents;
  entitySetupCostCents: Cents;
  assumedLiabilityCents: Cents;
  retainedLiabilityCents: Cents;
};

export type StructureCost = {
  structure: DealStructure;
  priceCents: Cents;
  contractCount: number;
  contractsToRepaper: number;
  reSignCents: Cents;
  consentCents: Cents;
  accountTransferCents: Cents;
  entitySetupCents: Cents;
  migrationCents: Cents;
  migrationBpsOfPrice: Bps;
  assumedLiabilityCents: Cents;
  retainedLiabilityCents: Cents;
  sellerNetCents: Cents;
};

export type StructureComparison = {
  cheaper: DealStructure | "equal";
  deltaMigrationCents: Cents;
  deltaSellerNetCents: Cents;
  deltaMigrationBpsOfPrice: Bps;
};

function isStructure(value: string): value is DealStructure {
  return (STRUCTURE_ORDER as string[]).includes(value);
}

/**
 * An asset deal re-papers every contract because the contracting party is a
 * new entity. A stock deal keeps the contracts inside the old entity and only
 * pays for change-of-control consents. Retained liabilities stay with the
 * seller in an asset deal; a stock deal has no retained liabilities because the
 * entity itself changes hands. This is a teaching model, not legal or tax
 * advice.
 */
export function assessStructure(
  input: StructureInput,
): Result<StructureCost> {
  if (!isStructure(input.structure)) {
    return fail("structure must be asset or stock");
  }
  if (!Number.isInteger(input.priceCents) || input.priceCents <= 0) {
    return fail("price must be a positive integer amount in cents");
  }
  if (
    !Number.isInteger(input.accountTransferCostCents) ||
    input.accountTransferCostCents < 0
  ) {
    return fail("account transfer cost must be a non-negative integer amount");
  }
  if (
    !Number.isInteger(input.entitySetupCostCents) ||
    input.entitySetupCostCents < 0
  ) {
    return fail("entity setup cost must be a non-negative integer amount");
  }
  if (
    !Number.isInteger(input.assumedLiabilityCents) ||
    input.assumedLiabilityCents < 0
  ) {
    return fail("assumed liability must be a non-negative integer amount");
  }
  if (
    !Number.isInteger(input.retainedLiabilityCents) ||
    input.retainedLiabilityCents < 0
  ) {
    return fail("retained liability must be a non-negative integer amount");
  }
  if (input.structure === "stock" && input.retainedLiabilityCents > 0) {
    return fail("a stock deal does not leave liabilities with the seller");
  }
  if (input.structure === "stock" && input.entitySetupCostCents > 0) {
    return fail("a stock deal reuses the target entity; no setup cost");
  }

  let contractCount = 0;
  let reSignCents = 0;
  let consentCents = 0;

  for (const group of input.contractGroups) {
    if (!Number.isInteger(group.count) || group.count < 0) {
      return fail("contract count must be a non-negative integer");
    }
    if (
      !Number.isInteger(group.reSignCostCents) ||
      group.reSignCostCents < 0
    ) {
      return fail("re-sign cost must be a non-negative integer amount");
    }
    if (
      !Number.isInteger(group.consentCostCents) ||
      group.consentCostCents < 0
    ) {
      return fail("consent cost must be a non-negative integer amount");
    }
    contractCount += group.count;
    if (input.structure === "asset") {
      reSignCents += group.count * group.reSignCostCents;
    }
    if (group.consentRequired) {
      consentCents += group.count * group.consentCostCents;
    }
  }

  const contractsToRepaper =
    input.structure === "asset" ? contractCount : 0;
  const entitySetupCents =
    input.structure === "asset" ? input.entitySetupCostCents : 0;
  const migrationCents =
    reSignCents +
    consentCents +
    input.accountTransferCostCents +
    entitySetupCents;
  const retainedLiabilityCents =
    input.structure === "asset" ? input.retainedLiabilityCents : 0;
  const sellerNetCents =
    input.priceCents - migrationCents - retainedLiabilityCents;

  return ok({
    structure: input.structure,
    priceCents: input.priceCents,
    contractCount,
    contractsToRepaper,
    reSignCents,
    consentCents,
    accountTransferCents: input.accountTransferCostCents,
    entitySetupCents,
    migrationCents,
    migrationBpsOfPrice: divide(migrationCents, input.priceCents),
    assumedLiabilityCents: input.assumedLiabilityCents,
    retainedLiabilityCents,
    sellerNetCents,
  });
}

/**
 * Compares the seller's net proceeds under both structures at the same price.
 * The comparison is deliberately narrow: it counts migration cost and retained
 * liabilities, not the buyer's post-closing risk, tax basis or financing.
 */
export function compareStructures(
  asset: StructureCost,
  stock: StructureCost,
): Result<StructureComparison> {
  if (asset.structure !== "asset" || stock.structure !== "stock") {
    return fail("expected one asset deal and one stock deal");
  }
  if (asset.contractCount !== stock.contractCount) {
    return fail("both structures must describe the same contract base");
  }
  if (asset.priceCents !== stock.priceCents) {
    return fail("both structures must use the same headline price");
  }

  const deltaMigrationCents = asset.migrationCents - stock.migrationCents;
  const deltaSellerNetCents = asset.sellerNetCents - stock.sellerNetCents;
  const cheaper =
    deltaSellerNetCents < 0
      ? "stock"
      : deltaSellerNetCents > 0
        ? "asset"
        : "equal";

  return ok({
    cheaper,
    deltaMigrationCents,
    deltaSellerNetCents,
    deltaMigrationBpsOfPrice: divide(
      Math.abs(deltaMigrationCents),
      asset.priceCents,
    ),
  });
}

把 Tidecast 和 LedgerLite 代进去,assessStructure 会返回各自的迁移成本和卖方净得,compareStructures 会告诉你哪种结构更便宜。Tidecast 的资产收购迁移成本 14,500 美元、净得 234,208 美元;股权收购 6,000 美元、净得 242,708 美元,股权结构便宜 8,500 美元。LedgerLite 的资产收购迁移成本 52,200 美元、留存负债 22,000 美元、净得 694,568 美元;股权收购 12,000 美元、净得 756,768 美元,股权结构便宜 62,200 美元。

代码里的 cheaper 只回答「在这组假设下哪种结构让卖方净得更高」,不回答「哪种结构更好」。股权收购可能让卖方多拿 62,200 美元,但买方会要求主体干净、历史负债可控、控制权变更同意可拿到;如果这些条件不成立,买方会把价格压低,或者坚持资产收购。结构选择不是挑一个数字最大的方案,而是挑一个双方都能完成交割的方案。

小结

  • 资产收购买的是挑选过的资产,股权收购买的是持有资产的主体;前者要重签合同,后者承接历史。
  • Tidecast 的资产收购要处理四份 IP 转让、一份音乐授权同意和 Steam 账号迁移,迁移成本 14,500 美元,占 248,708 美元的 5.8%。
  • Tidecast 的股权收购只剩音乐同意和账号迁移,成本 6,000 美元,占 2.4%;两种结构相差 8,500 美元。
  • LedgerLite 的资产收购要重签 260 份订阅合同,迁移成本 52,200 美元,加上 22,000 美元留存递延收入,卖方净得 694,568 美元。
  • LedgerLite 的股权收购只处理 40 份控制权变更同意,成本 12,000 美元,卖方净得 756,768 美元;两种结构相差 62,200 美元。
  • 合同数量越多,资产收购越像按客户数计价的税;SaaS 交易更常见股权结构,但前提是主体干净。
  • 税基和第三方同意会移动价格,也会改变交易能否完成;它们不是表格里的单价,而是谈判变量和交割条件。
  • 单位成本、同意费用和迁移比例都是教学假设;结构选择要看双方能否完成交割,不是看哪个净得数字最大。

阅读导航:上一节:6.3 技术与知识产权尽调 · 下一节:7.2 现金、票据与 earn-out 。

常见错误与边界

  1. 把资产收购当成「只买好东西」。 资产收购可以选择资产,但不能自动带走合同;反转让条款、平台条款和授权协议都要单独处理。
  2. 把股权收购当成「什么都省了」。 股权收购省掉重签,但买方承接主体全部历史负债;账套、税务、合同瑕疵都会回到价格里。
  3. 只算单价,不算同意风险。 每份合同 120 美元是纸面成本;一份关键同意拿不到,可能让交易边界改变。
  4. 忽略递延收入。 资产收购中买方不承接的递延收入会留在卖方主体里,卖方要么履约,要么退款,不能当成零。
  5. 把税基差异说成固定折扣。 资产重新计价和历史税基延续的方向取决于主体、资产和持有期限,不能用统一比例概括。
  6. 用报价比较结构。 报价相同不代表净得相同;迁移成本、留存负债和同意成本都要从报价里扣掉。
  7. 忽略股权结构的前置条件。 股权收购更便宜,但前提是主体干净、控制权变更同意可拿到、历史负债可控。
  8. 把迁移成本当成交割后的事。 迁移成本影响卖方净得,也影响买方报价;谈判时就要放进价格,而不是交割后再追加。
  9. 把「合同多」当成资产收购的优点。 合同越多,资产收购的重签成本越高;260 份合同乘以 120 美元只是起点,流失和延迟才是大头。
  10. 把模型当法律或税务意见。 模型只算迁移成本和留存负债;真实交易需要合同、税务和主体层面的专业核查。

本节的边界是「资产收购与股权收购的结构差异」:只讨论承接什么、留下什么、税基方向、第三方同意和迁移成本,不展开合同条款、税务申报或交割清单。所有单位成本、同意费用、迁移比例和留存负债都是教学假设,不构成法律或税务意见。交割清单见 8.1 交割清单与资金流 ,合同承继见 9.1 雇佣与外包合同的承继 。

继续阅读

探索更多技术文章

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

全部文章 返回首页

「books」更多文章

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