axiom-storekit-ref

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

StoreKit 2 — Complete API Reference

StoreKit 2 — 完整API参考文档

Overview

概述

StoreKit 2 is Apple's modern in-app purchase framework with async/await APIs, automatic receipt validation, and SwiftUI integration. This reference covers every API, iOS 18.4 enhancements, and comprehensive WWDC 2025 code examples.
StoreKit 2是苹果推出的现代应用内购买框架,支持async/await API、自动收据验证以及SwiftUI集成。本参考资料涵盖所有API、iOS 18.4的增强功能,并附带全面的WWDC 2025代码示例。

Product Types Supported

支持的产品类型

Consumable:
  • Products that can be purchased multiple times
  • Examples: coins, hints, temporary boosts
  • Do NOT restore on new devices
Non-Consumable:
  • Products purchased once, owned forever
  • Examples: premium features, level packs, remove ads
  • MUST restore on new devices
Auto-Renewable Subscription:
  • Subscriptions that renew automatically
  • Organized into subscription groups
  • MUST restore on new devices
  • Support: free trials, intro offers, promotional offers, win-back offers
Non-Renewing Subscription:
  • Fixed duration subscriptions (no auto-renewal)
  • Examples: seasonal passes
  • MUST restore on new devices
消耗型产品:
  • 可重复购买的产品
  • 示例:游戏币、提示、临时增益
  • 新设备上无需恢复
非消耗型产品:
  • 一次性购买,永久拥有
  • 示例:高级功能、关卡包、移除广告
  • 新设备上必须恢复
自动续订订阅:
  • 自动续订的订阅服务
  • 按订阅组进行组织
  • 新设备上必须恢复
  • 支持:免费试用、介绍性优惠、促销优惠、赢回优惠
非续订订阅:
  • 固定时长的订阅(无自动续订)
  • 示例:季票
  • 新设备上必须恢复

Key Improvements Over StoreKit 1

相比StoreKit 1的主要改进

  • Async/Await: Modern concurrency instead of delegates/closures
  • Automatic Verification: JSON Web Signature (JWS) verification built-in
  • Transaction Types: Strong Swift types instead of SKPaymentTransaction
  • Testing: StoreKit configuration files for local testing
  • SwiftUI Views: Pre-built purchase UIs (ProductView, SubscriptionStoreView)
  • Server APIs: App Store Server API and Server Notifications

  • Async/Await: 采用现代并发模型,替代代理/闭包
  • 自动验证: 内置JSON Web Signature (JWS)验证
  • 交易类型: 使用强类型Swift类型,替代SKPaymentTransaction
  • 测试: 支持StoreKit配置文件进行本地测试
  • SwiftUI视图: 预构建的购买UI(ProductView、SubscriptionStoreView)
  • 服务器API: App Store Server API和服务器通知

When to Use This Reference

何时使用本参考资料

Use this reference when:
  • Implementing in-app purchases with StoreKit 2
  • Understanding new iOS 18.4 fields (appTransactionID, offerPeriod, etc.)
  • Looking up specific API signatures and parameters
  • Planning subscription architecture
  • Debugging transaction issues
  • Implementing StoreKit Views
  • Integrating with App Store Server APIs
Related Skills:
  • axiom-in-app-purchases
    — Discipline skill with testing-first workflow, architecture patterns
  • (Future:
    iap-auditor
    agent for auditing existing IAP code)
  • (Future:
    iap-implementation
    agent for implementing IAP from scratch)

在以下场景使用本参考资料:
  • 使用StoreKit 2实现应用内购买
  • 了解iOS 18.4新增字段(appTransactionID、offerPeriod等)
  • 查询特定API签名和参数
  • 规划订阅架构
  • 调试交易问题
  • 实现StoreKit视图
  • 集成App Store Server API
相关技能:
  • axiom-in-app-purchases
    — 采用测试优先工作流、架构模式的专业技能
  • (未来:
    iap-auditor
    agent,用于审计现有IAP代码)
  • (未来:
    iap-implementation
    agent,用于从零开始实现IAP)

Product

Product

Overview

概述

Product
represents an in-app purchase item configured in App Store Connect or StoreKit configuration file.
Product
代表在App Store Connect或StoreKit配置文件中配置的应用内购买项目。

Loading Products

加载产品

Basic Loading:
swift
import StoreKit

let productIDs = [
    "com.app.coins_100",
    "com.app.premium",
    "com.app.pro_monthly"
]

let products = try await Product.products(for: productIDs)
基础加载:
swift
import StoreKit

let productIDs = [
    "com.app.coins_100",
    "com.app.premium",
    "com.app.pro_monthly"
]

let products = try await Product.products(for: productIDs)

From WWDC 2021-10114

来自WWDC 2021-10114

Handling Missing Products:
swift
let products = try await Product.products(for: productIDs)

// Check what loaded
let loadedIDs = Set(products.map { $0.id })
let missingIDs = Set(productIDs).subtracting(loadedIDs)

if !missingIDs.isEmpty {
    print("Missing products: \(missingIDs)")
    // Products not configured in App Store Connect or .storekit file
}
处理缺失的产品:
swift
let products = try await Product.products(for: productIDs)

// 检查已加载的产品
let loadedIDs = Set(products.map { $0.id })
let missingIDs = Set(productIDs).subtracting(loadedIDs)

if !missingIDs.isEmpty {
    print("Missing products: \(missingIDs)")
    // 产品未在App Store Connect或.storekit文件中配置
}

Product Properties

Product属性

Basic Properties:
swift
let product: Product

product.id // "com.app.premium"
product.displayName // "Premium Upgrade"
product.description // "Unlock all features"
product.displayPrice // "$4.99"
product.price // Decimal(4.99)
product.type // .nonConsumable
Product Type Enum:
swift
switch product.type {
case .consumable:
    // Coins, hints, boosts
case .nonConsumable:
    // Premium features, level packs
case .autoRenewable:
    // Monthly/annual subscriptions
case .nonRenewing:
    // Seasonal passes
@unknown default:
    break
}
基础属性:
swift
let product: Product

product.id // "com.app.premium"
product.displayName // "Premium Upgrade"
product.description // "Unlock all features"
product.displayPrice // "$4.99"
product.price // Decimal(4.99)
product.type // .nonConsumable
产品类型枚举:
swift
switch product.type {
case .consumable:
    // 游戏币、提示、增益
case .nonConsumable:
    // 高级功能、关卡包
case .autoRenewable:
    // 月度/年度订阅
case .nonRenewing:
    // 季票
@unknown default:
    break
}

Subscription-Specific Properties

订阅专属属性

Check if Product is Subscription:
swift
if let subscriptionInfo = product.subscription {
    // Product is auto-renewable subscription
    let groupID = subscriptionInfo.subscriptionGroupID
    let period = subscriptionInfo.subscriptionPeriod
}
Subscription Period:
swift
let period = product.subscription?.subscriptionPeriod

switch period?.unit {
case .day:
    print("\(period?.value ?? 0) days")
case .week:
    print("\(period?.value ?? 0) weeks")
case .month:
    print("\(period?.value ?? 0) months")
case .year:
    print("\(period?.value ?? 0) years")
default:
    break
}
Introductory Offer:
swift
if let introOffer = product.subscription?.introductoryOffer {
    print("Free trial: \(introOffer.period.value) \(introOffer.period.unit)")
    print("Price: \(introOffer.displayPrice)")

    switch introOffer.paymentMode {
    case .freeTrial:
        print("Free trial - no charge")
    case .payAsYouGo:
        print("Discounted price per period")
    case .payUpFront:
        print("One-time discounted price")
    @unknown default:
        break
    }
}
Promotional Offers:
swift
let offers = product.subscription?.promotionalOffers ?? []

for offer in offers {
    print("Offer ID: \(offer.id)")
    print("Price: \(offer.displayPrice)")
    print("Period: \(offer.period.value) \(offer.period.unit)")
}
检查产品是否为订阅:
swift
if let subscriptionInfo = product.subscription {
    // 产品是自动续订订阅
    let groupID = subscriptionInfo.subscriptionGroupID
    let period = subscriptionInfo.subscriptionPeriod
}
订阅周期:
swift
let period = product.subscription?.subscriptionPeriod

