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.py 的 Order.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 正常支付流程
-
访问订单详情页,点击“去支付”,跳转支付宝沙箱。
-
使用沙箱买家账号支付,支付成功后自动回跳到
payment_return。 -
终端输出:
[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 模拟支付后未回跳
-
支付成功后立刻关闭浏览器(模拟未触发 return_url)。
-
重新打开浏览器,登录,进入“我的订单”(第 22 篇才会做列表,目前直接访问订单详情)。
-
如果异步通知已到达(需内网穿透),订单状态为“待发货”。
-
如果异步通知延迟,点击“查询支付状态”按钮,主动触发查询。终端输出:
[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策士,未经授权禁止转载。

只靠异步通知太被动?这篇教你用支付宝主动查询 API 补齐支付状态,验签回跳+手动查询双重确认,再加状态流转日志全程追踪,让订单状态更新滴水不漏、不再有“已付未发货”的尴尬!
浙公网安备 33010602011771号