Legan Studio
Все статьи
~ 17 мин чтения

Как подключить оплату в боте MAX

Разбираем варианты приема оплаты в боте MAX: встроенные платежи, ссылки на эквайринг, чеки 54-ФЗ и нюансы интеграции для бизнеса.

  • MAX
  • оплата
  • интеграции

Оплата внутри бота — это не «прикрутить кнопку», а связка из эквайринга, фискализации, статусов заказа, обработки возвратов и юридического оформления. В мессенджере MAX (российский продукт VK Tech) экосистема платежей моложе, чем у западных конкурентов, и продавцу важно понимать, какие способы реально работают по состоянию на 2026 год, а где придётся доделывать обвязку самому. В этой статье разберём способы приёма оплаты в боте MAX, архитектуру платёжного потока, идемпотентность, фискализацию по 54-ФЗ, рекуррентные списания и типичные антипаттерны, которые ломают деньги в проде.

Способы приёма оплаты в боте MAX

В боте MAX в 2026 году доступно несколько принципиально разных способов принять деньги — выбор определяется не столько комиссией, сколько UX и юридической нагрузкой:

  1. Нативные платежи MAX Bot API — встроенная форма оплаты в чате через подключённый эквайринг. По состоянию на 2026 год возможности развиваются: VK Tech постепенно расширяет список поддерживаемых провайдеров. Сценарий близок к тому, что предлагают другие мессенджеры — кнопка «Оплатить» прямо в карточке бота, реальная валюта (RUB), а сам платёж проходит через подключённый шлюз.
  2. Ссылка на платёжную форму эквайера. Бот генерирует одноразовую ссылку (ЮKassa, CloudPayments, Tinkoff, Robokassa, СберПэй), пользователь оплачивает на странице провайдера, бот получает webhook с результатом и активирует услугу.
  3. СБП по QR. Подходит для офлайн-сценариев и для случаев, где пользователь в любом случае открывает банковское приложение. QR можно сформировать через провайдера или напрямую через банк-партнёра.
  4. Постоплата и счёт на оплату. Бот отправляет PDF-счёт юрлицу, оплата идёт через расчётный счёт. Удобно для B2B — там оплата по картам в боте редко имеет смысл из-за лимитов и бухгалтерских требований.
  5. Крипто-платежи. Для узких ниш (международные клиенты, специфичные SaaS) — через шлюзы вроде CryptoCloud или Cryptomus. Фискализация и юридическая часть сложнее, но иногда это единственный способ принять оплату от клиента за пределами РФ.

Для большинства B2C-сценариев оптимально нативные платежи или ссылка на эквайринг — это привычный для пользователя UX и автоматическая фискализация на стороне провайдера.

Сравнительная таблица шлюзов для бота MAX

ШлюзКомиссияСБПРекуррентЧеки 54-ФЗОсобенности
ЮKassa2.8–3.5% карты, 0.7% СБПдада (save_payment_method)да, из коробкиуниверсально, удобный API, тестовая среда
CloudPayments2.5–3.2%дада (subscription API)дахороший рекуррент, кастомные виджеты
Tinkoff Касса2.5–3.2%дада (Recurrent: Y)давыплата T+1, сильный антифрод
Robokassa3–5%дадада (через АТОЛ)простое подключение для микро-бизнеса
СБП напрямую (банк)0.4–0.7%данеттребует своей кассыминимальная комиссия, сложнее обвязка
Крипто-шлюз0.5–1.5%нетнетнет (вне РФ-периметра)для международных клиентов

Грубый ориентир: если оборот менее 500 000 ₽/мес — берите ЮKassa или CloudPayments, не тратьте время на оптимизацию комиссий. Если оборот больше — добавляйте СБП напрямую через банк, экономия на 1–2 п.п. комиссии быстро окупает доработки.

Архитектура платёжного потока

Минимальная схема надёжной оплаты в боте MAX:

handler  →  создание заказа в БД (status=pending)
         →  создание invoice через API провайдера (Idempotency-Key)
         →  отправка ссылки/inline-payment пользователю
         →  пользователь оплачивает
провайдер →  webhook на ваш сервер (HTTPS, подпись)
         →  валидация подписи + проверка суммы
         →  обновление статуса заказа (status=paid)
         →  активация услуги/выдача товара
         →  отправка фискального чека
         →  уведомление пользователю в боте

