Cluster · разработчикам

Оплата

В Cluster есть локальная проверка подписки и подключение Stripe. Параметр --test-payments включает продление без провайдера. Параметр --payments-file подключает Stripe: по умолчанию песочницу, с явным mode=live рабочий приём настоящих платежей.

Ключ и события должны соответствовать выбранному режиму. Простая подмена тестового ключа рабочим отклоняется. Подключение второго провайдера, управление выплатами, автоматическое оформление возвратов и продажа модулей не реализованы.

Настройка

Ядру нужны три закрытых файла: ключ API Stripe, секрет подписи конкретного адреса доставки и JSON с путями к ним. Они должны принадлежать учётке ядра, быть обычными файлами и не иметь прав для группы и остальных пользователей. Символические ссылки не принимаются. Рекомендуемые права: 0600 для файлов, 0700 для каталога.

Содержимое /etc/cluster/stripe-test.json:

{
  "mode": "test",
  "key_file": "/etc/cluster/stripe-test.key",
  "webhook_file": "/etc/cluster/stripe-test-webhook.key",
  "origin": "https://cluster.example.com"
}

В stripe-test.key находится тестовый секретный ключ, начинающийся с sk_test_ или rk_test_. В stripe-test-webhook.key находится секрет whsec_ для выбранного адреса доставки. Ключи не помещаются в репозиторий, параметры команд, сайт или модуль. Допустимы режимы test и live; отсутствие поля mode означает test.

Добавь к существующей команде запуска ядра --payments-file /etc/cluster/stripe-test.json. Остальные параметры, включая образы, лимиты, администратора и ограничение регистрации, сохраняются. После изменения службы нужны systemctl daemon-reload и перезапуск ядра.

В песочнице Stripe создаётся адрес доставки https://cluster.example.com/api/payments/stripe. Для него используется версия API 2025-02-24.acacia и события:

Этот адрес должен проходить через HTTPS-прокси в HTTP-службу Cluster. Авторизация пользователя ему не нужна, подлинность проверяется по подписи Stripe. Секрет от другого адреса доставки или от Stripe CLI не подходит.

Создание оплаты

Сайт вызывает billing/checkout с периодом month, quarter или year и новым ID запроса. Нужны действующий сеанс и CSRF. Цену, валюту и срок задаёт ядро, браузер не передаёт сумму.

ПериодЦенаИнтервал Stripe
month20 USD30 суток
quarter60 USD90 суток
year200 USD365 суток

Используется обычный Stripe, в запросе явно передаётся managed_payments[enabled]=false. Автоматический расчёт налогов не включён. Настройка налогов, документов и рабочего аккаунта требует отдельного решения до приёма настоящих денег.

Для профиля создаётся покупатель Stripe с внутренним ID в служебных полях. Cluster не передаёт провайдеру почту, пароль, Telegram-сессию или токены ботов. Данные, которые человек вводит на странице Stripe, обрабатываются самим Stripe.

Для одного профиля может быть одна незавершённая оплата. Повтор с тем же ID и периодом возвращает прежний адрес. Параметры сохраняются до сетевого запроса, поэтому повтор после потери ответа совпадает с исходным. Другой период или другой ID при незавершённой оплате отклоняется. Срок ссылки составляет час. Если создание оплаты закончилось неопределённым результатом, нельзя открыть вторую до разрешения первой попытки или истечения этого срока.

Действующая подписка Stripe управляется отдельно. Пока она активна, новая не создаётся. Смена тарифа, скидки, несколько позиций, налоги и частичные счета не поддержаны, такие счета не выдают доступ автоматически.

Подтверждение

HTTP-служба передаёт ядру исходные байты запроса и Stripe-Signature. Ядро проверяет HMAC-SHA256, допустимый возраст подписи в 300 секунд и совпадение режима. Верхний предел тела: 256 KiB. Для временной ошибки возвращается 503, чтобы провайдер мог повторить доставку.

Событие не является единственным источником данных. Ядро получает актуальные сведения о подписке и счёте через API Stripe, проверяет покупателя, профиль, исходный запрос, период, валюту, сумму, количество и состав позиций.

checkout.session.completed привязывает подписку, но не добавляет срок. Доступ добавляется только по оплаченному ненулевому счёту. В одной транзакции сохраняются новый срок и запись счёта. Поэтому несколько уведомлений или повторная доставка не продлевают доступ ещё раз.

Нулевые счета отложенного начала списаний не дают дополнительного срока. Неудачная оплата тоже не продлевает доступ. Для каждого успешно оплаченного счёта добавляется 30, 90 или 365 суток. Отмена автопродления не удаляет уже выданный срок.

Восстановление пропущенных подтверждений

Раз в минуту ядро получает события через авторизованный API Stripe. Начальная выборка охватывает последний час, следующие выборки используют сохранённую отметку с перекрытием в пять минут. Отметка меняется после полной успешной обработки страниц. После простоя обрабатываются события начиная с сохранённой отметки, пока они доступны у Stripe.

