Представим, что вы разрабатываете SaaS-приложение на React и FastAPI.
Пользователи могут зарегистрироваться, создать организацию, пригласить сотрудников и пользоваться базовыми функциями бесплатно.
Теперь вы хотите добавить платные тарифы.
Например:
Free — бесплатный доступ с ограничениями.
Pro — расширенные возможности за ежемесячную оплату.
Business — дополнительные функции для команд.
Enterprise — индивидуальные условия.
На первый взгляд задача выглядит просто.
Пользователь нажимает кнопку «Оплатить», вводит данные карты, а Backend активирует подписку.
Но в реальности возникают десятки вопросов.
Что делать, если пользователь закрыл вкладку после оплаты?
Как определить, что деньги действительно списаны?
Что произойдёт, если Stripe отправит один Webhook несколько раз?
Как защититься от повторного создания платежа?
Как обрабатывать неуспешные списания?
Как реализовать возврат средств?
Что делать, если подписка отменена, но оплаченный период ещё не закончился?
Как не раскрыть данные другого клиента в Multi-Tenant SaaS?
И как построить платёжную архитектуру, которая будет корректно работать при сетевых сбоях?
Для решения этих задач нужна надёжная архитектура платежной системы.
В этой статье разберём, как интегрировать Stripe в React и FastAPI, рассмотрим Stripe Checkout, Payment Intents, Billing, Webhooks, Idempotency Keys, PostgreSQL, Refunds, безопасность транзакций и практический Production Checklist.
1. Что такое платежная система веб-приложения
Payment Integration Architecture — подсистема приложения, которая управляет созданием платежей, взаимодействием с платёжным провайдером и предоставлением пользователям оплаченных возможностей.
Типовая схема:
React Frontend
|
v
FastAPI Backend
|
v
Payment Provider
|
v
Payment Processing
|
v
Webhook
|
v
PostgreSQL
|
v
Activate Paid Features
Ключевая особенность такой архитектуры — разделение пользовательского интерфейса и подтверждения финансового результата.
Frontend может инициировать оплату.
Но решение о предоставлении платного доступа должно приниматься Backend на основании проверенного состояния платежа.
2. Почему нельзя доверять Frontend после оплаты
Payment Verification Backend — обязательный принцип безопасной интеграции.
Плохой пример:
const paymentCompleted = true;
if (paymentCompleted) {
activateProPlan();
}
Любой пользователь может изменить JavaScript в браузере.
Даже если Frontend получил сообщение об успешной оплате, Backend должен самостоятельно проверить результат.
Например, через проверенный Webhook или запрос к Stripe API.
Главное правило: клиентский браузер не является источником истины о статусе платежа.
3. Какие платежные сценарии существуют
Payment Flow Types:
Сценарий | Пример |
One-Time Payment | Покупка отчёта |
Subscription | Ежемесячный SaaS |
Usage-Based Billing | Оплата по использованию |
Free Trial | Пробный период |
Upgrade | Переход на дорогой тариф |
Downgrade | Переход на дешёвый тариф |
Refund | Возврат средств |
Cancellation | Отмена подписки |
Для каждого сценария необходимы отдельные бизнес-правила.
Например, отмена подписки не всегда означает немедленное прекращение доступа.
Stripe Checkout и Payment Intents
4. Что такое Stripe
Stripe Payment Gateway — платёжная платформа, предоставляющая API для приёма платежей, управления подписками и выполнения связанных финансовых операций.
Для Full Stack разработчика особенно важны:
Stripe Checkout;
Payment Intents;
Stripe Billing;
Customers;
Products;
Prices;
Subscriptions;
Webhooks;
Refunds.
Доступность конкретных продуктов и способов оплаты зависит от страны, типа бизнеса и настроек аккаунта.
5. Что такое Stripe Checkout
Stripe Checkout Integration — готовая платёжная страница, размещённая Stripe.
Пример:
User
|
v
React Pricing Page
|
v
FastAPI
|
v
Create Checkout Session
|
v
Stripe Hosted Checkout
|
v
Payment
Преимущества:
меньше собственного платёжного UI;
готовые механизмы ввода платёжных данных;
поддержка различных платёжных методов;
интеграция с подписками;
снижение сложности работы с чувствительными данными.
Для многих SaaS-приложений Stripe Checkout — разумная отправная точка.
6. Что такое Payment Intent
Stripe Payment Intents API управляет жизненным циклом конкретной попытки оплаты.
Payment Intent может иметь различные состояния, например:
requires_payment_method
requires_confirmation
requires_action
processing
succeeded
canceled
В некоторых сценариях также используется requires_capture.
Payment Intents полезны, если приложение требует более гибкого пользовательского платёжного интерфейса.
7. Stripe Checkout vs Payment Intents
Stripe Checkout vs Payment Intents:
Критерий | Stripe Checkout | Payment Intents |
Готовый платёжный UI | Да | Требуется отдельная интеграция |
Скорость разработки | Обычно выше | Обычно ниже |
Контроль интерфейса | Ограниченнее | Более гибкий |
Разовые платежи | Да | Да |
Подписки | Да | Через соответствующие Billing-интеграции |
Сложность поддержки | Обычно ниже | Обычно выше |
Для первого платёжного функционала SaaS часто удобно начать со Stripe Checkout.
8. Что такое Stripe Customer
Stripe Customer Object представляет клиента в платёжной системе.
Например:
Application User
|
v
Stripe Customer
|
+-- Payment Methods
|
+-- Subscriptions
|
+-- Invoices
Обычно Backend хранит связь между пользователем или организацией и Stripe Customer ID.
9. Что такое Product и Price
Stripe Product and Price — два разных объекта.
Product описывает продукт или услугу.
Price определяет условия оплаты.
Например:
Product: SaaS Pro
|
+-- Monthly Price
|
+-- Yearly Price
Один продукт может иметь несколько цен.
Это удобно для ежемесячных и годовых подписок.
Backend на FastAPI
10. Как установить Stripe Python SDK
Stripe Python SDK FastAPI:
pip install stripe fastapi uvicorn
Дополнительно для работы с PostgreSQL можно использовать SQLAlchemy и соответствующий драйвер.
11. Как хранить Stripe API Keys
Stripe API Keys Security требует разделять публичные и секретные ключи.
Пример переменных окружения:
STRIPE_SECRET_KEY=...
STRIPE_WEBHOOK_SECRET=...
STRIPE_PRO_PRICE_ID=...
Секретный Stripe API Key должен использоваться только на Backend.
Его нельзя добавлять в React Bundle или публиковать в Git.
В Production предпочтительно использовать Secret Manager.
12. Как настроить Stripe Client
Stripe Python Client Configuration:
import os
import stripe
stripe.api_key = os.environ["STRIPE_SECRET_KEY"]
Для простого проекта этого достаточно, чтобы использовать методы Stripe SDK.
При росте приложения полезно выделить отдельный Payment Service и централизовать обработку ошибок.
13. Как создать Checkout Session
FastAPI Stripe Checkout Session:
import os
import stripe
from fastapi import FastAPI, Depends
app = FastAPI()
@app.post("/api/billing/checkout")
async def create_checkout(
current_user=Depends(get_current_user),
):
organization = await get_current_organization(
current_user
)
customer_id = await get_or_create_stripe_customer(
organization
)
session = stripe.checkout.Session.create(
mode="subscription",
customer=customer_id,
line_items=[
{
"price": os.environ[
"STRIPE_PRO_PRICE_ID"
],
"quantity": 1,
}
],
success_url=(
"https://app.example.com/billing/success"
"?session_id={CHECKOUT_SESSION_ID}"
),
cancel_url=(
"https://app.example.com/billing/cancel"
),
client_reference_id=str(organization.id),
metadata={
"organization_id": str(organization.id)
},
)
return {
"checkout_url": session.url
}
В примере используются вспомогательные функции приложения, которые необходимо реализовать отдельно.
В Production также нужно проверять право пользователя управлять оплатой организации, наличие действующей подписки и допустимость создания новой Checkout Session.
Stripe SDK в данном примере вызывается синхронно; для асинхронного приложения следует учитывать блокирующие операции и возможности используемой версии SDK.
14. Почему Price ID должен задаваться сервером
Server Side Pricing Validation защищает от подмены стоимости.
Плохой вариант:
{
"plan": "enterprise",
"price": 1
}
Если Backend доверяет переданной цене, злоумышленник может попытаться оплатить дорогой тариф за минимальную сумму.
Правильнее передавать идентификатор тарифного плана:
{
"plan": "pro"
}
А Backend должен сопоставить его с разрешённым Stripe Price ID.
15. Как открыть Checkout из React
React Stripe Checkout Integration:
async function startCheckout() {
const response = await fetch(
"/api/billing/checkout",
{
method: "POST",
credentials: "include",
}
);
if (!response.ok) {
throw new Error(
"Не удалось создать платёж"
);
}
const data: {
checkout_url: string;
} = await response.json();
const url = new URL(data.checkout_url);
if (
url.protocol !== "https:" ||
!(
url.hostname === "checkout.stripe.com" ||
url.hostname.endsWith(".checkout.stripe.com")
)
) {
throw new Error("Invalid checkout URL");
}
window.location.assign(url.toString());
}
Если используется Cookie Authentication, для POST-запроса также нужна защита от CSRF.
Вместо проверки произвольного URL можно организовать строго контролируемый переход к Stripe Checkout.
Webhooks и подтверждение оплаты
16. Что такое Stripe Webhook
Stripe Webhooks — HTTP-уведомления о событиях, происходящих в Stripe.
Например:
Stripe
|
v
Webhook Event
|
v
FastAPI Endpoint
|
v
Verify Signature
|
v
Process Event
|
v
Update Database
Webhooks позволяют Backend узнавать о событиях независимо от браузера пользователя.
17. Почему Success URL не подтверждает оплату
Stripe Success URL Security — важный момент.
Пользователь может открыть страницу успешной оплаты напрямую.
Кроме того, он может закрыть вкладку до возвращения из Checkout.
Поэтому нельзя активировать подписку только потому, что пользователь попал на /billing/success.
Страница успеха должна отображать состояние, полученное от Backend.
А Backend должен опираться на проверенное состояние Stripe.
18. Какие Stripe Events важны для SaaS
Stripe Billing Webhook Events:
Событие | Назначение |
checkout.session.completed | Checkout Session завершена |
invoice.paid | Счёт оплачен |
invoice.payment_failed | Ошибка оплаты счёта |
customer.subscription.created | Создана подписка |
customer.subscription.updated | Подписка изменена |
customer.subscription.deleted | Подписка завершена |
charge.refunded | Информация о возврате по Charge |
refund.created | Создан Refund |
refund.updated | Изменён Refund |
Конкретный набор событий зависит от выбранной платёжной модели.
Не стоит обрабатывать все события одинаково.
19. Как проверить подпись Webhook
Stripe Webhook Signature Verification:
import os
import stripe
from fastapi import Request, HTTPException
@app.post("/api/webhooks/stripe")
async def stripe_webhook(
request: Request,
):
payload = await request.body()
signature = request.headers.get(
"stripe-signature"
)
if not signature:
raise HTTPException(
status_code=400,
detail="Missing signature"
)
try:
event = stripe.Webhook.construct_event(
payload,
signature,
os.environ[
"STRIPE_WEBHOOK_SECRET"
],
)
except (
ValueError,
stripe.error.SignatureVerificationError,
):
raise HTTPException(
status_code=400,
detail="Invalid webhook"
)
await persist_webhook_event(event)
return {"received": True}
Здесь persist_webhook_event — функция приложения.
Она должна надёжно сохранить идентификатор и необходимые данные события для последующей обработки.
В зависимости от версии Stripe SDK классы исключений могут отличаться.
20. Почему важен Raw Request Body
Stripe Webhook Raw Body используется при проверке подписи.
Нельзя сначала преобразовать JSON, затем заново сериализовать его и ожидать, что подпись совпадёт.
Даже небольшое изменение форматирования может повлиять на проверку.
Поэтому в FastAPI следует получать исходные байты:
payload = await request.body()
21. Почему Webhook может приходить несколько раз
Stripe Webhook Duplicate Events — нормальная ситуация в распределённых системах.
Например:
Stripe sends event
|
v
Backend processes event
|
v
Response is lost
|
v
Stripe retries event
Если приложение не учитывает дубликаты, оно может повторно изменить состояние подписки или выполнить другое действие.
Поэтому Webhook Handler должен быть идемпотентным.
Idempotency и PostgreSQL
22. Что такое Idempotency
Payment Idempotency означает, что повторное выполнение операции с тем же идентификатором не должно создавать неконтролируемые повторные последствия.
Например:
Request A
Request A repeated
Request A repeated again
Для одной логической операции должен сохраняться согласованный результат.
Особенно это важно для платежей, возвратов и обработки Webhooks.
23. Как хранить обработанные Stripe Events
Stripe Webhook Events PostgreSQL:
CREATE TABLE stripe_webhook_events (
event_id TEXT PRIMARY KEY,
event_type TEXT NOT NULL,
status TEXT NOT NULL
DEFAULT 'pending',
received_at TIMESTAMPTZ
NOT NULL DEFAULT NOW(),
processed_at TIMESTAMPTZ,
last_error TEXT
);
Уникальный event_id помогает обнаруживать повторную доставку одного события.
Но важно различать received и processed.
Нельзя считать событие полностью обработанным только потому, что его ID записан в базу.
24. Как безопасно принимать Webhook
Reliable Stripe Webhook Processing:
Receive Webhook
|
v
Verify Signature
|
v
INSERT Event with UNIQUE ID
|
v
COMMIT
|
v
Return 2xx
|
v
Background Worker
|
v
Process Business Logic
Если событие уже существует, Backend может вернуть успешный ответ, не создавая дубликат.
Но Worker должен отдельно обрабатывать события со статусом pending или failed.
25. Почему нельзя выполнять тяжёлую обработку внутри Webhook
Asynchronous Payment Webhook Processing помогает избежать Timeout.
Плохая архитектура:
Webhook
|
v
Update Subscription
|
v
Generate Invoice PDF
|
v
Send Email
|
v
Call Analytics
|
v
Return Response
Если обработка занимает слишком много времени, Stripe может повторить доставку.
Лучше быстро проверить и сохранить событие, а остальные действия выполнять через Worker.
26. Как использовать Celery для Webhooks
Celery Stripe Webhook Processing:
Stripe Webhook
|
v
FastAPI
|
v
PostgreSQL Event Inbox
|
v
Worker
|
v
Update Subscription
Celery может использоваться для выполнения фоновых задач.
Но сохранение события в PostgreSQL и публикация задачи в Broker — две разные операции.
Для высокой надёжности можно использовать Transactional Outbox или Worker, который периодически читает необработанные события из базы.
27. Как использовать Stripe Idempotency Key
Stripe API Idempotency Key помогает безопаснее повторять некоторые запросы к Stripe API.
Пример:
session = stripe.checkout.Session.create(
mode="payment",
line_items=[
{
"price": price_id,
"quantity": 1,
}
],
success_url=success_url,
cancel_url=cancel_url,
idempotency_key=operation_key,
)
operation_key должен соответствовать конкретной логической операции и формироваться Backend.
Нельзя использовать один постоянный ключ для всех платежей пользователя.
У Stripe есть правила хранения и повторного использования Idempotency Keys, поэтому механизм не заменяет собственную базу операций.
28. Почему Idempotency Key не гарантирует Exactly Once
Exactly Once Payments — сложная тема.
Даже при использовании Idempotency Key возможны:
сетевые сбои;
повторные Webhooks;
частично выполненные операции;
ошибки локальной базы данных;
задержки подтверждения;
конкурентные запросы.
Поэтому надёжная платёжная система использует несколько уровней защиты.
Например:
уникальные идентификаторы операций;
ограничения уникальности в PostgreSQL;
проверку состояния Stripe;
идемпотентные Workers;
сверку платежей.
Модель данных для платежей
29. Как спроектировать Billing Database
SaaS Billing Database Schema:
organizations
|
+-- billing_customers
|
+-- subscriptions
|
+-- payment_records
|
+-- billing_events
Важно хранить собственную бизнес-модель.
Но не нужно пытаться полностью дублировать все объекты Stripe.
Локальная база должна содержать данные, необходимые приложению.
30. Как хранить Stripe Customer
Stripe Customer PostgreSQL:
CREATE TABLE billing_customers (
organization_id UUID PRIMARY KEY,
stripe_customer_id TEXT
NOT NULL UNIQUE,
created_at TIMESTAMPTZ
NOT NULL DEFAULT NOW()
);
Один Stripe Customer может быть связан с организацией.
Для другого типа продукта связь может быть с конкретным пользователем.
Это архитектурное решение нужно принять заранее.
31. Как хранить подписки
SaaS Subscriptions PostgreSQL:
CREATE TABLE subscriptions (
id UUID PRIMARY KEY,
organization_id UUID NOT NULL,
stripe_subscription_id TEXT
NOT NULL UNIQUE,
stripe_price_id TEXT NOT NULL,
status TEXT NOT NULL,
current_period_end TIMESTAMPTZ,
cancel_at_period_end BOOLEAN
NOT NULL DEFAULT FALSE,
updated_at TIMESTAMPTZ
NOT NULL DEFAULT NOW()
);
Это упрощённая схема.
Для Production также могут потребоваться:
дата начала;
Trial Period;
Quantity;
несколько Subscription Items;
Billing Interval;
история изменений;
версия синхронизации.
32. Почему Subscription Status важнее названия тарифа
Subscription Status Management должно учитывать фактическое состояние подписки.
Например:
trialing
active
past_due
canceled
unpaid
incomplete
incomplete_expired
paused
Нельзя считать любую существующую подписку активной.
Но и простая проверка status == active не всегда соответствует бизнес-правилам.
Например, пробный период может предоставлять платный функционал.
33. Как хранить платежи
Payment Records PostgreSQL:
CREATE TABLE payment_records (
id UUID PRIMARY KEY,
organization_id UUID NOT NULL,
stripe_payment_intent_id TEXT UNIQUE,
amount_minor BIGINT NOT NULL,
currency TEXT NOT NULL,
status TEXT NOT NULL,
created_at TIMESTAMPTZ
NOT NULL DEFAULT NOW()
);
Суммы удобно хранить в минимальных единицах валюты, например в центах для валют с двумя десятичными знаками.
Но не все валюты имеют одинаковую точность.
Поэтому правила работы с денежными суммами должны учитывать конкретную валюту и платёжный API.
Подписки и управление доступом
34. Как активировать платный тариф
SaaS Subscription Activation:
Payment Confirmed
|
v
Webhook Verified
|
v
Subscription State Updated
|
v
Entitlements Updated
|
v
User Gets Paid Features
Важный принцип: предоставление доступа должно зависеть от проверенного состояния подписки и бизнес-политики.
35. Что такое Entitlements
SaaS Feature Entitlements — разрешённые возможности тарифного плана.
Например:
Возможность | Free | Pro | Business |
Проекты | 3 | 50 | Без установленного продуктового лимита |
Участники | 2 | 10 | 100 |
API Access | Нет | Да | Да |
Advanced Reports | Нет | Да | Да |
Audit Logs | Нет | Нет | Да |
Entitlements лучше проверять на Backend.
Frontend может скрывать недоступные кнопки, но это только UX.
36. Как проверять платный доступ в FastAPI
FastAPI Subscription Authorization:
from fastapi import Depends, HTTPException
async def require_pro_plan(
current_user=Depends(get_current_user),
):
organization = await get_current_organization(
current_user
)
allowed = await entitlement_service.has_feature(
organization_id=organization.id,
feature="advanced_reports",
)
if not allowed:
raise HTTPException(
status_code=403,
detail="Upgrade required"
)
return organization
entitlement_service — компонент приложения.
Он должен учитывать тариф, состояние подписки и бизнес-правила доступа.
37. Почему нельзя проверять подписку только в React
Frontend Subscription Security:
if (plan === "pro") {
showAdvancedReports();
}
Такой код может использоваться для отображения интерфейса.
Но пользователь способен вызвать API напрямую.
Поэтому Backend должен независимо проверять Entitlements.
Отмена подписок и смена тарифа
38. Как реализовать отмену подписки
Stripe Subscription Cancellation может происходить двумя способами.
Первый — немедленная отмена.
Второй — отмена в конце оплаченного периода.
Для SaaS часто удобен второй вариант.
Например:
Subscription active
|
v
User cancels
|
v
cancel_at_period_end = true
|
v
Access continues
|
v
Period ends
|
v
Subscription ends
Конкретная политика зависит от условий продукта.
39. Как реализовать Upgrade
Stripe Subscription Upgrade — переход на более дорогой тариф.
Например:
Pro
|
v
Business
При смене тарифа могут возникать Proration Adjustments.
То есть Stripe может рассчитывать доплату или кредит за оставшуюся часть расчётного периода.
Но точное поведение зависит от настроек изменения подписки.
40. Как реализовать Downgrade
Stripe Subscription Downgrade — переход на более дешёвый тариф.
Возможные варианты:
изменить тариф сразу;
применить новый тариф в следующем периоде;
использовать Subscription Schedule.
Важно определить, что делать с данными, превышающими лимиты нового тарифа.
Например, если у пользователя 30 проектов, а новый план допускает только 3.
Обычно безопаснее ограничить создание новых ресурсов, чем автоматически удалять существующие данные.
41. Что такое Stripe Customer Portal
Stripe Billing Customer Portal — готовый интерфейс управления платёжными настройками.
В зависимости от конфигурации он может позволять:
изменять платёжные методы;
просматривать счета;
отменять подписку;
менять тариф;
управлять некоторыми Billing-настройками.
Это может значительно сократить объём собственной разработки.
42. Как создать Customer Portal Session
FastAPI Stripe Billing Portal:
session = stripe.billing_portal.Session.create(
customer=stripe_customer_id,
return_url=(
"https://app.example.com/settings/billing"
),
)
return {
"portal_url": session.url
}
Перед созданием Portal Session Backend обязан проверить, что текущий пользователь имеет право управлять оплатой соответствующей организации.
Нельзя принимать произвольный stripe_customer_id от клиента без проверки владельца.
Неуспешные платежи и повторные списания
43. Что такое Failed Payment
Stripe Failed Payment Handling — обработка ситуации, когда платёж не удалось завершить.
Причины могут включать:
недостаток средств;
отклонение банком;
необходимость дополнительной аутентификации;
недействительный платёжный метод;
временные технические ошибки.
Не каждую ошибку можно устранить автоматическим повтором.
44. Как обрабатывать invoice.payment_failed
Stripe Invoice Payment Failed Webhook:
invoice.payment_failed
|
v
Verify Event
|
v
Update Billing State
|
v
Notify User
|
v
Request Payment Method Update
Не стоит немедленно удалять пользовательские данные из-за одного неуспешного платежа.
Лучше определить Grace Period и политику восстановления доступа.
45. Что такое Dunning
SaaS Dunning Management — процесс работы с неуспешными регулярными платежами.
Он может включать:
повторные попытки списания;
Email-уведомления;
просьбу обновить карту;
Grace Period;
ограничение функций;
окончательное завершение подписки.
Stripe Billing предоставляет инструменты для части этих процессов.
Refunds и возвраты
46. Что такое Refund
Stripe Refund API позволяет инициировать возврат по поддерживаемому платежу.
Возврат может быть:
полным;
частичным.
Но возможность и сроки возврата зависят от платёжного метода и условий провайдера.
47. Как создать Refund через Backend
FastAPI Stripe Refund:
import stripe
def create_refund(
payment_intent_id: str,
operation_key: str,
):
return stripe.Refund.create(
payment_intent=payment_intent_id,
idempotency_key=operation_key,
)
Это упрощённый пример.
В Production необходимо:
проверить полномочия инициатора;
проверить принадлежность платежа;
проверить допустимость возврата;
записать операцию в базу;
использовать уникальный Idempotency Key;
обработать результат и Webhooks.
48. Почему Refund Endpoint особенно опасен
Refund API Security требует строгой авторизации.
Например, нельзя разрешать обычному пользователю выполнять возврат по произвольному Payment Intent ID.
Backend должен проверить:
Authenticated User
|
v
Organization Permission
|
v
Payment Ownership
|
v
Refund Policy
|
v
Create Refund
Для административных возвратов может потребоваться дополнительное подтверждение или Audit Log.
49. Как учитывать Partial Refund
Partial Refund Handling:
Original Payment: 100.00
Refund 1: 20.00
Refund 2: 30.00
Remaining: 50.00
Нельзя хранить только Boolean refunded.
Нужно учитывать сумму уже выполненных возвратов и текущее состояние каждого Refund.
Безопасность платёжной системы
50. Что такое PCI DSS
PCI DSS Payment Security — стандарт безопасности данных платёжных карт.
Объём требований зависит от того, как приложение принимает и обрабатывает платёжные данные.
Использование Stripe Checkout может уменьшить объём прямой обработки карточных данных приложением.
Но это не означает автоматического отсутствия всех требований безопасности и соответствия.
51. Почему не стоит хранить номера банковских карт
Card Data Security SaaS:
Не следует самостоятельно хранить:
полный номер карты;
CVV/CVC;
секретные данные аутентификации;
другие чувствительные карточные данные без соответствующей инфраструктуры и оснований.
Для большинства SaaS-приложений безопаснее использовать платёжные компоненты провайдера.
52. Как защитить Billing API
Billing API Security Checklist:
Authentication;
Authorization;
Tenant Isolation;
CSRF Protection при Cookie Authentication;
Rate Limiting;
Webhook Signature Verification;
Idempotency;
Audit Logs;
Secure Secrets;
HTTPS.
Особенно важно проверять права на управление оплатой организации.
53. Как защититься от BOLA
Billing BOLA IDOR Protection:
Плохой запрос:
GET /api/billing/organizations/123/invoices
Если Backend проверяет только Authentication, пользователь может попробовать заменить 123.
Правильная логика:
User Authenticated
|
v
Organization Membership
|
v
Billing Permission
|
v
Return Invoices
Проверка доступа должна выполняться для каждого ресурса.
54. Почему нельзя доверять Metadata
Stripe Metadata Security требует учитывать происхождение данных.
Metadata полезна для связывания Stripe Objects с внутренними сущностями.
Но она не должна заменять проверку принадлежности объектов.
При обработке Webhook лучше сверять Stripe Customer ID и Subscription ID с локальными записями.
55. Как защитить Webhook Endpoint
Secure Stripe Webhook Endpoint:
использовать HTTPS;
проверять подпись;
работать с Raw Body;
ограничивать размер запроса;
не раскрывать секреты в логах;
учитывать дубликаты;
обрабатывать события асинхронно;
контролировать ошибки;
регулярно обновлять зависимости.
IP Allowlisting может быть дополнительной мерой, но не заменяет проверку криптографической подписи.
Согласованность и порядок событий
56. Почему Webhooks могут приходить не по порядку
Stripe Webhook Event Ordering нельзя считать гарантированным.
Например:
Event A: subscription.created
Event B: subscription.updated
Delivery:
Event B
Event A
Если приложение просто перезаписывает локальное состояние по каждому событию, оно может восстановить устаревший статус.
57. Как избежать устаревшего состояния подписки
Subscription State Reconciliation — сверка локального состояния с Stripe.
Например:
Webhook received
|
v
Identify Subscription
|
v
Fetch Current Stripe State
|
v
Update Local Subscription
Это полезный подход для событий изменения подписки.
Но необходимо учитывать ограничения API, конкурентную обработку и ошибки сети.
58. Что такое Reconciliation Job
Payment Reconciliation Job — периодическая задача сверки платежей и подписок.
Например:
Every 30 minutes
|
v
Load Billing Records
|
v
Compare with Stripe
|
v
Detect Differences
|
v
Repair or Alert
Периодичность зависит от требований бизнеса.
Такая задача помогает обнаруживать пропущенные Webhooks и расхождения между системами.
59. Как избежать Race Conditions
Payment Race Conditions могут возникать при одновременной обработке событий.
Например:
Worker A updates subscription
Worker B updates subscription
Для защиты можно использовать:
Database Transactions;
Row-Level Locks;
Optimistic Concurrency;
уникальные ограничения;
сериализацию обработки событий одного объекта.
Важно не допускать, чтобы старое состояние перезаписало новое.
Тестирование платежей
60. Как тестировать Stripe Integration
Stripe Integration Testing должно покрывать:
создание Checkout Session;
успешную оплату;
отмену Checkout;
ошибку оплаты;
повторную доставку Webhook;
неверную подпись;
события не по порядку;
отмену подписки;
Upgrade;
Downgrade;
Refund;
Tenant Isolation.
Для тестирования следует использовать Stripe Test Mode и тестовые сценарии провайдера.
61. Как тестировать Webhook Signature
Stripe Webhook Signature Testing:
Тест должен проверять:
Valid Signature -> Accepted
Invalid Signature -> Rejected
Missing Signature -> Rejected
Duplicate Event -> No Duplicate Effects
При этом важно тестировать именно Raw Body и механизм проверки подписи.
62. Как тестировать повторную доставку
Stripe Webhook Idempotency Test:
def test_duplicate_webhook_does_not_duplicate_access(
webhook_client,
subscription_repository,
):
event = make_signed_test_event(
event_id="evt_test_123"
)
first = webhook_client.post(
"/api/webhooks/stripe",
content=event.body,
headers=event.headers,
)
second = webhook_client.post(
"/api/webhooks/stripe",
content=event.body,
headers=event.headers,
)
assert first.status_code == 200
assert second.status_code == 200
process_pending_events()
assert subscription_repository.count_activations(
"evt_test_123"
) == 1
Это иллюстрация тестового сценария.
Вспомогательные функции и Fixtures необходимо реализовать в проекте.
63. Как тестировать Multi-Tenant Billing
Multi Tenant Billing Tests:
User A -> Organization A
User B -> Organization B
Проверяем:
User A не может открыть Billing Portal организации B.
User A не может получить счета организации B.
User A не может отменить подписку организации B.
User A не может инициировать Refund для платежа B.
Такие тесты критически важны для SaaS.
Мониторинг и Production
64. Какие метрики платежей отслеживать
Payment Monitoring Metrics:
Метрика | Назначение |
Checkout Creation Errors | Ошибки создания Checkout |
Payment Success Rate | Доля успешных платежей |
Payment Failure Rate | Доля неуспешных платежей |
Webhook Processing Lag | Задержка обработки событий |
Webhook Failure Rate | Ошибки Webhooks |
Duplicate Events | Повторные события |
Refund Rate | Доля возвратов |
Subscription Churn | Отток подписок |
Reconciliation Differences | Расхождения состояния |
Мониторинг должен помогать выявлять как технические, так и бизнес-проблемы.
65. Как логировать платежи безопасно
Secure Payment Logging:
Можно логировать:
внутренний Operation ID;
Stripe Event ID;
тип события;
статус обработки;
длительность;
код ошибки.
Не следует логировать:
Stripe Secret Key;
Webhook Signing Secret;
полный платёжный Payload без необходимости;
чувствительные персональные данные;
секретные платёжные параметры.
66. Как настроить Alerts
Billing Production Alerts:
Webhook failures > threshold
|
v
Alert
Payment reconciliation mismatch
|
v
Alert
Queue lag too high
|
v
Alert
Особенно важно быстро обнаруживать ошибки, которые приводят к неправильному предоставлению платного доступа.
67. Как построить Production-архитектуру
Production Stripe Architecture React FastAPI:
React Frontend
|
v
FastAPI Backend
|
+------------+------------+
| |
v v
PostgreSQL Stripe API
| |
| v
| Stripe Checkout
| |
| v
| Payment
| |
| v
| Stripe Webhook
| |
+------------+------------+
|
v
Webhook Inbox Table
|
v
Background Worker
|
+------------+------------+
| |
v v
Subscription State Payment Records
|
v
Entitlements
|
v
SaaS Features
Здесь PostgreSQL хранит локальную бизнес-модель.
Stripe управляет платёжными объектами.
Webhook Inbox позволяет надёжно принимать события.
Worker обрабатывает их независимо от HTTP-запросов.
Entitlement Service управляет доступом к платным возможностям.
68. Как внедрять платежи поэтапно
SaaS Billing Implementation Roadmap:
Этап 1. Базовая оплата
Реализовать:
Stripe Customer;
Products;
Prices;
Checkout Session;
Success Page;
Webhook Verification.
Этап 2. Подписки
Добавить:
Subscription Table;
Billing Events;
Entitlements;
Cancellation;
Customer Portal.
Этап 3. Надёжность
Добавить:
Webhook Inbox;
Idempotency;
Background Processing;
Reconciliation;
Retry.
Этап 4. Расширенные функции
Добавить:
Upgrade;
Downgrade;
Refunds;
Usage-Based Billing;
Billing Analytics.
Этап 5. Production Hardening
Добавить:
Security Tests;
Audit Logs;
Monitoring;
Alerts;
Load Testing;
Incident Runbooks.
69. Типичные ошибки при интеграции Stripe
Первая ошибка — активировать подписку после перехода на Success URL.
Вторая — доверять цене, переданной Frontend.
Третья — хранить Stripe Secret Key в React.
Четвёртая — не проверять Webhook Signature.
Пятая — использовать преобразованный JSON вместо Raw Body.
Шестая — не учитывать Duplicate Events.
Седьмая — считать событие обработанным сразу после получения.
Восьмая — выполнять тяжёлую работу внутри Webhook Endpoint.
Девятая — не учитывать Event Ordering.
Десятая — не проверять Tenant Ownership.
Одиннадцатая — позволять обычному пользователю управлять Billing организации.
Двенадцатая — не использовать Idempotency Keys.
Тринадцатая — повторять финансовые операции без проверки состояния.
Четырнадцатая — не хранить Payment Records.
Пятнадцатая — считать любую существующую подписку активной.
Шестнадцатая — немедленно удалять данные после Failed Payment.
Семнадцатая — не обрабатывать Partial Refund.
Восемнадцатая — не тестировать ошибки Stripe API.
Девятнадцатая — не проводить Reconciliation.
Двадцатая — не мониторить Billing Events.
Эти ошибки Stripe Integration могут приводить к неправильным списаниям, потере платного доступа, дублированию операций и финансовым расхождениям.
70. Чек-лист Stripe Integration для Production
Stripe Production Checklist:
Backend
Stripe Secret Key хранится безопасно.
Checkout Session создаётся на Backend.
Price ID выбирается сервером.
Проверяются права на Billing.
Stripe Customer связан с внутренней организацией.
Есть Subscription Table.
Есть Payment Records.
Есть Entitlement Service.
Webhooks
Используется HTTPS.
Проверяется Stripe Signature.
Используется Raw Request Body.
События сохраняются надёжно.
Повторные события не создают дубликаты.
Обработка выполняется асинхронно.
Учитывается возможный неправильный порядок событий.
Есть Retry.
Есть Reconciliation Job.
Security
Проверяется Authentication.
Проверяется Authorization.
Есть Tenant Isolation.
Refund Endpoint защищён.
Billing Portal создаётся только для разрешённого Customer.
Настроен Rate Limiting.
Есть Audit Logs.
Секреты не попадают в логи.
Frontend не определяет статус платежа самостоятельно.
Billing
Обрабатываются Failed Payments.
Реализована политика Grace Period.
Реализована Cancellation Policy.
Корректно обрабатываются Upgrade и Downgrade.
Учитываются Partial Refunds.
Проверяются лимиты тарифов.
Есть история Billing Operations.
Testing
Проверена успешная оплата.
Проверена неуспешная оплата.
Проверены Duplicate Webhooks.
Проверена Invalid Signature.
Проверены события не по порядку.
Проверена Tenant Isolation.
Проверены Refunds.
Проверены конкурентные операции.
Monitoring
Мониторятся Webhook Errors.
Мониторится Payment Success Rate.
Мониторится Webhook Lag.
Есть Alerts.
Выполняется Reconciliation.
Подготовлен Incident Response Plan.
FAQ
Что такое Stripe Checkout?
Готовая платёжная страница Stripe, позволяющая принимать платежи без разработки собственного интерфейса ввода карточных данных.
Что такое Payment Intent?
Объект Stripe, управляющий жизненным циклом попытки оплаты.
Что лучше: Stripe Checkout или Payment Intents?
Для быстрого запуска часто удобнее Stripe Checkout. Payment Intents полезны при более гибкой платёжной интеграции.
Можно ли использовать Stripe с React?
Да. React может инициировать оплату и отображать её состояние, а Backend взаимодействует со Stripe API.
Как интегрировать Stripe с FastAPI?
Через Stripe Python SDK, Checkout Sessions, Webhook Endpoint и локальную Billing Database.
Нужно ли хранить Stripe Secret Key на Backend?
Да. Секретный ключ нельзя публиковать во Frontend.
Можно ли активировать подписку после Success URL?
Нет. Необходимо подтвердить платёж через доверенный Backend.
Что такое Stripe Webhook?
HTTP-событие от Stripe, уведомляющее приложение об изменениях платёжных объектов.
Почему Webhook приходит несколько раз?
Потому что доставка может повторяться после сетевых сбоев или отсутствия подтверждения.
Что такое Webhook Idempotency?
Защита от повторных бизнес-эффектов при повторной обработке одного события.
Что такое Stripe Idempotency Key?
Ключ, позволяющий безопаснее повторять поддерживаемые запросы к Stripe API.
Гарантирует ли Idempotency Key Exactly Once?
Нет. Необходимы дополнительные механизмы контроля состояния и обработки повторов.
Что такое Stripe Customer?
Объект Stripe, представляющий клиента и связанные с ним платёжные данные.
Что такое Stripe Price?
Объект, определяющий условия оплаты продукта.
Как реализовать подписки в SaaS?
Через Stripe Billing, локальную таблицу подписок, Webhooks и Entitlement Service.
Что такое Entitlements?
Набор возможностей и лимитов, доступных пользователю согласно тарифу.
Как реализовать отмену подписки?
Через Stripe API или Customer Portal с выбранной бизнес-политикой отмены.
Что такое Proration?
Расчёт корректировки стоимости при изменении условий подписки в течение расчётного периода.
Что такое Stripe Customer Portal?
Готовый интерфейс Stripe для управления некоторыми настройками оплаты и подписок.
Как обрабатывать Failed Payments?
Через Billing Events, уведомления, повторные попытки и Grace Period.
Что такое Dunning?
Процесс восстановления регулярных платежей после неуспешного списания.
Как реализовать Refund?
Через Stripe Refund API после проверки прав, принадлежности платежа и бизнес-условий.
Что такое Partial Refund?
Возврат части ранее оплаченной суммы.
Что такое PCI DSS?
Стандарт безопасности данных платёжных карт.
Нужно ли самостоятельно хранить номера карт?
Для большинства SaaS-приложений лучше использовать платёжные компоненты провайдера и не хранить карточные данные самостоятельно.
Что такое Reconciliation?
Сверка локальных Billing Records с состоянием платёжного провайдера.
Почему Webhooks могут приходить не по порядку?
Потому что порядок доставки событий не гарантируется.
Как защитить Billing в Multi-Tenant SaaS?
Проверять права пользователя, организацию, принадлежность Stripe Customer и доступ к каждой финансовой операции.
Заключение
Платёжная система — одна из наиболее ответственных частей SaaS-приложения.
Ошибки в обычном CRUD Endpoint могут привести к неправильному отображению данных.
Ошибки в Billing могут привести к финансовым потерям, некорректным списаниям или неправильному предоставлению платного доступа.
Поэтому интеграция Stripe должна строиться вокруг нескольких принципов.
Первый — Frontend никогда не является источником истины о платеже.
Второй — стоимость и условия оплаты определяются сервером.
Третий — Stripe Webhooks должны проверяться криптографически.
Четвёртый — повторные события и запросы не должны создавать неконтролируемые финансовые последствия.
Пятый — локальная Billing Database должна хранить согласованное бизнес-состояние.
Шестой — доступ к платным функциям должен управляться через Entitlements.
Седьмой — ошибки, задержки и неправильный порядок событий необходимо считать нормальными сценариями распределённой системы.
Восьмой — финансовые операции требуют строгой Authorization и Tenant Isolation.
Девятый — Reconciliation помогает обнаруживать расхождения между Stripe и локальной базой.
Десятый — мониторинг должен выявлять проблемы до того, как они затронут большое количество клиентов.
Главный принцип: Stripe отвечает за обработку платежей, Backend — за проверку и согласование финансового состояния, а приложение — за безопасное предоставление оплаченных возможностей.
Для большинства небольших SaaS-продуктов хорошая стартовая архитектура — Stripe Checkout, FastAPI, PostgreSQL, Webhook Inbox и Entitlement Service.
По мере роста можно добавлять Customer Portal, Refund Management, Usage-Based Billing, Reconciliation Jobs и расширенный мониторинг.
Так платёжная система будет оставаться надёжной, понятной и безопасной даже при росте нагрузки и сложности продукта.