switch period?.unit {
case .day:
    print("\(period?.value ?? 0) days")
case .week:
    print("\(period?.value ?? 0) weeks")
case .month:
    print("\(period?.value ?? 0) months")
case .year:
    print("\(period?.value ?? 0) years")
default:
    break
}
介绍性优惠:
swift
if let introOffer = product.subscription?.introductoryOffer {
    print("Free trial: \(introOffer.period.value) \(introOffer.period.unit)")
    print("Price: \(introOffer.displayPrice)")

    switch introOffer.paymentMode {
    case .freeTrial:
        print("Free trial - no charge")
    case .payAsYouGo:
        print("Discounted price per period")
    case .payUpFront:
        print("One-time discounted price")
    @unknown default:
        break
    }
}
促销优惠:
swift
let offers = product.subscription?.promotionalOffers ?? []

for offer in offers {
    print("Offer ID: \(offer.id)")
    print("Price: \(offer.displayPrice)")
    print("Period: \(offer.period.value) \(offer.period.unit)")
}

Purchase Methods

购买方法

Purchase with UI Context (iOS 18.2+):
swift
let product: Product
let scene: UIWindowScene

let result = try await product.purchase(confirmIn: scene)
带UI上下文的购买(iOS 18.2+):
swift
let product: Product
let scene: UIWindowScene

let result = try await product.purchase(confirmIn: scene)

From WWDC 2025-241:9:32

来自WWDC 2025-241:9:32

Purchase with Options:
swift
let accountToken = UUID()

let result = try await product.purchase(
    confirmIn: scene,
    options: [
        .appAccountToken(accountToken)
    ]
)
带选项的购买:
swift
let accountToken = UUID()

let result = try await product.purchase(
    confirmIn: scene,
    options: [
        .appAccountToken(accountToken)
    ]
)

From WWDC 2025-241:11:01

来自WWDC 2025-241:11:01

Purchase with Promotional Offer (JWS Format):
swift
let jwsSignature: String // From your server

let result = try await product.purchase(
    confirmIn: scene,
    options: [
        .promotionalOffer(offerID: "promo_winback", signature: jwsSignature)
    ]
)
带促销优惠的购买(JWS格式):
swift
let jwsSignature: String // 来自你的服务器

let result = try await product.purchase(
    confirmIn: scene,
    options: [
        .promotionalOffer(offerID: "promo_winback", signature: jwsSignature)
    ]
)

From WWDC 2025-241:10:55

来自WWDC 2025-241:10:55

Purchase with Custom Intro Eligibility:
swift
let jwsSignature: String // From your server

let result = try await product.purchase(
    confirmIn: scene,
    options: [
        .introductoryOfferEligibility(signature: jwsSignature)
    ]
)
带自定义介绍性优惠资格的购买:
swift
let jwsSignature: String // 来自你的服务器

let result = try await product.purchase(
    confirmIn: scene,
    options: [
        .introductoryOfferEligibility(signature: jwsSignature)
    ]
)

From WWDC 2025-241:10:42

来自WWDC 2025-241:10:42

SwiftUI Purchase (Using Environment):
swift
struct ProductView: View {
    let product: Product
    @Environment(\.purchase) private var purchase

    var body: some View {
        Button("Buy \(product.displayPrice)") {
            Task {
                do {
                    let result = try await purchase(product)
                    // Handle result
                } catch {
                    print("Purchase failed: \(error)")
                }
            }
        }
    }
}
SwiftUI购买(使用环境变量):
swift
struct ProductView: View {
    let product: Product
    @Environment(\.purchase) private var purchase

    var body: some View {
        Button("Buy \(product.displayPrice)") {
            Task {
                do {
                    let result = try await purchase(product)
                    // 处理结果
                } catch {
                    print("Purchase failed: \(error)")
                }
            }
        }
    }
}

From WWDC 2025-241:9:50

来自WWDC 2025-241:9:50

PurchaseResult

PurchaseResult

Handling Purchase Results:
swift
let result = try await product.purchase(confirmIn: scene)

switch result {
case .success(let verificationResult):
    // Purchase succeeded - verify transaction
    guard let transaction = try? verificationResult.payloadValue else {
        print("Transaction verification failed")
        return
    }

    // Grant entitlement
    await grantEntitlement(for: transaction)
    await transaction.finish()

case .userCancelled:
    // User tapped "Cancel" in payment sheet
    print("User cancelled purchase")

case .pending:
    // Purchase requires action (Ask to Buy, payment issue)
    // Transaction will arrive via Transaction.updates when approved
    print("Purchase pending approval")

@unknown default:
    break
}
处理购买结果:
swift
let result = try await product.purchase(confirmIn: scene)

switch result {
case .success(let verificationResult):
    // 购买成功 - 验证交易
    guard let transaction = try? verificationResult.payloadValue else {
        print("Transaction verification failed")
        return
    }

    // 授予权限
    await grantEntitlement(for: transaction)
    await transaction.finish()

case .userCancelled:
    // 用户在支付表单中点击了“取消”
    print("User cancelled purchase")

case .pending:
    // 购买需要操作(请求购买、支付问题)
    // 交易通过Transaction.updates在批准后送达
    print("Purchase pending approval")

@unknown default:
    break
}

From WWDC 2025-241

来自WWDC 2025-241



Transaction

Transaction

Overview

概述

Transaction
represents a successful in-app purchase. Contains purchase metadata, product ID, purchase date, and for subscriptions, expiration date.
Transaction
代表成功的应用内购买,包含购买元数据、产品ID、购买日期,对于订阅产品还包含过期日期。

New Fields (iOS 18.4)

iOS 18.4新增字段

appTransactionID:
swift
let transaction: Transaction
let appTransactionID = transaction.appTransactionID
// Unique ID for app download (same across all purchases by same Apple Account)
appTransactionID:
swift
let transaction: Transaction
let appTransactionID = transaction.appTransactionID
// 应用下载的唯一ID(同一苹果账户的所有购买共用此ID)

From WWDC 2025-241:4:13

来自WWDC 2025-241:4:13

offerPeriod:
swift
if let offerPeriod = transaction.offer?.period {
    print("Offer duration: \(offerPeriod)")
    // ISO 8601 duration format (e.g., "P1M" for 1 month)
}
offerPeriod:
swift
if let offerPeriod = transaction.offer?.period {
    print("Offer duration: \(offerPeriod)")
    // ISO 8601时长格式(例如"P1M"代表1个月)
}

From WWDC 2025-249:3:11

来自WWDC 2025-249:3:11

advancedCommerceInfo:
swift
if let advancedInfo = transaction.advancedCommerceInfo {
    // Only present for Advanced Commerce API purchases
    // nil for standard IAP
}
advancedCommerceInfo:
swift
if let advancedInfo = transaction.advancedCommerceInfo {
    // 仅在使用Advanced Commerce API购买时存在
    // 标准IAP为nil
}

From WWDC 2025-241:4:42

来自WWDC 2025-241:4:42

Essential Properties

核心属性

Basic Fields:
swift
let transaction: Transaction

transaction.id // Unique transaction ID
transaction.originalID // Original transaction ID (consistent across renewals)
transaction.productID // "com.app.pro_monthly"
transaction.productType // .autoRenewable
transaction.purchaseDate // Date of purchase
transaction.appAccountToken // UUID set at purchase time (if provided)
Subscription Fields:
swift
transaction.expirationDate // When subscription expires
transaction.isUpgraded // true if user upgraded to higher tier
transaction.revocationDate // Date of refund (nil if not refunded)
transaction.revocationReason // .developerIssue or .other
Offer Fields:
swift
if let offer = transaction.offer {
    offer.type // .introductory or .promotional or .code
    offer.id // Offer identifier from App Store Connect
    offer.paymentMode // .freeTrial, .payAsYouGo, .payUpFront, .oneTime
}
基础字段:
swift
let transaction: Transaction

