Django 从 0 到 1 打造完整电商平台:支付结果处理与订单状态更新

IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在公众号、今日头条持续发布最新文章,助你少走弯路。


上一篇我们集成了支付宝沙箱支付,用户从下单到付款,整个交易链路已经跑通。但你有没有想过一个问题:如果用户付了钱,但异步通知因为网络波动没收到,订单状态岂不是永远卡在“待支付”? 或者更糟,用户支付后关了浏览器,同步回跳根本没触发,怎么办?

今天我们就来解决这些“支付后的麻烦事”。核心思路是:不依赖单一回调,主动查询 + 被动接收双管齐下,确保支付结果万无一失。 同时完善订单状态流转日志,为后续的定时任务(Celery)打下基础。


一、支付结果的不确定性分析

在第 20 篇中,我们使用了两个回调:

但异步通知也存在问题:

  • 本地开发环境支付宝无法访问 127.0.0.1,必须依赖内网穿透;

  • 网络抖动可能导致通知延迟几分钟甚至更久;

  • 极端情况下,支付宝重试机制也可能失败。

因此,一个健壮的支付系统应该具备 主动查询 能力——在同步回跳时、用户手动刷新订单页时,主动向支付宝发起查询,确认这笔订单的真实支付状态。


二、实现支付宝订单查询

python-alipay-sdk 提供了 api_alipay_trade_query 方法,可根据商户订单号查询交易状态。

apps/payment/views.py 中添加查询函数:

from alipay import AliPay, AliPayConfig
from django.conf import settings
import logging

logger = logging.getLogger('payment')


def query_alipay_order(out_trade_no):
    """
    查询支付宝订单支付状态
    返回 (支付成功: bool, 支付宝交易号: str | None, 支付金额: str | None)
    """
    alipay = AliPay(
        appid=settings.ALIPAY_APPID,
        app_private_key_string=settings.ALIPAY_APP_PRIVATE_KEY,
        alipay_public_key_string=settings.ALIPAY_PUBLIC_KEY,
        sign_type='RSA2',
        debug=settings.ALIPAY_DEBUG,
        config=AliPayConfig(timeout=15),
    )

    try:
        result = alipay.api_alipay_trade_query(out_trade_no=out_trade_no)
    except Exception as e:
        logger.error(f'查询支付宝订单 {out_trade_no} 失败:{e}')
        return False, None, None

    # 支付宝返回的 trade_status
    trade_status = result.get('trade_status', '')
    total_amount = result.get('total_amount', '')
    trade_no = result.get('trade_no', '')

    # 成功状态:TRADE_SUCCESS(即时到账成功)、TRADE_FINISHED(交易完成不可退款)
    if trade_status in ('TRADE_SUCCESS', 'TRADE_FINISHED'):
        logger.info(f'订单 {out_trade_no} 支付成功,支付宝交易号={trade_no}')
        return True, trade_no, total_amount
    else:
        logger.info(f'订单 {out_trade_no} 支付状态为 {trade_status}')
        return False, None, None

关键参数说明:

  • out_trade_no:即我们生成订单时填入的 order_no,这是支付宝和本地系统的“关联键”。

  • trade_status:支付宝的交易状态,TRADE_SUCCESS 表示付款成功,TRADE_CLOSED 表示交易关闭(如超时未支付被取消),WAIT_BUYER_PAY 表示等待买家付款。


三、改造同步回跳视图(增加主动查询)

修改 apps/payment/views.py 中的 payment_return 视图,在验签成功后再做一次主动查询,双重确认支付结果:

@login_required(login_url='users:login')
def payment_return(request):
    """支付完成后的同步回跳页面(增强版:验签 + 主动查询)"""
    alipay = AliPay(
        appid=settings.ALIPAY_APPID,
        app_private_key_string=settings.ALIPAY_APP_PRIVATE_KEY,
        alipay_public_key_string=settings.ALIPAY_PUBLIC_KEY,
        sign_type='RSA2',
        debug=settings.ALIPAY_DEBUG,
        config=AliPayConfig(timeout=15),
    )

    data = request.GET.dict()
    sign = data.pop('sign', None)

    # 先验签
    if not sign or not alipay.verify(data, sign):
        logger.warning('同步回跳验签失败')
        messages.error(request, '支付验证失败,请联系客服。')
        return redirect('home')

    out_trade_no = data.get('out_trade_no')

    # 主动查询支付宝,确认支付结果
    is_success, trade_no, total_amount = query_alipay_order(out_trade_no)

    try:
        order = Order.objects.get(order_no=out_trade_no)
        payment = order.payment

        if is_success:
            # 支付成功,更新订单状态
            if order.status == 0:
                order.set_status(1)  # 待支付 → 待发货
            if payment.status != 1:
                payment.trade_no = trade_no
                payment.status = 1
                payment.save(update_fields=['trade_no', 'status', 'update_time'])

            return render(request, 'payment/pay_success.html', {
                'order': order,
                'trade_no': trade_no,
            })
        else:
            # 支付未成功(用户可能中途离开或支付失败)
            return render(request, 'payment/pay_pending.html', {
                'order': order,
            })
    except Order.DoesNotExist:
        messages.error(request, '订单不存在。')
        return redirect('home')

