iOS 应用内购买:StoreKit 与收据验证

FreeGuideOnline 最新 2026-06-17

什么是应用内购买

应用内购买(In-App Purchase,简称 IAP)是 iOS 应用在 App Store 内销售数字内容与服务的核心机制。你可以通过它在应用中提供订阅、解锁高级功能、出售虚拟商品或移除广告。所有使用 IAP 的交易都必须通过 Apple 的 StoreKit 框架处理,并且需要配合服务端收据验证来保证安全性。本教程将带你从零开始,实现一个完整的应用内购买流程,并搭建可靠的收据验证。

环境准备与项目配置

在编写代码之前,你需要完成以下准备工作:

  1. 一个已加入 Apple Developer Program 的 Apple ID。
  2. 在 Xcode 中创建项目,并设置好 Bundle Identifier。
  3. App Store Connect 中为你的 App 创建对应的 App 记录。

在 App Store Connect 中创建购买项目

登录 App Store Connect,进入你的 App 页面,点击 功能 标签下的 App 内购买项目。点击加号,选择你要销售的类型:

  • 消耗型:每次购买都会消耗,例如游戏币、体力值。用户可以多次购买同一商品。
  • 非消耗型:一次性购买,永久拥有。例如移除广告、解锁完整版功能。
  • 自动续期订阅:按周期扣费并自动续订,例如月度会员。
  • 非续期订阅:有时限的服务访问,结束后不自动续费,需要用户再次购买。

填写参考名称(仅用于后台显示)、产品 ID(代码中使用的唯一标识)、定价等必要信息,并将状态设为“准备提交”。记下你设置的产品 ID,稍后会在代码中使用。

使用 StoreKit 实现购买流程

StoreKit 是处理 IAP 的原生框架。在 iOS 15 之后,Apple 引入了基于 Swift 并发的新 API(StoreKit 2),它更简洁安全。本教程将展示 StoreKit 2 的实现方式,如果你的 App 需要支持 iOS 14 及更低版本,再回退到原 StoreKit 框架。

导入 StoreKit 并请求产品信息

在你的购买管理类中,首先导入 StoreKit,然后根据你在 App Store Connect 中设置的产品 ID 数组,请求产品列表。

import StoreKit

class StoreManager: ObservableObject {
    static let productIDs = ["com.yourapp.premium", "com.yourapp.coins.100"]
    
    @Published var products: [Product] = []
    
    func requestProducts() async {
        do {
            let storeProducts = try await Product.products(for: Self.productIDs)
            await MainActor.run {
                self.products = storeProducts
            }
        } catch {
            print("加载产品失败: \(error.localizedDescription)")
        }
    }
}

Product.products(for:) 返回一个 Product 数组。每个 Product 都包含了在店内显示的本地化信息:价格、名称、描述等。你可以直接用它们构建购买界面。

构建购买按钮并处理交易

在 SwiftUI 视图中,列出产品并使用 AsyncButton 或手动触发购买。以下是购买动作的关键代码:

func purchase(_ product: Product) async {
    do {
        let result = try await product.purchase()
        
        switch result {
        case .success(let verification):
            // 验证交易并完成
            let transaction = try checkVerified(verification)
            await transaction.finish()
            // 这里可以解锁内容,并调用服务端收据验证
            await deliverProduct(for: transaction)
        case .userCancelled:
            break
        case .pending:
            // 例如家长审批等待中
            break
        @unknown default:
            break
        }
    } catch {
        print("购买失败: \(error.localizedDescription)")
    }
}

func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
    switch result {
    case .verified(let safe):
        return safe
    case .unverified:
        throw StoreError.failedVerification
    }
}

product.purchase() 会触发系统购买界面。成功后你会得到一个 VerificationResult<Transaction>,它由 StoreKit 自动生成并签名。务必调用 checkVerified 检查数据是否真实可信,然后调用 transaction.finish() 告诉 StoreKit 交易已完成。如果不调用 finish(),这笔交易会一直处于未完成状态,并在一段时间后可能导致重复弹出购买成功提示。

观察已完成的交易和订阅状态

为了处理在 App 外完成的交易(例如家长审批后),以及恢复之前的购买记录,你需要持续监听交易更新。在 iOS 15 以上,可以通过监听 Transaction.updates 异步序列来实现:

func observeTransactionUpdates() async {
    for await result in Transaction.updates {
        do {
            let transaction = try checkVerified(result)
            await deliverProduct(for: transaction)
            await transaction.finish()
        } catch {
            print("交易验证失败")
        }
    }
}

恢复购买可以直接使用 AppStore.sync() 来重新同步用户的所有交易。

收据验证的必要性与原理

StoreKit 的客户端验证只能确保数据在设备与 Apple 服务器之间未篡改,但攻击者可能绕过客户端验证,直接使用伪造的返回结果。因此,任何严肃的应用内购买实现都需要设置服务端收据验证(Server-Side Receipt Validation),在你的服务器上向 Apple 的验证接口发起请求,再由服务器返回结果给客户端。

