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视图无法获取参数进而触发重定向。
修复步骤
创建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})修改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错误通常源于签名验证失败、原始请求体获取错误或事件处理逻辑异常,以下是针对性修复:
正确获取原始请求体并验证签名
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)关键配置检查
- 确保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中设置的版本匹配。
- 确保Stripe Dashboard中Webhook的签名密钥与
本地调试技巧
使用Stripe CLI转发Webhook事件到本地:stripe listen --forward-to localhost:8000/stripe-webhook/
额外注意事项
- 订阅状态更新需同时依赖支付成功页和Webhook逻辑,避免用户中途关闭页面导致状态不一致。
- 生产环境中,务必为Webhook添加日志记录,方便排查异常。
内容的提问来源于stack exchange,提问作者A_K
相关产品推荐
相关产品推荐