Ключевой принцип — статус заказа меняется только по webhook от провайдера, а не по событию из мессенджера или клика пользователя. Иначе один сетевой сбой превращает оплаченный заказ в «висящий», а пользователь уже видит списание в банке.

Второй принцип — активация услуги после записи факта платежа в БД, не до. В одной транзакции: записали платёж, активировали подписку. Иначе при падении сервера между этими шагами получите услугу без оплаты в учёте — пользователь будет рад, бухгалтер нет.

Создание invoice через ЮKassa

Базовый код создания платежа на Python с requests:

import requests
from uuid import uuid4

YOOKASSA_SHOP_ID = "123456"
YOOKASSA_SECRET_KEY = "live_xxx"

def create_payment(order_id: str, amount_kopecks: int, description: str, return_url: str) -> dict:
    idempotence_key = f"order-{order_id}-{uuid4()}"

    resp = requests.post(
        "https://api.yookassa.ru/v3/payments",
        auth=(YOOKASSA_SHOP_ID, YOOKASSA_SECRET_KEY),
        headers={
            "Idempotence-Key": idempotence_key,
            "Content-Type": "application/json",
        },
        json={
            "amount": {
                "value": f"{amount_kopecks / 100:.2f}",
                "currency": "RUB",
            },
            "capture": True,
            "confirmation": {
                "type": "redirect",
                "return_url": return_url,
            },
            "description": description,
            "metadata": {
                "order_id": order_id,
            },
            "receipt": build_receipt(order_id, amount_kopecks),
        },
        timeout=10,
    )
    resp.raise_for_status()
    return resp.json()

Важные моменты:

  • Idempotence-Key обязателен — если запрос ретраится из-за сетевой ошибки, провайдер вернёт исходный платёж, а не создаст второй.
  • metadata.order_id — ваш внутренний идентификатор. По нему позже найдёте заказ в БД при webhook.
  • amount.value — строка с двумя знаками после запятой ("499.00"), а не число. Это требование API ЮKassa, частая ошибка.
  • receipt — секция фискализации (см. ниже). Если её не передать, чек придётся выбивать через свою кассу отдельно.

Webhook handler на FastAPI

Webhook — самая критичная часть платёжной интеграции. Рабочий шаблон на FastAPI:

from fastapi import APIRouter, Request, HTTPException
from ipaddress import ip_address, ip_network

router = APIRouter()

YOOKASSA_IPS = [
    ip_network("185.71.76.0/27"),
    ip_network("185.71.77.0/27"),
    ip_network("77.75.153.0/25"),
    ip_network("77.75.156.11/32"),
    ip_network("77.75.156.35/32"),
    ip_network("77.75.154.128/25"),
    ip_network("2a02:5180::/32"),
]

def is_yookassa_ip(client_ip: str) -> bool:
    addr = ip_address(client_ip)
    return any(addr in net for net in YOOKASSA_IPS)

@router.post("/webhooks/yookassa")
async def yookassa_webhook(request: Request):
    client_ip = request.headers.get("X-Forwarded-For", request.client.host).split(",")[0].strip()
    if not is_yookassa_ip(client_ip):
        raise HTTPException(status_code=403, detail="forbidden")

    body = await request.json()
    event = body.get("event")
    payment = body.get("object", {})
    payment_id = payment.get("id")
    order_id = payment.get("metadata", {}).get("order_id")

    if not payment_id or not order_id:
        raise HTTPException(status_code=400, detail="missing fields")

    if event == "payment.succeeded":
        await handle_payment_succeeded(order_id, payment_id, payment)
    elif event == "payment.canceled":
        await handle_payment_canceled(order_id, payment_id, payment)
    elif event == "refund.succeeded":
        await handle_refund_succeeded(order_id, payment_id, payment)

    return {"ok": True}

ЮKassa не подписывает webhook'и HMAC — она использует whitelist IP-адресов. У других провайдеров (CloudPayments, Tinkoff) подпись через HMAC-SHA256 обязательна — без проверки злоумышленник пришлёт поддельный «успешный платёж» и получит услугу бесплатно.

Идемпотентность webhook и защита от двойных списаний

Webhook может прилететь дважды (а то и трижды): таймаут на вашей стороне, ретрай на стороне провайдера, дубль из-за переадресации. Дедуп нужен на двух уровнях.

