Реалізація екрана управління підпискою у мобільних застосунках
Екран управління підпискою — це не просто сторінка з кнопкою «Відмінити». Це точка, де користувач приймає рішення залишитися або піти. Неправильно реалізований екран прискорює відмови: користувач не розуміє, що саме він втратить, коли спливе доступ та чи можна отримати знижку.
Дані, які потрібно показати
Мінімальний набір:
- Поточний тариф (назва, ціна, період)
- Дата наступного списання або дата спливу при відміні
- Статус: активна / відмінена (але доступ до) / grace period / billing retry
- Кнопка переходу на інший тариф (upgrade/downgrade)
- Посилання на системний екран Apple/Google для відмени
Читання поточного стану (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.
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 вимагає, щоб в застосунку був доступ до системного екрана управління підписками. Без цього — rejection за гайдлайном 3.1.2:
Button("Управління підпискою в App Store") {
Task {
try? await AppStore.showManageSubscriptions(in: windowScene)
}
}
На Android — deeplink у Google Play:
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 — не просто кнопка
Кращі практики утримання при спробі отмени:
- Перед уходом на системний екран — показати що втратить користувач (список фіч)
- Якщо підписка активна давно — запропонувати паузу замість отмены (Google Play підтримує нативно)
- Для користувачів з давною історією — запропонувати Promotional Offer зі знижкою
Це не dark pattern — це чесне нагадування про цінність. Важливо не зловживати: один екран підтвердження максимум.
Що входить у роботу
- Читання статусу через StoreKit 2 / Google Play Billing Library
- Компонент відображення: тариф, дата, статус, grace period
- Переключувач тарифів (upgrade/downgrade) з описом умов
- Інтеграція
AppStore.showManageSubscriptions/ Play Store deeplink - Опціональний cancellation flow з retention-оффером
Терміни
2–3 дні для базового екрана з актуальним статусом. Зі складним cancellation flow та retention-офферами — до 5 днів. Вартість розраховується індивідуально.







