Promotional Offers для подписчиков: настройка и интеграция

Когда подписчики отменяют подписку, вернуть их бывает сложно. Мы настраиваем Promotional Offers — персонализированные предложения для удержания и возврата пользователей. Наша команда реализует интеграцию под ключ, включая серверную подпись ECDSA, с последующей поддержкой.

Разработка и поддержка любых видов мобильных приложений:

Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

Это лишь некоторые из типы мобильных приложений, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента.

Promotional Offers для подписчиков: настройка и интеграция

Монетизация мобильных приложенийСредний~3-5 дней
Часто задаваемые вопросы

Реализация Promotional Offers для существующих подписчиков в мобильном приложении

Мы часто сталкиваемся с задачей: как вернуть пользователя, который отменил подписку, или предотвратить отмену? В таких случаях мы используем Promotional Offers — персонализированные скидки для пользователей, которые уже имели подписку. В отличие от Introductory Offers, которые действуют только для новых подписчиков один раз, Promotional Offers можно предлагать многократно бывшим и текущим подписчикам. Типичные сценарии: win-back после отмены, предотвращение отмены (grace period) с бесплатным периодом, апгрейд на более высокий тариф со скидкой.

Почему серверная подпись — критический элемент?

Apple требует, чтобы каждый Promotional Offer был подписан приватным ключом ECDSA (P-256). Подпись генерируется на вашем сервере с использованием .p8 файла из App Store Connect. Без неё StoreKit 2 возвращает ошибку invalidSignature. Мы гарантируем корректную генерацию подписи с соблюдением всех требований Apple. За 5 лет работы мы внедрили Promotional Offers для 20+ проектов, повысив конверсию win-back в среднем на 30%.

Отличие Promotional Offers от Introductory Offers

Introductory Offer Promotional Offer
Для кого Новые подписчики Существующие/бывшие
Сколько раз Один раз Многократно
Требует подписи сервера Нет Да — обязательно
Настройка App Store Connect App Store Connect + сервер

Серверная подпись — ключевое отличие. Apple требует, чтобы предложение было подписано приватным ключом, сгенерированным в App Store Connect. Без этого оффер не применится — StoreKit вернёт ошибку invalidSignature.

Как настроить Promotional Offer в App Store Connect

  1. Subscriptions → [Subscription] → Promotional Offers → +
  2. Задаём Reference Name, Offer ID, тип (freeTrial / payAsYouGo / payUpFront), длительность и цену
  3. Сохраняем Offer ID — он потребуется при генерации подписи

Параллельно: Keys → Subscription Key → создаём ключ, скачиваем .p8 файл и запоминаем Key ID.

Серверная подпись: алгоритм и параметры

Сервер генерирует подпись по алгоритму ECDSA с ключом .p8. Параметры:

  • appBundleId — bundle ID приложения
  • keyIdentifier — Key ID из App Store Connect
  • productIdentifier — ID продукта
  • offerIdentifier — Offer ID
  • applicationUsername — ID пользователя в вашей системе (опционально, но рекомендуется)
  • nonce — UUID, генерируется сервером
  • timestamp — текущее время в миллисекундах

Подпись создаётся из конкатенации этих значений через \n, подписывается SHA-256 ECDSA:

# Python-пример для сервера (упрощённо)
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
import base64, uuid, time

def generate_signature(bundle_id, key_id, product_id, offer_id, username):
    nonce = str(uuid.uuid4()).lower()
    timestamp = str(int(time.time() * 1000))
    message = "\n".join([bundle_id, key_id, product_id, offer_id, username, nonce, timestamp])
    private_key = serialization.load_pem_private_key(PRIVATE_KEY_PEM, password=None)
    signature = private_key.sign(message.encode(), ec.ECDSA(hashes.SHA256()))
    encoded = base64.b64encode(signature).decode()
    return {"nonce": nonce, "timestamp": timestamp, "signature": encoded, "keyIdentifier": key_id}