Уровень 1: middleware на уникальность события.

from sqlalchemy import select
from datetime import datetime

async def handle_payment_succeeded(order_id: str, payment_id: str, payload: dict):
    async with db.transaction():
        existing = await db.fetch_one(
            select(Payment).where(Payment.provider_payment_id == payment_id)
        )
        if existing and existing.status == "paid":
            return

        await db.execute(
            insert(Payment).values(
                order_id=order_id,
                provider_payment_id=payment_id,
                amount_kopecks=int(float(payload["amount"]["value"]) * 100),
                currency=payload["amount"]["currency"],
                status="paid",
                paid_at=datetime.utcnow(),
                raw_payload=payload,
            )
        )
        await db.execute(
            update(Order).where(Order.id == order_id).values(status="paid")
        )
        await activate_service(order_id)
        await notify_user(order_id, status="paid")

Уровень 2: уникальный индекс в БД. На колонке payments.provider_payment_id поставьте UNIQUE — даже если кодовая логика проморгает дубль, БД не даст вставить второй раз и кинет IntegrityError, который обработчик превратит в 200 OK для провайдера.

Уровень 3: проверка суммы. Никогда не доверяйте сумме из webhook буквально — сравнивайте с тем, что выставили на сервере по order_id. Если суммы не совпадают (расхождение даже на копейку) — логируйте инцидент и не активируйте услугу.

Фискализация по 54-ФЗ

Для всех платежей физлицам в РФ обязателен онлайн-чек через ОФД. Варианты:

  • Чек через провайдера. ЮKassa, CloudPayments и Tinkoff формируют чек автоматически, если в платёж передать секцию receipt. Это самый простой путь.
  • Своя облачная касса. АТОЛ Онлайн, OFD.ru, Бизнес.Ру, ferma.ofd.ru — отдельная интеграция с фискальным накопителем по протоколу ФФД 1.2. Нужна, если хотите контролировать формирование чеков самостоятельно или интегрироваться с собственной товароучётной системой.

Структура секции receipt для ЮKassa:

def build_receipt(order_id: str, amount_kopecks: int) -> dict:
    return {
        "customer": {
            "email": get_customer_email(order_id),
        },
        "items": [
            {
                "description": "Подписка Pro на месяц",
                "quantity": "1.00",
                "amount": {
                    "value": f"{amount_kopecks / 100:.2f}",
                    "currency": "RUB",
                },
                "vat_code": 1,
                "payment_subject": "service",
                "payment_mode": "full_prepayment",
            }
        ],
        "tax_system_code": 2,
    }

Что значат поля:

  • vat_code — НДС: 1 без НДС, 2 НДС 0%, 3 НДС 10%, 4 НДС 20%, 5 НДС 10/110, 6 НДС 20/120.
  • payment_subject — предмет расчёта: commodity (товар), service (услуга), payment (платёж), intellectual_activity (РИД).
  • payment_mode — способ расчёта: full_prepayment (полная предоплата), partial_prepayment, advance, full_payment (полный расчёт).
  • tax_system_code — система налогообложения: 1 ОСН, 2 УСН доходы, 3 УСН доходы−расходы, 5 ЕСХН, 6 ПСН.
  • customer.email — обязательно для отправки чека покупателю по 54-ФЗ. Если нет email, можно phone.

Своя касса через АТОЛ Онлайн

Если используете не провайдерскую фискализацию, а АТОЛ Онлайн, поток другой — двухшаговый:

import requests

ATOL_LOGIN = "your_login"
ATOL_PASSWORD = "your_password"
ATOL_GROUP_CODE = "your_group_code"

def atol_get_token() -> str:
    resp = requests.post(
        "https://online.atol.ru/possystem/v4/getToken",
        json={"login": ATOL_LOGIN, "pass": ATOL_PASSWORD},
        timeout=10,
    )
    return resp.json()["token"]

