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, создаваемые на бэкенде для разовых списаний, будут работать как задумано — без повторного ввода данных карты.