四、改造异步通知视图(加入主动查询兜底)

修改 payment_notify 视图,验签后也执行一次主动查询,确保和支付宝状态完全一致:

@csrf_exempt
def payment_notify(request):
    if request.method != 'POST':
        return HttpResponse('Method Not Allowed', status=405)

    alipay = AliPay(...)  # 同上
    data = request.POST.dict()
    sign = data.pop('sign', None)

    if not sign or not alipay.verify(data, sign):
        logger.error('异步通知验签失败')
        return HttpResponse('failure')

    out_trade_no = data.get('out_trade_no')

    # 主动查询支付宝确认
    is_success, trade_no, total_amount = query_alipay_order(out_trade_no)

    try:
        order = Order.objects.select_for_update().get(order_no=out_trade_no)

        if is_success:
            if order.status == 0:
                payment = order.payment
                payment.trade_no = trade_no
                payment.status = 1
                payment.save(update_fields=['trade_no', 'status', 'update_time'])
                order.set_status(1)
                logger.info(f'订单 {out_trade_no} 异步通知确认支付成功')
        else:
            # 如果查询结果显示未支付,但之前可能已误标记为支付成功,这里不做处理
            logger.warning(f'订单 {out_trade_no} 异步通知但查询未支付')

    except Order.DoesNotExist:
        logger.error(f'订单 {out_trade_no} 不存在')
        return HttpResponse('failure')

    return HttpResponse('success')

设计思路:异步通知和主动查询互为补充。即使通知延迟,查询能填补空白;即使查询暂时失败,通知能最终一致。这叫 对账思想


五、订单详情页增加“查询支付状态”按钮

用户可能在支付后未看到成功页面,进入订单详情页时仍显示“待支付”。我们可以给用户提供一个手动刷新支付状态的按钮。

编辑 apps/orders/templates/orders/order_detail.html(第 19 篇创建),在待支付订单上方增加:

{% if order.status == 0 %}
<div class="alert alert-warning">
    该订单尚未支付。
    <a href="{% url 'payment:payment_go' order.pk %}" class="btn btn-warning btn-sm ms-3">去支付</a>
    <a href="{% url 'payment:payment_query' order.pk %}" class="btn btn-outline-info btn-sm ms-2">查询支付状态</a>
</div>
{% endif %}

新增一个查询视图,在 apps/payment/views.py 中:

@login_required(login_url='users:login')
def payment_query(request, order_id):
    """手动查询订单支付状态"""
    order = get_object_or_404(Order, pk=order_id, user=request.user)

    if order.status != 0:
        messages.info(request, '订单已处理。')
        return redirect('orders:order_detail', pk=order.pk)

    is_success, trade_no, _ = query_alipay_order(order.order_no)

    if is_success:
        payment = order.payment
        payment.trade_no = trade_no
        payment.status = 1
        payment.save(update_fields=['trade_no', 'status', 'update_time'])
        order.set_status(1)
        messages.success(request, '支付状态已更新!该订单已支付成功。')
    else:
        messages.warning(request, '该订单尚未支付,请尽快完成付款。')

    return redirect('orders:order_detail', pk=order.pk)

apps/payment/urls.py 中添加路由:

path('query/<int:order_id>/', views.payment_query, name='payment_query'),

六、支付待处理页面模板

创建 apps/payment/templates/payment/pay_pending.html,当支付结果不确定时展示友好提示:

{% extends 'base.html' %}
{% block title %}支付处理中{% endblock %}

{% block content %}
<div class="row justify-content-center">
    <div class="col-md-6 text-center">
        <div class="card shadow-sm">
            <div class="card-body py-5">
                <h1 class="text-warning mb-3">⏳</h1>
                <h3>支付处理中</h3>
                <p class="text-muted">您的支付正在处理,请稍后查看订单状态。</p>
                <hr>
                <p><strong>订单号:</strong>{{ order.order_no }}</p>
                <p><strong>订单金额:</strong>¥{{ order.total_amount|floatformat:2 }}</p>
                <a href="{% url 'payment:payment_query' order.pk %}" class="btn btn-primary mt-2">刷新支付状态</a>
                <a href="{% url 'orders:order_detail' order.pk %}" class="btn btn-outline-secondary mt-2">查看订单</a>
            </div>
        </div>
    </div>
