《独立开发者的商业指南·资本与退出》8.1 交割清单与资金流

交割不是签字那一刻,而是六类清单全部闭合、资金按顺序到账的过程。本节用一笔 300,000 美元的交易拆出 70/30 现金与 earn-out、10% 托管、12 个月放款条件和延迟 30 天的 1,183.56 美元利息与 12,000 美元现金占用,并给出阻断项与托管放款的判断口径。

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

交割(closing)常被写成“签字当天”。签字只是把合同生效,真正决定卖方什么时候拿到钱、拿到多少的,是交割清单是否闭合、资金按什么顺序走。一笔 300,000 美元的交易,合同上的价格是 300,000,交割日到账可能只有 180,000;剩下的 120,000 要等托管释放和 earn-out 条件满足。把这两个数字混为一谈,是交割阶段最常见的预算错误。

这一节把交割拆成两个可以分别检查的部分。第一部分是清单:公司决议、合同签署、付款凭证、资产交付、第三方同意、托管安排,六类各证明一件事,缺一类就可能阻断交割。第二部分是资金流:先付款还是先交付、托管扣多少、什么时候放款。最后用一笔 300,000 美元的交易画表,并算清“延迟 30 天”的利息与现金占用。所有费率与天数都是教学假设。

六类交割清单

清单不是“把文件收齐”,而是“每一类都有人签字、有证据、有兜底”。六类各回答一个不同的问题,不能用一份合同代替另一份。下表列出每类证明什么、缺失时会发生什么,以及它是否通常阻断交割。

类别证明什么缺失时会发生什么通常是否阻断
公司决议董事会或股东会批准交易,签字人有授权买方无法确认卖方有权出售,付款可能被银行或托管方拦下是
合同签署definitive agreement、转让书、披露函已生效资产范围、赔偿上限、托管条款没有法律载体是
付款凭证买方汇款、托管入账、卖方债务清偿已确认卖方以为钱到了,实际卡在合规审查或中间行是
资产交付代码、域名、数据、账号、合同已实际转移钱付了但资产还在卖方名下,交割后还要追是
第三方同意银行、平台、房东、关键客户同意变更合同或账号不能转,买方拿到的是空壳视条款
托管安排托管协议、放款条件、索赔流程已签署有争议时没有扣款机制,买方只能事后追偿视结构

公司决议最容易被一人公司忽略。一人公司常常认为“我自己签字就够了”,但买方、银行和托管方要看的是“这个签字人有权代表公司出售这项资产”。决议不需要很长,但必须写清交易、价格、授权签字人和生效条件。缺少决议,交割日可能变成“文件补签日”。

合同签署与资产交付是两件事。合同签了,只说明双方同意交易;资产交付,才说明买方真的拿到了东西。资产交付又分“法律交付”和“实际交付”:法律交付是转让书、域名注册人变更、账号所有权变更;实际交付是代码仓库权限、数据库导出、客户名单、密钥清单。两者都完成,才算交付。

付款凭证不是“买方说已经汇了”。跨境汇款可能经过中间行、合规审查、外汇额度,卖方账户看到“待入账”不等于可用余额。交割日要拿到的是银行回单、托管方入账确认和债务清偿证明,而不是一张截图。付款凭证缺失时,卖方不应交付关键资产,否则钱和资产可能同时不在自己手上。

第三方同意决定交易能不能“原样承继”。平台账号、云合同、支付通道、域名注册商、关键客户合同,很多都写着“未经同意不得转让”。这些条款不一定阻断整个交易,但会改变交割结构:要么在交割前拿到同意,要么把这项资产从交易里剔除,要么设置过渡期由卖方继续持有。清单上要写清每一项的处理方式,而不是笼统写“已处理”。

托管安排是最后一道现金流阀门。它不产生交割,但决定交割后发生争议时钱在哪里。托管比例、期限、放款条件、索赔流程要单独列项,不能只写在主合同的一句话里。下一节会展开托管如何放款。

资金流的顺序:先付款还是先交付

“先付款还是先交付”没有统一答案,只有风险分配。先付款,买方承担卖方收钱不交付的风险;先交付,卖方承担买方收货不付款的风险。交易双方通常用三种结构之一来分配这个风险。

