Реализация Billing Retry Logic при неудачном платеже подписки

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.

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

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

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

Услуги, которые мы предлагаем
Показано 1 из 1Все 1735 услуг
Реализация Billing Retry Logic при неудачном платеже подписки
Средний
~2-3 дня
Часто задаваемые вопросы

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

Этапы разработки

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

  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    792
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    671
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1097
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    969
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    914
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    495

Реализация Billing Retry Logic при неудачном платеже подписки

Платёж по подписке упал с кодом insufficient_funds или card_declined. Что дальше? Если сразу заблокировать пользователя — он уйдёт. Если бесконечно повторять без логики — банк пометит карту как скомпрометированную. Правильная retry-стратегия балансирует между возвратом денег и сохранением пользователя.

Умная экспоненциальная задержка

Стандарт де-факто для retry — экспоненциальный backoff с jitter:

import random
from datetime import datetime, timedelta

RETRY_SCHEDULE = [
    timedelta(hours=1),    # Попытка 2: через 1 час
    timedelta(hours=24),   # Попытка 3: через 1 день
    timedelta(days=3),     # Попытка 4: через 3 дня
    timedelta(days=7),     # Попытка 5: через 7 дней
]

def schedule_next_retry(subscription_id: str, attempt: int) -> datetime | None:
    if attempt >= len(RETRY_SCHEDULE):
        # Исчерпаны все попытки — переводим в grace period или отменяем
        return None

    base_delay = RETRY_SCHEDULE[attempt]
    jitter = timedelta(minutes=random.randint(-30, 30))
    next_attempt_at = datetime.utcnow() + base_delay + jitter

    db.update_subscription_retry(
        subscription_id=subscription_id,
        next_retry_at=next_attempt_at,
        attempt_number=attempt + 1
    )
    return next_attempt_at

Jitter важен: без него все подписки, упавшие одновременно (например, при сбое эквайера), будут повторяться синхронно и создадут пиковую нагрузку.

Классификация ошибок: что retry-ить, что нет

Не все коды ошибок Stripe одинаково полезны для retry:

Код ошибки Retry Причина
insufficient_funds Да Средства появятся
card_declined (generic) Да Временный отказ банка
do_not_honor Да, с задержкой Временная блокировка
stolen_card Нет Карта заблокирована навсегда
card_velocity_exceeded Да, через 24ч Лимит операций
expired_card Нет Нужна новая карта
incorrect_cvc Нет Пользователь ввёл неверно
NON_RETRYABLE_CODES = {
    'card_declined': ['stolen_card', 'lost_card', 'fraudulent'],
    'incorrect_cvc': None,
    'expired_card': None,
    'invalid_account': None,
}

def should_retry(stripe_error: dict) -> bool:
    code = stripe_error.get('code', '')
    decline_code = stripe_error.get('decline_code', '')

    if code in NON_RETRYABLE_CODES:
        blocked = NON_RETRYABLE_CODES[code]
        if blocked is None or decline_code in blocked:
            return False
    return True

Grace Period: пользователь не теряет доступ немедленно

После первого неудачного платежа — не блокируем, а даём grace period (обычно 3-7 дней):

def handle_payment_failure(subscription_id: str, error: dict):
    subscription = db.get_subscription(subscription_id)

    if not should_retry(error):
        # Неисправимая ошибка — уведомляем, просим обновить карту
        notify_update_payment_method(subscription.user_id)
        db.set_subscription_status(subscription_id, 'past_due')
        return

    attempt = subscription.retry_attempt or 0
    next_retry = schedule_next_retry(subscription_id, attempt)

    if next_retry is None:
        # Исчерпаны retry — переходим к grace period или отмене
        grace_end = datetime.utcnow() + timedelta(days=3)
        db.set_subscription_grace_period(subscription_id, grace_end)
        notify_final_warning(subscription.user_id, grace_end)
    else:
        db.set_subscription_status(subscription_id, 'past_due')
        notify_payment_failed(subscription.user_id, next_retry, attempt + 1)

Stripe: автоматический Smart Retries

Stripe предоставляет встроенный механизм — Smart Retries — который использует ML для выбора оптимального времени повтора на основе паттернов успешных платежей. Включается в Dashboard → Billing → Subscriptions → Smart Retries.

Но Smart Retries не заменяет вашу бизнес-логику: Stripe не знает, сколько дней вы готовы давать grace period и какие уведомления отправлять пользователю.

Если используете Stripe Billing, подписывайтесь на webhook-события:

@app.post("/webhooks/stripe")
async def stripe_webhook(request: Request):
    payload = await request.body()
    sig_header = request.headers.get("stripe-signature")

    try:
        event = stripe.Webhook.construct_event(
            payload, sig_header, WEBHOOK_SECRET
        )
    except stripe.error.SignatureVerificationError:
        raise HTTPException(400)

    match event['type']:
        case 'invoice.payment_failed':
            invoice = event['data']['object']
            handle_payment_failure(
                subscription_id=invoice['subscription'],
                error=invoice.get('last_payment_error', {})
            )
        case 'invoice.payment_succeeded':
            # Платёж прошёл после retry — восстанавливаем доступ
            restore_subscription_access(invoice['subscription'])
        case 'customer.subscription.deleted':
            # Подписка окончательно отменена после всех попыток
            handle_subscription_cancelled(invoice['subscription'])

Уведомления пользователю

Серия уведомлений важна: 42% пользователей обновляют платёжные данные после первого напоминания. Push через FCM/APNs + email — обязательная комбинация.

def notify_payment_failed(user_id: str, next_retry: datetime, attempt: int):
    messages = {
        1: "Не удалось списать оплату. Повторим попытку {date}.",
        2: "Вторая попытка оплаты не прошла. Обновите карту или попробуем {date}.",
        3: "Последняя попытка — {date}. После этого доступ будет ограничен."
    }
    template = messages.get(attempt, messages[3])
    send_push(user_id, template.format(date=next_retry.strftime("%d.%m в %H:%M")))
    send_email(user_id, subject="Проблема с оплатой подписки", body=template)

Сроки

2–3 дня. Логика retry с классификацией ошибок + grace period + webhook-обработка — 2 дня. Серия уведомлений + тестирование всех сценариев — 0,5–1 день. Стоимость рассчитывается индивидуально.