Платежи — место, где проект Django встречается с реальными деньгами, поэтому сценарий должен быть скучным и предсказуемым. В Stripe Checkout вы никогда не видите данные карты: ваш view создаёт Checkout Session, клиент платит на странице Stripe, а результат Stripe сообщает через вебхук. В этой теме мы делаем сторону Django: модели Order и StripeEvent, view для Checkout, webhook-view с csrf_exempt и проверкой подписи, идемпотентную обработку, тесты и обзор dj-stripe. Общие понятия Stripe разобраны в уроках Stripe Checkout и Вебхуки Stripe. Везде тестовый режим и ключи-заглушки.
Сценарий
- Браузер отправляет POST на ваш view Checkout.
- View создаёт
Order(pending) и Session в Stripe, затем делает редирект наsession.url. - Клиент платит на странице Stripe.
- Stripe отправляет POST
checkout.session.completedна ваш вебхук; только он переводит заказ вpaid.
| Часть | Где живёт |
|---|---|
| Цена и товар | ваша база (Product), никогда не запрос |
| Статус заказа | Order.status, меняется только вебхуком |
| Секретный ключ, секрет вебхука | переменные окружения, читаются в settings.py |
| Защита от дублей | StripeEvent.event_id с unique=True |
💡 Страница успеха может написать «спасибо, подтверждаем оплату» и прочитать статус заказа. Помечать заказ оплаченным она не должна.
Настройки и модели
SDK ставится командой pip install stripe, ключи читаются из окружения (настройки и окружения):
# settings.py
import os
STRIPE_SECRET_KEY = os.environ["STRIPE_SECRET_KEY"] # sk_test_...
STRIPE_WEBHOOK_SECRET = os.environ["STRIPE_WEBHOOK_SECRET"] # whsec_...Order хранит сумму целым числом в минимальных единицах (центах), а StripeEvent помнит каждое уже обработанное событие вебхука:
from django.conf import settings
from django.db import models
class Order(models.Model):
class Status(models.TextChoices):
PENDING = "pending", "Pending"
PAID = "paid", "Paid"
CANCELED = "canceled", "Canceled"
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
amount = models.PositiveIntegerField(help_text="minor units (cents)")
currency = models.CharField(max_length=3, default="usd")
status = models.CharField(
max_length=10, choices=Status.choices, default=Status.PENDING
)
stripe_session_id = models.CharField(
max_length=255, blank=True, db_index=True
)
created_at = models.DateTimeField(auto_now_add=True)
class StripeEvent(models.Model):
event_id = models.CharField(max_length=255, unique=True)
type = models.CharField(max_length=100)
received_at = models.DateTimeField(auto_now_add=True)⚠️ Не используйте
FloatFieldдля денег. Берите целые центы (как здесь) илиDecimalField.