结构顺序谁承担主要风险适用场景额外成本
先款后交买方先汇款,卖方确认到账后交付买方资产可快速验证、卖方信用高买方尽调成本
先交后款卖方先交付,买方验收后付款卖方买方信用高、资产可回退卖方催收与回退成本
同步交割双方把资金与资产交给托管方,条件满足后同时释放双方分摊跨境、资产复杂、金额较大托管费与时间

同步交割是大多数 100,000 美元以上交易的默认结构,但不是“没有风险”,而是“把风险换成流程”。买方把现金存入托管账户,卖方把转让文件、域名转移授权、代码仓库所有权交给托管方;托管方确认条件满足后,一边放款、一边释放资产。任何一方不配合,另一方可以在托管协议下索赔,而不是直接去法院。

对独立开发者,最重要的不是选哪种结构,而是不要在“钱没到”时交出“拿不回来”的资产。代码可以备份、域名可以转回、客户名单可以复制,但平台账号、支付通道、数据库主密钥一旦交出去,回退成本可能高于交易本身。资金流表要标出每一项资产的“可回退性”,再决定交付顺序。

300,000 美元交易的资金流表

设一笔教学假设的资产收购:总价 300,000 美元,交割日支付 70% 现金,30% 作为 12 个月 earn-out,托管为总价的 10%,从现金部分扣留。现金部分 210,000 美元,托管 30,000 美元,卖方交割日实收 180,000 美元;12 个月后托管释放 30,000 美元,earn-out 在条件满足时支付最多 90,000 美元。

时点付款方收款方金额触发条件
交割日买方卖方180,000清单阻断项清零、资产交付确认
交割日买方托管账户30,000托管协议生效
交割日 + 12 个月托管账户卖方30,000无未决赔偿,营运资金调整结清
交割日 + 12 个月买方卖方0–90,000ARR 不低于基准的 95%,按比例支付
合计300,000

这张表说明三件事。第一,交割日现金流不是合同价,而是合同价减去托管和 earn-out;对这笔交易,交割日实收 180,000 美元,占价格的 60%。第二,托管不是“罚款”,而是“先扣后放”:30,000 美元在 12 个月内可能因为赔偿、营运资金调整或未披露负债被部分扣减。第三,earn-out 是或有对价,不是应收款;如果条件不满足,卖方拿不到这 90,000 美元,但交易在交割日已经完成。

LedgerLite 的实际锚是 768,768 美元,结构同样是 70% 现金加 30% earn-out、10% 托管。现金部分 538,138 美元,托管 76,877 美元,交割日实收 461,261 美元,占价格的 60.0%。比例与 300,000 美元的示例相同,金额放大了 2.56 倍。把比例记住,比记住单笔金额更有用。

营运资金调整是资金流表里最容易被漏掉的一行。交易常约定“交割日营运资金达到目标值”,实际值低于目标时从价款里下调。LedgerLite 的目标营运资金 15,000 美元、实际 12,000 美元,下调 3,000 美元;这 3,000 美元不是罚款,而是“买方接手时少拿到的现金”。它通常从交割日付款或托管里扣,不改变 headline price,但改变卖方实际到手。

托管如何放款

托管(escrow)的核心不是“把钱扣住”,而是“把放款条件写成可验证的事件”。常见条件有三类:时间条件、无索赔条件、调整条件。时间条件是“交割后 12 个月释放”;无索赔条件是“期间没有买方提出赔偿请求”;调整条件是“营运资金、应收、债务按约定结算完毕”。

放款情形托管余额释放金额扣留金额说明
无索赔,12 个月到期30,00030,0000标准情形
买方提出 8,000 美元赔偿30,00022,0008,000索赔在托管内解决
营运资金下调 3,00030,00027,0003,000调整优先于释放
索赔 8,000 加调整 3,00030,00019,00011,000两项可叠加,以余额为限

托管的关键数字是“比例 × 期限 × 索赔上限”。比例决定扣多少,期限决定扣多久,索赔上限决定最坏情况下卖方还能拿回多少。10% 托管、12 个月、赔偿上限等于托管金额,是常见组合;如果买方要求 20% 托管、24 个月,卖方的交割日现金会更少,资金占用时间会更长。

托管还会产生一笔“看不见的成本”:被扣住的钱不能用于经营,也不能用于再投资。按教学假设 8% 的年资金成本,30,000 美元托管 12 个月的机会成本是 2,400 美元;如果托管比例提高到 20%,60,000 美元的机会成本是 4,800 美元。谈判时不能只看“托管会不会被扣”,还要看“被扣住的这段时间值多少钱”。

