Auto-renewable subscriptions — самый сложный тип IAP. Не потому что StoreKit сложен сам по себе, а потому что вокруг него вырастает целая экосистема: grace periods, billing retry, downgrade/upgrade между тирами, promo offers, introductory pricing, и наконец — обработка churn через expirationIntent. Ошибка на любом этапе ведёт к потерям дохода. Мы внедряем подписки под ключ за 3–5 дней, гарантируя корректную логику на всех этапах жизненного цикла.
Жизненный цикл подписки и где он ломается
Подписка в iOS существует не только пока активна. Apple автоматически продлевает её за 24 часа до истечения. Если оплата не прошла — начинается billing retry period (до 60 дней). В это время статус подписки expired, но Apple продолжает попытки списания. Большинство приложений блокируют доступ сразу после expirationDate — это неверно.
Правильная логика: проверять renewalInfo.isInBillingRetryPeriod. Если true — даём grace period (настраивается в App Store Connect, обычно 6 дней для годовых, 3 дня для месячных). Пользователь с проблемой карты не должен терять доступ немедленно.
StoreKit 2 обрабатывает grace period прозрачно, что снижает churn на 20–30% по сравнению с ручной реализацией на StoreKit 1. Вот пример корректной проверки:
for await result in Transaction.currentEntitlements { guard case .verified(let transaction) = result else { continue } if transaction.productType == .autoRenewable { let renewalInfo = try? await transaction.subscriptionStatus.first?.renewalInfo let isInGracePeriod = renewalInfo?.gracePeriodExpirationDate != nil let isRetrying = renewalInfo?.isInBillingRetryPeriod == true if transaction.revocationDate == nil && (transaction.expirationDate ?? .distantPast > .now || isInGracePeriod || isRetrying) { unlockPremium() } } } Почему важен grace period?
Без grace period пользователи с проблемами оплаты мгновенно теряют доступ. Apple позволяет настроить до 16 дней отсрочки — за это время клиент успевает сменить карту, а вы сохраняете его. Наш опыт показывает, что 70% пользователей возвращаются к оплате в течение grace period. Если же блокировать доступ сразу — они уходят навсегда.
Тиры и переходы между ними
Если в приложении несколько тиров (Basic, Pro, Enterprise) — нужна subscription group в App Store Connect. Все тиры в одной группе, пользователь может иметь только одну активную подписку в группе одновременно.
Upgrade (переход на более дорогой тир) — действует сразу, Apple пересчитывает остаток. Downgrade — вступает в силу в следующий расчётный период. Crossgrade (одна цена, другой тир) — зависит от настройки: можно сделать immediate или deferred.
Отслеживать это на клиенте через originalTransactionID и subscriptionGroupID. На сервере — хранить полную историю транзакций и обрабатывать Apple Server Notifications v2 (App Store Server Notifications). Типы событий, которые обязательно обрабатывать: DID_RENEW, DID_FAIL_TO_RENEW, EXPIRED, GRACE_PERIOD_EXPIRED, REFUND.
Как обрабатывать переходы между тирами?
Upgrade активируется немедленно — Apple пропорционально пересчитывает стоимость. Downgrade откладывается до следующего цикла. Crossgrade можно настроить как immediate или deferred в App Store Connect. Важно корректно обновлять UI, показывая пользователю дату вступления изменения в силу. Для этого используйте renewalInfo.renewalDate и offerType.
Introductory и promotional offers
Introductory pricing (первые N периодов по сниженной цене) настраивается в App Store Connect и автоматически применяется для новых подписчиков. Проблема — пользователь, который отписался и хочет вернуться, по умолчанию не получает интро-цену снова. Для этого существуют promotional offers — их можно выдавать по своей логике (win-back кампании).
| Тип предложения | Настройка | Кому доступно | Условия применения |
|---|---|---|---|
| Introductory | В App Store Connect | Новые подписчики | Автоматически, один раз |
| Promotional | Генерация подписи на сервере | Любые пользователи | По вашей логике, любой период |
| Promo-код | App Store Connect | Все | Фиксированная скидка, ограниченное количество |
Подпись для promotional offer генерируется на сервере с приватным ключом из App Store Connect:
// На клиенте создаём paymentDiscount let discount = SKPaymentDiscount( identifier: "winback_3months", keyIdentifier: keyID, nonce: nonce, // UUID с сервера signature: signature, // подпись с сервера timestamp: timestamp ) payment.paymentDiscount = discount Без серверной подписи promotional offer недействителен — Apple проверяет подпись на своей стороне.
Что входит в работу
- Аудит текущей реализации IAP и подписочных групп
- Проектирование схемы тиров и групп (subscription group) с учётом бизнес-логики
- Интеграция StoreKit 2 с поддержкой grace period, billing retry и обработки ошибок
- Настройка App Store Server Notifications (v2) на бэкенде с обработкой всех критических событий
- Реализация win-back логики с использованием promotional offers
- Документирование кода и интеграция аналитики отписок
- Обучение команды работе с Sandbox и тестированием подписок
- Поддержка после запуска (1 месяц бесплатно)
Процесс работы
Аудит текущей реализации → проектирование структуры подписочных групп и тиров → интеграция StoreKit 2 с поддержкой grace period и billing retry → настройка App Store Server Notifications на бэкенде → реализация логики win-back и промо-офферов → тестирование в Sandbox с имитацией истечения подписки (Sandbox сокращает периоды: месяц = 5 минут).
Сроки — 3–5 дней в зависимости от количества тиров, наличия бэкенда и требований к аналитике отписок. Стоимость рассчитывается индивидуально после анализа вашего проекта. Свяжитесь с нами — оценим объём и предложим оптимальное решение. Получите консультацию сертифицированного iOS-разработчика с 5+ лет опыта внедрения подписок.
Мы гарантируем: корректную обработку billing retry, grace period и всех типов переходов, соответствие App Store Review Guidelines (Section 4.2 и 5.1), а также полную поддержку на этапе тестирования и публикации.







