站内有一篇 独立游戏全球财务与多币种结构 ,那是资金架构层面的顶层设计——境外主体、多币种账户、利润回流路径怎么搭。本节不碰那套架构,也不重做卷四 12.1 平台分成与结算 讲过的通用结算算术(扣点、净额、账期怎么算)。本节只做一件出海特有的事:把海外几个平台的分成与结算条款摆在一起横向对比,回答「同一条收入结构,在哪个平台条款下、我能拿回多少、什么时候拿」。卷四那节是「怎么算」,本节是「各平台条款差在哪」。
分成与结算,先分清三件事
谈「平台拿走多少」之前,必须把三件事分开,否则很容易被一个「分成 30%」的说法骗到:
| 事项 | 决定什么 | 本节的字段 |
|---|---|---|
| 分成比例 | 毛收入里平台抽走千分之几 | iaaSharePermille / iapSharePermille |
| 内购可用性 | 这个平台能不能做内购 | supportsIap |
| 结算条款 | 多少净额才打款、多久到账、走什么通道 | minPayoutFen / payoutCycleDays / method |
前两件决定账面净额,第三件决定现金何时到。出海最容易踩的坑,是只盯第一件——看到某网页平台「只抽 30%」就以为最划算,却没注意它根本不支持内购,那一整条内购流水直接归零。
条款矩阵(D 层)
下面这段是 D 层配置,把八个平台各记一行:广告 / 内购抽成、是否支持内购、结算门槛、账期、付款方式。数值是按各平台公开机制整理的建模输入,标为公开可观察(观察时点 2026-10),不是任何平台后台结算单实测。
{
"version": 1,
"game": "merge-island",
"observedAt": "2026-10",
"note": "海外各平台分成与结算条款。revenueMix 是喂给所有平台的同一条满潜力收入结构(便于横向对比):grossIaaFen=广告毛收入(分/月)、grossIapFen=内购毛流水(分/月)。iaaSharePermille/iapSharePermille=平台(含聚合网络/支付通道)抽成千分比;supportsIap=是否支持内购(0 则内购净额记 0);minPayoutFen=结算门槛(分);payoutCycleDays=结算周期(天);method=付款方式。全部为「公开可观察机制」口径的建模假设,观察时点 2026-10;不是任何平台后台结算单实测,真实分成比例、真实结算门槛与账期需真实业务环境,命令行不可复现(X)。全整数,无浮点。",
"revenueMix": { "grossIaaFen": 5000000, "grossIapFen": 2000000 },
"platforms": [
{ "id": "poki", "name": "Poki", "kind": "web", "iaaSharePermille": 300, "iapSharePermille": 1000, "supportsIap": 0, "minPayoutFen": 100000, "payoutCycleDays": 30, "method": "PayPal / 银行转账" },
{ "id": "crazygames", "name": "CrazyGames", "kind": "web", "iaaSharePermille": 500, "iapSharePermille": 1000, "supportsIap": 0, "minPayoutFen": 100000, "payoutCycleDays": 30, "method": "PayPal / 银行转账" },
{ "id": "gamedist", "name": "GameDistribution", "kind": "web", "iaaSharePermille": 300, "iapSharePermille": 1000, "supportsIap": 0, "minPayoutFen": 100000, "payoutCycleDays": 30, "method": "PayPal / 银行转账" },
{ "id": "yt_playables", "name": "YouTube Playables", "kind": "web", "iaaSharePermille": 450, "iapSharePermille": 1000, "supportsIap": 0, "minPayoutFen": 100000, "payoutCycleDays": 30, "method": "AdSense 结算" },
{ "id": "fb_instant", "name": "Facebook Instant Games", "kind": "superapp", "iaaSharePermille": 300, "iapSharePermille": 300, "supportsIap": 1, "minPayoutFen": 100000, "payoutCycleDays": 30, "method": "Meta 结算" },
{ "id": "appstore", "name": "App Store", "kind": "store", "iaaSharePermille": 300, "iapSharePermille": 300, "supportsIap": 1, "minPayoutFen": 100000, "payoutCycleDays": 30, "method": "银行 / 税务表单" },
{ "id": "gplay", "name": "Google Play", "kind": "store", "iaaSharePermille": 300, "iapSharePermille": 300, "supportsIap": 1, "minPayoutFen": 100000, "payoutCycleDays": 30, "method": "银行 / 税务表单" },
{ "id": "steam", "name": "Steam(PC 对照)", "kind": "store", "iaaSharePermille": 300, "iapSharePermille": 300, "supportsIap": 1, "minPayoutFen": 100000, "payoutCycleDays": 30, "method": "银行 / 税务表单" }
]
}
从表里先读出一个结构:四个网页平台(Poki / CrazyGames / GameDistribution / YouTube Playables)的 supportsIap 全是 0。它们没有内购通道,收入只能来自广告。而超级 App 与商店平台(Facebook、App Store、Google Play、Steam)的 supportsIap 是 1,广告 + 内购两条腿都能走。这个差异比分成比例的差异大得多,下一节的仿真会把它量化。
还要注意 kind 字段:web(网页门户)、superapp(超级 App 内嵌)、store(应用商店)。它和 1.1 的三层结构一一对应,方便把「流量结构」与「条款结构」两张表叠起来看——一个平台值不值得接,取决于这两张表叠加后的结果,而不是任何单一字段。
两个口径:条款抽成 vs 净额实现率
把同一份收入结构喂给所有平台,必须同时看两个数,否则会得出相反的结论:
takeRatePermille(条款抽成):只在「这个平台能变现的毛收入」上算抽成,纯粹反映条款——谁抽多少。它回答「这个平台的条款友不友好」。realizedSharePermille(净额实现率):净额相对满潜力收入(广告 + 内购全部加起来)的比例,还额外反映内购能不能变现。它回答「这个平台最终让我拿回多少」。
一个条款友好但不支持内购的平台,takeRate 可能很低,realizedShare 却高不了——因为一整块内购收入根本没机会变成钱。这就是为什么必须两个口径一起看。
S 层:条款对比仿真
下面这段是 S 层仿真脚本 sim/g01_share_terms.py。它读 config/g01_share_terms.json,把同一份满潜力收入喂给每个平台,算扣点净额、条款抽成、实现率、丢掉的 IAP 与是否达结算门槛,产出 JSON + CSV + SVG。
#!/usr/bin/env python3
# file: sim/g01_share_terms.py
# tier: S
# verify: /opt/homebrew/bin/python3 sim/g01_share_terms.py --config config/ --out out/
"""海外各平台分成与结算条款对比:读 config/g01_share_terms.json,把同一条满潜力收入
(广告 + 内购)喂给每个平台,分别算「条款抽成」与「实现率」两个口径。全整数,无浮点。
netIaaFen = grossIaa * (1000 - iaaSharePermille) // 1000
netIapFen = grossIap * (1000 - iapSharePermille) // 1000 (supportsIap=0 记 0)
netFen = netIaaFen + netIapFen
monetizableGrossFen = grossIaa + (supportsIap ? grossIap : 0)
takeRatePermille = (monetizableGross - netFen) * 1000 // monetizableGross (纯条款抽成)
lostIapFen = supportsIap ? 0 : grossIap (无内购平台丢掉的流水)
realizedSharePermille = netFen * 1000 // (grossIaa + grossIap) (相对满潜力的实现率)
两个口径必须分开看:takeRate 只反映**条款**(谁抽多少),realizedShare 还反映**内购能不能
变现**。条款友好但不支持内购的平台,实现率可能反而更低。口径:抽成比例、结算门槛与账期是
「公开可观察机制」口径的建模假设,观察时点 2026-10;不是任何平台后台结算单实测(X)。
产出 out/g01_share_terms.json + .csv + .svg。
"""
from __future__ import annotations
import argparse
import csv
import json
import os
import xml.etree.ElementTree as ET
def evaluate(p: dict, mix: dict) -> dict:
gross_iaa, gross_iap = mix["grossIaaFen"], mix["grossIapFen"]
potential = gross_iaa + gross_iap
net_iaa = gross_iaa * (1000 - p["iaaSharePermille"]) // 1000
net_iap = gross_iap * (1000 - p["iapSharePermille"]) // 1000 if p["supportsIap"] else 0
net = net_iaa + net_iap
monetizable = gross_iaa + (gross_iap if p["supportsIap"] else 0)
return dict(p, netIaaFen=net_iaa, netIapFen=net_iap, netFen=net,
monetizableGrossFen=monetizable,
takeRatePermille=(monetizable - net) * 1000 // monetizable if monetizable else 0,
lostIapFen=0 if p["supportsIap"] else gross_iap,
realizedSharePermille=net * 1000 // potential if potential else 0,
meetsThreshold=1 if net >= p["minPayoutFen"] else 0)
def draw_svg(path: str, rows: list[dict]) -> int:
"""每个平台一条横条:净额实现率(netFen / 满潜力,千分比,越高越好)。"""
W, H, PAD, ROW = 780, 70 + 40 * len(rows), 210, 40
svg = ET.Element("svg", {"xmlns": "http://www.w3.org/2000/svg", "width": str(W),
"height": str(H), "viewBox": f"0 0 {W} {H}"})
ET.SubElement(svg, "rect", {"width": str(W), "height": str(H), "fill": "#ffffff"})
ET.SubElement(svg, "text", {"x": "16", "y": "28", "font-size": "15",
"fill": "#111827"}).text = "各平台净额实现率(千分比,满潜力=1000)"
span, hi = W - PAD - 160, max([r["realizedSharePermille"] for r in rows] + [1000])
for i, r in enumerate(rows):
y = 52 + i * ROW
ET.SubElement(svg, "text", {"x": "16", "y": str(y + 18), "font-size": "12",
"fill": "#374151"}).text = r["name"]
w = span * r["realizedSharePermille"] // hi
color = "#16a34a" if r["supportsIap"] else "#f59e0b"
ET.SubElement(svg, "rect", {"x": str(PAD), "y": str(y + 4), "width": str(w),
"height": "18", "rx": "3", "fill": color})
ET.SubElement(svg, "text", {"x": str(PAD + w + 6), "y": str(y + 18), "font-size": "11",
"fill": "#6b7280"}).text = (
f'{r["realizedSharePermille"]}‰ · 抽成 {r["takeRatePermille"]}‰ · 净额 {r["netFen"]}分')
os.makedirs(os.path.dirname(path), exist_ok=True)
ET.ElementTree(svg).write(path, encoding="utf-8", xml_declaration=True)
return len(ET.tostring(svg))
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--config", default="config/")
ap.add_argument("--out", default="out/")
args = ap.parse_args()
with open(os.path.join(args.config, "g01_share_terms.json"), encoding="utf-8") as f:
cfg = json.load(f)
mix = cfg["revenueMix"]
rows = [evaluate(p, mix) for p in cfg["platforms"]]
best_take = min(rows, key=lambda r: r["takeRatePermille"])
best_realized = max(rows, key=lambda r: r["realizedSharePermille"])
digest = ",".join(f'{r["id"]}:{r["netFen"]}:{r["takeRatePermille"]}'
f':{r["realizedSharePermille"]}:{r["meetsThreshold"]}' for r in rows)
os.makedirs(args.out, exist_ok=True)
svg_bytes = draw_svg(os.path.join(args.out, "g01_share_terms.svg"), rows)
report = {"version": cfg["version"], "game": cfg["game"], "observedAt": cfg["observedAt"],
"revenueMix": mix, "potentialFen": mix["grossIaaFen"] + mix["grossIapFen"],
"platformCount": len(rows), "platforms": rows, "digest": digest,
"bestTakeRate": best_take["id"], "bestRealized": best_realized["id"],
"noIapPlatforms": [r["id"] for r in rows if not r["supportsIap"]],
"svg": os.path.join(args.out, "g01_share_terms.svg"), "svgBytes": svg_bytes}
with open(os.path.join(args.out, "g01_share_terms.json"), "w", encoding="utf-8") as f:
json.dump(report, f, ensure_ascii=False, indent=2)
cols = ("id", "name", "kind", "iaaSharePermille", "iapSharePermille", "supportsIap",
"netIaaFen", "netIapFen", "netFen", "monetizableGrossFen", "takeRatePermille",
"lostIapFen", "realizedSharePermille", "minPayoutFen", "payoutCycleDays",
"meetsThreshold", "method")
with open(os.path.join(args.out, "g01_share_terms.csv"), "w", encoding="utf-8", newline="") as f:
w = csv.writer(f)
w.writerow(cols)
for r in rows:
w.writerow([r[k] for k in cols])
print(json.dumps({
"potentialFen": report["potentialFen"], "bestTakeRate": best_take["id"],
"bestRealized": best_realized["id"],
"platforms": [{"id": r["id"], "netFen": r["netFen"], "takeRatePermille": r["takeRatePermille"],
"lostIapFen": r["lostIapFen"],
"realizedSharePermille": r["realizedSharePermille"],
"meetsThreshold": r["meetsThreshold"]} for r in rows],
"digest": digest}, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
在本机跑 /opt/homebrew/bin/python3 sim/g01_share_terms.py --config config/ --out out/,把同一条满潜力收入(广告 5,000,000 分 + 内购 2,000,000 分 = 7,000,000 分)喂给八个平台,结果是(同机相对量级,绝对数值不可移植):
| 平台 | 类型 | 条款抽成‰ | 丢掉的 IAP(分) | 净额(分) | 实现率‰ |
|---|---|---|---|---|---|
| Poki | web | 300 | 2000000 | 3500000 | 500 |
| CrazyGames | web | 500 | 2000000 | 2500000 | 357 |
| GameDistribution | web | 300 | 2000000 | 3500000 | 500 |
| YouTube Playables | web | 450 | 2000000 | 2750000 | 392 |
| Facebook Instant Games | superapp | 300 | 0 | 4900000 | 700 |
| App Store | store | 300 | 0 | 4900000 | 700 |
| Google Play | store | 300 | 0 | 4900000 | 700 |
| Steam(PC 对照) | store | 300 | 0 | 4900000 | 700 |
条款抽成最低的是 Poki(300‰),净额实现率最高的却是 Facebook / App Store / Google Play / Steam(700‰)。两个口径给出了不同的赢家,这正是本节要教的核心判断。
顺带看结算门槛:这份配置里每个平台的 minPayoutFen 都是 100000 分(1000 元),而所有平台的净额都远超它,所以 meetsThreshold 全是 1——本期都能结算。真正会卡住你的是门槛与净额量级不匹配的小平台:如果一个平台的月净额只有几百元、门槛却要 1000 元,那这笔钱会一直挂在账上不结算,账期字段再短也没用。所以结算门槛必须和你的收入量级一起看,单看门槛数值没有意义。
无内购平台:条款友好不等于拿到多
把这张表读透,会看到一条清晰的分界:
- 网页平台(
kind: web)四个全是无内购:它们的条款抽成从 300‰ 到 500‰ 不等,但因为supportsIap = 0,每个都丢掉了整整 2,000,000 分的内购流水,实现率被压到 357‰~500‰。Poki 的条款(300‰)其实和 Facebook 一样友好,但实现率只有 500‰——差的那 200‰,全是「内购没法做」。 - 超级 App 与商店平台:广告 + 内购都能走,条款抽成 300‰,实现率 700‰。它们拿到了满潜力里七成,另外三成是平台按条款抽走的。
这条分界直接决定了平台组合策略:如果《合成小岛》的收入里内购占比越高,网页平台就越吃亏;如果游戏是纯 IAA(只有广告),那网页平台的条款优势才真正体现出来。第 4 章会专门讲「无内购平台的纯 IAA 结构」,本节只需记住——评估一个平台,先看它支不支持内购,再看它抽多少。
X 层:真实条款不可复现
边界必须划清。上面算的是条款模型:给定抽成、内购可用性与结算条款,净额与实现率是多少。下面这些属于 X 层,需真实业务环境,命令行不可复现:
- 真实的抽成比例与结算门槛(各平台协议里写的是区间与条件,且会改;实际到账比例以签约协议为准);
- 真实的账期与到账日(受财务流程、最低打款额、税务表单齐备度共同影响);
- 真实的内购可用性(某些平台对开发者资质、地区、游戏类型有额外限制)。
工程上正确的做法是:把可复现的条款模型与不可复现的真实协议分开——模型回答「如果条款是这样,净额是多少」,真实协议用来在签约后反标定 config 参数。真实结算单的逐笔核对,是第 11 章跨境结算与资金的主题。
自检表
| 检查项 | 合格标准 |
|---|---|
| 三件事分清 | 分成比例 / 内购可用性 / 结算条款分开记录 |
| 两口径齐算 | 条款抽成与净额实现率都要有,不混为一谈 |
| 内购缺口显式 | 无内购平台丢掉的流水单独列出 |
| 门槛判定 | 每个平台给出是否达结算门槛 |
| 账期留痕 | 结算周期与付款方式随条款一起交代 |
| 边界清晰 | 真实抽成与账期标 X,不写成「实测」 |
条款对比最危险的错觉,是拿一个「分成低」的数字下结论。分成低只是条款友好;能不能真正拿到,还取决于这个平台能不能做内购、结算门槛高不高、账期拖多久。下一节把平台分层(1.1)与条款(1.2)合成一个可执行的决策:在有限的人力预算下,到底该先接哪几个平台。
阅读导航:上一节:1.1 平台分层与流量结构 · 下一节:1.3 平台选择与组合策略 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。