def atol_send_receipt(order_id: str, email: str, items: list, total_kopecks: int) -> str:
    token = atol_get_token()
    external_id = f"order-{order_id}"

    payload = {
        "external_id": external_id,
        "receipt": {
            "client": {"email": email},
            "company": {
                "email": "shop@example.ru",
                "sno": "usn_income",
                "inn": "7707083893",
                "payment_address": "https://example.ru",
            },
            "items": items,
            "payments": [
                {"type": 1, "sum": total_kopecks / 100}
            ],
            "total": total_kopecks / 100,
        },
        "timestamp": datetime.now().strftime("%d.%m.%Y %H:%M:%S"),
    }

    resp = requests.post(
        f"https://online.atol.ru/possystem/v4/{ATOL_GROUP_CODE}/sell",
        headers={"Token": token},
        json=payload,
        timeout=15,
    )
    return resp.json()["uuid"]

После отправки чека АТОЛ возвращает uuid, по которому через несколько секунд через /report/{uuid} забираете финальный фискальный документ с номером чека и QR-кодом для покупателя.

Webhook-события и их обработка

Минимальный набор событий, которые нужно обрабатывать:

СобытиеКогда приходитЧто делать
payment.waiting_for_captureпользователь авторизовал, но не списанокапчурить через /payments/{id}/capture или авто-capture
payment.succeededсредства списаныактивировать услугу, выбить чек, уведомить
payment.canceledбанк отказал, истёк таймаутпометить заказ failed, предложить повтор
refund.succeededвозврат прошёлотозвать услугу, выбить чек коррекции, уведомить
deal.closed(для маркетплейсов) сделка закрытафинализировать расчёты с продавцом

Для большинства простых сценариев (предоплата) достаточно payment.succeeded + payment.canceled + refund.succeeded. Для отложенного капча (бронирования, такси) добавьте waiting_for_capture.

Возвраты и частичные возвраты

Возврат — через API провайдера, не через мессенджер. Полный возврат ЮKassa:

def refund_payment(payment_id: str, amount_kopecks: int, reason: str) -> dict:
    resp = requests.post(
        "https://api.yookassa.ru/v3/refunds",
        auth=(YOOKASSA_SHOP_ID, YOOKASSA_SECRET_KEY),
        headers={
            "Idempotence-Key": str(uuid4()),
            "Content-Type": "application/json",
        },
        json={
            "amount": {
                "value": f"{amount_kopecks / 100:.2f}",
                "currency": "RUB",
            },
            "payment_id": payment_id,
            "description": reason,
        },
        timeout=10,
    )
    resp.raise_for_status()
    return resp.json()

Частичный возврат — указываете сумму меньше изначальной. Можно делать несколько частичных возвратов подряд, пока сумма всех возвратов не достигнет суммы платежа.

При возврате провайдер автоматически выбьет чек коррекции в ОФД (если был передан receipt при изначальном платеже) — это важно для 54-ФЗ. Если делаете возврат через свою кассу (АТОЛ), фискальный документ типа «возврат прихода» формируете отдельным запросом.

Срок добровольного возврата по закону «О защите прав потребителей» — 10 дней на банковский счёт после заявления покупателя. Чарджбеки (споры через банк) разбираются 30–45 дней через переписку с провайдером.

Рекуррентные платежи и подписки

В боте MAX нативного механизма подписок нет — реализуете на стороне сервера. Архитектура:

  1. Первый платёж с сохранением метода. При создании платежа передаёте save_payment_method: true:
    payload = {
        "amount": {"value": "499.00", "currency": "RUB"},
        "save_payment_method": True,
        "capture": True,
        "confirmation": {"type": "redirect", "return_url": return_url},
        "metadata": {"order_id": order_id, "subscription_id": sub_id},
    }
    
  2. Сохранение токена карты. В webhook payment.succeeded приходит payment_method.id — сохраняете в БД, привязываете к подписке. Сама карта остаётся у провайдера, у вас только токен.
  3. Списание по cron. Каждый месяц по расписанию создаёте платёж с payment_method_id и без confirmation — деньги списываются автоматически:
    payload = {
        "amount": {"value": "499.00", "currency": "RUB"},
        "payment_method_id": stored_method_id,
        "capture": True,
        "description": "Продление подписки Pro",
        "metadata": {"subscription_id": sub_id, "period": "2026-05"},
    }
    
  4. Обработка неудач. Если статус canceled (нет средств, истёк срок карты) — даёте grace-period 3 дня, шлёте напоминание в бот с кнопкой обновить карту.