transaction.id // 唯一交易ID
transaction.originalID // 原始交易ID(续订时保持一致)
transaction.productID // "com.app.pro_monthly"
transaction.productType // .autoRenewable
transaction.purchaseDate // 购买日期
transaction.appAccountToken // 购买时设置的UUID(如果提供)
订阅字段:
swift
transaction.expirationDate // 订阅过期时间
transaction.isUpgraded // 如果用户升级到更高层级则为true
transaction.revocationDate // 退款日期(未退款则为nil)
transaction.revocationReason // .developerIssue或.other
优惠字段:
swift
if let offer = transaction.offer {
    offer.type // .introductory或.promotional或.code
    offer.id // App Store Connect中的优惠标识符
    offer.paymentMode // .freeTrial、.payAsYouGo、.payUpFront、.oneTime
}

From WWDC 2025-241:8:00

来自WWDC 2025-241:8:00

Current Entitlements

当前权限

Get All Current Entitlements:
swift
var purchasedProductIDs: Set<String> = []

for await result in Transaction.currentEntitlements {
    guard let transaction = try? result.payloadValue else {
        continue
    }

    // Only include non-refunded transactions
    if transaction.revocationDate == nil {
        purchasedProductIDs.insert(transaction.productID)
    }
}
获取所有当前权限:
swift
var purchasedProductIDs: Set<String> = []

for await result in Transaction.currentEntitlements {
    guard let transaction = try? result.payloadValue else {
        continue
    }

    // 仅包含未退款的交易
    if transaction.revocationDate == nil {
        purchasedProductIDs.insert(transaction.productID)
    }
}

From WWDC 2025-241

来自WWDC 2025-241

Get Entitlements for Specific Product (iOS 18.4+):
swift
let productID = "com.app.premium"

for await result in Transaction.currentEntitlements(for: productID) {
    if let transaction = try? result.payloadValue,
       transaction.revocationDate == nil {
        // User owns this product
        return true
    }
}
获取特定产品的权限(iOS 18.4+):
swift
let productID = "com.app.premium"

for await result in Transaction.currentEntitlements(for: productID) {
    if let transaction = try? result.payloadValue,
       transaction.revocationDate == nil {
        // 用户拥有该产品
        return true
    }
}

From WWDC 2025-241:3:31

来自WWDC 2025-241:3:31

Deprecated API (iOS 18.4):
swift
// ❌ Deprecated in iOS 18.4
let entitlement = await Transaction.currentEntitlement(for: productID)

// ✅ Use this instead (returns sequence, handles Family Sharing)
for await result in Transaction.currentEntitlements(for: productID) {
    // ...
}
已弃用API(iOS 18.4):
swift
// ❌ 在iOS 18.4中已弃用
let entitlement = await Transaction.currentEntitlement(for: productID)

// ✅ 请使用此替代方案(返回序列,支持家庭共享)
for await result in Transaction.currentEntitlements(for: productID) {
    // ...
}

From WWDC 2025-241:3:31

来自WWDC 2025-241:3:31

Transaction History

交易历史

Get All Transactions:
swift
for await result in Transaction.all {
    guard let transaction = try? result.payloadValue else {
        continue
    }

    print("Transaction: \(transaction.productID) on \(transaction.purchaseDate)")
}
Get Transactions for Product:
swift
for await result in Transaction.all(matching: productID) {
    guard let transaction = try? result.payloadValue else {
        continue
    }

    // All transactions for this product
}
获取所有交易:
swift
for await result in Transaction.all {
    guard let transaction = try? result.payloadValue else {
        continue
    }

    print("Transaction: \(transaction.productID) on \(transaction.purchaseDate)")
}
获取特定产品的交易:
swift
for await result in Transaction.all(matching: productID) {
    guard let transaction = try? result.payloadValue else {
        continue
    }

    // 该产品的所有交易
}

Transaction Listener

交易监听器

Listen for Real-Time Updates (REQUIRED):
swift
func listenForTransactions() -> Task<Void, Never> {
    Task.detached {
        for await verificationResult in Transaction.updates {
            await handleTransaction(verificationResult)
        }
    }
}

func handleTransaction(_ result: VerificationResult<Transaction>) async {
    guard let transaction = try? result.payloadValue else {
        return
    }

    // Grant or revoke entitlement
    if transaction.revocationDate != nil {
        await revokeEntitlement(for: transaction.productID)
    } else {
        await grantEntitlement(for: transaction)
    }

    // CRITICAL: Always finish transaction
    await transaction.finish()
}
监听实时更新(必填):
swift
func listenForTransactions() -> Task<Void, Never> {
    Task.detached {
        for await verificationResult in Transaction.updates {
            await handleTransaction(verificationResult)
        }
    }
}

func handleTransaction(_ result: VerificationResult<Transaction>) async {
    guard let transaction = try? result.payloadValue else {
        return
    }

    // 授予或撤销权限
    if transaction.revocationDate != nil {
        await revokeEntitlement(for: transaction.productID)
    } else {
        await grantEntitlement(for: transaction)
    }

    // 关键:务必完成交易
    await transaction.finish()
}

From WWDC 2021-10114

来自WWDC 2021-10114

Transaction Sources:
  • In-app purchases
  • Purchases from App Store (promoted IAP)
  • Offer code redemptions
  • Subscription renewals
  • Family Sharing transactions
  • Pending purchases (Ask to Buy) that complete
  • Refund notifications
交易来源:
  • 应用内购买
  • App Store购买(推广IAP)
  • 优惠码兑换
  • 订阅续订
  • 家庭共享交易
  • 待处理购买(请求购买)完成
  • 退款通知

Verification

验证

VerificationResult:
swift
let result: VerificationResult<Transaction>

switch result {
case .verified(let transaction):
    // ✅ Transaction signed by App Store
    await grantEntitlement(for: transaction)
    await transaction.finish()

case .unverified(let transaction, let error):
    // ❌ Transaction signature invalid
    print("Unverified: \(error)")
    // DO NOT grant entitlement
    await transaction.finish() // Still finish to clear queue
}
What Verification Checks:
  • Transaction signed by App Store (not fraudulent)
  • Transaction belongs to this app (bundle ID match)
  • Transaction belongs to this device
VerificationResult:
swift
let result: VerificationResult<Transaction>

switch result {
case .verified(let transaction):
    // ✅ 交易由App Store签名
    await grantEntitlement(for: transaction)
    await transaction.finish()

case .unverified(let transaction, let error):
    // ❌ 交易签名无效
    print("Unverified: \(error)")
    // 请勿授予权限
    await transaction.finish() // 仍需完成以清除队列
}
验证检查内容:
  • 交易由App Store签名(非欺诈)
  • 交易属于当前应用(Bundle ID匹配)
  • 交易属于当前设备

Finishing Transactions

完成交易

Always Call finish():
swift
await transaction.finish()
When to finish:
  • ✅ After granting entitlement to user
  • ✅ After storing transaction receipt/ID
  • ✅ Even for unverified transactions (to clear queue)
  • ✅ Even for refunded transactions
What happens if you don't finish:
  • Transaction redelivered on next app launch
  • Transaction.updates
    re-emits transaction
  • Queue builds up over time

务必调用finish():
swift
await transaction.finish()
何时完成:
  • ✅ 向用户授予权限后
  • ✅ 存储交易收据/ID后
  • ✅ 即使是未验证的交易(清除队列)
  • ✅ 即使是已退款的交易
不完成的后果:
  • 应用下次启动时会重新发送交易
  • Transaction.updates
    会重新发出交易
  • 队列会随时间堆积

AppTransaction

AppTransaction

Overview

概述

AppTransaction
represents the original app download. Available via
AppTransaction.shared
.
AppTransaction
代表原始应用下载记录,可通过
AppTransaction.shared
获取。

New Fields (iOS 18.4)

iOS 18.4新增字段

appTransactionID:
swift
let appTransaction = try await AppTransaction.shared

switch appTransaction {
case .verified(let transaction):
    let appTransactionID = transaction.appTransactionID
    // Globally unique ID for this Apple Account + app
    // Same value appears in Transaction and RenewalInfo

case .unverified(_, let error):
    print("AppTransaction verification failed: \(error)")
}
appTransactionID:
swift
let appTransaction = try await AppTransaction.shared

