内购类型

App 内购买是 iOS 上变现的官方通道。类型分四种:消耗型(一次用掉)、非消耗型(买断功能)、自动续期订阅、非自动续期订阅。选错类型,功能逻辑全要返工。

内购不是"加个支付",是商品、订阅和账务三套体系。

消耗型适合金币、道具;非消耗型适合解锁专业版;订阅适合持续服务。苹果对虚拟商品强制走内购,实物商品走自己的支付,别混。

配置与沙盒

先到 App Store Connect 建商品:标识符、价格、本地化、审核信息。开发期用 StoreKit 配置文件在 Xcode 里本地模拟,不碰真钱;真机测试用沙盒账号。

// StoreKit 配置文件(.storekit)本地模拟购买
let products = try await Product.products(for: ["com.app.pro"])
for product in products {
  print(product.displayName, product.displayPrice)
}
商品 ID 约定
  • com.app.pro非消耗型。
  • com.app.coins.100消耗型。
  • com.app.sub.month订阅。

沙盒账号在 App Store 里退出登录后创建,测试购买不真实扣款。订阅测试还有快速续期按钮,方便测周期逻辑。

商品列表

别把价格写死在代码里:从 StoreKit 拉商品,displayPrice 已经带货币和本地化。商品拉不到就隐藏购买入口,并提示稍后重试。

Task {
  let products = try await Product.products(for: productIDs)
  await MainActor.run {
    self.products = products
    // 刷新 UI
  }
}
StoreKit:发光商店柜台内金币与星形亮点、两侧支付徽章与清单卡片、上方循环箭头的蓝调插画
商店柜台陈列内购商品。

商品状态用 Product.ProductType 区分,订阅商品还要拉 subscription group 和周期信息。服务端要同步一份商品配置,客户端和服务端各自校验。

商品列表展示顺序和文案也要由服务端下发,改价改名不用重新发版。

购买流程

StoreKit 2 的购买是异步的:purchase() 返回结果,判断 success、pending、userCancelled 三种状态。用户取消不是失败,别弹错误。

let result = try await product.purchase()
switch result {
case .success(let verification):
  let transaction = try checkVerified(verification)
  await transaction.finish()
  // 发放权益
case .userCancelled:
  break // 用户取消,安静退出
case .pending:
  print("等待家长批准或外部支付")
}

pending 是家长控制或外部支付(如部分国家)的等待态,别当成失败。购买完成先校验再发权益,顺序不能反。

购买完成后的权益发放要讲究幂等:交易 ID 只处理一次,重复收到同一笔交易直接跳过,防止用户重复购买同一商品时权益被叠加或错乱。消耗型商品的余额变动放服务端记账,客户端展示即可,避免本地篡改。非消耗型解锁则要和服务端对账,用户换设备重装后能通过恢复找回已购权益。所有购买结果都要记录日志,但别记录交易票据全文,摘要即可,完整票据只发往服务端校验,别留在客户端日志里。

UI 上,购买按钮在流程进行中要禁用并显示加载态,防止连点;交易完成、失败、取消三种结果给出不同的提示文案,取消时保持静默即可。用户等待时给个明确进度,购买弹窗是系统级的,应用内只需要做好前置校验。