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

Django集成Stripe Checkout后session_id缺失及Webhook 500错误排查

Django Stripe Checkout 订阅支付问题解决方案

问题1:支付成功跳转未携带session_id

原因分析

Stripe Checkout支付完成后会跳转到创建Session时指定的success_url,如果该URL未包含{CHECKOUT_SESSION_ID}占位符,Stripe不会自动携带session_id参数,导致payment_success视图无法获取参数进而触发重定向。

修复步骤

  1. 创建Checkout Session时正确配置success_url
    在生成Stripe Session的视图中,确保success_url拼接{CHECKOUT_SESSION_ID}占位符,用Django的reverse生成基础URL:

    # views.py
    from django.shortcuts import reverse
    import stripe
    from django.conf import settings
    from .models import SubscriptionPlan
    
    def create_checkout_session(request, plan_id):
        plan = SubscriptionPlan.objects.get(id=plan_id)
        stripe.api_key = settings.STRIPE_SECRET_KEY
    
        session = stripe.checkout.Session.create(
            payment_method_types=['card'],
            line_items=[{
                'price': plan.stripe_price_id,
                'quantity': 1,
            }],
            mode='subscription',
            # 关键:添加{CHECKOUT_SESSION_ID}占位符
            success_url=f"{request.scheme}://{request.get_host()}{reverse('payment_success')}?session_id={{CHECKOUT_SESSION_ID}}",
            cancel_url=reverse('plan_list'),
        )
        return JsonResponse({'sessionId': session.id})
    
  2. 修改payment_success视图接收session_id
    Stripe通过GET请求跳转,从request.GET中获取session_id并验证:

    def payment_success(request):
        session_id = request.GET.get('session_id')
        if not session_id:
            # 可添加日志排查,临时重定向到套餐列表
            return redirect('plan_list')
        
        stripe.api_key = settings.STRIPE_SECRET_KEY
        try:
            # 验证Session有效性
            session = stripe.checkout.Session.retrieve(session_id)
            # 更新用户订阅状态(根据你的Model结构调整)
            user = request.user
            user_sub = user.subscription
            user_sub.subscription_id = session.subscription
            user_sub.customer_id = session.customer
            user_sub.status = 'active'
            user_sub.save()
        except stripe.error.StripeError as e:
            # 记录Stripe API错误日志
            return redirect('plan_list')
        
        return render(request, 'payment_success.html', {'session': session})
    

问题2:Webhook接收事件返回500错误

常见原因及修复

500错误通常源于签名验证失败、原始请求体获取错误或事件处理逻辑异常,以下是针对性修复:

  1. 正确获取原始请求体并验证签名
    Django默认会解析POST数据,Stripe Webhook需要原始请求体验证签名,直接读取request.body:

    # views.py
    import stripe
    import logging
    from django.http import HttpResponse, HttpResponseBadRequest
    from django.views.decorators.csrf import csrf_exempt
    from django.conf import settings
    from .models import User, Subscription
    
    logger = logging.getLogger(__name__)
    
    @csrf_exempt
    def stripe_webhook(request):
        webhook_secret = settings.STRIPE_WEBHOOK_SECRET
        payload = request.body
        sig_header = request.META.get('HTTP_STRIPE_SIGNATURE')
        event = None
    
        # 签名验证
        try:
            event = stripe.Webhook.construct_event(
                payload, sig_header, webhook_secret
            )
        except ValueError:
            # 请求体格式错误
            return HttpResponseBadRequest()
        except stripe.error.SignatureVerificationError:
            # 签名验证失败
            return HttpResponseBadRequest()
    
        # 处理事件逻辑
        try:
            if event['type'] == 'checkout.session.completed':
                session = event['data']['object']
                # 根据customer_id关联用户(需确保你的Subscription模型存储了customer_id)
                user = User.objects.filter(subscription__customer_id=session['customer']).first()
                if user:
                    user.subscription.subscription_id = session['subscription']
                    user.subscription.status = 'active'
                    user.subscription.save()
    
            elif event['type'] == 'invoice.payment_succeeded':
                invoice = event['data']['object']
                # 根据subscription_id更新订阅状态
                sub = Subscription.objects.filter(subscription_id=invoice['subscription']).first()
                if sub:
                    sub.status = 'active'
                    # 可选:更新到期时间
                    sub.save()
    
            return HttpResponse(status=200)
        except Exception as e:
            # 捕获所有异常并记录日志,避免直接返回500
            logger.error(f"Webhook处理失败: {str(e)}")
            return HttpResponse(status=500)
    
  2. 关键配置检查

    • 确保Stripe Dashboard中Webhook的签名密钥与settings.STRIPE_WEBHOOK_SECRET完全一致。
    • Webhook URL需排除CSRF验证(已用@csrf_exempt),生产环境必须使用HTTPS。
    • 统一Stripe API版本:在settings.py中添加stripe.api_version = '2024-06-20',与Stripe Dashboard中设置的版本匹配。
  3. 本地调试技巧
    使用Stripe CLI转发Webhook事件到本地:

    stripe listen --forward-to localhost:8000/stripe-webhook/
    

额外注意事项

  • 订阅状态更新需同时依赖支付成功页和Webhook逻辑,避免用户中途关闭页面导致状态不一致。
  • 生产环境中,务必为Webhook添加日志记录,方便排查异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 19:14:52