交割延迟 30 天的利息与现金占用

交割延迟不是“晚几天签字”,而是“钱晚几天到、经营成本照付”。延迟成本分两块:第一块是延迟到账的资金成本,按交割日实收金额 × 年资金成本 × 延迟天数 ÷ 365;第二块是现金占用,即延迟期间卖方仍要支付的运营成本,按月度运营成本 × 延迟天数 ÷ 30。

项目基数费率或天数金额
延迟到账的利息180,0008% × 30/3651,183.56
现金占用12,000/月30/3012,000.00
合计13,183.56
占交割日实收比例180,0007.3%

30 天延迟的成本是 13,183.56 美元,相当于交割日实收 180,000 美元的 7.3%。这个数字比很多人预期的高,因为主要成本不是利息 1,183.56 美元,而是现金占用 12,000 美元。对一人公司,运营成本可能更低;对有外包和工资的团队,现金占用会更高。延迟 30 天的真实价格,要用自己的月度运营成本替换 12,000 美元再算。

利息的基数也要选对。如果延迟的是全部 300,000 美元,利息是 1,972.60 美元;如果延迟的是交割日实收 180,000 美元,利息是 1,183.56 美元;如果只是托管 30,000 美元晚释放 30 天,利息是 197.26 美元。三个数字对应三种延迟,谈判时要写清“哪一笔钱延迟了多久”,而不是笼统说“交易晚了”。

延迟还会改变交割清单的状态。原本“已完成”的第三方同意可能过期,原本“已确认”的付款凭证可能失效,原本“已交付”的账号可能在等待期间被修改密码。延迟 30 天不是把同样的清单往后挪 30 天,而是要在新的交割日重新确认阻断项。清单上的日期、签字和凭证都有有效期,这一点常被忽略。

写成代码:closing.ts

下面的 closing.ts 把清单和资金流放在同一个函数里。closingChecklist 统计清单是否覆盖六类、完成率、阻断项和缺失类别;同时按 cashAtClosingBps 与 escrowBps 算出交割日实收和托管金额,earn-out 由总价减现金部分推导。延迟成本拆成 delayInterestCents 与 cashOccupancyCents,避免把利息和运营现金混为一谈。

// file: src/capital/closing.ts
// tier: L1
// verify: node --test test/capital/closing.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 ChecklistCategory =
  | "corporate-resolution"
  | "contract-signature"
  | "payment-proof"
  | "asset-delivery"
  | "third-party-consent"
  | "escrow-arrangement";

export const CHECKLIST_CATEGORIES: ChecklistCategory[] = [
  "corporate-resolution",
  "contract-signature",
  "payment-proof",
  "asset-delivery",
  "third-party-consent",
  "escrow-arrangement",
];

export type ChecklistItem = {
  category: ChecklistCategory;
  label: string;
  done: boolean;
  blocking: boolean;
};

export type ClosingInput = {
  priceCents: Cents;
  cashAtClosingBps: Bps;
  escrowBps: Bps;
  escrowMonths: number;
  delayDays: number;
  annualCapitalCostBps: Bps;
  monthlyOperatingCents: Cents;
  items: ChecklistItem[];
};

export type CategoryProgress = {
  total: number;
  done: number;
  pending: number;
};

export type ClosingOutcome = {
  itemCount: number;
  doneCount: number;
  pendingCount: number;
  completionBps: Bps;
  blockingPending: number;
  missingCategories: ChecklistCategory[];
  categoryProgress: Record<ChecklistCategory, CategoryProgress>;
  readyToClose: boolean;
  cashPortionCents: Cents;
  earnOutCents: Cents;
  escrowCents: Cents;
  sellerCashAtClosingCents: Cents;
  deferredCents: Cents;
  closingCashShareBps: Bps;
  delayInterestCents: Cents;
  cashOccupancyCents: Cents;
  delayCostCents: Cents;
  escrowReleaseMonth: number;
};

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;
}

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 emptyProgress(): Record<ChecklistCategory, CategoryProgress> {
  return {
    "corporate-resolution": { total: 0, done: 0, pending: 0 },
    "contract-signature": { total: 0, done: 0, pending: 0 },
    "payment-proof": { total: 0, done: 0, pending: 0 },
    "asset-delivery": { total: 0, done: 0, pending: 0 },
    "third-party-consent": { total: 0, done: 0, pending: 0 },
    "escrow-arrangement": { total: 0, done: 0, pending: 0 },
  };
}

