Реализация промокодов и скидок в мобильном приложении
Промокоды в мобильных приложениях — это три разных механизма с разной сложностью реализации: нативные промокоды App Store/Google Play, кастомная серверная логика с собственными кодами, и promotional offers через RevenueCat/Adapty. Смешивать их без понимания ограничений — типичная ошибка.
Нативные промокоды App Store
Apple позволяет генерировать до 1000 промокодов на версию приложения (для paid apps) или на IAP. Пользователь вводит код в App Store → получает продукт. В приложение это приходит как обычная транзакция — обрабатывается стандартным StoreKit flow.
Проблема: промокоды App Store нельзя применить внутри приложения. Пользователя нужно перенаправить в App Store. Для большинства монетизационных сценариев (скидка для конкретного пользователя, реферальная программа) это не работает.
Собственные промокоды — серверная реализация
Кастомные промокоды живут целиком на бэкенде:
Схема данных:
promo_codes: id, code, discount_type (percent|fixed|trial_days), value,
max_uses, used_count, expires_at, product_ids[], user_id (опционально)
promo_redemptions: id, code_id, user_id, created_at, purchase_id
Флоу на клиенте:
- Пользователь вводит код → клиент вызывает
POST /api/promo/validateс кодом и product_id - Сервер возвращает тип скидки и применяемую цену
- Клиент показывает итоговую цену
- Покупка через нативный IAP по полной цене → на сервере после верификации транзакции применяем скидку (начисляем разницу валютой, продлеваем trial, etc.)
Прямое изменение цены IAP на клиенте невозможно — Apple и Google не позволяют динамически менять стоимость продукта. Вся логика скидок реализуется post-purchase на сервере или через отдельные продукты с уже заниженной ценой.
Promotional offers как механизм скидки
Для подписок на iOS — SKPaymentDiscount / SubscriptionOffer в StoreKit 2. Создаём offer в App Store Connect с нужной ценой, сервер генерирует подпись для конкретного пользователя. Это легальный способ предложить скидку без обхода нативного биллинга.
// Получаем offer с сервера
let offerSignature = await serverAPI.getPromoSignature(userID: user.id, offerID: "discount_50")
let product = try await Product.products(for: ["premium_monthly"]).first!
let purchaseOption = product.subscriptionOffer(
offerID: "discount_50",
keyID: offerSignature.keyID,
nonce: offerSignature.nonce,
signature: offerSignature.signature,
timestamp: offerSignature.timestamp
)
let result = try await product.purchase(options: [purchaseOption!])
Ввод промокода в UI
Поле ввода промокода — отдельный экран или bottom sheet с debounce-валидацией (запрос к серверу через 300мс после последнего символа). Важно: показывать загрузку во время валидации и обрабатывать ошибки — «код истёк», «код уже использован», «не применим к выбранному продукту».
Сроки — 2–3 дня: серверная модель промокодов, API валидации и применения, UI компонент, интеграция с IAP flow.