收据(receipt)是描述购买数字商品的记录,包含交易 ID、产品 ID、购买时间、到期时间等信息。每次成功交易或订阅续期时,系统都会自动生成新的收据,并更新到设备上。

获取收据数据

在 StoreKit 2 中,所有历史交易都存储在 Transaction 对象中,你可以直接从 Transaction.currentEntitlements 获取当前有效的购买,而不需要再手动解析收据二进制文件。但若需完整的原始收据(例如某些订阅粒度较细的场景),仍可使用旧的 appStoreReceiptURL 方式获取收据。

服务端验证流程

  1. 客户端获取收据:调用 Transaction.currentEntitlements 提取有效授权,或者通过 AppStore.sync() 获取最新状态。
  2. 将收据发送到你的服务器:客户端把交易信息或原始收据数据发送给你自己的后端。
  3. 服务端向 Apple 验证:后端使用收到的凭据向 Apple 的验证 URL(https://buy.itunes.apple.com/verifyReceipt 或沙盒环境 https://sandbox.itunes.apple.com/verifyReceipt)发起 POST 请求,JSON 中包含 receipt-datapassword(用于订阅的共享密钥)。
  4. 解析响应:Apple 返回一个 JSON,其中 status 为 0 表示成功,并且包含 receipt 信息。你需要检查 receipt.in_app 数组中对应的交易信息是否有效,尤其是订阅的 expires_dateis_trial_period 等字段。
  5. 向客户端返回授权结果:服务端确认购买有效后,将对应权限同步给客户端,并记录到数据库。

服务端代码示例(Python Flask)

此处提供一个简化的服务端验证示例,方便你理解逻辑。使用 Flask 框架,接收客户端发来的原始收据数据。

import requests
from flask import Flask, request, jsonify

app = Flask(__name__)
SHARED_SECRET = "你的共享密钥"

VERIFY_URL = "https://buy.itunes.apple.com/verifyReceipt"
SANDBOX_URL = "https://sandbox.itunes.apple.com/verifyReceipt"

@app.route("/verify", methods=["POST"])
def verify_receipt():
    data = request.get_json()
    receipt_data = data.get("receipt_data")
    
    payload = {
        "receipt-data": receipt_data,
        "password": SHARED_SECRET,
        "exclude-old-transactions": True
    }
    
    # 先尝试生产环境
    response = requests.post(VERIFY_URL, json=payload)
    result = response.json()
    
    # 如果状态码为21007,代表需要调用沙盒环境
    if result.get("status") == 21007:
        response = requests.post(SANDBOX_URL, json=payload)
        result = response.json()
    
    if result.get("status") == 0:
        # 验证通过,解析 receipt 字段,处理订阅或非消耗型商品逻辑
        return jsonify({"valid": True, "receipt": result["receipt"]})
    else:
        return jsonify({"valid": False, "error": "收据无效"})

在实际生产中,你应该还加上对 receipt.in_app 内每一项的验证:确认 product_id 与期望一致,检查购买时间是否合理,防止重放攻击等。

处理订阅状态与自动续期

对于自动续期订阅,你需要在服务端定期轮询或使用**服务端通知(App Store Server Notifications)**来接收状态变化。Apple 提供了 V2 版本的通知,可以在订阅扣款成功、即将到期、续期失败等时机向你的服务器发送 JSON 事件。

配置方法:进入 App Store Connect > 你的 App > 通用 > App Store 服务器通知,设置你的服务器 URL。当事件发生时 Apple 会向其发送 POST 请求。你需要按官方文档解析 signedPayload JWS 格式的数据,其中包含事件类型和最新的订阅信息。

常见问题与最佳实践

  • 沙盒测试:在沙盒环境中,订阅会加速到期时间,方便快速测试续费逻辑。请务必使用沙盒账号测试,不要使用生产 Apple ID。
  • 用户退款:当用户申请退款成功,Apple 可能发送 REFUND 类型的通知,或者通过状态更新告知你,务必在你的服务端处理权限回收。
  • 防收据重放:客户端发往服务端的收据,服务端应记录交易 ID,确保同一交易 ID 不会被重复使用。
  • 避免主 UI 线程卡顿Product.products(for:)purchase() 都是异步方法,不要在同步队列中阻塞等待。
  • 本地化提示:利用 Product.displayNameProduct.description 展示系统原生购买表单,可以极大提升用户体验。

总结与下一步

至此,你已经掌握了在 iOS 应用中集成应用内购买的核心步骤:从 App Store Connect 配置产品,到使用 StoreKit 2 实现购买流程,再到搭建服务端收据验证。安全的收据验证是商业化 App 的基石,而 StoreKit 的新 API 让这一切变得更加简单可靠。

接下来,你可以深入阅读 Apple 官方文档《StoreKit》和《In-App Purchase》,以及实现订阅状态服务器通知,让你的购买系统更加健壮。