Специальные предложения¶
Скидка, которую сервер предлагает пользователю после того, как тот закрыл основной paywall. Предложение живёт минутами: у него есть серверный дедлайн, после которого продукт по этой цене купить нельзя.
Всё, что здесь описано, работает только для зарегистрированного аккаунта. Гостю предложения не выдаются - он и заплатить не может, а экран со скидкой и нерабочей кнопкой хуже, чем его отсутствие.
Флоу целиком¶
пользователь закрыл основной paywall
POST /v1/offers/paywall-dismissed → предложение или null
null → показать обычный paywall, ничего больше делать не нужно
показать экран предложения с таймером на expires_in секунд
POST /v1/payments {product_id из предложения} → обычная оплата, см. «Оплата»
Дополнительно:
GET /v1/offers/current → то же предложение, без побочных эффектов (баннер, возврат на экран)
POST /v1/offers/{id}/dismiss → пользователь закрыл экран предложения
Получить предложение¶
{
"id": "…",
"code": "first_paywall_discount",
"expires_in": 600,
"is_dismissed": false,
"product": {
"id": "…", "code": "discount_30d", "title": "30 дней",
"price_minor": 29900, "credits": 1000, "badge": "Только сейчас"
}
}
| Поле | Что с ним делать |
|---|---|
id |
идентификатор выдачи: он нужен для dismiss, не для оплаты |
code |
машинное имя предложения - для вашей аналитики |
expires_in |
секунды до конца окна; заводите таймер от этого числа |
is_dismissed |
экран уже закрывали: показывайте баннер, а не полноэкранное предложение |
product |
что и почём продаётся; product.id идёт в POST /v1/payments |
Ответ null (а не ошибка) означает, что предложения нет: оно уже выдавалось столько раз,
сколько разрешено, у пользователя есть оплаченные покупки, или сейчас ничего не настроено.
Это штатный ответ - показывайте обычный paywall.
Таймер серверный
expires_in приходит числом секунд, а не временем истечения: часы устройства на
предложение не влияют. Перезапуск приложения окно не продлевает - повторный
POST /v1/offers/paywall-dismissed вернёт то же самое предложение с уже уменьшившимся
expires_in. Проверяйте остаток через GET /v1/offers/current, а не по своему
сохранённому значению.
Оплата предложения¶
Отдельной ручки нет: предложение оплачивается обычным POST /v1/payments с product_id
из product.id. Дальше всё как в разделе Оплата - страница ПЦ, SSE,
зачисление кредитов на сервере.
| HTTP | code |
Когда |
|---|---|---|
| 403 | forbidden |
окно закрылось, предложение уже оплачено или его не выдавали этому пользователю |
Это и есть «после десяти минут оплата по скидке невозможна»: проверка идёт на сервере, до регистрации заказа в ПЦ.
Цена продления обязана быть на экране
Если предложение продаёт подписку со скидкой на первый период, экран обязан явно показывать, сколько и как часто спишется дальше, по базовой цене. Это требование App Store и Google Play (и закона о защите прав потребителей), а не пожелание: без него приложение отклоняют на ревью.
Закрыть предложение¶
Отмечает, что пользователь закрыл экран. Скидка при этом не сгорает: до конца окна
предложение остаётся в GET /v1/offers/current (с is_dismissed: true), и его по-прежнему
можно оплатить - по сценарию пользователь возвращается к нему через баннер в шапке.
Заканчивает предложение только время или оплата.
Чужая или несуществующая выдача - 404 not_found. Повторный dismiss - снова 204.