Юридически нужны: оферта на рекуррент с явным упоминанием суммы и периодичности, отдельное согласие пользователя галочкой перед первой оплатой, хранение факта акцепта в БД (user_id, offer_version, accepted_at, ip) для разрешения споров.

Безопасность платежей

Базовые правила, которые часто нарушают:

  • Не храните данные карт у себя. PCI DSS — отдельная сертификация ценой в миллионы рублей. Всегда работайте через токены провайдера (payment_method_id).
  • Проверяйте подписи webhook. ЮKassa — IP whitelist, CloudPayments — HMAC-SHA256 в заголовке Content-HMAC, Tinkoff — SHA-256 от конкатенации полей. Без проверки = бесплатные подписки для злоумышленников.
  • HTTPS обязательно. Webhook-эндпоинт только по TLS с валидным сертификатом. HTTP-эндпоинты провайдеры не вызывают вообще.
  • Не доверяйте данным от клиента. Сумма, состав корзины, скидки — всё считается на сервере. Клиент присылает только order_id или cart_id.
  • Логирование без чувствительных полей. В логи можно payment_id, order_id, amount, status. Нельзя — secret_key, тело webhook целиком, email клиента в открытом виде, токены карт.
  • Rate limiting на API. Создание заказов и инициация платежей — типичная цель для скриптовых атак. Лимит 10 запросов в минуту с одного user_id плюс капча на подозрительные паттерны.

Тестовые карты для разработки

У всех провайдеров есть sandbox с тестовыми картами. Несколько ходовых:

  • ЮKassa тест: 5555 5555 5555 4444 (Mastercard, успех), 5555 5555 5555 4477 (отказ), 4111 1111 1111 1026 (Visa, требует 3-D Secure).
  • Tinkoff тест: 4300 0000 0000 0777 (успех), 5000 0000 0000 0009 (отказ от банка).
  • CloudPayments тест: 4242 4242 4242 4242 (успех), 4012 8888 8888 1881 (отказ).

CVC и срок действия — любые валидные. На приёмке прогоняйте не только happy path, но и: отказ банка, таймаут запроса, дублирующий webhook, частичный возврат, оплата того же order_id дважды, истечение confirmation_url.

Метрики платёжной воронки

Что мерить, чтобы понимать, где теряются деньги:

  • Conversion от инициации до оплатыpayment.succeeded / payment.created. Норма для эквайринга 60–80%, для СБП 75–90%. Падение ниже — повод смотреть UX оплаты и выбор провайдера.
  • Refund raterefunds / payments. Нормально 1–3%; выше 5% — проблема либо с продуктом, либо с антифродом.
  • Средний чек (AOV) — общая выручка / число заказов. Используется для сегментации и для определения, нужен ли вам upsell на этапе чекаута.
  • Time-to-payment — медианное время от создания заказа до payment.succeeded. Если больше 5 минут — пользователь откладывает оплату, и значит надо слать напоминание через бот через 10–15 минут.
  • Failed-by-bank ratepayment.canceled с reason=bank_rejected. Если выше 10%, играйте 3-D Secure-настройками или меняйте антифрод-правила.

152-ФЗ и обработка персональных данных

Приём оплаты — это автоматически обработка ПДн. Что нужно:

  • Согласие на обработку ПДн перед сбором email, телефона, имени. Отдельная страница /consent на сайте + чекбокс перед первой оплатой.
  • Минимизация данных в логах. Email — только в БД с шифрованием на уровне колонки или Application Layer Encryption. В логи попадают user_id и order_id, не email и не телефон.
  • Не передавать ПДн в провайдер сверх необходимого. В metadata платежа не пишите ФИО, паспорт, адрес доставки — только идентификаторы.
  • Уведомление РКН. Для приёма оплаты с ПДн нужно подать уведомление об обработке ПДн в Роскомнадзор. Если данные передаются в Telegram-бот для уведомлений — отдельное уведомление о трансграничной передаче.
  • Хранение факта акцепта. В БД: user_id, consent_version, accepted_at, ip, user_agent. Это доказательство в случае спора.

Антипаттерны, которые ломают деньги

