本地FastAPI环境下Stripe Webhook超时及支付状态不一致问题
本地测试Stripe Webhook:支付页面无响应超时,后台流程却正常完成
问题现象
支付后Stripe的checkout.session.completed事件能被立即接收,调试器确认已进入return JSONResponse(status_code=200, content={"message": "Success"})代码段,但Stripe支付页面无任何反应,一段时间后超时跳转到预设的success URL,且后台业务流程已正确处理完成。
同时发现同一会话在Stripe仪表盘的Events标签显示payment_status为"paid",但Logs中显示为"unpaid",存在状态不一致情况。
Webhook代码
async def my_webhook_view(request: Request, db: Session = Depends(get_db)): payload = await request.body() sig_header = request.headers.get('stripe-signature') event = None try: event = stripe.Webhook.construct_event( payload, sig_header, ENDPOINT_SECRET ) except ValueError as e: # Invalid payload raise HTTPException(status_code=400, detail="Invalid payload") except stripe.error.SignatureVerificationError as e: # Invalid signature raise HTTPException(status_code=400, detail="Invalid signature") if event['type'] == 'checkout.session.completed': #email = event['data']['object']['customer_details']['email'] # Check if the event has already been processed event_id = event['id'] if db.query(ProcessedEvent).filter_by(id=event_id).first(): return {"message": "Event already processed"} # Process the event session = event['data']['object'] db_event = ProcessedEvent(id=event_id) db.add(db_event) db.commit() msg = fulfill_checkout(event['data']['object']['id']) if msg == "Event received": print("Event received") return JSONResponse(status_code=200, content={"message": "Success"}) else: print("Event unprocessed") return JSONResponse(status_code=200, content={"message": "Cancel"})
Checkout会话创建代码
#Stripe Payment @app.post('/create-checkout-session') async def create_checkout_session(price_id: str = Form(...)): try: checkout_session = stripe.checkout.Session.create( line_items=[ { # Provide the exact Price ID (for example, pr_1234) of the product you want to sell 'price': price_id, 'quantity': 1, }, ], mode='payment', success_url= f'{DOMAIN}/success', cancel_url= f'{DOMAIN}/cancel', billing_address_collection = 'required' ) except Exception as e: raise HTTPException(status_code=400, detail=str(e)) return RedirectResponse(checkout_session.url,303)
额外细节
- 本地Webhook仅监听
checkout.session.completed事件,通过命令stripe listen --events checkout.session.completed --forward-to localhost:8000/webhook转发请求 - Stripe仪表盘Webhook配置正确
问题原因及解决办法
1. Webhook响应超时(核心原因)
Stripe要求Webhook端点必须在10秒内返回响应,如果你的fulfill_checkout函数执行耗时较长(比如复杂数据库操作、外部API调用),会导致Stripe无法及时收到响应,进而触发支付页面超时跳转。
解决办法:
将业务处理逻辑放入后台任务,先快速返回200响应给Stripe,再异步处理业务:
from fastapi import BackgroundTasks async def my_webhook_view(request: Request, db: Session = Depends(get_db), background_tasks: BackgroundTasks = Depends()): # 签名验证等代码保持不变 if event['type'] == 'checkout.session.completed': event_id = event['id'] if db.query(ProcessedEvent).filter_by(id=event_id).first(): return {"message": "Event already processed"} # 先记录事件已接收并提交数据库 db_event = ProcessedEvent(id=event_id) db.add(db_event) db.commit() # 将业务处理放入后台任务 background_tasks.add_task(fulfill_checkout, event['data']['object']['id']) # 立即返回200响应 print("Event received, processing in background") return JSONResponse(status_code=200, content={"message": "Success"})
2. 响应格式简化
Stripe对Webhook响应的内容要求极低,仅需200状态码即可。可以简化响应内容,避免因格式或编码问题导致Stripe判定响应异常:
from fastapi import Response # 替换原返回语句 return Response(status_code=200)
3. 本地转发工具排查
检查stripe listen的运行日志,确认请求转发是否存在延迟;同时用curl测试本地Webhook端点的响应时间:
curl -X POST localhost:8000/webhook -d '{}' -w "%{time_total}\n"
确保响应时间在1秒以内。
4. 会话状态不一致问题
Events标签显示的是会话最终状态,Logs中显示的是Webhook触发时的快照状态。解决Webhook响应超时问题后,Stripe会及时更新会话状态,这个不一致现象会自动消失。
内容的提问来源于stack exchange,提问作者TropicalViking
相关产品推荐
相关产品推荐