switch appTransaction {
case .verified(let transaction):
    let appTransactionID = transaction.appTransactionID
    // 此苹果账户+应用的全局唯一ID
    // 该值也会出现在Transaction和RenewalInfo中

case .unverified(_, let error):
    print("AppTransaction verification failed: \(error)")
}

From WWDC 2025-241:1:42

来自WWDC 2025-241:1:42

originalPlatform:
swift
if let appTransaction = try? await AppTransaction.shared.payloadValue {
    let platform = appTransaction.originalPlatform

    switch platform {
    case .iOS:
        print("Originally downloaded on iPhone/iPad")
    case .macOS:
        print("Originally downloaded on Mac")
    case .tvOS:
        print("Originally downloaded on Apple TV")
    case .visionOS:
        print("Originally downloaded on Vision Pro")
    @unknown default:
        break
    }
}
originalPlatform:
swift
if let appTransaction = try? await AppTransaction.shared.payloadValue {
    let platform = appTransaction.originalPlatform

    switch platform {
    case .iOS:
        print("最初下载于iPhone/iPad")
    case .macOS:
        print("最初下载于Mac")
    case .tvOS:
        print("最初下载于Apple TV")
    case .visionOS:
        print("最初下载于Vision Pro")
    @unknown default:
        break
    }
}

From WWDC 2025-241:2:11

来自WWDC 2025-241:2:11

Note: Apps downloaded on watchOS show
originalPlatform = .iOS
注意: 在watchOS上下载的应用会显示
originalPlatform = .iOS

Essential Properties

核心属性

swift
let appTransaction: AppTransaction

appTransaction.appVersion // "1.2.3"
appTransaction.originalAppVersion // "1.0.0"
appTransaction.originalPurchaseDate // First download date
appTransaction.bundleID // "com.company.app"
appTransaction.deviceVerification // UUID for device
appTransaction.deviceVerificationNonce // Nonce for verification
swift
let appTransaction: AppTransaction

appTransaction.appVersion // "1.2.3"
appTransaction.originalAppVersion // "1.0.0"
appTransaction.originalPurchaseDate // 首次下载日期
appTransaction.bundleID // "com.company.app"
appTransaction.deviceVerification // 设备UUID
appTransaction.deviceVerificationNonce // 验证用随机数

Use Cases

使用场景

Check App Version:
swift
if let appTransaction = try? await AppTransaction.shared.payloadValue {
    if appTransaction.appVersion != currentVersion {
        // Prompt user to update
    }
}
检查应用版本:
swift
if let appTransaction = try? await AppTransaction.shared.payloadValue {
    if appTransaction.appVersion != currentVersion {
        // 提示用户更新
    }
}

From WWDC 2025-241:0:51

来自WWDC 2025-241:0:51

Business Model Migration:
swift
// Moving from paid app to free app with IAP
if appTransaction.originalPlatform == .iOS,
   appTransaction.originalPurchaseDate < migrationDate {
    // User paid for app before migration - grant premium
    await grantPremiumAccess()
}
商业模式迁移:
swift
// 从付费应用迁移到带IAP的免费应用
if appTransaction.originalPlatform == .iOS,
   appTransaction.originalPurchaseDate < migrationDate {
    // 用户在迁移前购买了应用 - 授予高级权限
    await grantPremiumAccess()
}

From WWDC 2025-241:2:32

来自WWDC 2025-241:2:32



Product.SubscriptionInfo.RenewalInfo

Product.SubscriptionInfo.RenewalInfo

Overview

概述

RenewalInfo
provides information about auto-renewable subscription renewal state, including whether it will renew, expiration reason, and upcoming offers.
RenewalInfo
提供自动续订订阅的续订状态信息,包括是否会续订、过期原因以及即将到来的优惠。

New Fields (iOS 18.4)

iOS 18.4新增字段

appTransactionID:
swift
let renewalInfo: RenewalInfo
let appTransactionID = renewalInfo.appTransactionID
appTransactionID:
swift
let renewalInfo: RenewalInfo
let appTransactionID = renewalInfo.appTransactionID

From WWDC 2025-241:6:40

来自WWDC 2025-241:6:40

offerPeriod:
swift
if let offerPeriod = renewalInfo.offerPeriod {
    print("Next renewal offer period: \(offerPeriod)")
    // ISO 8601 duration (applies at next renewal)
}
offerPeriod:
swift
if let offerPeriod = renewalInfo.offerPeriod {
    print("Next renewal offer period: \(offerPeriod)")
    // ISO 8601时长(下次续订时生效)
}

From WWDC 2025-249:3:11

来自WWDC 2025-249:3:11

appAccountToken:
swift
if let token = renewalInfo.appAccountToken {
    // UUID associating subscription with your server account
}
appAccountToken:
swift
if let token = renewalInfo.appAccountToken {
    // 将订阅与你的服务器账户关联的UUID
}

From WWDC 2025-241:6:56

来自WWDC 2025-241:6:56

advancedCommerceInfo:
swift
if let advancedInfo = renewalInfo.advancedCommerceInfo {
    // Only for Advanced Commerce API subscriptions
}
advancedCommerceInfo:
swift
if let advancedInfo = renewalInfo.advancedCommerceInfo {
    // 仅适用于Advanced Commerce API订阅
}

From WWDC 2025-241:6:50

来自WWDC 2025-241:6:50

Essential Properties

核心属性

Renewal State:
swift
let renewalInfo: RenewalInfo

renewalInfo.willAutoRenew // true if subscription will renew
renewalInfo.autoRenewPreference // Product ID customer will renew to
renewalInfo.expirationReason // Why subscription expired (if expired)
Expiration Reasons:
swift
switch renewalInfo.expirationReason {
case .autoRenewDisabled:
    // User turned off auto-renewal
case .billingError:
    // Payment method issue
case .didNotConsentToPriceIncrease:
    // User didn't accept price increase - show win-back offer!
case .productUnavailable:
    // Product no longer available
case .unknown:
    // Unknown reason
@unknown default:
    break
}
续订状态:
swift
let renewalInfo: RenewalInfo

renewalInfo.willAutoRenew // 订阅是否会自动续订
renewalInfo.autoRenewPreference // 用户将续订到的产品ID
renewalInfo.expirationReason // 订阅过期原因(如果已过期)
过期原因:
swift
switch renewalInfo.expirationReason {
case .autoRenewDisabled:
    // 用户关闭了自动续订
case .billingError:
    // 支付方式问题
case .didNotConsentToPriceIncrease:
    // 用户未接受价格上涨 - 展示赢回优惠!
case .productUnavailable:
    // 产品不再可用
case .unknown:
    // 未知原因
@unknown default:
    break
}

From WWDC 2025-241:5:38

来自WWDC 2025-241:5:38

Grace Period:
swift
if let gracePeriodExpiration = renewalInfo.gracePeriodExpirationDate {
    // Subscription in grace period - billing issue
    // Show update payment method UI
}
Price Increase Consent:
swift
if let consentStatus = renewalInfo.priceIncreaseStatus {
    switch consentStatus {
    case .agreed:
        // User accepted price increase
    case .notYetResponded:
        // User hasn't responded - show consent UI
    @unknown default:
        break
    }
}
宽限期:
swift
if let gracePeriodExpiration = renewalInfo.gracePeriodExpirationDate {
    // 订阅处于宽限期 - 支付问题
    // 展示更新支付方式的UI
}
价格上涨同意状态:
swift
if let consentStatus = renewalInfo.priceIncreaseStatus {
    switch consentStatus {
    case .agreed:
        // 用户接受了价格上涨
    case .notYetResponded:
        // 用户尚未回应 - 展示同意UI
    @unknown default:
        break
    }
}

Accessing RenewalInfo

获取RenewalInfo

From SubscriptionStatus:
swift
let statuses = try await Product.SubscriptionInfo.status(for: groupID)

for status in statuses {
    switch status.renewalInfo {
    case .verified(let renewalInfo):
        print("Will renew: \(renewalInfo.willAutoRenew)")
    case .unverified(_, let error):
        print("Renewal info verification failed: \(error)")
    }
}