Грабли, на которые регулярно наступают:

  • Хранение карт самостоятельно без PCI DSS. Запрещено законодательно, штрафы и блокировка эквайером. Только токены провайдера.
  • Доверие к статусу платежа из клиента. Сценарий: клиент отправляет POST /order/paid после редиректа с формы оплаты, бэкенд верит и активирует услугу. Любой школьник с DevTools получает услугу бесплатно. Статус — только из webhook от провайдера.
  • Игнор идемпотентности. Webhook прилетает дважды → подписка продлевается на 2 месяца сразу → пользователь возмущён → возврат + ручная сверка. Идемпотентность на уровне БД (уникальный индекс) — обязательна.
  • Отсутствие обработки payment.canceled. Заказ висит в pending бесконечно, пользователь не понимает, что делать. Нужно: пометить failed, прислать уведомление в бот с кнопкой «Повторить оплату».
  • Отсутствие фискализации. Продажа без чека = штраф ФНС от 30 000 ₽ за каждое нарушение. Подключайте receipt в платёж сразу, не «потом доделаем».
  • Webhook-эндпоинт без авторизации. Открытый POST-эндпоинт принимает любые JSON-ы как успешные платежи. IP-whitelist или HMAC-подпись — критично с первого дня.
  • Тяжёлая логика синхронно в webhook. Webhook должен ответить 200 за 5–10 секунд, иначе провайдер ретраит. Отправку email, выбивку чека, обновление CRM — в фоновую очередь (Celery, RQ, ARQ), в webhook только запись в БД.

Тестовый режим и приёмка

У всех приличных эквайеров есть sandbox: тестовые карты, симуляция отказов, ускоренные таймауты. Чек-лист приёмки:

  1. Успешная оплата картой → активация услуги → чек на email.
  2. Отказ банка → корректное сообщение в бот, кнопка «Повторить».
  3. Таймаут оплаты → заказ переведён в expired, кнопка создать новый.
  4. Дубль webhook → второй раз не активирует услугу повторно.
  5. Полный возврат → услуга деактивирована, чек возврата выбит.
  6. Частичный возврат → корректно скорректирован остаток.
  7. Рекуррентное списание → новый платёж создан, услуга продлена.
  8. Истёк срок карты при рекурренте → пользователь уведомлён, grace-period работает.

Без прогона негативных сценариев первое же ребро в проде будет ваше — и оно почти всегда происходит ночью в выходной.

Итого

Подключение оплаты в боте MAX — это интеграция бот ↔ провайдер ↔ касса с правильной обработкой webhook, идемпотентностью, фискализацией по 54-ФЗ и юридическим оформлением. Технически проект занимает от недели на простой эквайринг до месяца на сценарии с возвратами, рекуррентными подписками и собственной кассой. Главное — не экономить на тестировании негативных сценариев, обработке дублей webhook и проверке подписей. Деньги пользователя — это самое больное место продукта, и любая ошибка стоит дороже, чем разработка нормальной инфраструктуры с первого дня.

Частые вопросы

Какие способы оплаты можно подключить в боте MAX?

Пять основных. Нативные платежи MAX Bot API — встроенная форма оплаты в чате через подключённый эквайринг (возможности развиваются по состоянию на 2026 год). Ссылка на платёжную форму эквайера (ЮKassa, CloudPayments, Tinkoff, Robokassa, СберПэй) — бот генерирует одноразовую ссылку, пользователь оплачивает на странице, бот получает webhook. СБП по QR — для офлайн-сценариев или быстрых платежей через банковское приложение. Постоплата или счёт PDF — оплата вне платформы, удобно для B2B. Крипто-шлюзы (CryptoCloud, Cryptomus) — для международных клиентов в узких нишах. Для B2C обычно нативные платежи или ссылка эквайера, для B2B — счёт.

Сколько стоит подключить оплату в боте MAX?

От недели работы на простой эквайринг (создание заказа, ссылка на оплату, webhook, обновление статуса) до месяца на сценарии с возвратами, рекуррентными подписками и подключением своей кассы. Стоимость интеграции — от 30 000 ₽ за базовую схему до 150 000 ₽ за полноценную с фискализацией, частичными возвратами и автосписаниями. Плюс комиссии провайдера (2–3% за карты, 0,4–0,7% за СБП напрямую), фиксированная стоимость облачной кассы (1500–5000 ₽/мес для АТОЛ Онлайн и аналогов). Сравнивать провайдеров стоит не только по комиссии, но и по качеству API, наличию тестовой среды и встроенной фискализации.

