Подписки StoreKit 2 в SwiftUI: практическое руководство

Всё, что нужно, чтобы продавать подписку в приложении на SwiftUI со StoreKit 2, в одном небольшом классе Store, плюс правила для пейвола, которые проверяет App Review.

Mustafa GüderОснователь GuderTech4 мин чтения
storekit-2-subscriptions-swiftui.md

StoreKit 2 превратил встроенные покупки из лабиринта делегатов в несколько строк асинхронного Swift. Вот схема, которую мы используем в своих приложениях: один небольшой класс Store, пейвол и несколько правил, которые устраивают App Review.

1. Создайте продукты

В App Store Connect создайте группу подписок и добавьте планы, например месячный и годовой. Запишите идентификаторы продуктов. Для локального тестирования добавьте в Xcode StoreKit Configuration File с теми же идентификаторами и выберите его в схеме. Тогда в симуляторе можно покупать, продлевать и отменять подписку без sandbox-аккаунта.

2. Один Store на всё приложение

import StoreKit

@MainActor
final class Store: ObservableObject {
    @Published private(set) var products: [Product] = []
    @Published private(set) var isPro = false

    private let ids = ["com.example.pro.monthly", "com.example.pro.yearly"]
    private var updates: Task<Void, Never>?

    init() {
        // Renewals, refunds and purchases made on other devices arrive here.
        updates = Task { [weak self] in
            for await result in Transaction.updates {
                await self?.handle(result)
            }
        }
        Task {
            await loadProducts()
            await refreshEntitlements()
        }
    }

    func loadProducts() async {
        do {
            products = try await Product.products(for: ids).sorted { $0.price < $1.price }
        } catch {
            print("Could not load products: \(error)")
        }
    }

    func buy(_ product: Product) async throws {
        switch try await product.purchase() {
        case .success(let result):
            await handle(result)
        case .userCancelled, .pending:
            break
        @unknown default:
            break
        }
    }

    func restore() async {
        try? await AppStore.sync()
        await refreshEntitlements()
    }

    func refreshEntitlements() async {
        var active = false
        for await result in Transaction.currentEntitlements {
            if case .verified(let transaction) = result, transaction.revocationDate == nil {
                active = true
            }
        }
        isPro = active
    }

    private func handle(_ result: VerificationResult<Transaction>) async {
        guard case .verified(let transaction) = result else { return }
        await transaction.finish()
        await refreshEntitlements()
    }
}

Создайте его один раз, например как @StateObject в вашем App, и передавайте вниз через .environmentObject. Начинайте слушать Transaction.updates как можно раньше, иначе покупки, завершённые вне приложения, можно пропустить.

3. Почему важна проверка

Каждая транзакция приходит в обёртке VerificationResult. StoreKit проверяет подпись Apple на устройстве, поэтому .verified означает, что покупка настоящая. Открывайте контент только для проверенных транзакций и всегда вызывайте finish() после выдачи.

4. Пейвол в несколько строк

В iOS 17 и новее SubscriptionStoreView даёт готовый пейвол, удобный для App Review:

SubscriptionStoreView(groupID: "YOUR_GROUP_ID") {
    VStack(spacing: 8) {
        Text("Go Pro").font(.largeTitle.bold())
        Text("Unlimited exports and every template.")
    }
}
.storeButton(.visible, for: .restorePurchases)

Если делаете свой, показывайте цену и период каждого плана через product.displayPrice, добавьте ссылки на условия использования и конфиденциальность и кнопку восстановления.

5. Пробные периоды и вводные предложения

Вводные предложения настраиваются в App Store Connect. Чтобы показывать «Начать бесплатный период» только тем, кто может им воспользоваться, сначала проверьте право:

let eligible = await product.subscription?.isEligibleForIntroOffer ?? false

6. Что App Review проверяет в пейволе

  • Цена и период оплаты понятны до покупки
  • Длительность бесплатного периода и что будет после
  • Работающая кнопка восстановления покупок
  • Ссылки на условия использования и политику конфиденциальности
  • Никаких тёмных паттернов вроде спрятанной кнопки закрытия

Остальное — в нашем списке самых частых отказов в App Store.

7. Серверная часть, когда она нужна

Для приложения на одном устройстве хватает прав доступа на устройстве. Если пользователи входят на нескольких платформах или вам нужны данные о доходе, добавьте в бэкенд App Store Server Notifications V2, чтобы сервер сразу узнавал о продлениях и возвратах.

Частые вопросы

Нужна ли проверка чеков со StoreKit 2?

Не на устройстве: StoreKit 2 сам проверяет подписанные транзакции. Сервер нужен, только если вы делите доступ между платформами или аккаунтами.

Как тестировать подписки без оплаты?

Для локальных тестов используйте StoreKit Configuration File в Xcode, для сквозных — sandbox-аккаунт или TestFlight. В sandbox продления идут по ускоренному графику.

Какая минимальная версия iOS для StoreKit 2?

StoreKit 2 работает на iOS 15 и новее. SubscriptionStoreView и другим витринам нужна iOS 17.

Сколько Apple берёт с подписок?

По умолчанию 30 %, 15 % для участников Small Business Program и 15 % с подписки после первого года подписчика.

ПоделитьсяXLinkedInWhatsApp
Mustafa Güder

Об авторе

Mustafa Güder

Mustafa Güder — основатель GuderTech и iOS-разработчик. После двух лет учёбы в Австралии он вернулся в Адану и создал Scevia, Paw & Home и Street Legends.

Учёба в Австралии, 2 года Адана, Турция

Все статьи