Stripe-подписки без кода: ошибки, которые приводят к утечке доходов
Stripe-подписки без кода: избегайте утечки доходов, исправляя обработку вебхуков, логику триалов, проревацию и повторные попытки неудачных платежей при помощи чеклиста QA.

Где обычно начинается утечка доходов от подписок
Утечка доходов в подписках редко выглядит драматично. Она проявляется мелкими, повторяющимися ошибками: клиенты сохраняют доступ дольше, чем должны, апгрейды не списывают полную сумму или кредит начислен дважды. Одна неучтённая пограничная ситуация может тихо повторяться неделями, особенно по мере роста числа подписок.
Даже если вы строите Stripe-подписки без кода, биллинг всё ещё требует логики. Stripe — это движок биллинга, но ваше приложение решает, что значит «активно», когда открывать функции и как реагировать на продления и неудачные платежи. No-code инструменты убирают много работы, но они не знают ваших правил.
Большинство утечек начинается в четырёх местах:
- Вебхуки обрабатываются неправильно (пропущенные события, дубли, неправильный порядок)
- Триалы не заканчиваются так, как вы ожидаете (доступ в триале продолжается после отмены или неудачного платежа)
- Проревация при смене плана (апгрейды/понижения списывают слишком мало или создают неожиданные кредиты)
- Неудачные платежи и повторные попытки (доступ остаётся включённым во время дандинга или отключается слишком рано)
Распространённый сценарий — «всё работает в тесте по счастью». Вы подписываетесь, получаете доступ, первый счёт оплачен. А потом реальная жизнь: карта не проходит, клиент делает апгрейд в середине цикла, кто-то отменяет в триале, или Stripe повторно пытается списать платёж в ночи. Если ваше приложение проверяет только одно поле (или слушает только одно событие), оно может дарить бесплатное время или случайно начислять двойные кредиты.
Если вы используете платформу вроде AppMaster, легко быстро строить экраны и потоки. Риск в том, чтобы предположить, что дефолтный поток равен корректной биллинговой политике. Вам всё равно нужно описать правила доступа и убедиться, что бэкенд последовательно реагирует на события Stripe.
Решите, что является источником правды для доступа
Если вы реализуете Stripe-подписки без кода, одно решение предотвращает множество утечек: какая система решает, есть ли у пользователя доступ прямо сейчас.
Есть два распространённых варианта:
- Stripe — источник правды: вы при каждом решении запрашиваете состояние подписки в Stripe.
- Ваша база данных — источник правды: вы храните состояние доступа и обновляете его при наступлении биллинговых событий.
Второй вариант обычно быстрее для приложения и проще держать консистентным между вебом и мобильной версией, но только если вы надёжно его обновляете.
Практичный подход для многих продуктов: Stripe — источник правды по биллингу, ваша база данных — источник правды по доступу. Базу данных не должны править вручную или кнопками UI вроде «отметить как оплачено». Она должна выводиться из событий Stripe (и периодически сверяться).
Для этого нужны стабильные идентификаторы. Как минимум, храните эти поля в записи пользователя или аккаунта:
- Stripe customer ID (кто платит)
- Stripe subscription ID (на каком плане они находятся)
- Latest invoice ID (что было выставлено, включая проревацию)
- Latest payment_intent ID (что действительно пыталось списать платёж)
Далее определите, что каждое состояние подписки значит в вашем продукте. Запишите это простыми правилами до того, как вы начнёте строить экраны, автоматизации или вебхуки.
Чёткая дефолтная политика, которую часто используют команды:
- active: полный доступ
- trialing: полный доступ до
trial_end, затем повторная проверка статуса - past_due: ограниченный доступ (например, только чтение) в течение короткого льготного периода
- unpaid: блокировка платных функций; доступ к странице биллинга и экспортам данных
- canceled: сохранять доступ до
period_end, если вы это разрешаете, затем блокировать
Избегайте «бесплатных навсегда» дыр. Если вы разрешаете полный доступ в past_due, нужен жёсткий край (по датам, которые вы храните), а не расплывчатое «мы это позже исправим».
Если вы строите в AppMaster, относитесь к решению о доступе как к бизнес-логике: храните текущее состояние доступа в аккаунте, обновляйте его из событий Stripe и пусть веб/мобильный UI проверяют именно это поле. Так поведение останется предсказуемым, даже если события Stripe придут поздно или вне порядка.
Вебхуки: шаблоны, которые предотвращают пропущенные события и двойную обработку
Вебхуки — тихое место, где начинаются утечки доходов. Stripe может присылать события повторно, в неправильном порядке или доставлять их через часы. Рассматривайте каждый вебхук как «возможно поздний» и «возможно дублирующийся», и проектируйте обновления доступа так, чтобы они оставались корректными.
События, которые важны (и те, которыми можно обычно пренебречь)
Ограничьтесь небольшим набором событий, которые отражают реальные изменения состояния подписки. Для большинства настроек это покрывает почти всё, что нужно:
checkout.session.completed(когда вы используете Checkout для начала подписки)customer.subscription.created,customer.subscription.updated,customer.subscription.deletedinvoice.paid(момент, когда период фактически оплачен)invoice.payment_failed(момент, когда оплата не удалась)
Многие команды реагируют на шумные события вроде charge.updated или payment_intent.* и в итоге получают противоречивые правила. Если вы уже хорошо обрабатываете инвойсы и подписки, низкоуровневые события часто только путают.
Идемпотентность: остановите двойное открытие доступа при повторных попытках Stripe
Stripe повторяет вебхуки. Если вы «предоставляете доступ» каждый раз при invoice.paid, некоторые клиенты получат лишнее время, дополнительные кредиты или повторные права. Простая схема работает:
- Сохраняйте
event.idкак обработанное до любых необратимых действий - Если вы видите тот же
event.idснова, выходите сразу - Записывайте, что изменилось (пользователь/аккаунт, subscription ID, предыдущее состояние доступа, новое состояние доступа)
В AppMaster это легко сопоставляется с таблицей в базе данных и Business Process, который проверяет «уже обработано?» перед обновлением доступа.
Порядок событий: проектируйте систему для поздних и внепорядочных сообщений
Не предполагаете, что customer.subscription.updated придёт раньше invoice.paid, или что вы увидите все события в последовательности. Основывайте доступ на последнем известном статусе подписки и счёта, а не на ожиданиях.
Когда что-то выглядит несогласованным, получите текущую подписку из Stripe и сверьте состояния.
Также храните сырые полезные данные вебхуков (хотя бы 30–90 дней). Когда саппорт спрашивает «почему я потерял доступ?» или «почему меня списали дважды?», этот аудит превращает загадку в ответ.
Ошибки с вебхуками, которые создают бесплатный доступ или путаницу в биллинге
Вебхуки — это сообщения от Stripe о реальных событиях. Если ваше приложение игнорирует их или реагирует не в тот момент, вы можете раздать доступ бесплатно или вызвать непоследовательное поведение биллинга.
Распространённая ошибка — давать доступ при завершении checkout вместо того, чтобы ждать получения денег. «Checkout completed» может означать, что клиент начал подписку, а не то, что первый счёт оплачен. Карты не проходят, 3D Secure может быть брошен, и некоторые методы платёжей подтверждаются позже. Для доступа считайте моментом включения функций invoice.paid (или успешный payment_intent, привязанный к инвойсу).
Другой источник утечек — слушать только путь «всё хорошо». Подписки меняются со временем: апгрейды, даунгрейды, отмены, паузы и состояния past due. Если вы никогда не обрабатываете обновления подписки, отменивший клиент может сохранять доступ неделями.
Четыре ловушки, за которыми нужно следить:
- Доверие клиенту (front end) в том, что подписка активна, вместо обновления базы данных из вебхуков
- Не проверять подписи вебхуков, что упрощает фальшивые запросы, переворачивающие доступ
- Смешивание тестовых и боевых событий (например, принятие тестовых вебхуков в проде)
- Обработка только одного типа события с предположением, что остальное «как-то само сработает»
Реальная ошибка: клиент завершает checkout, ваше приложение открывает премиум, а первый инвойс не проходит. Если система не обработала событие об ошибке, клиент остаётся премиум без оплаты.
Если вы строите Stripe-подписки без кода в платформе вроде AppMaster, цель та же: иметь одну серверную запись доступа и менять её только при проверенных Stripe вебхуках о том, что платёж прошёл, не прошёл или статус подписки изменился.
Триалы: избегайте бесконечного бесплатного времени
Триал — это не просто «бесплатный биллинг». Это чёткое обещание: что клиент может использовать, в течение какого времени и что будет потом. Главный риск — воспринимать триал как метку в UI вместо правила с ограничением по времени.
Решите, что значит «доступ в триале» в вашем продукте. Это полный доступ или ограниченные места, функции или лимиты использования? Решите, как вы будете напоминать людям до конца триала (email, внутриигровой баннер) и что показывает страница биллинга, когда у клиента нет карты.
Привязывайте доступ к датам, которые можно проверить, а не к локальному булю is_trial = true. Предоставляйте триал, когда Stripe сообщает, что подписка создана с триалом, и снимайте триальный доступ, когда триал заканчивается, если подписка не перешла в активное и оплаченное состояние. Если ваше приложение хранит trial_ends_at, обновляйте его из событий Stripe, а не при клике пользователя.
Время сбора карты — именно то место, где «бесплатно навсегда» чаще всего прокрадывается. Если вы запускаете триалы без сбора платёжного метода, спланируйте путь конверсии:
- Показывайте явно шаг «добавить платёжный метод» перед окончанием триала
- Решите, разрешаете ли запускать триал без карты вообще
- Если платёж не проходит при конверсии, снижайте доступ сразу или после короткого льготного периода
- Всегда показывайте точную дату окончания триала в приложении
Пограничные случаи важны, потому что триалы редактируются. Саппорт может продлить триал, или пользователь отменяет в первый день. Пользователь может апгрейдиться в триале и ожидать новый план немедленно. Выберите простые правила и держите их последовательными: апгрейд в триале либо сохраняет дату окончания триала, либо заканчивает триал и начинает биллинг немедленно. Что бы вы ни выбрали — делайте это предсказуемым и видимым.
Типичная ошибка: вы даёте триал при клике «Start trial», но снимаете его только при клике «Cancel». Если пользователь закрыл вкладку или вебхук упал, он сохраняет доступ. В no-code приложении (включая AppMaster) базируйте доступ на статусе подписки и временных метках trial_end, полученных из вебхуков Stripe, а не на ручном флаге, выставленном фронтендом.
Проревация: остановите случайное недовзимание при смене планов
Проревация происходит, когда клиент меняет подписку в середине цикла, и Stripe корректирует счёт так, чтобы он платил только за использованное время. Stripe может создать проревационный инвойс при апгрейде, даунгрейде, изменении количества мест или переключении цены.
Наиболее распространённая утечка дохода — недовзимание при апгрейде. Это случается, когда ваше приложение открывает функции нового плана сразу, но биллинг меняется позже или проревационный инвойс так и не оплачивается. Клиент получает лучший план бесплатно до следующего продления.
Выберите политику проревации и придерживайтесь её
Апгрейды и даунгрейды не должны обрабатываться одинаково, если вы специально этого не хотите.
Простой, последовательный набор правил:
- Апгрейды: применяются сразу, списывается проревационная разница сейчас
- Даунгрейды: применяются на следующую дату обновления (без возвратов в середине цикла)
- Увеличение количества мест: применяется сразу с проревацией
- Уменьшение количества мест: применяется при обновлении цикла
- Опционально: разрешать «без проревации» только в особых случаях (например, годовые контракты), а не по ошибке
Если вы строите Stripe-подписки без кода в AppMaster, убедитесь, что поток смены плана и правила контроля доступа соответствуют выбранной политике. Если апгрейд должен списываться сейчас, не открывайте премиум, пока Stripe не подтвердит оплату проревационного инвойса.
Изменения в середине цикла могут быть сложными с местами или уровнями по использованию. Команда может добавить 20 мест на 25-й день, затем убрать 15 мест на 27-й. Если логика непоследовательна, вы можете дать дополнительные места без оплаты или создать путаницу с кредитами, которые приведут к возвратам и тикетам в саппорт.
Объясните проревацию перед кликом клиента
Споры по проревации часто возникают из-за неожиданных счетов, а не злого умысла. Добавьте одну короткую фразу рядом с кнопкой подтверждения, которая соответствует вашей политике и таймингам:
- «Апгрейд начнётся сегодня, и с вас будет взята проревационная сумма сейчас.»
- «Даунгрейд вступит в силу в следующую дату списания.»
- «Добавление мест списывается немедленно; удаление мест вступает в силу в следующем цикле.»
Чёткие ожидания снижают число чарджбэков, возвратов и вопросов «почему меня списали дважды?».
Неудачные платежи и повторные попытки: настраиваем дандинг и доступ правильно
Неудачные платежи — это то место, где конфигурации подписки незаметно сливают деньги. Если ваше приложение оставляет доступ открытым навсегда после провала списания, вы предоставляете сервис, не получая оплату. Если вы отключаете доступ слишком рано, вы создаёте тикеты в саппорт и лишние отказы.
Знайте состояния, которые важны
После неудачного списания Stripe может перевести подписку через past_due и позже в unpaid (или отмену, в зависимости от настроек). Обращайтесь с этими состояниями по-разному. past_due обычно означает, что клиента можно вернуть и Stripe делает повторные попытки. unpaid обычно означает, что счёт не оплачивается и стоит остановить услугу.
Распространённая ошибка при Stripe-подписках без кода — проверять только одно поле (например, «подписка активна») и никогда не реагировать на неудачи инвойсов. Доступ должен следовать биллинговым сигналам, а не предположениям.
Простая dunning-политика, которая защищает доход
Решите заранее график повторных попыток и льготный период, затем зафиксируйте это в правилах, которые приложение может применять. Stripe обрабатывает повторные попытки, если настроен, но ваше приложение всё ещё решает, что происходит с доступом в окно повторных попыток.
Практичная модель:
- При
invoice.payment_failed: пометить аккаунт как «проблема с оплатой», сохранить доступ на короткий льготный период (например 3–7 дней) - Пока подписка в
past_due: показывать внутриигровой баннер и отправлять сообщение «обновите карту» - Когда платёж проходит (
invoice.paidилиinvoice.payment_succeeded): убрать флаг проблемы и восстановить полный доступ - Когда подписка переходит в
unpaid(или отменяется): переключить в режим только чтения или заблокировать ключевые действия, а не просто скрыть страницу биллинга - Логировать статус последнего инвойса и время следующей попытки, чтобы саппорт видел, что происходит
Избегайте бесконечной льготы — храните жёсткий дедлайн у себя. Например, при первом событии о неудаче вычисляйте timestamp конца льготного периода и применяйте его, даже если поздние события задержатся или пропустятся.
Для флоу «обновить карту» не думайте, что проблема решена, когда клиент ввёл новые данные. Подтвердите восстановление только после того, как Stripe покажет оплаченный инвойс или успешное платёжное событие. В AppMaster это может быть явный Business Process: когда приходит вебхук об успешном платеже — переключайте пользователя обратно в active, открывайте функции и отправляйте подтверждение.
Пример сценария: путь одного клиента и четыре типичных ошибки
Майя регистрируется на 14-дневный триал. Она вводит карту, начинает триал, делает апгрейд на 10-й день, затем её банк позже отклоняет продление. Это нормально, и именно тут происходят утечки дохода.
Пошаговая временная шкала (и что должно делать ваше приложение)
- Триал начинается: Stripe создаёт подписку и ставит
trial_end. Обычно вы увидитеcustomer.subscription.createdи (в зависимости от настройки) предстоящий инвойс. Ваше приложение должно дать доступ, потому что подписка в триале, и записать дату окончания триала, чтобы доступ менялся автоматически.
Подвох 1: дать доступ только при «успешной регистрации», а затем никогда его не обновить по окончании триала.
- Апгрейд во время триала: Майя переходит с Basic на Pro на 10-й день. Stripe обновляет подписку и может сгенерировать инвойс или проревацию. Вы можете увидеть
customer.subscription.updated,invoice.created,invoice.finalized, а затемinvoice.paid, если деньги собраны.
Подвох 2: считать «смену плана» как немедленный платный доступ, даже если инвойс остаётся открытым или платёж позже не проходит.
- Продление: на 14-й день начинается первый платный период, затем в следующем месяце пытаются списать продление.
Подвох 3: полагаться на один вебхук и пропускать другие, из-за чего вы либо не снимаете доступ после invoice.payment_failed, либо снимаете доступ даже после invoice.paid (дубли и внепорядочные события).
- Карта не проходит: Stripe помечает инвойс как unpaid и запускает повторные попытки в соответствии с настройками.
Подвох 4: сразу блокировать пользователя вместо использования короткого льготного периода и предоставления понятного пути «обновите карту».
Что хранить, чтобы саппорт мог быстро исправлять проблемы
Держите небольшой аудит: Stripe customer ID, subscription ID, текущий статус, trial_end, current_period_end, последний invoice ID, дата последнего успешного платежа и последний обработанный event.id с временной отметкой.
Когда Майя обращается в саппорт во время проблемы, команда должна быстро ответить на два вопроса: что Stripe говорит прямо сейчас и что наше приложение применило последним.
Чеклист QA: проверьте поведение биллинга перед запуском
Относитесь к биллингу как к фиче, которую нужно тестировать, а не как к переключателю. Большая часть утечек дохода случается в разрывах между событиями Stripe и тем, что ваше приложение решает о доступе.
Начните с разделения «может ли Stripe взять платёж?» и «дает ли приложение доступ?» и тестируйте оба в той же среде, в которой вы будете выкатывать.
Проверки перед запуском
- Подтвердите разделение тестового и боевого: ключи, эндпоинты вебхуков, продукты/цены, переменные окружения
- Проверьте безопасность вебхуков: верификация подписи включена, неподписанные или искажённые события отклоняются
- Проверьте идемпотентность: повторяющиеся события не создают лишних прав, инвойсов или писем
- Сделайте логи информативными: храните
event.id, customer, subscription и окончательное решение о доступе - Проверьте маппинг: каждый пользователь соответствует ровно одному Stripe customer (или у вас есть ясное правило для мульти-клиентов)
В AppMaster это обычно означает подтверждение интеграции Stripe, настроек окружения и того, что Business Process сохраняет чистый аудит для каждого события вебхука и изменения доступа.
Тест-кейсы поведения подписки
Проведите короткий скриптованный QA. Используйте реальные роли (обычный пользователь, админ) и зафиксируйте, что значит «доступ включён/выключен» в вашем продукте.
- Триалы: запустить триал, отменить в триале, дать триалу завершиться, продлить один раз, подтвердить, что конверсия происходит только при успешной оплате
- Проревация: апгрейд посреди цикла, даунгрейд посреди цикла, две смены плана в один день; подтвердить, что инвойс и доступ соответствуют политике
- Кредиты/возвраты: выписать кредит или сделать возврат и проверить, что вы не держите премиум доступ навсегда (или не удаляете слишком рано)
- Неудачные платежи: симулировать неудачное продление, проверить тайминги повторных попыток и льготный период, подтвердить, когда доступ ограничивается или удаляется
- Восстановление: после неудачного платежа провести платёж и подтвердить, что доступ возвращается немедленно (и только один раз)
Для каждого теста фиксируйте три вещи: хронологию событий Stripe, состояние вашей базы данных и то, что пользователь фактически может делать в приложении. Когда эти три источника расходятся — вы нашли утечку.
Следующие шаги: внедряйте безопасно и делайте биллинг предсказуемым
Запишите правила биллинга простым языком и делайте их конкретными: когда начинается доступ, когда он прекращается, что считается «оплатой», как завершаются триалы и что происходит при смене плана. Если два человека прочитают это и представят разные сценарии — ваш поток будет терять деньги.
Преобразуйте эти правила в повторяемый план тестирования, который вы запускаете при каждом изменении биллинговой логики. Несколько выделенных тестовых клиентов Stripe и фиксированный скрипт лучше, чем «покликивать и посмотреть, что получится».
Во время тестирования держите аудит. Саппорт и финансы должны быстро получать ответы вроде «почему этот пользователь сохранил доступ?» или «почему мы списали дважды?». Логируйте ключевые изменения подписок и инвойсов (статус, даты текущего периода, trial_end, последний инвойс, итог payment intent) и храните event.id вебхуков, чтобы доказывать, что произошло, и не обрабатывать одно событие дважды.
Если вы реализуете это без кода, AppMaster (appmaster.io) поможет держать структуру консистентной. Вы можете моделировать данные биллинга в Data Designer (PostgreSQL), обрабатывать Stripe вебхуки в Business Process Editor с проверками идемпотентности и контролировать доступ одним полем «источника правды», которое читают веб и мобильные UI.
Закончите сухим прогоном, похожим на реальную жизнь: коллега регистрируется, использует приложение, делает апгрейд, у него падает платёж, затем он исправляет это. Если каждый шаг совпадает с вашими письменными правилами — вы готовы.
Следующий шаг: попробуйте построить минимальный поток подписки Stripe в AppMaster и затем пройдите чеклист QA перед запуском.