Нужна ли касса для приёма оплаты в боте MAX по 54-ФЗ?

Да, обязательно. Для российского бизнеса любой приём оплаты от физлица — это автоматически фискализация по 54-ФЗ. Варианты: провайдер сам формирует чеки (ЮKassa, CloudPayments, Tinkoff умеют — это самый простой путь, передаёте секцию receipt в платёж), или подключается отдельная облачная касса (АТОЛ Онлайн, OFD.ru, Бизнес.Ру, ferma.ofd.ru). В чек входят: позиции с описанием, количеством, ценой и НДС, признак расчёта (полная предоплата, аванс, полный расчёт), предмет расчёта (товар, услуга), система налогообложения, email или телефон покупателя. Продажа без чека — штраф ФНС от 30 000 ₽ за каждое нарушение.

Как обеспечить идемпотентность платежей в боте MAX?

Три уровня защиты от дублей. Первый — Idempotency-Key в API провайдера: уникальный UUID в заголовке POST /payments, повторный запрос с тем же ключом вернёт исходный платёж, не создаст второй. Второй — уникальный индекс на колонке payments.provider_payment_id в БД: даже если кодовая логика проморгает дубль webhook, БД не даст вставить второй раз и кинет IntegrityError. Третий — проверка event_id или хеша тела webhook перед обработкой. Webhook от провайдера может прилететь дважды (таймаут на вашей стороне, ретрай провайдера) — без идемпотентности подписка продлевается на 2 месяца сразу и пользователь возмущён. Идемпотентность нужна с первого дня, не «потом доделаем».

Как защитить webhook оплаты в боте MAX от подделки?

Два механизма в зависимости от провайдера. ЮKassa использует IP-whitelist — проверяете, что webhook пришёл с IP-адреса из официального списка ЮKassa (185.71.76.0/27 и другие подсети). CloudPayments и Tinkoff используют HMAC-подпись: провайдер кладёт в заголовок (Content-HMAC для CloudPayments) HMAC-SHA256 от тела запроса с secret-ключом, вы пересчитываете и сравниваете. Без проверки злоумышленник пришлёт поддельный webhook вида {"event": "payment.succeeded", "object": {"metadata": {"order_id": "X"}}} и получит услугу бесплатно. HTTPS обязателен — провайдеры не вызывают HTTP-эндпоинты вообще. Дополнительно — rate limiting на webhook-роут и алёрты на резкий рост подозрительных запросов.

Как сделать рекуррентные платежи и подписки в боте MAX?

В боте MAX нативного механизма подписок нет — реализуется на стороне сервера. Архитектура: при первом платеже передаёте провайдеру флаг сохранения метода (save_payment_method: true для ЮKassa, Recurrent: Y для Tinkoff), получаете payment_method_id (токен карты, сама карта остаётся у провайдера). По cron каждый месяц создаёте платёж с payment_method_id без confirmation — деньги списываются автоматически. При статусе succeeded продлеваете подписку в БД, отправляете уведомление в бот. При canceled (нет средств, истёк срок карты) даёте grace-period 3 дня и шлёте напоминание с кнопкой обновить карту через новый invoice с save_payment_method. Юридически нужны: оферта на рекуррент с явным упоминанием суммы и периодичности, отдельное согласие галочкой перед первой оплатой, хранение факта акцепта в БД (user_id, offer_version, accepted_at, ip).

Какие антипаттерны при приёме оплаты в боте MAX встречаются чаще всего?

Семь типичных грабель. Хранение данных карт самостоятельно без PCI DSS-сертификации — запрещено законом, всегда токены провайдера. Доверие к статусу платежа из клиента (POST /order/paid с фронта вместо webhook) — даёт услугу бесплатно любому со скриптом. Игнор идемпотентности webhook — двойное продление подписок и возмущённые пользователи. Отсутствие обработки payment.canceled — заказы висят в pending бесконечно. Отсутствие фискализации по 54-ФЗ — штрафы ФНС от 30 000 ₽ за каждое нарушение. Открытый webhook-эндпоинт без IP-whitelist или HMAC-подписи — поддельные платежи. Тяжёлая логика синхронно в webhook (отправка email, выбивка чека, обновление CRM) — провайдер таймаутит и ретраит, фоновые задачи через Celery или ARQ обязательны.