Все статьи

Как реализовать платежи в веб-приложении: Stripe, Webhooks и безопасность транзакций

Полное руководство для Full Stack разработчика: React, FastAPI, Stripe Checkout, Payment Intents, подписки, Idempotency, Refunds и Production-архитектура платежей

26 минут чтения

Представим, что вы разрабатываете 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 и расширенный мониторинг.

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


Наверх