WWDC Quick Look 💓 By SwiftGGTeam
What's new in Apple In-App Purchase

What's new in Apple In-App Purchase

观看原视频

Highlight

Apple 为年度订阅新增了 12 个月承诺期的按月付费方案,用户按月扣款降低决策门槛,开发者获得锁定的长期订阅收入;StoreKit 新增 PricingTermsbillingPlanType API 用于展示和购买,服务端 JWSTransaction 新增 commitmentInfo 字段追踪履约进度,App Store Connect 审核提交流程也统一支持将 IAP 与其他审核项打包提交。

核心内容

年度订阅的定价困境

做订阅业务的开发者都遇到过这个难题:推年度订阅,用户看到一次性扣款金额直接放弃;推月度订阅,用户随时取消,LTV(生命周期价值)难以保障。两种方案各有利弊,但没有中间地带。

Apple 在 iOS 26.5 给出了官方解法:12 个月承诺期的按月订阅(Monthly subscriptions with a 12-month commitment)。用户每个月支付较小金额,但合约锁定 12 个月。开发者获得确定的长期收入预期,用户获得更低的决策门槛。

这个方案在 App Store Connect 中直接配置。选择一个一年期的自动续订订阅,在 “monthly with a 12-month commitment availability” 下设置可用性,按步骤完成配置。每个计费方案可以独立配置订阅优惠,比如只为承诺期用户提供免费试用。

01:53

用 StoreKit 展示新的计费方案

配置完成后,StoreKit 通过新的 PricingTerms 属性暴露计费方案信息。SubscriptionInfo.pricingTerms 返回一个数组,包含该产品的所有可用计费方案。默认的年度预付方案类型是 .upFront,如果配置了按月承诺,数组中会多出一个 .monthly 类型的对象。

在 SwiftUI 中使用 SubscriptionStoreView 时,可以通过 .preferredSubscriptionPricingTerms 修饰符筛选并优先展示按月承诺方案。

03:29

服务端追踪承诺期进度

用户订阅后,服务端需要知道当前处于承诺期的第几个月、还剩多少期。JWSTransaction 和 JWSRenewalInfo 新增了 commitmentInfo 字段,包含 billingPeriodNumber(当前期数)、totalBillingPeriods(总期数)、commitmentExpiresDate(承诺到期时间)等信息。

关键点:如果 billingPeriodNumber 小于 totalBillingPeriods,用户仍在承诺期内。此时即使用户在系统设置中取消自动续订,Apple 仍会按月扣款直到承诺期结束。服务端不能提前剥夺用户权益。

07:45

Offer Code Redemption API 升级

优惠码兑换接口现在返回 VerificationResult,兑换成功时直接拿到 transaction 对象,失败时拿到具体错误信息。不再需要依赖 Transaction.updates 去盲猜兑换结果。

SwiftUI 中使用 .offerCodeRedemption 修饰符,UIKit 中使用 presentOfferCodeRedeemSheet。Xcode 27 的 StoreKit Testing 支持测试新的兑换 API。

09:58

App Store Connect 审核提交流验统一

IAP 审核提交现在可以和其他审核项(In-App Events、自定义产品页面、产品页面优化)打包成一个 Submission 统一提交。提交后在集中视图中查看所有审核项的状态。

App Store Connect API 的 reviewSubmissions 接口扩展支持 IAP、订阅和订阅组资源。旧的 IAP 专属提交 API 将被废弃,建议迁移到新的 reviewSubmissionreviewSubmissionItems 资源。

10:42

详细内容

在 StoreKit 视图中展示按月承诺方案

03:29

import StoreKit
import SwiftUI

struct SubscriptionStore: View {
    var body: some View {
        SubscriptionStoreView(groupID: "3F19ED53") {
            // Custom marketing content
        }
        .preferredSubscriptionPricingTerms { _, subscriptionInfo in
            subscriptionInfo.pricingTerms.first {
                $0.billingPlanType == .monthly
            }
        }
    }
}

关键点:

  • SubscriptionStoreView 自动从 App Store 加载产品元数据并适配各平台布局
  • .preferredSubscriptionPricingTerms 是 iOS 26.5 新增的修饰符,用于筛选优先展示的计费方案
  • subscriptionInfo.pricingTerms 返回所有可用计费方案,.upFront 始终存在,.monthly 仅在配置了承诺期时出现
  • 如果不加筛选,系统默认展示 .upFront,按月承诺的低价优势无法体现

自定义商店 UI 中获取定价信息并购买

04:02

import StoreKit

var product: Product?
// Fetch and assign product

let pricingTerms = product?.subscription?.pricingTerms
    .first(where: { $0.billingPlanType == .monthly })

if let pricingTerms {
    let monthlyPrice = pricingTerms.billingDisplayPrice
    let totalCommitmentPrice = pricingTerms.commitmentInfo.price
    // Display both monthly and total commitment price to the customer
}

let result = try? await product?.purchase(options: [.billingPlanType(.monthly)])
switch result {
    // Verify the transaction, give the customer access to
    // the purchased content, and then finish the transaction
}

关键点:

  • billingDisplayPrice 是展示给用户的月供金额
  • commitmentInfo.price 是 12 个月的总价,应在 UI 中同时展示月供和总价
  • 购买时传入 .billingPlanType(.monthly) 选项,指定使用按月承诺方案
  • 计费方案元数据只在用户所在市场的商店可用时才会返回

管理订阅 Sheet

05:05

import SwiftUI
import StoreKit

struct ManageSubscriptionsButton: View {
    let subscriptionGroupID: String
    @State var presentingManageSubscriptionsSheet: Bool = false