billing/refresh также проверяет незавершённую оплату, подписку и последние десять её счетов. Сайт вызывает его при возвращении на страницу подписки, есть и ручная кнопка проверки.

Для неоплаченного счёта сайт показывает переход на страницу Stripe. Там можно завершить оплату, в том числе пройти подтверждение банка. После оплаченного счёта эта ссылка снимается. Замена карты без открытого счёта пока выполняется через оператора, отдельного личного кабинета Stripe в этой версии нет.

Подтверждения могут приходить повторно и в разном порядке. Состояние подписки запрашивается заново, счета учитываются по ID. Ошибка не записывается как успешно обработанное событие. Длительный сбой провайдера или неизвестный формат счёта требует разбора оператором.

Автопродление и коды

billing/renew меняет только cancel_at_period_end. Отключённое автопродление оставляет подписку до конца расчётного периода, его можно снова включить до окончательной отмены. Пароль и TOTP для этого не запрашиваются, действующий сеанс и CSRF обязательны.

billing/cancel закрывает незавершённую оплату и отключает следующие продления. Повторная отмена уже завершённой подписки безопасна.

Перед активацией подарочного кода незавершённая оплата закрывается. Для действующей подписки первое или следующее списание переносится через trial_end, без перерасчёта суммы. После ответа Stripe код расходуется и добавляется срок в Cluster. Если провайдер не принял перенос, код не расходуется. Числовые ограничения Stripe на отложенное начало списаний сохраняются.

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

Возвраты и споры

Возврат создаётся оператором в Stripe. Cluster получает подтверждение, отменяет соответствующую подписку и вычитает из выданного срока долю, равную доле возврата. Например, возврат 10 USD из месячного счёта на 20 USD снимает 15 суток. Несколько подтверждений одного возврата не снимают срок повторно. Последующий возврат оставшейся суммы снимает только оставшуюся долю. Бессрочный доступ не уменьшается.

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

Если оплата поступила для уже заблокированного или удаляемого профиля, доступ не выдаётся. Подтверждение остаётся необработанным, оператор должен проверить счёт и решить вопрос с деньгами. Автоматического возврата в этом случае нет.

Проверка перед рабочим приёмом

Песочница позволяет проверить создание оплаты, оплаченный счёт, продление, повторную доставку, отказ банка, отмену, коды и возвраты. Проверка в песочнице не подтверждает выплаты в банк, активацию рабочего аккаунта или приём карт из конкретной страны.

Для рабочего запуска проверяются состояние рабочего аккаунта Stripe, банковский счёт для выплат, налоги, порядок возвратов и обработки спорных или запоздалых счетов. Рабочий ключ используется только с явной настройкой mode=live.

Рабочий режим

В панели Stripe у рабочего аккаунта должны быть разрешены платежи и выплаты, а также указан действующий банковский счёт. Тестовые показатели аккаунта не подтверждают рабочий приём или выплаты. Активация аккаунта у провайдера сама по себе не переключает Cluster.

Создай отдельные закрытые файлы рабочего ключа и секрета подписи. Пример /etc/cluster/stripe-live.json:

{
  "mode": "live",
  "key_file": "/etc/cluster/stripe-live.key",
  "webhook_file": "/etc/cluster/stripe-live-webhook.key",
  "origin": "https://cluster.example.com"
}

Рабочий ключ начинается с sk_live_ или rk_live_. Адрес доставки создаётся в рабочем аккаунте, с теми же событиями и закреплённой версией API, что и в песочнице. Секрет подписи рабочего адреса отличается от тестового. Один процесс ядра принимает один выбранный режим. После переключения тестовый адрес доставки на тот же сайт отключается в песочнице.

К команде ядра применяется --payments-file /etc/cluster/stripe-live.json, параметр --test-payments убирается. Само указание рабочего файла не снимает ограничение регистрации --pilot-email. После перезапуска проверяются billing/info, статус подписки и отклонение неподписанного запроса на адрес доставки.

Покупатели Stripe, запросы оплаты, счета, обработанные события и отметка восстановления хранятся раздельно: тестовые в billing/, рабочие в billing/live/. При переключении не используются тестовые ID покупателей, ссылки оплаты или история счетов. Уже выданный срок доступа и подарочные коды сохраняются.

billing/info доступен без входа и возвращает только наличие подключения, признак тестового режима и ограничение регистрации. Сайт использует его для обозначения режима оплаты. Ключи и сведения покупателей в ответ не попадают.

Рабочие списания нельзя использовать вместо проверки в песочнице. Успешное подключение рабочего API также не доказывает успешную выплату: она подтверждается отдельно провайдером и банком. Переключение назад в песочницу не отменяет действующие рабочие подписки, их нельзя оставлять без обработки подтверждений.

В мини-приложении Telegram карточная оплата не отображается и запрос её создания отклоняется. Для цифрового доступа внутри Telegram требуется отдельная интеграция Stars, которой в этой версии нет.