Реалізація Offer Codes для підписок у мобільних застосунках
Offer Codes — промокоди Apple для підписок. Дозволяють надати безплатний доступ або знижку без прив'язки до рекламної мережі, без необхідності вводити дані карти. Типові сценарії: партнерські акції, коди для інфлюенсерів, офлайн-розповсюдження (QR на упаковці), корпоративні продажі.
Принципова відмінність від Promotional Offers: Offer Code не вимагає серверного підпису і може бути активований через системний діалог прямо в застосунку або через посилання https://apps.apple.com/redeem?ctx=offercodes&id=...&code=....
Створення кодів у App Store Connect
Subscriptions → [Subscription] → Offer Codes → +. Встановлюємо Reference Name, Offer ID, тип знижки (freeTrial / payAsYouGo / payUpFront), тривалість. Apple дозволяє створювати одноразові коди (One-Time Use) або багаторазові (Custom). Ліміт одноразових кодів — 150 000 на квартал на підписку.
Реалізація діалогу активації в застосунку
StoreKit 2 надає OfferCodeRedeemSheet — системний UI для введення кода:
import StoreKit
import SwiftUI
struct SettingsView: View {
@State private var showRedeemSheet = false
var body: some View {
List {
Button("Введіть промокод") {
showRedeemSheet = true
}
}
.offerCodeRedemption(isPresented: $showRedeemSheet) { result in
switch result {
case .success(let transaction):
// Користувач активував код — оновлюємо UI
await updateSubscriptionStatus(transaction)
case .failure(let error):
handleRedemptionError(error)
case .pending:
// Транзакція у обробці
break
}
}
}
}
На UIKit (iOS 16+) аналог через AppStore.presentOfferCodeRedeemSheet(in:):
Task {
do {
try await AppStore.presentOfferCodeRedeemSheet(in: windowScene)
} catch {
// Показуємо fallback — посилання на apps.apple.com/redeem
}
}
На iOS нижче 16 presentOfferCodeRedeemSheet недоступен. Fallback: відкриваємо URL https://apps.apple.com/redeem?ctx=offercodes&id=APP_ID&code=CODE через UIApplication.open.
Обробка транзакцій після активації
Після успішної активації кода StoreKit генерує транзакцію. Її потрібно перехопити через Transaction.updates:
// Запускається при старті застосунку та слухає весь час
for await result in Transaction.updates {
if case .verified(let transaction) = result {
if transaction.offerType == .code {
// Offer Code активирован — розблокуємо premium контент
await unlockPremiumContent()
await transaction.finish()
}
}
}
Важливо вивати transaction.finish() — без цього транзакція залишається у pending-черзі і може з'явитися знову при наступному запуску.
Android — Google Play Promo Codes
На Android аналог — Promo Codes у Google Play. Відрізняється архітектурою: коди створюються у Google Play Console, активуються через Play Store або deeplink. На боці застосунку через Billing Library 5+:
// Google Play сповіщає через PurchasesUpdatedListener
override fun onPurchasesUpdated(result: BillingResult, purchases: List<Purchase>?) {
if (result.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
for (purchase in purchases) {
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
handleNewPurchase(purchase)
}
}
}
}
Типові проблеми
Одноразові коди не мають Preview у Sandbox до їх фактичного випуску — тестування вимагає створення Custom кода або sandbox-аккаунту з ручно застосованим кодом через тестове посилання Apple.
Ще одна проблема: якщо застосунок не слухає Transaction.updates при холодному старті, а користувач активував код через web-посилання — транзакція чекає у черзі, але застосунок не розблокує контент до наступного явного виклику Transaction.currentEntitlements.
Що входить у роботу
- Налаштування Offer Code у App Store Connect
- Інтеграція
OfferCodeRedeemSheet/AppStore.presentOfferCodeRedeemSheet - Fallback для iOS нижче 16
- Обробка транзакцій через
Transaction.updates - Тестування з Sandbox Offer Codes
Терміни
3–5 днів з урахуванням інтеграції в існуючий paywall та subscription-flow. Вартість розраховується індивідуально.