    var body: some View {
        Button("Manage Subscriptions") {
            presentingManageSubscriptionsSheet = true
        }
        .manageSubscriptionsSheet(
            isPresented: $presentingManageSubscriptionsSheet,
            subscriptionGroupID: subscriptionGroupID
        )
    }
}

关键点:

  • .manageSubscriptionsSheet 是 SwiftUI 中展示订阅管理界面的方式
  • UIKit 中对应 showManageSubscriptions API
  • 用户可以在管理界面查看剩余付款期数和承诺期续订时间
  • App Store 自动为承诺期订阅提供新的管理 UI

服务端 JWSTransaction 中的承诺期信息

07:45

{
    "expiresDate": 1783503660000,
    "price": 10990,
    "productId": "plus.pro.annual",
    "purchaseDate": 1780911660000,
    "type": "Auto-Renewable Subscription",
    "billingPlanType": "MONTHLY",
    "commitmentInfo": {
        "billingPeriodNumber": 1,
        "totalBillingPeriods": 12,
        "commitmentExpiresDate": 1812447660000,
        "commitmentPrice": 131880
    }
}

关键点:

  • billingPlanType"MONTHLY" 表示这是按月承诺订阅
  • commitmentInfo.billingPeriodNumber 表示当前是第几个计费周期
  • commitmentInfo.totalBillingPeriods 固定为 12
  • commitmentInfo.commitmentExpiresDate 是整个承诺期的结束时间
  • 对于 .upFront 类型的交易,commitmentInfonil

服务端 JWSRenewalInfo 中的承诺期续订信息

07:59

{
    "renewalBillingPlanType": "MONTHLY",
    "commitmentInfo": {
        "commitmentAutoRenewProductId": "plus.standard.annual",
        "commitmentAutoRenewStatus": 0,
        "commitmentRenewalDate": 1812447660000,
        "commitmentRenewalPrice": 10990,
        "commitmentRenewalBillingPlanType": "BILLED_UPFRONT"
    }
}

关键点:

  • commitmentAutoRenewStatus 为 0 表示用户设置了承诺期结束后不续订
  • commitmentRenewalBillingPlanType 表示承诺期结束后的续订方案类型
  • 这个例子中用户选择在承诺期结束后转为一次性预付的年度订阅
  • commitmentInfo 只在当前订阅处于承诺期时才会出现在 renewal info 中

优惠码兑换 Sheet

09:58

struct OfferCodeRedemption: View {
    @State var presentingOfferCodeSheet: Bool = false

    var body: some View {
        Button("Redeem Offer Code") {
            presentingOfferCodeSheet = true
        }
        .offerCodeRedemption(options: [], isPresented: $presentingOfferCodeSheet) { result in
            switch result {
            case .success(let verificationResult):
                switch verificationResult {
                    // Verify the transaction, give the customer access to
                    // the purchased content, and then finish the transaction
                }
            case .failure(let error):
                // Handle error
            }
        }
    }
}

关键点:

  • .offerCodeRedemption 现在返回 VerificationResult,兑换结果明确可追踪
  • options 参数接受 RedeemOption 值数组,用于配置兑换行为
  • 兑换成功时 verificationResult 中包含 transaction 对象
  • UIKit 中使用 presentOfferCodeRedeemSheet 实现相同功能

核心启发

为订阅产品添加按月承诺方案

做什么:为现有的年度订阅产品配置 12 个月承诺期的按月付费选项,并在 App 中优先展示。

为什么值得做:降低用户的首次付费门槛,同时锁定 12 个月的订阅收入。对于价格敏感型用户,月供金额比年度总价更容易接受。

怎么开始:在 App Store Connect 中找到现有的一年期自动续订订阅,设置 “monthly with a 12-month commitment” 可用性。在代码中用 .preferredSubscriptionPricingTerms 筛选 .monthly 方案并优先展示。

构建承诺期进度追踪 UI

做什么:在 App 的账户或订阅管理页面中,展示用户当前处于承诺期的第几个月、还剩多少期。

为什么值得做:透明度提升用户信任,减少”我不知道签了 12 个月”的客诉。同时可以帮助用户在承诺期结束前做出续订决策。

怎么开始:从 Transaction.currentEntitlements 获取最新交易,读取 commitmentInfo 字段,用 billingPeriodNumbertotalBillingPeriods 计算进度并展示。

升级优惠码兑换逻辑

做什么:将现有的优惠码兑换调用点迁移到新的 API,获得明确的兑换结果回调。

为什么值得做:旧 API 兑换完成后没有直接结果,需要依赖 Transaction.updates 异步监听,实现复杂且容易出错。新 API 直接返回成功或失败。

怎么开始:搜索代码中的 presentOfferCodeRedeemSheet 调用,替换为带 VerificationResult 回调的新版本。在 Xcode 27 的 StoreKit Testing 中测试兑换流程。

统一审核提交流程

做什么:将 IAP 审核提交纳入统一的 reviewSubmissions 工作流,和 App 二进制文件、In-App Events 一起打包提交。

为什么值得做:避免”App 已上架但 IAP 还在审核中”的尴尬情况。统一提交意味着所有审核项一起过审或一起被拒,状态完全同步。

怎么开始:检查现有的 CI/CD 脚本,将旧的 IAP 专属提交 API 调用迁移到 reviewSubmissionsreviewSubmissionItems 资源。

准备订阅 Bundles 和 Suites

做什么:如果你有多个 App 或多种订阅产品,提前规划订阅 Bundle 或 Suite 的产品结构。

为什么值得做:Bundle 将多个可单独购买的订阅打包以优惠价格销售,Suite 将仅作为整体存在的订阅组提供给一套相关 App。两者都能提升订阅收入。

怎么开始:在 Xcode 27 中测试 Bundles 和 Suites 的 API,关注 Apple 下半年公布的商业化细节。

关联 Session

评论

GitHub Issues · utterances