/**
 * Closing has two independent questions. The checklist answers "can the deal
 * legally close": all six categories must be represented, and no blocking item
 * may remain open. The funds flow answers "how much cash arrives on the day":
 * the escrow and the earn-out are withheld from the headline price, so the
 * seller's closing cash is smaller than the number in the agreement.
 */
export function closingChecklist(input: ClosingInput): Result<ClosingOutcome> {
  const priceError = checkAmount(input.priceCents, "price");
  if (priceError) {
    return fail(priceError);
  }
  if (input.priceCents === 0) {
    return fail("price must be positive");
  }
  for (const [value, label] of [
    [input.cashAtClosingBps, "cash at closing"],
    [input.escrowBps, "escrow"],
    [input.annualCapitalCostBps, "annual capital cost"],
  ] as Array<[Bps, string]>) {
    const error = checkBps(value, label);
    if (error) {
      return fail(error);
    }
  }
  if (input.escrowBps > input.cashAtClosingBps) {
    return fail("escrow cannot exceed the cash portion paid at closing");
  }
  if (!Number.isInteger(input.escrowMonths) || input.escrowMonths < 0) {
    return fail("escrow months must be a non-negative integer");
  }
  if (
    !Number.isInteger(input.delayDays) ||
    input.delayDays < 0 ||
    input.delayDays > 365
  ) {
    return fail("delay days must be an integer between 0 and 365");
  }
  const operatingError = checkAmount(
    input.monthlyOperatingCents,
    "monthly operating cost",
  );
  if (operatingError) {
    return fail(operatingError);
  }
  if (input.items.length === 0) {
    return fail("checklist must contain at least one item");
  }

  const categoryProgress = emptyProgress();
  let doneCount = 0;
  let blockingPending = 0;
  for (const item of input.items) {
    if (!CHECKLIST_CATEGORIES.includes(item.category)) {
      return fail(`unknown checklist category: ${item.category}`);
    }
    const progress = categoryProgress[item.category];
    progress.total += 1;
    if (item.done) {
      progress.done += 1;
      doneCount += 1;
    } else {
      progress.pending += 1;
      if (item.blocking) {
        blockingPending += 1;
      }
    }
  }

  const missingCategories = CHECKLIST_CATEGORIES.filter(
    (category) => categoryProgress[category].total === 0,
  );
  const itemCount = input.items.length;
  const pendingCount = itemCount - doneCount;

  const cashPortionCents = applyRate(input.priceCents, input.cashAtClosingBps);
  const earnOutCents = input.priceCents - cashPortionCents;
  const escrowCents = applyRate(input.priceCents, input.escrowBps);
  const sellerCashAtClosingCents = cashPortionCents - escrowCents;
  const deferredCents = input.priceCents - sellerCashAtClosingCents;
  const delayInterestCents = Math.round(
    (sellerCashAtClosingCents *
      input.annualCapitalCostBps *
      input.delayDays) /
      (10000 * 365),
  );
  const cashOccupancyCents = Math.round(
    (input.monthlyOperatingCents * input.delayDays) / 30,
  );

  return ok({
    itemCount,
    doneCount,
    pendingCount,
    completionBps: divide(doneCount, itemCount),
    blockingPending,
    missingCategories,
    categoryProgress,
    readyToClose: blockingPending === 0 && missingCategories.length === 0,
    cashPortionCents,
    earnOutCents,
    escrowCents,
    sellerCashAtClosingCents,
    deferredCents,
    closingCashShareBps: divide(sellerCashAtClosingCents, input.priceCents),
    delayInterestCents,
    cashOccupancyCents,
    delayCostCents: delayInterestCents + cashOccupancyCents,
    escrowReleaseMonth: input.escrowMonths,
  });
}

代码里有四个刻意的选择。第一,托管从现金部分扣,不从总价另加;escrowBps > cashAtClosingBps 会直接报错,因为那意味着托管超过了交割日现金。第二,earn-out 由“总价减现金部分”推导,保证 70/30、80/20 等结构不会出现第三块未定义的钱。第三,readyToClose 只看阻断项和六类是否齐全,不看非阻断项;非阻断项可以交割后补,阻断项不能。第四,延迟利息按 365 天、现金占用按 30 天,两个分母不同,因为利息是年化资金成本,占用是月度运营节奏。

