iOS 知识点 - IAP 是怎样的?

4 阅读7分钟

一、IAP 基本概念

  • 定义: In-App Purchase 是苹果提供的支付机制,允许用户在 App 内购买虚拟商品或订阅服务,所有数字内容和服务的交易必须通过 IAP 完成(苹果会抽成 15%~30%),否则会拒审。

  • 类型:

类型描述使用场景特点
Consumable(消耗型)用完即消失,可反复购买金币、体力、道具不可恢复,需自己记录消耗
Non-Consumable(非消耗型)永久可用,购买一次即可解锁关卡、去广告、付费功能可恢复,支持跨设备恢复
Auto-Renewable Subscription(自动续订订阅)周期性付费,自动续订VIP 会员、内容订阅苹果处理续订、退款、过期提醒
Non-Renewing Subscription(非续订订阅)周期性付费,但需手动续订课程、限时会员不自动续订,需要自己管理过期

消耗型商品必须在验证成功后调 finishTransaction,否则 Apple 会认为你还没发货,下次启动继续提醒。

  • 四个核心名词:

    • 商品(Product):
      • 在 App Store Connect 后台配置的可购买项目。
      • 每个商品都有唯一的 productIdentifier,App 需要用这个 ID 去 Apple 查询商品的实时价格和货币信息,返回类型是 SPProduct(StoreKit 91) 或 Product(StoreKit 2)。
    • 订单(Order):
      • 自己服务器创建的记录。
      • 在调用 Apple 支付之前创建,拿到 order_id,用于后期的对账(钱对应哪个商品、给哪个用户、是购买还是赠送等等)。
      • Apple 不知道这个东西的存在,业务层概念。
    • 交易(Transaction):
      • Apple 侧产生的记录。
      • 在 Apple 的支付弹窗上确认付款后,Apple 会生成一笔交易。
      • 交易有多种状态:
      purchasing(支付中) → purchased(已支付) → finished(已完成)
                        → failed(失败)
                        → deferred(等待家长审批)
                        → restored(恢复购买)
      
    • 收据(Receipt):
      • Apple 侧产生的付款凭证,证明用户确实付了钱。
      • 需要拿该凭证去服务器校验,服务器确认为真后才能发货,有两种格式:
        • StoreKit 1: 一个 Base64 编码的二进制文件(ASN.1),存在 App 沙盒里(Bundle.main.appStoreReceiptURL)。
        • StoreKit 2: 一个 JWS(JSON Web Signature)字符串,带有 Apple 的数字签名,更安全,不存本地,通过 Transaction API 获取。
  • 流程概述:

用户点击购买
  │
  ├─ ① App 用 productIdentifier 向 Apple 查询商品信息
  │     └─ Apple 返回 Product(价格、货币、描述)
  │
  ├─ ② App 向自己服务器创建订单,拿到 order_id
  │
  ├─ ③ 调用购买 API,Apple 弹出支付确认框
  │     └─ 用户输入密码 / Face ID / Touch ID
  │
  ├─ ④ Apple 返回交易结果(Transaction)
  │     ├─ purchased → 继续验证
  │     ├─ failed → 提示用户
  │     └─ deferred → 等待家长审批
  │
  ├─ ⑤ 拿 receipt/transaction 发给自己服务器验证
  │     └─ 服务器向 Apple 验证真伪(或本地验签 JWS)
  │     └─ 服务器确认后发货(加金币/开会员/解锁功能)
  │
  └─ ⑥ 调用 finishTransaction,告诉 Apple "我已发货"

二、StoreKit 1 与 StoreKit 2

