> Работали ли с подписками и покупками через StoreKit или другие сервисы (iOS, Swift)
Уровень: senior · Роль: mobile · Категория: Технические вопросы
Компании: КРЕЙТЕКС
Стек: iOS, Swift
> Пример ответа
Короткий ответ
Да, работал с StoreKit 2 и StoreKit 1: оформление подписок (auto-renewable), non-consumable покупок, восстановление транзакций, обработка ошибок (например, StoreKitError, .pending, .failed), проверка чека на сервере через App Store Server API, а также с SKProductsRequest для загрузки продуктов. Использовал Transaction.updates для обработки изменений статуса подписки в реальном времени и AppTransaction для валидации. Также есть опыт с RevenueCat для упрощения работы с подписками и аналитикой.
Подробное объяснение
StoreKit 2 - это современный API, который сильно упрощает работу по сравнению с StoreKit 1. Основные сущности: Product, Transaction, Entitlement, AppStore. Ключевое отличие - асинхронная модель на Swift Concurrency: Product.products(for:), product.purchase(), Transaction.currentEntitlements и Transaction.updates (поток изменений транзакций). В StoreKit 1 приходилось вручную реализовывать SKPaymentTransactionObserver, обрабатывать SKPaymentTransactionState, а также следить за SKPaymentQueue.
Для подписок важно понимать:
Product.SubscriptionInfo- период, группу, уровень (tier),isFamilyShareable.Transaction.revocationDate- для отмены подписки.Transaction.expirationDate- для проверки активного статуса.Transaction.ownershipType-.purchasedили.familyShared.
Валидация чека: на клиенте можно использовать AppTransaction.shared и Transaction.latest(for:), но для критичных сценариев (например, premium-доступ) лучше проверять на сервере через App Store Server API (endpoint /verifyReceipt устарел, теперь используется App Store Server API с JWT-авторизацией).
Также стоит упомянуть StoreKit Configuration File для локального тестирования и StoreKitTest framework для UI-тестов покупок.
На практике
В реальном проекте обычно:
- Создаётся сервис-обёртка (например,
StoreManager), который инкапсулирует всю логику StoreKit. - На старте приложения подписываемся на
Transaction.updates- это важно для обработки покупок, сделанных на других устройствах, и для восстановления. - Проверяем
Transaction.currentEntitlements- это источник прав (entitlements) для текущего пользователя. - Для каждой покупки вызываем
product.purchase(), обрабатываем результат:.success(let verification),.userCancelled,.pending. - После успешной покупки обязательно завершаем транзакцию через
await transaction.finish(). - Для подписок - слушаем
Transaction.updatesи обновляем UI при изменении статуса (например, при истечении или продлении).
Также важно учитывать:
- Песочница (sandbox) и TestFlight имеют разные правила для подписок (например, скорость продления).
StoreKit 2не поддерживаетSKPaymentQueueнапрямую, но можно использоватьTransaction.updatesдля совместимости.- Для аналитики и A/B-тестов часто используют RevenueCat или Adapty - они берут на себя управление транзакциями и дают вебхуки.
Пример кода
SWIFTimport StoreKitfinal class StoreManager {static let shared = StoreManager()private var updatesTask: Task<Void, Never>?private(set) var products: [Product] = []private init() {updatesTask = listenForTransactions()}func loadProducts() async throws {let productIDs = Set(["com.example.subscription.monthly", "com.example.premium"])products = try await Product.products(for: productIDs)}func purchase(_ product: Product) async throws -> Bool {let result = try await product.purchase()switch result {case .success(let verification):let transaction = try checkVerified(verification)await transaction.finish()return truecase .userCancelled:return falsecase .pending:// например, ожидание подтверждения родителя (Ask to Buy)return false@unknown default:return false}}func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {switch result {case .unverified:throw StoreError.failedVerificationcase .verified(let safe):return safe}}func isSubscriptionActive() async -> Bool {guard let product = products.first(where: { $0.type == .autoRenewable }) else {return false}guard let status = try? await product.subscription?.status.first else {return false}switch status.state {case .subscribed, .inGracePeriod:return truedefault:return false}}private func listenForTransactions() -> Task<Void, Never> {Task.detached { [weak self] infor await update in Transaction.updates {do {let transaction = try self?.checkVerified(update)// обновляем UI, отправляем на серверawait transaction?.finish()} catch {// логируем ошибку}}}}enum StoreError: Error {case failedVerification}}
Как отвечать на собеседовании
Начните с того, что у вас есть практический опыт с StoreKit 2, и упомяните, что знаете отличия от StoreKit 1. Расскажите про конкретный кейс: например, как реализовывали подписку с пробным периодом, как обрабатывали восстановление покупок, как решали проблему с истекшими подписками. Подчеркните, что понимаете важность серверной валидации и умеете работать с App Store Server API. Если использовали RevenueCat - скажите, что понимаете, зачем он нужен (упрощение, аналитика, кросс-платформа), но также умеете работать с чистым StoreKit.
Что проверяет интервьюер
- Понимание жизненного цикла транзакции: от покупки до
finish(). - Знание механизма восстановления покупок и
Transaction.updates. - Понимание разницы между
currentEntitlementsиTransaction.latest. - Умение обрабатывать ошибки и edge cases:
pending,userCancelled,unverified. - Знание серверной валидации и почему она важна.
- Понимание типов продуктов: consumable, non-consumable, auto-renewable subscription, non-renewing subscription.
Типичные ошибки
- Не вызывать
transaction.finish()- это приводит к повторным уведомлениям и проблемам с App Store. - Полагаться только на клиентскую проверку чека без серверной валидации.
- Игнорировать
Transaction.updates- тогда не обрабатываются покупки с других устройств и изменения статуса подписки. - Путать
currentEntitlements(текущие права) сTransaction.latest(последняя транзакция по продукту). - Не обрабатывать случай
.pending(например, Ask to Buy) - пользователь думает, что покупка не прошла, а она ещё в обработке. - Использовать устаревший
verifyReceiptвместо App Store Server API.
> Похожие задачи по mobile
Что такое селектор в iOS и как работает target-action?
Приходилось ли определять органических и неорганических пользователей в приложении
Как справиться с JSON, если поля не совпадают со структурой в Swift
Что такое Set, его особенности, зачем нужен и какие есть реализации
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью