很多 Mac 开发者因为沙盒限制,选择不在 Mac App Store 上架、直接卖给用户。随之而来的不是省事,而是三件事:自己收钱、自己管授权、自己当商户主体去处理各国的销售税和退款。这篇以真实 Mac App(Arborist)的落地过程为例,讲清怎么用 Stripe 的 Managed Payments 把「商户主体」这口锅甩出去,再让 iCloud 当身份把授权变成「跟着账号走」。
不少 Mac App 是被「逼」出 Mac App Store 的。
沙盒限制对 iOS 已经够严格,对 Mac App 更甚。很多「Mac 味十足」的功能——在任意路径执行命令、读写用户指定的目录——开着沙盒根本做不到。一旦关掉沙盒,就不能在 Mac App Store 上架。于是这些 App 走直接销售:自己收钱、自己处理授权。
于是「开发者怎么做授权」成了 Mac 开发者社区里每隔几个月就会重演一遍的问题。下面这套方案来自 RevenueCat 的工程博客:他们的工程师和好友 Jared 一起,把他的 Mac App Arborist(一个 git 仓库/工作树管理工具,定价 $39 一次性买断)从「我要不要自己写个授权服务器」一路捋到「几分钟跑通」,并把踩过的坑都写了出来。
离开 App Store 要自己扛三件事
直接销售的 Mac App,等于放弃了 App Store 替你做的三件事:
收钱:没有 App Store 内购管道,结账得自己接。 授权:没有 StoreKit,得自己判断「这个用户买过没有」。 税务:销售税、增值税(VAT)、商品及服务税(GST),得自己收、自己申报、自己缴。
前两件是技术活,第三件是合规活。对个人开发者来说,第三件往往才是真正的劝退点。
商户主体(merchant of record)是什么
「商户主体」(MoR)指在法律上为这笔销售负责的实体:它负责收取和申报销售税/VAT/GST,也处理退款。
很多第一次直接卖软件的开发者根本没听过这个词。它的意思是:如果你自己当商户主体,每一笔卖到不同国家的订单,税务责任都在你头上。对个人开发者来说,这复杂度高到可以直接劝退。
用 Stripe 的 Managed Payments,让 Stripe 当商户主体
好消息是:Stripe 提供一项叫 Managed Payments 的服务,Stripe 作为商户主体,逐笔帮你承担税务的收取和申报。你不用改自己的收款流程,只是 Stripe 会多收几个百分点——Arborist 的作者觉得这笔钱花得值:
「让 Stripe 当我的商户主体,等于把所有收税和申报的负担都交给它。我乐意给它几个点。」
顺便说一句,这套组合依托的是 RevenueCat 的网页账单(web-based billing):配置一个 Web Purchase Link,用户在浏览器里完成购买,购买记录挂到同一个用户档案上,App 里的 RevenueCat SDK 一刷新就能看到授权。RevenueCat 只是把 Stripe 接进来当支付通道,并把「用户有没有买」这个唯一事实来源托管起来。
授权不用写服务器:让 iCloud 当身份
Jared 最初纠结的点是授权服务器的复杂度:要不要自己发授权码?要不要存一堆激活码?要不要做「恢复购买」按钮?
他的解法是把身份交给 iCloud:用 CloudKit 的 userRecordID() 取到用户在本机 iCloud 账号下的唯一 ID,再把这个 ID 当作客户 ID。
于是授权天然「跟着账号走」:在一台 Mac 上买的授权,用户换一台登录同一 iCloud 账号的 Mac,授权自动生效。不需要邮件发送授权码,也没有「恢复购买」按钮,App 也不收集任何身份信息。代价是要求用户登录 iCloud——对一个面向资深用户的工具类 App 来说,这是合理的假设。
落地:六步配置 + App 里三段代码
配置部分(在 RevenueCat 和 Stripe 后台):
在 RevenueCat 建好授权(entitlement),App 靠它判断有没有买过。 在 Stripe 建商品,确认符合 Managed Payments 的准入条件。 把 Stripe 接为 RevenueCat 的网页支付渠道,勾选「可用时使用 Managed Payments」。 从 Stripe 导入商品到 RevenueCat。 建一个包含该商品的 offering,期限设为「永久」(等效一次性买断)。 为 offering 生成 Web Purchase Link,带上结账后跳回 App 的深链回调地址。
App 端其实只有三个改动点:购买入口(跳去浏览器)、从浏览器回来后的验权、以及启动时检查已有授权。
有一个细节很关键:**验权时必须用 fetchCurrent**,强制拉取最新数据。RevenueCat SDK 默认会走缓存——如果用户刚在浏览器里付完钱,App 用缓存判断就看不到新授权,体验会「卡一下」。
三个坑
别用 App 内 WKWebView 结账。Jared 试过在 App 里用 WKWebView 打开结账页,撞上一个已知的 WebKit bug——Apple Pay 在 WKWebView 里不可用。他选择跳出到系统浏览器,让用户能用 Apple Pay 而不是手输卡号。 结账 URL 要自己拼。RevenueCat SDK 面向 StoreKit 场景,不直接给你网页购买链接。Jared 的做法是硬编码结账地址( pay.rev.cat/{web_link_id}/{user_id}),生产/沙盒各一份。要求 iCloud 登录。这套方案的前提是用户登录了 iCloud;没登录的用户得先登录,或走网页版 iCloud 登录(作者说 1.0 之后再补)。
移动独立开发者怎么套
认清「离开商店 = 当商户主体」这口锅。直接销售(Mac 桌面、Web、官网订阅)时,税务责任在你;接一个 Managed Payments 这类商户主体服务,比自己去研究各国税制划算得多。 iOS App 别想着绕开内购。苹果对 iOS 的应用内购买限制很严,站外收钱的灰色地带风险高。这套方案适用的是 Mac 桌面(非沙盒合法分发)和 Web 端,不要拿去绕过 iOS 内购。 「身份即授权」是可复用的思路。不用自建授权服务器:用户在你信任的系统里已经有一个持久身份(iCloud/Google/自有账号),让它当授权载体,省掉激活码和恢复流程。 验权别信缓存。任何「刚付完钱要立刻生效」的场景,拉取用户最新状态都要显式刷新,而不是读本地缓存。
编译/解读自 RevenueCat Engineering《Sell a Mac App Outside the App Store》(作者 Jared Sorge 的 Arborist 落地过程)。本文保留其免商户主体的方案与踩坑,补充了移动端内购边界和面向出海开发者的应用说明;文中的 RevenueCat/Stripe 为作者自述流程,价格与服务条款以官方最新为准。
夜雨聆风