</div>
{% endblock %}

七、订单状态流转日志(可选但推荐)

为方便追踪每一笔订单的状态变化,我们可以在模型中增加一个日志记录。简单起见,我们使用 Django 的 logging 模块记录关键状态变更。

apps/orders/models.pyOrder.set_status 方法中增加日志:

import logging
logger = logging.getLogger('orders')

def set_status(self, new_status):
    if not self.can_transition_to(new_status):
        raise ValueError(f'不允许从状态 {self.status} 转换到 {new_status}')
    old_status = self.status
    self.status = new_status
    if new_status == 1:
        self.pay_time = timezone.now()
    self.save(update_fields=['status', 'update_time', 'pay_time'])
    logger.info(f'订单 {self.order_no} 状态变更:{old_status} → {new_status}')

这样在订单状态改变时,控制台会输出类似:

[27/May/2026 10:15:30] INFO [orders] 订单 20260527101530X7K9M2 状态变更:0 → 1

八、配置日志(基础)

django_ecommerce/settings.py 中增加日志配置(为后续第 25 篇详细展开做准备):

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'simple': {
            'format': '[{asctime}] {levelname} [{name}] {message}',
            'style': '{',
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'formatter': 'simple',
        },
    },
    'loggers': {
        'payment': {
            'handlers': ['console'],
            'level': 'INFO',
        },
        'orders': {
            'handlers': ['console'],
            'level': 'INFO',
        },
    },
}

九、完整支付流程测试(增强版)

启动开发服务器:

python manage.py runserver

9.1 正常支付流程

  1. 访问订单详情页,点击“去支付”,跳转支付宝沙箱。

  2. 使用沙箱买家账号支付,支付成功后自动回跳到 payment_return

  3. 终端输出:

[27/May/2026 10:15:30] INFO [payment] 订单 20260527101530X7K9M2 支付成功,支付宝交易号=2026052722001412345678901234
[27/May/2026 10:15:30] INFO [orders] 订单 20260527101530X7K9M2 状态变更:0 → 1
[27/May/2026 10:15:30] "GET /payment/return/?... HTTP/1.1" 200 5432

页面显示“支付成功”。

9.2 模拟支付后未回跳

  1. 支付成功后立刻关闭浏览器(模拟未触发 return_url)。

  2. 重新打开浏览器,登录,进入“我的订单”(第 22 篇才会做列表,目前直接访问订单详情)。

  3. 如果异步通知已到达(需内网穿透),订单状态为“待发货”。

  4. 如果异步通知延迟,点击“查询支付状态”按钮,主动触发查询。终端输出:

[27/May/2026 10:20:00] INFO [payment] 订单 20260527101530X7K9M2 支付成功,支付宝交易号=2026052722001412345678901234
[27/May/2026 10:20:00] INFO [orders] 订单 20260527101530X7K9M2 状态变更:0 → 1
[27/May/2026 10:20:00] "GET /payment/query/1/ HTTP/1.1" 302 0

订单状态更新为“待发货”。

9.3 支付失败或中途取消

在支付宝收银台点击“取消支付”或关闭页面,回跳到 pay_pending.html,显示“支付处理中”。

9.4 防重复支付测试

在订单已支付的情况下,再次访问 /payment/go/1/,视图会判断 order.status != 0,提示“该订单当前状态为「待发货」,无法支付”,并跳回订单详情。

终端输出:

[27/May/2026 10:25:00] "GET /payment/go/1/ HTTP/1.1" 302 0

十、总结与下集预告

今天我们为支付加上了“双保险”——主动查询与异步通知相结合,确保支付结果不漏网:

  • 实现了支付宝订单查询功能 query_alipay_order

  • 改造同步回跳和异步通知,均加入主动查询兜底;

  • 在订单详情页提供“查询支付状态”按钮,用户手动触发;

  • 增加了状态流转日志,方便追踪问题;

  • 为后续 Celery 定时扫描未支付订单做好了铺垫(第 23 篇会实现超时未支付自动取消)。

现在,支付体系已经相当稳健。下一步,我们要让用户能方便地管理自己的订单。第 22 篇,我将实现 “我的订单”列表与订单详情,按状态分类展示,支持查看物流(模拟)、确认收货等操作。

想了解更多还可以去公众号、今日头条搜索「IT策士」,一起升级 IT 思维 !


本文为《Django 从 0 到 1 打造完整电商平台》系列第 21 篇,作者:IT策士,未经授权禁止转载。

posted @ 2026-05-25 22:24  IT策士  阅读(7)  评论(0)    收藏  举报