Ми реалізуємо екран керування підпискою, який не просто показує кнопку «Скасувати». Це точка прийняття рішення: користувач повинен бачити, що саме він втрачає, коли закінчиться доступ, і які є альтернативи. Погано спроектований екран прискорює скасування — за нашими даними, до 40% користувачів відписуються, не розібравшись в умовах. Ми інтегруємо читання статусу через StoreKit 2 на iOS та Google Play Billing Library 6 на Android, додаємо grace period, retention-офери та системну кнопку керування підпискою. Наш досвід: 5+ років роботи з підписочними моделями в мобільних додатках, понад 20 реалізованих екранів керування підпискою для додатків з аудиторією від 10 000 до 500 000 активних користувачів. Знаємо нюанси кожної платформи та допомагаємо уникнути відмов при App Review за guideline 3.1.2. Ми гарантуємо сумісність з останніми версіями SDK та успішне проходження App Review.
Які дані потрібно відображати?
Мінімальний набір:
- Поточний тариф: назва, ціна, період
- Дата наступного списання або дата закінчення при скасуванні
- Статус: активна / скасована (але доступ до) / grace period / billing retry
- Кнопка переходу на інший тариф (upgrade/downgrade)
- Посилання на системний екран Apple/Google для скасування
Без grace period ви втрачаєте до 30% поновлень — користувачі просто не встигають поповнити картку. Утримання користувача обходиться в 5 разів дешевше залучення нового, тому інвестиції в cancellation flow окупаються вже в перший місяць. Наприклад, базовий екран коштує $500, а з retention — $1000, при цьому утримання зростає на 30%. Відображення статусу grace period окремим банером знижує тривожність користувача та підвищує ймовірність продовження підписки.
Як читати стан підписки через StoreKit 2?
import StoreKit
struct SubscriptionInfo {
let productID: String
let displayName: String
let price: String
let renewalDate: Date?
let expirationDate: Date?
let willAutoRenew: Bool
let state: Product.SubscriptionInfo.RenewalState
let isInGracePeriod: Bool
}
func loadSubscriptionInfo(productIds: [String]) async -> SubscriptionInfo? {
guard let product = try? await Product.products(for: productIds).first,
let status = try? await product.subscription?.status.first else {
return nil
}
let renewalInfo = try? status.renewalInfo.payloadValue
let transaction = try? status.transaction.payloadValue
return SubscriptionInfo(
productID: product.id,
displayName: product.displayName,
price: product.displayPrice,
renewalDate: renewalInfo?.renewalDate,
expirationDate: transaction?.expirationDate,
willAutoRenew: renewalInfo?.willAutoRenew ?? false,
state: status.state,
isInGracePeriod: status.state == .inGracePeriod
)
}
renewalInfo.willAutoRenew показує, чи активне автопродовження, чи воно скасоване. Якщо false, доступ зберігається до expirationDate.
Порівняння: StoreKit 2 vs Play Billing 6
| Параметр | iOS (StoreKit 2) | Android (Play Billing 6) |
|---|---|---|
| Читання статусу | SubscriptionInfo.state (.subscribed, .inGracePeriod, etc.) |
BillingClient.queryPurchasesAsync() + purchaseState |
| Grace period | Автоматично через .inGracePeriod |
Потребує перевірки acknowledgementState та purchaseTime |
| Upgrade/downgrade | Product.subscription?.update() |
BillingClient.launchBillingFlow() з оновленим SKU |
| Системний екран | AppStore.showManageSubscriptions |
Deeplink в Google Play |
| Promotional offer | SubscriptionOffer типу .introductory або .promotional |
BillingFlowParams.SubscriptionUpdateParams |
Екран з retention утримує користувачів у 4 рази краще, ніж базовий.
SwiftUI-компонент екрана
struct SubscriptionManagementView: View {
@StateObject private var viewModel = SubscriptionManagementViewModel()
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 24) {
if let info = viewModel.subscriptionInfo {
CurrentPlanCard(info: info)
if info.state == .inGracePeriod {
GracePeriodWarning()
}
if info.willAutoRenew, let date = info.renewalDate {
Text("Наступне списання: \(date.formatted(.dateTime.day().month().year()))")
.foregroundStyle(.secondary)
} else if let date = info.expirationDate {
Text("Доступ активний до: \(date.formatted(.dateTime.day().month().year()))")
.foregroundStyle(.secondary)
}
PlanPickerSection(currentPlan: info.productID)
}
ManageButton()
}
.padding()
}
.task { await viewModel.load() }
}
}
Системна кнопка керування підпискою
Apple вимагає доступ до системного екрана (guideline 3.1.2). Без цього — rejection. Використовуйте showManageSubscriptions, щоб відкрити системний екран керування підписками — Apple Developer Documentation. Реалізація проста:
Button("Керування підпискою в App Store") {
Task {
try? await AppStore.showManageSubscriptions(in: windowScene)
}
}
На Android — deeplink в Google Play (екран реалізується на Jetpack Compose):
val intent = Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse("https://play.google.com/store/account/subscriptions?sku=premium_monthly&package=${packageName}")
}
startActivity(intent)
Як реалізувати cancellation flow: покрокова інструкція
- Перевірити статус підписки через StoreKit 2 або Play Billing 6.
- Якщо користувач натискає «Скасувати», показати екран зі списком фіч, які він втратить.
- Якщо підписка активна понад 3 місяці, запропонувати паузу (на Android) або Promotional Offer.
- Якщо користувач погоджується — застосувати знижку або паузу через відповідний SDK.
- Якщо ні — перенаправити на системний екран скасування.
- Зафіксувати причину відмови через аналітику.
Це не dark pattern — це чесне нагадування. Важливо не зловживати: один екран підтвердження максимум. Наша статистика: retention після промо-офера — 60%. Retention-офер підвищує утримання в 4 рази порівняно з базовим екраном. Retention-офери приносять додатковий дохід до 15% від поточної виручки. Якщо користувач все одно йде, важливо зафіксувати причину через аналітику — це допоможе покращити продукт для майбутніх користувачів.
Детальний опис процесу upgrade/downgrade
При upgrade новий тариф набирає чинності негайно, а різниця перераховується пропорційно. При downgrade зміна застосовується в кінці поточного періоду. Користувач бачить порівняння тарифів і дату зміни. На iOS це автоматизовано через StoreKit 2, на Android вимагає ручного керування запитами. Для реалізації потрібно використовувати методи Product.subscription?.update() на iOS та BillingFlowParams.SubscriptionUpdateParams на Android.
Порівняння підходів: базовий vs з retention
| Параметр | Базовий екран | Екран з retention |
|---|---|---|
| Читання статусу | StoreKit 2 / Play Billing | + grace period + billing retry |
| Відображення | тариф, дата, статус | + список фіч, промо-офер |
| Cancel flow | тільки системний екран | системний + другий екран з вибором паузи/знижки |
| Термін розробки | 2–3 дні | 4–5 днів |
| Retained users | 10–15% | 40–60% |
Що входить в роботу
- Читання статусу через StoreKit 2 / Google Play Billing Library 6
- Компонент відображення: тариф, дата, статус, grace period
- Перемикач тарифів (upgrade/downgrade) з описом умов
- Інтеграція
AppStore.showManageSubscriptions/ Play Store deeplink - Опціональний cancellation flow з retention-офером
- Документація по компонентах та архітектурі підписочного екрана
- Підтримка протягом 14 днів після здачі роботи
Терміни та вартість
2–3 дні для базового екрана з актуальним статусом. Зі складним cancellation flow та retention-оферами — до 5 днів. Базова версія — від $500, з retention — від $1000. Точна вартість залежить від складності бізнес-логіки. Отримайте консультацію — оцінимо ваш проект.
Наша команда має 5+ років досвіду роботи з підписочними моделями в мобільних додатках. Замовте розробку екрана керування підпискою вже сьогодні. Зв'яжіться з нами — ми підберемо рішення під вашу бізнес-логіку.