维度StoreKit 1(已废弃,但仍可用)StoreKit 2(推荐)
语言Objective-C / Swift 均可Swift only
异步模式Delegate 回调async/await
最低版本iOS 3+iOS 15+
交易监听SKPaymentTransactionObserverTransaction.updates(AsyncSequence)
恢复购买手动调 restoreCompletedTransactions()自动可用,Transaction.currentEntitlements
收据验证/verifyReceipt(服务端,已废弃)JWS 本地验签 / App Store Server API
订阅状态自己解析 receipt 推算Product.SubscriptionInfo.status 直接获取
退款处理收不到通知Transaction.revocationDate 有值 = 已退款
家庭共享不支持内置支持

2.1 StoreKit 1 核心类

SKProductsRequest          → 请求商品信息
  └─ SKProductsRequestDelegate  → 回调商品列表
       └─ SKProduct            → 单个商品(价格、标题、描述)

SKPayment                  → 支付请求对象
SKPaymentQueue             → 交易队列(单例)
  └─ SKPaymentTransactionObserver  → 交易状态回调
       └─ SKPaymentTransaction     → 单笔交易(状态、receipt)

SKReceiptRefreshRequest    → 强制刷新本地收据
  • StoreKit 1 典型代码流程:
// 1. 查询商品
let request = SKProductsRequest(productIdentifiers: ["com.app.coin100"])
request.delegate = self
request.start()

// 2. 收到商品信息
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
    let product = response.products.first!
    // 展示价格:product.localizedTitle, product.price
}

// 3. 发起购买
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)

// 4. 监听交易状态
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
    for tx in transactions {
        switch tx.transactionState {
        case .purchased:
            // 验证收据 → 发货 → finish
            verifyAndDeliver(tx)
            queue.finishTransaction(tx)
        case .failed:
            queue.finishTransaction(tx)
        case .restored:
            queue.finishTransaction(tx)
        case .deferred, .purchasing:
        break
        }
    }
}

2.2 StoreKit 2 核心类

Product                         → 商品(静态方法 .products(for:) 查询)
Product.purchase()              → 发起购买,返回 PurchaseResult
Transaction                     → 交易(自带签名验证)
Transaction.updates             → AsyncSequence,监听新交易
Transaction.currentEntitlements → 当前有效权益(自动含恢复)
Transaction.finish()            → 确认已发货
Product.SubscriptionInfo        → 订阅状态(续订、过期、宽限期)
  • StoreKit 2 典型代码流程:
// 1. 查询商品
let products = try await Product.products(for: ["com.app.coin100"])
let product = products.first!

// 2. 发起购买
let result = try await product.purchase()

switch result {
case .success(let verification):
    // 3. 验证交易
    switch verification {
    case .verified(let transaction):
        // 发货
        await deliverContent(transaction)
        // 4. 告诉 Apple 已发货
        await transaction.finish()
    case .unverified(_, let error):
        // 签名验证失败,可能被篡改
        handleError(error)
    }
case .userCancelled:
    break
case .pending:
    // 等待家长审批 / 支付确认
    break
}

// 5. App 启动时监听未完成的交易
Task {
    for await result in Transaction.updates {
        if case .verified(let transaction) = result {
            await deliverContent(transaction)
            await transaction.finish()
        }
    }
}

三、购买流程

3.1 正常购买流程

    App                     Apple                   自己服务器
     │                        │                        │
     │──Product.products()──> │                        │
     │<──返回商品信息─────────  │                        │
     │                        │                        │
     │──创建订单────────────────────────────────────────>│
     │<──返回 order_id──────────────────────────────────│
     │                        │                        │
     │──product.purchase()──> │                        │
     │    (用户确认支付)        │                        │
     │<──Transaction───────── │                        │
     │                        │                        │
     │──发送 transaction/receipt 验证─────────────────> │
     │                        │          │──验证签名/调 Apple API──>│
     │                        │          │<──确认有效──────────────│
     │<──验证通过,已发货─────────────────────────────────│
     │                        │                        │
     │──transaction.finish()─>│                        │
     │                        │                        │

3.2 异常场景处理

