2025, Dec 14 18:01
Повторные списания в Stripe без ввода карты: настройка Checkout
Пошагово: как в Stripe Checkout сохранить карту (setup_future_usage), получить PaymentMethod и проводить off-session платежи с бэкенда без повторного ввода.
Взыскать плату с вернувшегося клиента в Stripe после сеанса Checkout кажется простым — пока не выясняется, что у Customer не сохраняется ни одной карты для повторного использования. Первый платеж проходит, но следующая серверная попытка списания падает, потому что к объекту Customer не привязан ни один PaymentMethod. Ниже — как правильно выстроить процесс, чтобы делать полноценный «один клик» для последующего платежа без повторного показа интерфейса Stripe.
Постановка задачи
После первого сеанса Checkout у объекта Customer нет поля payment_method, запрос списка платежных методов клиента ничего не возвращает, а попытка переиспользовать PaymentMethod из первого списания приводит к ошибке о невозможности повторного использования. Цель — выполнить второй разовый платеж только с бэкенда, без повторного ввода карты.
Код для воспроизведения
Бэкенд создает сеанс Checkout, затем проверяет его и сохраняет customer_id. Для дополнительного списания он пытается переиспользовать ранее использованный PaymentMethod:
@api_view(['POST'])
def begin_checkout(request):
checkout_sess = BillingHub().checkout.Session.create(
success_url=f'{settings.STRIPE_SUCCESS_URL}?session_id={{CHECKOUT_SESSION_ID}}',
line_items=[{'price': 'price_XXXX', 'quantity': 1}],
mode='payment',
)
return Response({'success_url': checkout_sess['url']}, status=200)
@api_view(['GET'])
def verify_checkout(request):
sid = request.GET.get('session_id')
chk = BillingHub().checkout.Session.retrieve(sid)
# сохранить chk.customer и email
# позже — выполнить дополнительное разовое списание
charge_pi = stripe.PaymentIntent.create(
amount=2000,
currency='mxn',
customer=stored_customer_id,
payment_method=old_pm_id, # попытка переиспользовать из первого списания
off_session=True,
confirm=True,
description="Extra advice - one-off charge",
)
Почему это не работает
Сеанс Checkout был создан без указания Stripe сохранить карту для последующих списаний. В итоге у Customer нет привязанного PaymentMethod. Список платежных методов пуст, потому что ничего не было сохранено. PaymentMethod, использованный для первого PaymentIntent, нельзя переиспользовать, если он не был прикреплён к Customer — отсюда и ошибка о попытке повторного использования.
Решение
При создании первого сеанса Checkout явно попросите Stripe сохранить карту у Customer, указав payment_intent_data[setup_future_usage]. С этой настройкой Stripe привяжет к Customer PaymentMethod (например, pm_1234) после успешной оплаты. Затем можно получить идентификатор PaymentMethod из PaymentIntent, связанного с этим сеансом, и сохранить его на сервере для последующих списаний.
Исправленная реализация
Сначала создайте сеанс Checkout с параметром setup_future_usage. Затем, после успешной оплаты, извлеките PaymentIntent и возьмите из него payment_method, чтобы сохранить вместе с данными клиента.
@api_view(['POST'])
def start_session(request):
cs = BillingHub().checkout.Session.create(
success_url=f'{settings.STRIPE_SUCCESS_URL}?session_id={{CHECKOUT_SESSION_ID}}',
line_items=[{'price': 'price_XXXX', 'quantity': 1}],
mode='payment',
payment_intent_data={
'setup_future_usage': 'off_session', # обеспечивает сохранение карты у Customer
},
)
return Response({'success_url': cs['url']}, status=200)
@api_view(['GET'])
def finalize_session(request):
sess_id = request.GET.get('session_id')
sess = BillingHub().checkout.Session.retrieve(sess_id)
# сохранить sess.customer и sess.customer_details.email
pi_id = sess['payment_intent']
pi = stripe.PaymentIntent.retrieve(pi_id)
saved_pm = pi['payment_method']
# сохранить saved_pm вместе с sess.customer для будущих платежей
# позже выполнить дополнительное разовое списание без интерфейса
followup_pi = stripe.PaymentIntent.create(
amount=2000,
currency='mxn',
customer=stored_customer_id,
payment_method=saved_pm_id_from_db,
off_session=True,
confirm=True,
description="Extra advice - one-off charge",
)
Почему это важно
Если вы делаете первый платеж через Checkout, но не просите Stripe сохранить карту, то сами лишаете себя возможности бесшовных последующих списаний. Параметр payment_intent_data[setup_future_usage] делает Customer пригодным для повторного использования безопасным и предсказуемым способом и дает вам канонический идентификатор PaymentMethod, который можно применять для последующих списаний только с бэкенда.
Выводы
Еще на первом сеансе Checkout определитесь, нужны ли вам будущие списания. Если да, включите payment_intent_data[setup_future_usage], чтобы Stripe привязал к Customer PaymentMethod. После завершения сеанса получите payment_method из PaymentIntent и сохраните его. С этим подготовленным шагом далее PaymentIntent, создаваемые на бэкенде для разовых списаний, будут работать как задумано — без повторного ввода данных карты.