把 300,000 美元交易代入:价格 30,000,000 美分、现金 7,000 基点、托管 1,000 基点、托管 12 个月、延迟 30 天、年资金成本 800 基点、月运营 1,200,000 美分。返回现金部分 21,000,000 美分、earn-out 9,000,000 美分、托管 3,000,000 美分、交割日实收 18,000,000 美分、递延 12,000,000 美分、交割日实收占比 6,000 基点;延迟利息 118,356 美分、现金占用 1,200,000 美分、延迟成本 1,318,356 美分。

小结

  • 交割清单分六类:公司决议、合同签署、付款凭证、资产交付、第三方同意、托管安排;每类证明一件不同的事,不能用一份合同代替。
  • 阻断项决定能不能交割,非阻断项可以交割后补;清单要标出 blocking,而不是只标“完成/未完成”。
  • 300,000 美元、70% 现金、30% earn-out、10% 托管的交易,交割日实收 180,000 美元,占价格 60%;剩余 120,000 美元分 12 个月释放。
  • 托管放款看三个条件:时间、无索赔、调整;30,000 美元托管在 12 个月内可能因 8,000 美元索赔和 3,000 美元调整只释放 19,000 美元。
  • 延迟 30 天的利息是 1,183.56 美元,现金占用是 12,000 美元,合计 13,183.56 美元,占交割日实收 7.3%。
  • 利息基数要写清:全部 300,000 美元是 1,972.60 美元,交割日实收 180,000 美元是 1,183.56 美元,托管 30,000 美元是 197.26 美元。
  • 延迟会重置清单有效期;第三方同意、付款凭证、账号密码都可能在新交割日失效,不能把旧清单直接平移。

阅读导航:上一节:7.3 陈述保证、赔偿与托管 · 下一节:8.2 客户、合同与账号迁移 。

常见错误与边界

  1. 把合同价当交割日现金。 300,000 美元的交易,交割日实收只有 180,000 美元;托管和 earn-out 合计 120,000 美元要等 12 个月。
  2. 清单只列文件,不列证据。 “合同已签”不是证据,“签字页、生效条件、授权决议”才是;付款凭证要看银行回单和托管入账,不是截图。
  3. 把公司决议当形式。 一人公司也需要证明签字人有权出售资产;缺少决议,银行和托管方可能拒绝放款。
  4. 先交付不可回退的资产。 代码可以备份、域名可以转回,但平台账号、支付通道、主密钥交出去后回退成本很高;资金未到账前不要交。
  5. 忽略第三方同意。 平台账号、云合同、关键客户合同可能禁止转让;没有同意,买方拿到的是空壳。
  6. 托管比例只看“会不会被扣”。 10% 托管 12 个月的机会成本是 2,400 美元;20% 托管 24 个月的机会成本是 4,800 美元,谈判时要一起算。
  7. 延迟成本只算利息。 30 天延迟的主要成本是 12,000 美元现金占用,不是 1,183.56 美元利息;用月度运营成本替换后再算。
  8. 利息基数含糊。 全部价款、交割日实收、托管余额三个基数对应 1,972.60、1,183.56、197.26 美元,谈判要写清哪一笔延迟。
  9. 把营运资金调整当罚款。 目标 15,000 美元、实际 12,000 美元,下调 3,000 美元是“少拿到的现金”,不是违约罚金。
  10. 延迟后直接复用旧清单。 第三方同意会过期、付款凭证会失效、账号密码会被改;新交割日要重新确认阻断项。

本节的边界是“单一买方、固定价格、70/30 现金与 earn-out、10% 托管”的静态模型。它假设托管按总价扣、earn-out 按年支付、延迟只影响现金流,不含跨境预提税、外汇管制、买方融资失败、托管争议诉讼和 earn-out 会计确认。它足够回答“交割日能拿多少、延迟 30 天值多少钱”,但不足以回答“买方违约时能不能强制执行”。后者要把托管协议、赔偿上限和争议解决放在一起看,见 7.3 陈述保证、赔偿与托管 ;交割后的客户迁移见 8.2 客户、合同与账号迁移 。本节不构成法律意见,具体交割文件请由律师审阅。

继续阅读

探索更多技术文章

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

全部文章 返回首页

「books」更多文章

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