How do you show a subscriber their status (renewing, in grace, will expire) and let them change or cancel the plan?

28.7.4 · L3 · Interview

Predict first

Does code that reads status, renewal info and opens the manage-subscriptions sheet compile against the iOS SDK?

import UIKit
import StoreKit

func summary(_ groupID: String) async -> String {
    guard let statuses = try? await Product.SubscriptionInfo.status(for: groupID),
          let best = statuses.first(where: { $0.state == .subscribed || $0.state == .inGracePeriod || $0.state == .inBillingRetryPeriod }) ?? statuses.first
    else { return "no subscription" }
    var parts = ["state \(best.state.rawValue)"]
    if case .verified(let renewal) = best.renewalInfo {
        parts.append("auto-renew \(renewal.willAutoRenew)")
        if let until = renewal.gracePeriodExpirationDate { parts.append("grace until \(until)") }
        if let pending = renewal.autoRenewPreference { parts.append("renews as \(pending)") }
        if let reason = renewal.expirationReason { parts.append("expired because \(reason)") }
    }
    if case .verified(let t) = best.transaction, let exp = t.expirationDate { parts.append("expires \(exp)") }
    return parts.joined(separator: ", ")
}
@MainActor func manage() async throws {
    #if os(iOS)
    if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene { try await AppStore.showManageSubscriptions(in: scene) }
    #endif
}
  • Yes, it compiles
  • No: status(for:) is not available
  • No: showManageSubscriptions needs a UIViewController

Mental model. The status is a membership card with an expiry date and a note about next month’s plan. You read it to greet the member honestly; cancelling or changing the plan is done at the club’s own front desk (the system sheet), not at yours.

Read the group’s status with Product.SubscriptionInfo.status(for: groupID), which returns the statuses (usually one) for the user’s subscription group. Choose the one that grants access (.subscribed, .inGracePeriod or .inBillingRetryPeriod, see the renewal-state question, Q1001) and then read two verified payloads: the renewal info (willAutoRenew, autoRenewPreference (the plan it will renew as, which differs after a downgrade), gracePeriodExpirationDate, expirationReason) and the transaction (expirationDate). With them you can write honest copy: “Renews on 3 June”, “Your plan will change to Monthly on 3 June”, “Payment failed: update your payment method by 7 June to keep access”, “Ends on 3 June”. The check compiles that reading code.

Do not build your own cancel or change screens: send the user to the system. AppStore.showManageSubscriptions(in: scene) (UIKit and async code) or the SwiftUI modifier .manageSubscriptionsSheet(isPresented:) opens the system sheet where the user cancels, changes plan, or fixes billing; when it closes, refresh the status (the updates stream also delivers the change). Put a Manage subscription row in settings, since a user who cannot find cancellation will do it through Apple, or worse, dispute the charge. For the win-back moment, read expirationReason and the offer eligibility to decide what to show an expired subscriber. Treat the status as a cache of the store’s truth: recompute on launch, on foreground, and on Transaction.updates, and never store “isSubscribed = true” permanently.

The calls exist with these shapes; no subscription was read.

Common traps

  • Hand-rolling a cancel flow. Only the system sheet can change the subscription; send users there.
  • Saving “is subscribed” in UserDefaults. It goes stale at the next renewal, refund or lapse; derive it from the status each time.

They might follow up with

  • How do I show “your plan changes on…”? Compare autoRenewPreference with the current product id in the renewal info.
  • What triggers a refresh after the user manages the plan? The sheet closing, plus the Transaction.updates and status updates; refresh on all of them.

Related questions

  • 28.7.3 What do the SwiftUI StoreKit views (ProductView, StoreView, SubscriptionStoreView) give you for free, and…
  • 28.7.5 How do you tie a purchase to your own user with appAccountToken, and why derive the token instead of storing…
  • 28.2.1 What are the subscription renewal states, how do grace period and billing retry differ, and who should keep…
  • 28.2.2 How do subscription groups and levels govern upgrades, and what are introductory, promotional, offer-code and…

← What do the SwiftUI StoreKit views (ProductView, StoreView…   How do you tie a purchase to your own user with appAccountToken, and… →