从SubscriptionStatus获取:
swift
let statuses = try await Product.SubscriptionInfo.status(for: groupID)

for status in statuses {
    switch status.renewalInfo {
    case .verified(let renewalInfo):
        print("Will renew: \(renewalInfo.willAutoRenew)")
    case .unverified(_, let error):
        print("Renewal info verification failed: \(error)")
    }
}

Product.SubscriptionInfo.Status

Product.SubscriptionInfo.Status

Overview

概述

SubscriptionStatus
represents the current state of an auto-renewable subscription, including whether it's active, expired, in grace period, or in billing retry.
SubscriptionStatus
代表自动续订订阅的当前状态,包括是否活跃、已过期、处于宽限期或账单重试期。

Subscription States

订阅状态

State Enum:
swift
let status: Product.SubscriptionInfo.Status

switch status.state {
case .subscribed:
    // User has active subscription - full access

case .expired:
    // Subscription expired - show resubscribe/win-back offer

case .inGracePeriod:
    // Billing issue but access maintained - show update payment UI

case .inBillingRetryPeriod:
    // Apple retrying payment - maintain access

case .revoked:
    // Family Sharing access removed - revoke access

@unknown default:
    break
}
状态枚举:
swift
let status: Product.SubscriptionInfo.Status

switch status.state {
case .subscribed:
    // 用户拥有活跃订阅 - 完整权限

case .expired:
    // 订阅已过期 - 展示重新订阅/赢回优惠

case .inGracePeriod:
    // 存在账单问题但仍保留权限 - 展示更新支付方式UI

case .inBillingRetryPeriod:
    // Apple正在重试支付 - 保留权限

case .revoked:
    // 家庭共享权限被移除 - 撤销权限

@unknown default:
    break
}

From WWDC 2025-241

来自WWDC 2025-241

Getting Subscription Status

获取订阅状态

For Subscription Group:
swift
let groupID = "pro_tier"

let statuses = try await Product.SubscriptionInfo.status(for: groupID)

// Find highest service level
let activeStatus = statuses
    .filter { $0.state == .subscribed }
    .max { $0.transaction.productID < $1.transaction.productID }
按订阅组获取:
swift
let groupID = "pro_tier"

let statuses = try await Product.SubscriptionInfo.status(for: groupID)

// 找到最高服务层级
let activeStatus = statuses
    .filter { $0.state == .subscribed }
    .max { $0.transaction.productID < $1.transaction.productID }

From WWDC 2025-241:6:22

来自WWDC 2025-241:6:22

For Specific Transaction (iOS 18.4+):
swift
let transactionID = transaction.id

let status = try await Product.SubscriptionInfo.status(for: transactionID)
按特定交易获取(iOS 18.4+):
swift
let transactionID = transaction.id

let status = try await Product.SubscriptionInfo.status(for: transactionID)

From WWDC 2025-241:6:40

来自WWDC 2025-241:6:40

Listen for Status Updates:
swift
for await statuses in Product.SubscriptionInfo.Status.updates(for: groupID) {
    // Process updated statuses
    for status in statuses {
        print("Status: \(status.state)")
    }
}
监听状态更新:
swift
for await statuses in Product.SubscriptionInfo.Status.updates(for: groupID) {
    // 处理更新后的状态
    for status in statuses {
        print("Status: \(status.state)")
    }
}

Status Properties

状态属性

swift
let status: Product.SubscriptionInfo.Status

status.state // .subscribed, .expired, etc.
status.transaction // VerificationResult<Transaction>
status.renewalInfo // VerificationResult<RenewalInfo>

swift
let status: Product.SubscriptionInfo.Status

status.state // .subscribed、.expired等
status.transaction // VerificationResult<Transaction>
status.renewalInfo // VerificationResult<RenewalInfo>

StoreKit Views

StoreKit视图

ProductView (iOS 17+)

ProductView(iOS 17+)

Basic Usage:
swift
import StoreKit

struct ContentView: View {
    let productID = "com.app.premium"

    var body: some View {
        ProductView(id: productID)
    }
}
基础用法:
swift
import StoreKit

struct ContentView: View {
    let productID = "com.app.premium"

    var body: some View {
        ProductView(id: productID)
    }
}

From WWDC 2023-10013

来自WWDC 2023-10013

With Loaded Product:
swift
struct ContentView: View {
    let product: Product

    var body: some View {
        ProductView(for: product)
    }
}
Custom Icon:
swift
ProductView(id: productID) {
    Image(systemName: "star.fill")
        .foregroundStyle(.yellow)
}
Control Styles:
swift
ProductView(id: productID)
    .productViewStyle(.regular)  // Default

ProductView(id: productID)
    .productViewStyle(.compact)  // Smaller

ProductView(id: productID)
    .productViewStyle(.large)  // Prominent
使用已加载的产品:
swift
struct ContentView: View {
    let product: Product

    var body: some View {
        ProductView(for: product)
    }
}
自定义图标:
swift
ProductView(id: productID) {
    Image(systemName: "star.fill")
        .foregroundStyle(.yellow)
}
控件样式:
swift
ProductView(id: productID)
    .productViewStyle(.regular)  // 默认

ProductView(id: productID)
    .productViewStyle(.compact)  // 小型

ProductView(id: productID)
    .productViewStyle(.large)  // 醒目

StoreView (iOS 17+)

StoreView(iOS 17+)

Basic Store:
swift
struct ContentView: View {
    let productIDs = [
        "com.app.coins_100",
        "com.app.coins_500",
        "com.app.coins_1000"
    ]

    var body: some View {
        StoreView(ids: productIDs)
    }
}
基础商店视图:
swift
struct ContentView: View {
    let productIDs = [
        "com.app.coins_100",
        "com.app.coins_500",
        "com.app.coins_1000"
    ]

    var body: some View {
        StoreView(ids: productIDs)
    }
}

From WWDC 2023-10013

来自WWDC 2023-10013

With Loaded Products:
swift
struct ContentView: View {
    let products: [Product]

    var body: some View {
        StoreView(products: products)
    }
}
使用已加载的产品:
swift
struct ContentView: View {
    let products: [Product]

    var body: some View {
        StoreView(products: products)
    }
}

SubscriptionStoreView (iOS 17+)

SubscriptionStoreView(iOS 17+)

Basic Subscription Store:
swift
struct SubscriptionView: View {
    let groupID = "pro_tier"

    var body: some View {
        SubscriptionStoreView(groupID: groupID) {
            // Marketing content above subscription options
            VStack {
                Image("app-icon")
                Text("Go Pro")
                    .font(.largeTitle.bold())
                Text("Unlock all features")
            }
        }
    }
}
基础订阅商店视图:
swift
struct SubscriptionView: View {
    let groupID = "pro_tier"

    var body: some View {
        SubscriptionStoreView(groupID: groupID) {
            // 订阅选项上方的营销内容
            VStack {
                Image("app-icon")
                Text("Go Pro")
                    .font(.largeTitle.bold())
                Text("Unlock all features")
            }
        }
    }
}

From WWDC 2023-10013

来自WWDC 2023-10013

Control Style:
swift
SubscriptionStoreView(groupID: groupID) {
    // Marketing content
}
.subscriptionStoreControlStyle(.automatic)    // Default
.subscriptionStoreControlStyle(.picker)       // Horizontal picker
.subscriptionStoreControlStyle(.buttons)      // Stacked buttons
.subscriptionStoreControlStyle(.prominentPicker) // Large picker (iOS 18.4+)
控件样式:
swift
SubscriptionStoreView(groupID: groupID) {
    // 营销内容
}
.subscriptionStoreControlStyle(.automatic)    // 默认
.subscriptionStoreControlStyle(.picker)       // 水平选择器
.subscriptionStoreControlStyle(.buttons)      // 堆叠按钮
.subscriptionStoreControlStyle(.prominentPicker) // 大型选择器(iOS 18.4+)

From WWDC 2025-241

来自WWDC 2025-241

SubscriptionOfferView (iOS 18.4+)

SubscriptionOfferView(iOS 18.4+)

