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

Django集成Zarinpal支付网关后CallbackURL回调时出现AnonymousUser对象不可迭代的登录状态丢失问题求助

解决Django集成Zarinpal支付时回调出现AnonymousUser的问题

看起来你遇到的问题是部分用户在支付完成回调后,会话丢失导致request.user变成AnonymousUser,进而调用get_user_pending_order时触发错误。这种情况通常和浏览器的Cookie策略、Django的会话配置或者支付流程中的会话超时有关,下面是具体的排查和解决方案:

核心原因分析

Zarinpal支付网关跳转属于跨站请求,部分浏览器(尤其是开启隐私模式或严格Cookie策略的)会在跳转回你的网站时,阻止携带会话Cookie(sessionid),导致Django无法识别用户身份,变成匿名用户。另外,如果支付流程耗时过长,超过了Django的会话超时时间,也会导致会话失效。

具体解决方案

1. 调整Django会话Cookie的SameSite和Secure配置

这是最常见的修复点,修改settings.py中的会话相关配置:

# settings.py
# 允许跨站GET请求携带Cookie(Zarinpal回调是GET请求)
SESSION_COOKIE_SAMESITE = 'Lax'
# 如果你的网站使用HTTPS,开启这个确保Cookie只在HTTPS环境下发送
SESSION_COOKIE_SECURE = True
# 可选:延长会话超时时间,比如设置为2小时(根据支付流程最长耗时调整)
SESSION_COOKIE_AGE = 7200
  • SameSite=Lax:允许在跨站的GET请求中携带Cookie,适合支付回调这种场景;如果设置为Strict,会完全阻止跨站跳转携带Cookie,这很可能是部分用户出现问题的原因。
  • SESSION_COOKIE_SECURE=True:确保Cookie仅通过HTTPS传输,避免被劫持,同时符合现代浏览器的安全要求。

2. 优化订单验证逻辑,不依赖会话

目前你的verify函数完全依赖用户会话来获取待支付订单,一旦会话丢失就会崩溃。可以修改逻辑,通过Zarinpal返回的authority来关联订单:

步骤1:在Order模型中添加authority字段

# models.py
from django.db import models

class Order(models.Model):
    # 保留原有字段...
    authority = models.CharField(max_length=255, blank=True, null=True)

然后执行迁移:python manage.py makemigrations && python manage.py migrate

步骤2:在send_request中保存authority到订单

def send_request(request):
    order = get_user_pending_order(request)
    if not order:
        return HttpResponse("No pending order found")
    
    req_data = {
        "merchant_id": MERCHANT,
        "amount": int(order.get_total()),
        "callback_url": CallbackURL,
        "description": description,
        "metadata": {"mobile": order.owner.phone_number, "email": request.user.email}
    }
    req_header = {"accept": "application/json", "content-type": "application/json"}
    req = requests.post(url=ZP_API_REQUEST, data=json.dumps(req_data), headers=req_header)
    
    if len(req.json()['errors']) == 0:
        authority = req.json()['data']['authority']
        # 保存authority到订单
        order.authority = authority
        order.save()
        return redirect(ZP_API_STARTPAY.format(authority=authority))
    else:
        e_code = req.json()['errors']['code']
        e_message = req.json()['errors']['message']
        return HttpResponse(f"Error code: {e_code}, Error Message: {e_message}")

步骤3:在verify函数中通过authority获取订单

def verify(request):
    t_status = request.GET.get('Status')
    t_authority = request.GET.get('Authority')
    
    if not t_authority:
        return HttpResponse("Invalid authority")
    
    # 通过authority找订单,而不是依赖会话
    try:
        order_to_purchase = Order.objects.get(authority=t_authority, is_ordered=False)
    except Order.DoesNotExist:
        return HttpResponse("Order not found or already processed")
    
    # 验证当前用户是否是订单所有者(如果用户已登录)
    if request.user.is_authenticated:
        if order_to_purchase.owner.user != request.user:
            return HttpResponse("You are not authorized to process this order")
    else:
        # 如果用户未登录,引导用户登录后再验证
        return redirect(f"/login?next=/payment/verify/?Status={t_status}&Authority={t_authority}")
    
    # 后续的支付验证逻辑保持不变...
    if t_status == 'OK':
        req_header = {"accept": "application/json", "content-type": "application/json"}
        req_data = {
            "merchant_id": MERCHANT,
            "amount": int(order_to_purchase.get_total()),
            "authority": t_authority
        }
        req = requests.post(url=ZP_API_VERIFY, data=json.dumps(req_data), headers=req_header)
        
        if len(req.json()['errors']) == 0:
            t_status = req.json()['data']['code']
            if t_status == 100:
                # 更新订单状态逻辑
                order_to_purchase.is_ordered = True
                order_to_purchase.date_ordered = datetime.datetime.now()
                order_to_purchase.created_on_time = datetime.datetime.now()
                order_to_purchase.save()
                
                # 更新库存逻辑
                order_items = order_to_purchase.items.all()
                for order_item in order_items:
                    order_item.product.quantity -= 1
                    order_item.product.save()
                order_items.update(is_ordered=True, date_ordered=datetime.datetime.now())
                
                # 发送邮件逻辑
                subject = 'Payment Successful'
                c = {
                    "refid": str(req.json()['data']['ref_id']),
                    "ref_code": order_to_purchase.ref_code,
                    "owner": order_to_purchase.owner,
                }
                email_template_name = "pay/after_pay_confirm_email.html"
                email_html = render_to_string(email_template_name, c)
                email_from = settings.EMAIL_HOST_USER
                send_mail(subject, email_html, email_from, [order_to_purchase.owner.user.email], html_message=email_html)
                
                ctx = {'message_good':'Payment completed successfully!'}
                return render(request, 'pay/verify.html', ctx)
            elif t_status == 101:
                ctx = {'message_info':'Payment already verified'}
                return render(request, 'pay/verify.html', ctx)
            else:
                ctx = {'message_bad':f'Payment failed with code: {t_status}'}
                return render(request, 'pay/verify.html', ctx)
        else:
            e_code = req.json()['errors']['code']
            e_message = req.json()['errors']['message']
            return HttpResponse(f"Verification error: {e_code}, {e_message}")
    else:
        ctx = {'message_bad':'Payment was canceled by user'}
        return render(request, 'pay/verify.html', ctx)

3. 增加错误处理和日志记录

在get_user_pending_order和verify函数中添加错误处理,避免直接崩溃,同时记录关键信息方便排查:

import logging

logger = logging.getLogger(__name__)

def get_user_pending_order(request):
    if not request.user.is_authenticated:
        logger.warning(f"Anonymous user tried to access pending order. Request params: {request.GET}")
        return None
    try:
        user_profile = get_object_or_404(Profile, user=request.user)
        order = Order.objects.filter(owner=user_profile, is_ordered=False).first()
        return order
    except Exception as e:
        logger.error(f"Error getting pending order: {str(e)}", exc_info=True)
        return None

4. 确保回调URL和网站协议一致

如果你的网站使用HTTPS,回调URL必须也是HTTPS;如果是HTTP,回调URL也得是HTTP。混合协议会导致浏览器拒绝发送会话Cookie。

总结

优先调整Django的会话Cookie配置(SameSite和Secure),这是解决跨站会话丢失最直接的方法。然后优化订单验证逻辑,通过authority关联订单,减少对会话的依赖,即使用户会话丢失也能处理回调。最后添加日志记录,方便后续排查特殊情况。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 12:37:48