场景现象处理方式
支付成功但 App 崩溃/网络断开没调 finish,下次启动 Apple 会重新推送交易App 启动时监听 Transaction.updates,重新验证并发货
用户取消支付result == .userCancelled不做任何处理
家庭共享/家长审批result == .pending提示用户等待,后续通过 Transaction.updates 接收结果
重复购买(消耗型)正常,消耗型可重复买每次都走完整验证流程
重复购买(非消耗型)Apple 提示"你已购买过"不会重复扣费,返回原始交易
退款Apple 后台处理服务端收到 S2S 通知 REFUND,或客户端检查 transaction.revocationDate
掉单(钱扣了但没收到交易)极少见,通常是网络问题服务端定期用 App Store Server API 查询用户交易历史

3.3 收据验证

客户端验证服务端验证(推荐)
安全性低,可被越狱设备绕过高,服务器可信环境
实现复杂度简单需要后端配合
适用场景个人开发者、低价值商品商业应用、涉及虚拟货币

四、服务端通知(Server-to-Server Notifications)

Apple 会主动推送事件到你配置的服务器 URL(在 App Store Connect 中设置)。

4.1 主要通知类型

通知类型含义
SUBSCRIBED用户首次订阅
DID_RENEW自动续订成功
EXPIRED订阅过期
DID_FAIL_TO_RENEW续订失败(信用卡过期等)
GRACE_PERIOD_EXPIRED宽限期结束
REFUND用户退款
REVOKE家庭共享撤销
CONSUMPTION_REQUESTApple 要求你提供消耗信息(用于退款裁决)
ONE_TIME_CHARGE一次性购买通知(2025 新增)

4.2 通知格式

{
  "signedPayload": "<JWS 字符串>"
}

解码 JWS 后得到:

{
  "notificationType": "DID_RENEW",
  "subtype": "AUTO_RENEW",
  "data": {
    "signedTransactionInfo": "<JWS>",
    "signedRenewalInfo": "<JWS>"
  }
}

五、App Store Connect 配置

商品要在 App Store Connect 对应 App 的 App 内购买项目中配置并审核。

  • 沙盒测试:
    • App Store Connect → 用户和访问 → 沙盒测试员 → 添加测试 Apple ID
    • 手机 App Store 登入沙盒账号,测试环境会使用沙盒账号模拟支付。

六、常见的架构策略

6.1 项目分层

View 层
  └─ 购买按钮、价格展示、订阅状态 UI

ViewModel / Manager 层
  └─ IAPManager(单例)
     ├─ 查询商品
     ├─ 发起购买
     ├─ 监听交易
     └─ 管理订阅状态

Service 层
  └─ IAPService(与服务器交互)
     ├─ 创建订单
     ├─ 验证收据
     └─ 查询购买记录

Apple 层
  └─ StoreKit 2 API

6.2 防掉单策略

                    正常流程                         异常恢复
                  ┌──────────┐                   ┌──────────────┐
用户购买  ───────> │ 服务器验证 │──> 发货            │ App 启动      │
                  │ + finish │                   │ Transaction   │
                  └──────────┘                   │ .updates 推送 │
                                                 │ 未 finish 的  │
                                                 │ 交易          │──> 重新验证发货
                                                 └──────────────┘

服务端兜底:
  - 定期调 App Store Server API 查用户交易历史
  - 对比自己数据库,找出 Apple 有但自己没发货的交易
  - 补发

七、一些 2025 年新动向

  • StoreKit 1 已被标记为 Deprecated(WWDC 2024),虽然仍能用,但新项目应该用 StoreKit 2
  • /verifyReceipt 接口已废弃,改用 App Store Server API
  • S2S Notifications V1 已废弃,改用 V2
  • iOS 18.4+ 新增:
    • appTransactionID:每个 Apple 账户唯一标识
    • Offer Codes 扩展到消耗型/非消耗型商品(之前只支持订阅)
    • Advanced Commerce API:支持复杂的订阅附加组件
  • Xcode 16.2+ 必须升级,否则 iOS 18.2 上可能出现购买失败