应用内购买(IAP)接入全解:从下单到服务端校验
在移动端卖数字内容——会员、AI 额度、去广告、解锁高级功能——你几乎只有一条路:应用内购买(IAP)。iOS 上苹果强制要求数字商品走 StoreKit,Android 上 Google Play 要求走 Play Billing。这是 变现地图 里那条「体验最顺、抽成最重、你管得最少」的路。
新手常以为 IAP 的难点是「弹出付款框让用户付钱」。恰恰相反——那部分最简单,平台都替你做好了。真正的难点在付款之后:你怎么确认这笔钱真的收到了?怎么知道用户下个月续没续费?怎么防止有人伪造一张「我已付费」的收据来白嫖?这篇就沿着一笔订阅的完整生命周期,把这些讲透。
本文基于 Apple、Google 公开的开发者文档与通行实践,用通用占位 ID 举例,不涉及任何特定产品的内部实现。
一笔购买的完整链路
先建立整体心智模型。一笔 IAP 购买,涉及四个角色:App 客户端、应用商店、你的服务端、用户的账号。它们的协作大致如下:
这张图里藏着 IAP 接入的第一原则:永远不要信任客户端。客户端很容易被改、被 hook、被伪造收据。发放权益的决定,必须建立在服务端独立验证过的凭据之上。
第 1 步:在后台配置商品
一切从在应用商店后台「登记你要卖什么」开始。
Apple(App Store Connect) 把内购分四类:消耗型(用一次就没,如一次性 AI 额度包)、非消耗型(永久解锁,如买断功能)、自动续期订阅、非续期订阅。卖会员用的是自动续期订阅。配置时要点:
- 同一个会员的「月付 / 年付」要放进同一个订阅群组(Subscription Group),这样用户能在档位间升降级,且同组只能同时订一个。
- 每个商品有一个全局唯一、创建后不可改的 Product ID,建议用反向域名 + 时长命名,如
com.example.app.pro.monthly。客户端和服务端都用这个 ID 对齐。 - 订阅周期最短 7 天;要配价格、可用区域、各语言的显示名与描述。
Google(Play Console) 概念类似:在「订阅」里建商品和基础方案(base plan),可叠加优惠(offer)。商品有 Product ID + base plan ID。
⚠️ 一个常被忽略的硬前置:你必须先在后台签好「付费应用协议」、填完税务与银行信息,并让它变成 Active 状态,否则客户端根本拉不到商品列表,连沙盒都测不了。很多人卡在「拉不到商品」,十有八九是协议没生效。
第 2 步:客户端发起购买
商品配好后,客户端的活其实不多——平台 SDK 把重活都包了。
iOS 用 StoreKit 2(Swift,现代且简洁):用 Product ID 拉取 Product → 调 product.purchase() 弹出系统付款框 → 用户用 Face ID / 密码确认 → 拿到一个 Transaction。这个 transaction 本身是经过 Apple 签名的 JWS(JSON Web Signature),可以直接交给服务端验。
Android 用 Play Billing Library:queryProductDetails → launchBillingFlow 拉起付款 → 在回调里拿到 Purchase,其中含一个 purchase token,这是后续服务端查询的钥匙。
客户端这一层有两条铁律:
- 必须提供「恢复购买(Restore Purchases)」入口。用户换设备、重装后要能找回已购订阅——这不仅是体验,也是苹果审核的强制项,缺了会被拒。
- 付款成功后要「完成交易」(iOS 的
transaction.finish()/ Android 的acknowledgePurchase/consumeAsync)。没确认完成的交易,平台会反复提醒甚至自动退款。但——只在服务端确认发放权益之后,再 finish,顺序别反。
第 3 步(真正的核心):服务端校验
这是 IAP 接入里最该花力气、也最容易被新手跳过的一步。客户端拿到的凭据,要由服务端独立验证后才作数。现代做法是验证签名(JWS),而不是早期那种把整张收据回传给苹果接口的 verifyReceipt(已不推荐)。
校验要确认四件事:
- 来源真:凭据确实由 Apple / Google 用其私钥签发——用平台公布的证书链/公钥验签,验签不过直接拒。
- 对得上:凭据里的 Product ID、Bundle ID 跟你预期的一致,不是别的 App、别的商品的凭据。
- 没过期:对订阅,看
expiresDate是否还在有效期内。 - 没用过:同一张凭据不能被重复兑换——服务端要按交易号(transaction id)做幂等,记下已处理的交易。
Apple 提供 App Store Server API(服务端用 ES256 签 JWT 调用,查交易/订阅状态)和官方的 App Store Server Library(Swift / Java / Node / Python,帮你做 JWS 验签,省去手撸证书链)。Google 侧用 Google Play Developer API 的 purchases.subscriptionsv2.get 凭 purchase token 查订阅真实状态。以服务端从平台查到的状态为权威,客户端传来的一切只是「线索」。
第 4 步:订阅是活的——续费通知
一次性购买验完就结束了,但订阅是会随时间变化的状态机:会续费、会到期、会被用户取消、会进入宽限期、会退款。如果你只在「用户点购买」那一刻更新会员状态,那么下个月自动续费成功、或用户取消后到期,你的系统根本不知道——会员该续的没续、该停的没停。
解法是服务端到服务端的通知:在后台配一个你的 HTTPS 回调地址,平台在订阅状态变化时主动 POST 通知你。
- Apple:App Store Server Notifications V2,推送
signedPayload(同样是 JWS,要验签),事件类型包括SUBSCRIBED、DID_RENEW、DID_FAIL_TO_RENEW、EXPIRED、REFUND等。注意配 Production 和 Sandbox 两个地址。 - Google:Real-time Developer Notifications (RTDN),通过 Pub/Sub 推送续费、取消、暂停等事件。
订阅的生命周期画出来是这样:
实践要点:把订阅通知当作会员状态的「权威更新源」,购买时刻的发放只是开端;通知到达时,以平台 API 复核一次再落库,避免被伪造的通知欺骗。
自动续订订阅的常见坑
把公开文档里反复出现、也最容易栽的坑列在一起:
- 沙盒续费快得离谱:沙盒环境里「一个月」可能压缩成几分钟,方便测续费,别误以为线上也这么快。
- 首个订阅必须随 App 版本一起提审:单独提交订阅商品不会被审核——它得绑在一个 App 版本里首次提交。这是高频卡点。
- 审核必备项:购买页要有可用的使用条款(EULA)+ 隐私政策链接、购买前清晰披露价格/时长/续期规则、提供恢复购买。缺任一项基本会被退回。
- 退款会发生:用户能向平台申请退款,你会收到
REFUND通知——记得据此收回权益,否则退了款还在享受会员。 - 跨端账号合并:用户可能 iOS 上买、又在网页上买。把订阅状态收敛到一个统一的权益账户(以你自己的用户 ID 为主键),才不会出现「同一人重复付费」或「换端找不到会员」。
- 降抽成别忘申请:符合条件务必申请苹果 Small Business Program / 谷歌的优惠,抽成 30%→15% 直接让你利润翻倍,而且新开发者几乎都符合资格。
小结
IAP 的体验由平台包办,但正确性由你的服务端负责。记住三条:
- 不信客户端——权益发放只认服务端验过签的凭据。
- 订阅是状态机——用服务端通知 + API 复核,让会员状态跟上续费/取消/退款。
- 幂等与统一账户——同一交易只兑一次,多端订阅收敛到一个权益账户。
把这三条做扎实,IAP 就从「能跑」变成「可信」。下一站,如果你的产品更适合免费 + 广告,去看 应用内广告(IAA)接入与变现;如果你在网页/桌面端收款,去看 三方支付接入。