You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Stripe结账会话完成后保存Django预订实例?

问题分析与解决方案

你的核心问题是误解了Stripe Checkout Session的状态逻辑:刚创建的会话状态默认就是open(因为用户还未完成支付流程),所以用status == 'completed'作为会话创建成功的判断条件完全错误,直接导致了错误提示;而直接跳过校验保存实例,又会出现未支付就生成预订的问题。

正确的流程应该是:先保存未确认的预订实例→创建会话并跳转支付→通过Stripe Webhook监听支付完成事件,再更新预订状态,具体实现如下:


1. 修正视图函数逻辑

移除错误的状态校验,改为捕获Stripe API异常,确保会话创建成功后跳转,同时先保存未确认的预订实例(必须先保存才能获取ID关联支付):

@transaction.atomic
def booking_form(request, id):
    franchise_id = id
    form = BookingForm(franchise_id=franchise_id)

    if request.method == 'POST':
        form = BookingForm(request.POST, franchise_id=franchise_id)
        if 'paynow-btn' in request.POST:
            if form.is_valid():
                instance = form.save(commit=False)
                instance.payment_status = 'due'
                instance.booking_status = 'unconfirmed'
                instance.franchise_id = franchise_id
                
                # 先保存实例,获取ID用于Stripe元数据关联
                instance.save()

                try:
                    # Stripe Connect 标准账户直接扣费
                    connected_account_id = create_or_get_connected_account(franchise_id)
                    success_urlpattern = reverse('booking:booking_successful', kwargs={'id': franchise_id})
                    success_urlpath = request.build_absolute_uri(success_urlpattern)

                    checkout_session = stripe.checkout.Session.create(
                        payment_method_types=['card'],
                        line_items=[{
                            "price_data": {
                                "currency": "gbp",
                                "unit_amount": int(instance.total_amount * 100),
                                "product_data": {
                                    "name": f'Booking for {instance.full_name}',
                                },
                            },
                            "quantity": 1,
                        }],
                        payment_intent_data={
                            'metadata': {
                                'booking_id': instance.id,  # 已保存实例,ID可用
                            },
                            'application_fee_amount': int(instance.total_amount * (franchise.payout_percentage / 100) * 100),
                        },
                        mode='payment',
                        success_url=success_urlpath,
                        cancel_url=settings.PAYMENT_CANCEL_URL,
                        stripe_account=connected_account_id,
                    )

                    # 会话创建成功,直接跳转至Stripe支付页面
                    return redirect(checkout_session.url)
                except stripe.error.StripeError as e:
                    # 捕获Stripe API调用异常,清理未支付的预订实例
                    messages.error(request, f'支付会话创建失败:{str(e)}')
                    instance.delete()
                    return redirect(reverse('booking:booking_form', kwargs={'id': franchise_id}))

2. 添加Stripe Webhook处理函数

创建视图接收Stripe的支付完成事件,更新对应预订的状态:

import stripe
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from .models import Booking

@csrf_exempt
def stripe_webhook(request):
    payload = request.body
    sig_header = request.META.get('HTTP_STRIPE_SIGNATURE')
    event = None

    try:
        # 验证Webhook签名,确保请求来自Stripe
        event = stripe.Webhook.construct_event(
            payload, sig_header, settings.STRIPE_WEBHOOK_SECRET
        )
    except ValueError:
        # 无效请求体
        return HttpResponse(status=400)
    except stripe.error.SignatureVerificationError:
        # 无效签名
        return HttpResponse(status=400)

    # 处理支付完成事件
    if event['type'] == 'checkout.session.completed':
        session = event['data']['object']
        # 从支付意图元数据中获取预订ID
        booking_id = session['payment_intent']['metadata']['booking_id']
        try:
            booking = Booking.objects.get(id=booking_id)
            # 更新支付状态与预订状态
            booking.payment_status = 'paid'
            booking.booking_status = 'confirmed'
            booking.save()
        except Booking.DoesNotExist:
            # 找不到对应预订,可添加日志记录
            pass

    return HttpResponse(status=200)

3. 配置Webhook路由

在项目的urls.py中添加Webhook的路由:

from django.urls import path
from . import views

urlpatterns = [
    # 其他路由...
    path('stripe-webhook/', views.stripe_webhook, name='stripe_webhook'),
]

关键说明

  • 先保存实例的必要性:未保存的实例没有ID,无法与Stripe支付流程关联,所以必须先保存并标记为未确认/待支付状态。
  • Webhook是唯一可靠的支付完成通知:用户可能在支付页面关闭窗口、取消支付,只有Stripe主动发送的checkout.session.completed事件能确保支付真的完成。
  • 异常清理:Stripe API调用失败时,要删除已保存的未支付实例,避免产生无效的脏数据。

内容的提问来源于stack exchange,提问作者betty_

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 21:45:33