Basic Offer View:
swift
struct ContentView: View {
    let productID = "com.app.pro_monthly"

    var body: some View {
        SubscriptionOfferView(id: productID)
    }
}
基础优惠视图:
swift
struct ContentView: View {
    let productID = "com.app.pro_monthly"

    var body: some View {
        SubscriptionOfferView(id: productID)
    }
}

From WWDC 2025-241:14:27

来自WWDC 2025-241:14:27

With Promotional Icon:
swift
SubscriptionOfferView(
    id: productID,
    prefersPromotionalIcon: true
)
With Custom Icon:
swift
SubscriptionOfferView(id: productID) {
    Image("custom-icon")
        .resizable()
        .frame(width: 60, height: 60)
} placeholder: {
    Image(systemName: "photo")
        .foregroundStyle(.gray)
}
带促销图标:
swift
SubscriptionOfferView(
    id: productID,
    prefersPromotionalIcon: true
)
带自定义图标:
swift
SubscriptionOfferView(id: productID) {
    Image("custom-icon")
        .resizable()
        .frame(width: 60, height: 60)
} placeholder: {
    Image(systemName: "photo")
        .foregroundStyle(.gray)
}

From WWDC 2025-241:15:14

来自WWDC 2025-241:15:14

With Detail Action:
swift
@State private var showStore = false

var body: some View {
    SubscriptionOfferView(id: productID)
        .subscriptionOfferViewDetailAction {
            showStore = true
        }
        .sheet(isPresented: $showStore) {
            SubscriptionStoreView(groupID: "pro_tier")
        }
}
带详情操作:
swift
@State private var showStore = false

var body: some View {
    SubscriptionOfferView(id: productID)
        .subscriptionOfferViewDetailAction {
            showStore = true
        }
        .sheet(isPresented: $showStore) {
            SubscriptionStoreView(groupID: "pro_tier")
        }
}

From WWDC 2025-241:15:38

来自WWDC 2025-241:15:38

Visible Relationship:
swift
// Only show if customer can upgrade
SubscriptionOfferView(
    groupID: "pro_tier",
    visibleRelationship: .upgrade
)

// Only show if customer can downgrade
SubscriptionOfferView(
    groupID: "pro_tier",
    visibleRelationship: .downgrade
)

// Show crossgrade options (same tier, different billing period)
SubscriptionOfferView(
    groupID: "pro_tier",
    visibleRelationship: .crossgrade
)

// Show current subscription (only if offer available)
SubscriptionOfferView(
    groupID: "pro_tier",
    visibleRelationship: .current
)

// Show any plan in group
SubscriptionOfferView(
    groupID: "pro_tier",
    visibleRelationship: .all
)
可见关系:
swift
// 仅当客户可以升级时显示
SubscriptionOfferView(
    groupID: "pro_tier",
    visibleRelationship: .upgrade
)

// 仅当客户可以降级时显示
SubscriptionOfferView(
    groupID: "pro_tier",
    visibleRelationship: .downgrade
)

// 显示跨级选项(同层级,不同计费周期)
SubscriptionOfferView(
    groupID: "pro_tier",
    visibleRelationship: .crossgrade
)

// 显示当前订阅(仅当有优惠时)
SubscriptionOfferView(
    groupID: "pro_tier",
    visibleRelationship: .current
)

// 显示组内所有方案
SubscriptionOfferView(
    groupID: "pro_tier",
    visibleRelationship: .all
)

From WWDC 2025-241:17:44

来自WWDC 2025-241:17:44

With App Icon:
swift
SubscriptionOfferView(
    groupID: groupID,
    visibleRelationship: .all,
    useAppIcon: true
)
带应用图标:
swift
SubscriptionOfferView(
    groupID: groupID,
    visibleRelationship: .all,
    useAppIcon: true
)

From WWDC 2025-241:19:06

来自WWDC 2025-241:19:06

Offer Modifiers

优惠修饰符

Promotional Offer (JWS):
swift
SubscriptionStoreView(groupID: groupID)
    .subscriptionPromotionalOffer(
        for: { subscription in
            // Return offer for this subscription
            return subscription.promotionalOffers.first
        },
        signature: { subscription, offer in
            // Get JWS signature from server
            let signature = try await server.signOffer(
                productID: subscription.id,
                offerID: offer.id
            )
            return signature
        }
    )
促销优惠(JWS):
swift
SubscriptionStoreView(groupID: groupID)
    .subscriptionPromotionalOffer(
        for: { subscription in
            // 返回此订阅的优惠
            return subscription.promotionalOffers.first
        },
        signature: { subscription, offer in
            // 从服务器获取JWS签名
            let signature = try await server.signOffer(
                productID: subscription.id,
                offerID: offer.id
            )
            return signature
        }
    )

From WWDC 2025-241:12:17

来自WWDC 2025-241:12:17



Offer Codes (iOS 18.2+)

优惠码(iOS 18.2+)

Overview

概述

Offer codes now support all product types (previously subscription-only):
  • Consumables
  • Non-consumables
  • Non-renewing subscriptions
  • Auto-renewable subscriptions
优惠码现在支持所有产品类型(之前仅支持订阅):
  • 消耗型产品
  • 非消耗型产品
  • 非续订订阅
  • 自动续订订阅

Redeem in App

在应用内兑换

UIKit:
swift
func showOfferCodeSheet() {
    guard let scene = view.window?.windowScene else { return }

    StoreKit.AppStore.presentOfferCodeRedeemSheet(in: scene)
}
UIKit:
swift
func showOfferCodeSheet() {
    guard let scene = view.window?.windowScene else { return }

    StoreKit.AppStore.presentOfferCodeRedeemSheet(in: scene)
}

From WWDC 2025-241:7:38

来自WWDC 2025-241:7:38

SwiftUI:
swift
.offerCodeRedemption(isPresented: $showRedeemSheet)
SwiftUI:
swift
.offerCodeRedemption(isPresented: $showRedeemSheet)

Payment Mode

支付模式

New: .oneTime:
swift
let transaction: Transaction

if let offer = transaction.offer {
    switch offer.paymentMode {
    case .freeTrial:
        // No charge during offer period
    case .payAsYouGo:
        // Discounted price per billing period
    case .payUpFront:
        // One-time discounted price for entire duration
    case .oneTime:
        // ✨ New: One-time offer code redemption (iOS 17.2+)
    @unknown default:
        break
    }
}
新增: .oneTime:
swift
let transaction: Transaction

if let offer = transaction.offer {
    switch offer.paymentMode {
    case .freeTrial:
        // 优惠期内不收费
    case .payAsYouGo:
        // 每个计费周期的折扣价格
    case .payUpFront:
        // 整个周期的一次性折扣价格
    case .oneTime:
        // ✨ 新增:一次性优惠码兑换(iOS 17.2+)
    @unknown default:
        break
    }
}

From WWDC 2025-241:8:17

来自WWDC 2025-241:8:17

Legacy Access (iOS 15-17.1):
swift
if let offerMode = transaction.offerPaymentModeStringRepresentation {
    // String representation for older OS versions
    print(offerMode) // "oneTime"
}
旧版系统兼容(iOS 15-17.1):
swift
if let offerMode = transaction.offerPaymentModeStringRepresentation {
    // 旧版系统的字符串表示
    print(offerMode) // "oneTime"
}

From WWDC 2025-241:8:49

来自WWDC 2025-241:8:49



App Store Server Library

App Store Server Library

Overview

概述

Open-source library for signing IAP requests and decoding server API responses. Available in Swift, Java, Python, Node.js.
用于签署IAP请求和解码服务器API响应的开源库,支持Swift、Java、Python、Node.js。

Create Promotional Offer Signature

创建促销优惠签名

Swift Example:
swift
import AppStoreServerLibrary

// Configure signing
let signingKey = "YOUR_PRIVATE_KEY"
let keyID = "YOUR_KEY_ID"
let issuerID = "YOUR_ISSUER_ID"
let bundleID = "com.app.bundle"

let creator = PromotionalOfferV2SignatureCreator(
    privateKey: signingKey,
    keyID: keyID,
    issuerID: issuerID,
    bundleID: bundleID
)