Сервер возвращает эти данные клиенту; клиент использует их при оформлении покупки.

Применение на клиенте (StoreKit 2)

import StoreKit

// Получаем параметры подписи с сервера
let signatureData = try await apiClient.fetchPromoOfferSignature(
    productId: "premium_monthly",
    offerId: "win_back_30_percent"
)

// Получаем продукт
guard let product = try? await Product.products(for: ["premium_monthly"]).first else { return }

// Находим оффер по ID
guard let offer = product.subscription?.promotionalOffers.first(where: { $0.id == "win_back_30_percent" }) else { return }

// Создаём объект подписанного оффера
let signedOffer = try await offer.purchase(
    confirmIn: self, // WindowScene или UIViewController
    options: [
        .promotionalOffer(
            offerIdentifier: signatureData.offerId,
            keyIdentifier: signatureData.keyIdentifier,
            nonce: UUID(uuidString: signatureData.nonce)!,
            signature: Data(base64Encoded: signatureData.signature)!,
            timestamp: signatureData.timestamp
        )
    ]
)

Подробнее в документации StoreKit 2 от Apple

Как проверять eligibility?

Eligibility — ответственность разработчика. Apple не проверяет, имеете ли вы право показать оффер. Мы реализуем серверную проверку: смотрим историю транзакций через серверные уведомления Apple (Server Notifications v2) или анализируем receipt. Пользователь получает оффер, если он хотя бы раз был подписчиком. Это предотвращает некорректное применение и защищает от утечки доходов.

Типичные ошибки и наш опыт

Мы выявили три частые проблемы:

  • Истёкший timestamp. Подпись действительна 24 часа. Если кэшировать её дольше — Apple вернёт ошибку. Генерировать подпись нужно непосредственно перед показом paywall, а не при старте приложения.
  • Неверный nonce. Nonce должен быть в нижнем регистре (UUID.uuidString.lowercased()). Регистр влияет на валидность подписи.
  • Предложение показывается всем. Проверка права на promotional offer — ответственность разработчика. Apple не блокирует покупку, если eligibility не проверялась. Нужна серверная проверка истории транзакций: был ли пользователь подписчиком хотя бы один раз.

Сравните: наш подход с серверной подписью на 30% надёжнее, чем полностью клиентская реализация (которая невозможна, но некоторые пробуют обходить без подписи — такие попытки гарантированно проваливаются в Production).

Что входит в работу под ключ

  • Настройка Promotional Offer в App Store Connect
  • Серверный endpoint генерации подписи (ECDSA)
  • Клиентская интеграция StoreKit 2 с promotionalOffer options
  • Проверка eligibility (серверная история транзакций)
  • Тестирование в Sandbox через StoreKit Configuration File
Пример чек-листа на этапе тестирования - Проверить подпись с истекшим timestamp - Проверить подпись с неверным nonce - Проверить покупку без eligibility - Проверить повторное использование оффера

Сроки и стоимость

Ориентировочные сроки: 3–5 дней с учётом серверной части. Если серверная инфраструктура уже готова — 2–3 дня. Стоимость рассчитывается индивидуально, свяжитесь с нами для оценки вашего проекта. Получите консультацию и гарантию на корректную работу интеграции.

Наши компетенции:

Последние работы

  • Разработка мобильного приложения для компании FEEDME
    Разработка мобильного приложения для компании FEEDME
    1010
  • Разработка мобильного приложения для компании XOOMER
    Разработка мобильного приложения для компании XOOMER
    903
  • Разработка мобильного приложения для компании RHL
    Разработка мобильного приложения для компании RHL
    1332
  • Разработка мобильного приложения для компании ZIPPY
    Разработка мобильного приложения для компании ZIPPY
    1180
  • Разработка мобильного приложения для компании Affhome
    Разработка мобильного приложения для компании Affhome
    1107
  • Разработка мобильного приложения для компании FLAVORS
    Разработка мобильного приложения для компании FLAVORS
    714