// Create signature
let productID = "com.app.pro_monthly"
let offerID = "promo_winback"
let transactionID = transaction.id // Optional but recommended

let signature = try creator.createSignature(
    productIdentifier: productID,
    subscriptionOfferIdentifier: offerID,
    applicationUsername: nil,
    nonce: UUID(),
    timestamp: Date().timeIntervalSince1970,
    transactionIdentifier: transactionID
)

// Send signature to app
return signature // Compact JWS string
Swift示例:
swift
import AppStoreServerLibrary

// 配置签名
let signingKey = "YOUR_PRIVATE_KEY"
let keyID = "YOUR_KEY_ID"
let issuerID = "YOUR_ISSUER_ID"
let bundleID = "com.app.bundle"

let creator = PromotionalOfferV2SignatureCreator(
    privateKey: signingKey,
    keyID: keyID,
    issuerID: issuerID,
    bundleID: bundleID
)

// 创建签名
let productID = "com.app.pro_monthly"
let offerID = "promo_winback"
let transactionID = transaction.id // 可选但建议提供

let signature = try creator.createSignature(
    productIdentifier: productID,
    subscriptionOfferIdentifier: offerID,
    applicationUsername: nil,
    nonce: UUID(),
    timestamp: Date().timeIntervalSince1970,
    transactionIdentifier: transactionID
)

// 将签名发送到应用
return signature // 紧凑的JWS字符串

From WWDC 2025-241:12:44, 2025-249

来自WWDC 2025-241:12:44, 2025-249

Server Endpoint Example:
swift
app.get("promo-offer") { req async throws -> String in
    let productID = try req.query.get(String.self, at: "productID")
    let offerID = try req.query.get(String.self, at: "offerID")

    let signature = try creator.createSignature(
        productIdentifier: productID,
        subscriptionOfferIdentifier: offerID,
        transactionIdentifier: nil
    )

    return signature
}
服务器端点示例:
swift
app.get("promo-offer") { req async throws -> String in
    let productID = try req.query.get(String.self, at: "productID")
    let offerID = try req.query.get(String.self, at: "offerID")

    let signature = try creator.createSignature(
        productIdentifier: productID,
        subscriptionOfferIdentifier: offerID,
        transactionIdentifier: nil
    )

    return signature
}

From WWDC 2025-241:12:52

来自WWDC 2025-241:12:52



App Store Server API

App Store Server API

Set App Account Token

设置应用账户令牌

Endpoint:
PATCH /inApps/v1/transactions/{originalTransactionId}
Request Body:
json
{
  "appAccountToken": "550e8400-e29b-41d4-a716-446655440000"
}
Usage:
  • Set appAccountToken for purchases made outside your app (offer codes, App Store)
  • Update appAccountToken when account ownership changes
  • Associates transaction with customer account on your server
端点:
PATCH /inApps/v1/transactions/{originalTransactionId}
请求体:
json
{
  "appAccountToken": "550e8400-e29b-41d4-a716-446655440000"
}
用途:
  • 为应用外购买的交易设置appAccountToken(优惠码、App Store)
  • 账户所有权变更时更新appAccountToken
  • 将交易与你的服务器上的客户账户关联

From WWDC 2025-249:5:19

来自WWDC 2025-249:5:19

Get App Transaction Info

获取应用交易信息

Endpoint:
GET /inApps/v2/appTransaction/{transactionId}
Response:
json
{
  "signedAppTransactionInfo": "eyJhbGc..."
}
Usage:
  • Get app download information on server
  • Check app version, platform, environment
  • Available later in 2025
端点:
GET /inApps/v2/appTransaction/{transactionId}
响应:
json
{
  "signedAppTransactionInfo": "eyJhbGc..."
}
用途:
  • 在服务器上获取应用下载信息
  • 检查应用版本、平台、环境
  • 2025年晚些时候可用

From WWDC 2025-249:10:48

来自WWDC 2025-249:10:48

Send Consumption Information V2

发送消费信息V2

Endpoint:
PUT /inApps/v2/transactions/consumption/{transactionId}
Request Body:
json
{
  "customerConsented": true,
  "sampleContentProvided": false,
  "deliveryStatus": "DELIVERED",
  "refundPreference": "GRANT_PRORATED",
  "consumptionPercentage": 25000
}
Fields:
  • customerConsented
    (required): User consented to send consumption data
  • sampleContentProvided
    (optional): Sample provided before purchase
  • deliveryStatus
    (required): "DELIVERED" or various UNDELIVERED statuses
  • refundPreference
    (optional): "NO_REFUND", "GRANT_REFUND", "GRANT_PRORATED"
  • consumptionPercentage
    (optional): 0-100000 (millipercent, e.g., 25000 = 25%)
Prorated Refund:
  • New in 2025
  • Supports partial consumption (consumables, non-consumables, non-renewing)
  • For auto-renewable subscriptions, App Store calculates based on time remaining
端点:
PUT /inApps/v2/transactions/consumption/{transactionId}
请求体:
json
{
  "customerConsented": true,
  "sampleContentProvided": false,
  "deliveryStatus": "DELIVERED",
  "refundPreference": "GRANT_PRORATED",
  "consumptionPercentage": 25000
}
字段说明:
  • customerConsented
    (必填): 用户同意发送消费数据
  • sampleContentProvided
    (可选): 购买前提供了样本
  • deliveryStatus
    (必填): "DELIVERED"或各种UNDELIVERED状态
  • refundPreference
    (可选): "NO_REFUND"、"GRANT_REFUND"、"GRANT_PRORATED"
  • consumptionPercentage
    (可选): 0-100000(毫百分比,例如25000=25%)
按比例退款:
  • 2025年新增
  • 支持部分消费的退款(消耗型、非消耗型、非续订订阅)
  • 对于自动续订订阅,App Store会根据剩余时间计算退款金额

From WWDC 2025-249:16:09

来自WWDC 2025-249:16:09

Refund Notifications

退款通知

REFUND Notification:
json
{
  "notificationType": "REFUND",
  "data": {
    "signedTransactionInfo": "...",
    "refundPercentage": 75,
    "revocationType": "REFUND_PRORATED"
  }
}
revocationType Values:
  • REFUND_FULL
    : 100% refund - revoke all access
  • REFUND_PRORATED
    : Partial refund - revoke proportional access
  • FAMILY_REVOKE
    : Family Sharing removed - revoke access
REFUND通知:
json
{
  "notificationType": "REFUND",
  "data": {
    "signedTransactionInfo": "...",
    "refundPercentage": 75,
    "revocationType": "REFUND_PRORATED"
  }
}
revocationType取值:
  • REFUND_FULL
    : 全额退款 - 撤销所有权限
  • REFUND_PRORATED
    : 部分退款 - 按比例撤销权限
  • FAMILY_REVOKE
    : 家庭共享权限被移除 - 撤销权限

From WWDC 2025-249:20:17

来自WWDC 2025-249:20:17



Edge Cases

边缘情况

Family Sharing

家庭共享

Detect Family Shared Transactions:
swift
// appAccountToken is NOT available for family shared transactions
let transaction: Transaction

if transaction.appAccountToken == nil {
    // Might be family shared (or appAccountToken not set)
    // Check ownershipType (if available)
}
Subscription Status for Family Sharing:
swift
// Each family member has unique appTransactionID
// Use appTransactionID to identify individual family members
检测家庭共享交易:
swift
// 家庭共享交易的appAccountToken不可用
let transaction: Transaction

if transaction.appAccountToken == nil {
    // 可能是家庭共享交易(或未设置appAccountToken)
    // 检查ownershipType(如果可用)
}
家庭共享的订阅状态:
swift
// 每个家庭成员有唯一的appTransactionID
// 使用appTransactionID识别单个家庭成员

From WWDC 2025-241:1:54

来自WWDC 2025-241:1:54

Refunds

退款

Handle Refund:
swift
func handleTransaction(_ transaction: Transaction) async {
    if let revocationDate = transaction.revocationDate {
        // Transaction was refunded
        print("Refunded on \(revocationDate)")

        switch transaction.revocationReason {
        case .developerIssue:
            // Refund due to app issue
        case .other:
            // Other refund reason
        @unknown default:
            break
        }

        // Revoke entitlement
        await revokeEntitlement(for: transaction.productID)
    }
}
处理退款:
swift
func handleTransaction(_ transaction: Transaction) async {
    if let revocationDate = transaction.revocationDate {
        // 交易已退款
        print("Refunded on \(revocationDate)")

        switch transaction.revocationReason {
        case .developerIssue:
            // 因应用问题退款
        case .other:
            // 其他退款原因
        @unknown default:
            break
        }

        // 撤销权限
        await revokeEntitlement(for: transaction.productID)
    }
}

Advanced Commerce API

Advanced Commerce API

Check if Transaction Uses Advanced Commerce:
swift
if transaction.advancedCommerceInfo != nil {
    // Transaction from Advanced Commerce API
    // Large catalogs, creator experiences, subscriptions with add-ons
}
More Info: Visit Advanced Commerce API documentation
检查交易是否使用Advanced Commerce:
swift
if transaction.advancedCommerceInfo != nil {
    // 来自Advanced Commerce API的交易
    // 适用于大型商品目录、创作者体验、带附加项的订阅
}
更多信息: 访问Advanced Commerce API文档

From WWDC 2025-241:4:51

来自WWDC 2025-241:4:51

Win-Back Offers

赢回优惠

Show Win-Back for Expired Subscription:
swift
let renewalInfo: RenewalInfo

if renewalInfo.expirationReason == .didNotConsentToPriceIncrease {
    // Perfect time for win-back offer!
    SubscriptionOfferView(
        groupID: groupID,
        visibleRelationship: .current
    )
    .preferredSubscriptionOffer(offer: winBackOffer)
}
为过期订阅展示赢回优惠:
swift
let renewalInfo: RenewalInfo

if renewalInfo.expirationReason == .didNotConsentToPriceIncrease {
    // 展示赢回优惠的最佳时机!
    SubscriptionOfferView(
        groupID: groupID,
        visibleRelationship: .current
    )
    .preferredSubscriptionOffer(offer: winBackOffer)
}

From WWDC 2025-241:5:38

来自WWDC 2025-241:5:38



Testing

测试

StoreKit Configuration File

StoreKit配置文件

Create:
  1. Xcode → File → New → StoreKit Configuration File
  2. Add products (consumables, non-consumables, subscriptions)
  3. Configure prices, images, descriptions
Enable in Scheme:
  1. Scheme → Edit Scheme → Run → Options
  2. StoreKit Configuration: Select .storekit file
Test Scenarios:
  • Successful purchases
  • Cancelled purchases
  • Subscription renewals (accelerated time)
  • Subscription expirations
  • Upgrades/downgrades
  • Offer code redemptions
  • Family Sharing (enable in config file)
创建:
  1. Xcode → File → New → StoreKit Configuration File
  2. 添加产品(消耗型、非消耗型、订阅)
  3. 配置价格、图片、描述
在Scheme中启用:
  1. Scheme → Edit Scheme → Run → Options
  2. StoreKit Configuration: 选择.storekit文件
测试场景:
  • 成功购买
  • 取消购买
  • 订阅续订(加速时间)
  • 订阅过期
  • 升级/降级
  • 优惠码兑换
  • 家庭共享(在配置文件中启用)

Sandbox Testing

Sandbox测试

Create Sandbox Account:
  1. App Store Connect → Users and Access → Sandbox Testers
  2. Create test Apple ID
  3. Sign in on device Settings → App Store → Sandbox Account
Clear Purchase History:
  • Settings → App Store → Sandbox Account → Clear Purchase History

创建Sandbox账户:
  1. App Store Connect → Users and Access → Sandbox Testers
  2. 创建测试Apple ID
  3. 在设备的Settings → App Store → Sandbox Account中登录
清除购买历史:
  • Settings → App Store → Sandbox Account → Clear Purchase History

Migration from StoreKit 1

从StoreKit 1迁移

Key Changes

主要变化

Delegates → Async/Await:
swift
// StoreKit 1
class StoreObserver: NSObject, SKPaymentTransactionObserver {
    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        // Handle transactions
    }
}

// StoreKit 2
for await result in Transaction.updates {
    // Handle transactions
}
Receipt → Transaction:
swift
// StoreKit 1
let receiptURL = Bundle.main.appStoreReceiptURL
let receipt = try Data(contentsOf: receiptURL!)

// StoreKit 2
let transaction: Transaction // Automatically verified!
Products → Product.products(for:):
swift
// StoreKit 1
let request = SKProductsRequest(productIdentifiers: Set(productIDs))
request.delegate = self
request.start()

// StoreKit 2
let products = try await Product.products(for: productIDs)

代理 → Async/Await:
swift
// StoreKit 1
class StoreObserver: NSObject, SKPaymentTransactionObserver {
    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        // 处理交易
    }
}

// StoreKit 2
for await result in Transaction.updates {
    // 处理交易
}
收据 → Transaction:
swift
// StoreKit 1
let receiptURL = Bundle.main.appStoreReceiptURL
let receipt = try Data(contentsOf: receiptURL!)

// StoreKit 2
let transaction: Transaction // 自动验证!
Products → Product.products(for:):
swift
// StoreKit 1
let request = SKProductsRequest(productIdentifiers: Set(productIDs))
request.delegate = self
request.start()

// StoreKit 2
let products = try await Product.products(for: productIDs)

Resources

资源

WWDC: 2025-241, 2025-249, 2024-10061, 2024-10062, 2024-10110, 2023-10013, 2023-10140, 2022-10007, 2022-110404, 2021-10114
Docs: /storekit
Skills: axiom-in-app-purchases

WWDC: 2025-241, 2025-249, 2024-10061, 2024-10062, 2024-10110, 2023-10013, 2023-10140, 2022-10007, 2022-110404, 2021-10114
文档: /storekit
技能: axiom-in-app-purchases

Quick Reference

快速参考

Product Types

产品类型

  • .consumable
    - Can purchase multiple times (coins, boosts)
  • .nonConsumable
    - Purchase once, own forever (premium, level packs)
  • .autoRenewable
    - Auto-renewing subscriptions
  • .nonRenewing
    - Fixed duration subscriptions
  • .consumable
    - 可重复购买(游戏币、增益)
  • .nonConsumable
    - 一次性购买永久拥有(高级功能、关卡包)
  • .autoRenewable
    - 自动续订订阅
  • .nonRenewing
    - 固定时长订阅

Transaction States

交易状态

  • success
    - Purchase completed
  • userCancelled
    - User tapped cancel
  • pending
    - Requires action (Ask to Buy)
  • success
    - 购买完成
  • userCancelled
    - 用户点击取消
  • pending
    - 需要操作(请求购买)

Subscription States

Subscription States

  • .subscribed
    - Active subscription
  • .expired
    - Subscription ended
  • .inGracePeriod
    - Billing issue, access maintained
  • .inBillingRetryPeriod
    - Apple retrying payment
  • .revoked
    - Family Sharing removed
  • .subscribed
    - 活跃订阅
  • .expired
    - 订阅已结束
  • .inGracePeriod
    - 账单问题,保留权限
  • .inBillingRetryPeriod
    - Apple正在重试支付
  • .revoked
    - 家庭共享权限被移除

Essential Calls

核心调用

swift
// Load products
try await Product.products(for: productIDs)

// Purchase
try await product.purchase(confirmIn: scene)

// Current entitlements
Transaction.currentEntitlements(for: productID)

// Transaction listener
Transaction.updates

// Subscription status
Product.SubscriptionInfo.status(for: groupID)

// Restore purchases
try await AppStore.sync()

// Finish transaction (REQUIRED)
await transaction.finish()
swift
// 加载产品
try await Product.products(for: productIDs)

// 购买
try await product.purchase(confirmIn: scene)

// 当前权限
Transaction.currentEntitlements(for: productID)

// 交易监听器
Transaction.updates

// 订阅状态
Product.SubscriptionInfo.status(for: groupID)

// 恢复购买
try await AppStore.sync()

// 完成交易(必填)
await transaction.finish()