IT策士 10余年一线大厂经验专注 IT 思维、架构、职场进阶。我也会在其它平台持续发布最新文章助你少走弯路。上一篇我们集成了支付宝沙箱支付用户从下单到付款整个交易链路已经跑通。但你有没有想过一个问题如果用户付了钱但异步通知因为网络波动没收到订单状态岂不是永远卡在“待支付”或者更糟用户支付后关了浏览器同步回跳根本没触发怎么办今天我们就来解决这些“支付后的麻烦事”。核心思路是不依赖单一回调主动查询 被动接收双管齐下确保支付结果万无一失。同时完善订单状态流转日志为后续的定时任务Celery打下基础。一、支付结果的不确定性分析在第 20 篇中我们使用了两个回调但异步通知也存在问题本地开发环境支付宝无法访问127.0.0.1必须依赖内网穿透网络抖动可能导致通知延迟几分钟甚至更久极端情况下支付宝重试机制也可能失败。因此一个健壮的支付系统应该具备主动查询能力——在同步回跳时、用户手动刷新订单页时主动向支付宝发起查询确认这笔订单的真实支付状态。二、实现支付宝订单查询python-alipay-sdk提供了api_alipay_trade_query方法可根据商户订单号查询交易状态。在apps/payment/views.py中添加查询函数from alipayimportAliPay, AliPayConfig from django.confimportsettingsimportlogging loggerlogging.getLogger(payment)def query_alipay_order(out_trade_no): 查询支付宝订单支付状态 返回(支付成功: bool, 支付宝交易号: str|None, 支付金额: str|None) alipayAliPay(appidsettings.ALIPAY_APPID,app_private_key_stringsettings.ALIPAY_APP_PRIVATE_KEY,alipay_public_key_stringsettings.ALIPAY_PUBLIC_KEY,sign_typeRSA2,debugsettings.ALIPAY_DEBUG,configAliPayConfig(timeout15),)try: resultalipay.api_alipay_trade_query(out_trade_noout_trade_no)except Exception as e: logger.error(f查询支付宝订单 {out_trade_no} 失败{e})returnFalse, None, None# 支付宝返回的 trade_statustrade_statusresult.get(trade_status,)total_amountresult.get(total_amount,)trade_noresult.get(trade_no,)# 成功状态TRADE_SUCCESS即时到账成功、TRADE_FINISHED交易完成不可退款iftrade_statusin(TRADE_SUCCESS,TRADE_FINISHED): logger.info(f订单 {out_trade_no} 支付成功支付宝交易号{trade_no})returnTrue, trade_no, total_amount else: logger.info(f订单 {out_trade_no} 支付状态为 {trade_status})returnFalse, 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_urlusers:login)def payment_return(request):支付完成后的同步回跳页面增强版验签 主动查询 alipayAliPay(appidsettings.ALIPAY_APPID,app_private_key_stringsettings.ALIPAY_APP_PRIVATE_KEY,alipay_public_key_stringsettings.ALIPAY_PUBLIC_KEY,sign_typeRSA2,debugsettings.ALIPAY_DEBUG,configAliPayConfig(timeout15),)datarequest.GET.dict()signdata.pop(sign, None)# 先验签ifnot sign or not alipay.verify(data, sign): logger.warning(同步回跳验签失败)messages.error(request,支付验证失败请联系客服。)returnredirect(home)out_trade_nodata.get(out_trade_no)# 主动查询支付宝确认支付结果is_success, trade_no, total_amountquery_alipay_order(out_trade_no)try: orderOrder.objects.get(order_noout_trade_no)paymentorder.paymentifis_success:# 支付成功更新订单状态iforder.status0: order.set_status(1)# 待支付 → 待发货ifpayment.status!1: payment.trade_notrade_no payment.status1payment.save(update_fields[trade_no,status,update_time])returnrender(request,payment/pay_success.html,{order:order,trade_no:trade_no,})else:# 支付未成功用户可能中途离开或支付失败returnrender(request,payment/pay_pending.html,{order:order,})except Order.DoesNotExist: messages.error(request,订单不存在。)returnredirect(home)四、改造异步通知视图加入主动查询兜底修改payment_notify视图验签后也执行一次主动查询确保和支付宝状态完全一致csrf_exempt def payment_notify(request):ifrequest.method!POST:returnHttpResponse(Method Not Allowed,status405)alipayAliPay(...)# 同上datarequest.POST.dict()signdata.pop(sign, None)ifnot sign or not alipay.verify(data, sign): logger.error(异步通知验签失败)returnHttpResponse(failure)out_trade_nodata.get(out_trade_no)# 主动查询支付宝确认is_success, trade_no, total_amountquery_alipay_order(out_trade_no)try: orderOrder.objects.select_for_update().get(order_noout_trade_no)ifis_success:iforder.status0: paymentorder.payment payment.trade_notrade_no payment.status1payment.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} 不存在)returnHttpResponse(failure)returnHttpResponse(success)设计思路异步通知和主动查询互为补充。即使通知延迟查询能填补空白即使查询暂时失败通知能最终一致。这叫对账思想。五、订单详情页增加“查询支付状态”按钮用户可能在支付后未看到成功页面进入订单详情页时仍显示“待支付”。我们可以给用户提供一个手动刷新支付状态的按钮。编辑apps/orders/templates/orders/order_detail.html第 19 篇创建在待支付订单上方增加{%iforder.status0%}divclassalert alert-warning该订单尚未支付。ahref{% url payment:payment_go order.pk %}classbtn btn-warning btn-sm ms-3去支付/aahref{% url payment:payment_query order.pk %}classbtn btn-outline-info btn-sm ms-2查询支付状态/a/div{% endif %}新增一个查询视图在apps/payment/views.py中login_required(login_urlusers:login)def payment_query(request, order_id):手动查询订单支付状态 orderget_object_or_404(Order,pkorder_id,userrequest.user)iforder.status!0: messages.info(request,订单已处理。)returnredirect(orders:order_detail,pkorder.pk)is_success, trade_no, _query_alipay_order(order.order_no)ifis_success: paymentorder.payment payment.trade_notrade_no payment.status1payment.save(update_fields[trade_no,status,update_time])order.set_status(1)messages.success(request,支付状态已更新该订单已支付成功。)else: messages.warning(request,该订单尚未支付请尽快完成付款。)returnredirect(orders:order_detail,pkorder.pk)在apps/payment/urls.py中添加路由path(query/int:order_id/, views.payment_query,namepayment_query),六、支付待处理页面模板创建apps/payment/templates/payment/pay_pending.html当支付结果不确定时展示友好提示{% extendsbase.html%}{% block title %}支付处理中{% endblock %}{% block content %}divclassrow justify-content-centerdivclasscol-md-6 text-centerdivclasscard shadow-smdivclasscard-body py-5h1classtext-warning mb-3⏳/h1h3支付处理中/h3pclasstext-muted您的支付正在处理请稍后查看订单状态。/phrpstrong订单号/strong{{order.order_no}}/ppstrong订单金额/strong¥{{order.total_amount|floatformat:2}}/pahref{% url payment:payment_query order.pk %}classbtn btn-primary mt-2刷新支付状态/aahref{% url orders:order_detail order.pk %}classbtn btn-outline-secondary mt-2查看订单/a/div/div/div/div{% endblock %}七、订单状态流转日志可选但推荐为方便追踪每一笔订单的状态变化我们可以在模型中增加一个日志记录。简单起见我们使用 Django 的logging模块记录关键状态变更。在apps/orders/models.py的Order.set_status方法中增加日志importlogging loggerlogging.getLogger(orders)def set_status(self, new_status):ifnot self.can_transition_to(new_status): raise ValueError(f不允许从状态 {self.status} 转换到 {new_status})old_statusself.status self.statusnew_statusifnew_status1: self.pay_timetimezone.now()self.save(update_fields[status,update_time,pay_time])logger.info(f订单 {self.order_no} 状态变更{old_status} → {new_status})这样在订单状态改变时控制台会输出类似[27/May/202610: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 runserver9.1 正常支付流程访问订单详情页点击“去支付”跳转支付宝沙箱。使用沙箱买家账号支付支付成功后自动回跳到payment_return。终端输出[27/May/202610:15:30]INFO[payment]订单 20260527101530X7K9M2 支付成功支付宝交易号2026052722001412345678901234[27/May/202610:15:30]INFO[orders]订单 20260527101530X7K9M2 状态变更0 →1[27/May/202610:15:30]GET /payment/return/?... HTTP/1.12005432页面显示“支付成功”。9.2 模拟支付后未回跳支付成功后立刻关闭浏览器模拟未触发 return_url。重新打开浏览器登录进入“我的订单”第 22 篇才会做列表目前直接访问订单详情。如果异步通知已到达需内网穿透订单状态为“待发货”。如果异步通知延迟点击“查询支付状态”按钮主动触发查询。终端输出[27/May/202610:20:00]INFO[payment]订单 20260527101530X7K9M2 支付成功支付宝交易号2026052722001412345678901234[27/May/202610:20:00]INFO[orders]订单 20260527101530X7K9M2 状态变更0 →1[27/May/202610:20:00]GET /payment/query/1/ HTTP/1.13020订单状态更新为“待发货”。9.3 支付失败或中途取消在支付宝收银台点击“取消支付”或关闭页面回跳到pay_pending.html显示“支付处理中”。9.4 防重复支付测试在订单已支付的情况下再次访问/payment/go/1/视图会判断order.status ! 0提示“该订单当前状态为「待发货」无法支付”并跳回订单详情。终端输出[27/May/202610:25:00]GET /payment/go/1/ HTTP/1.13020十、总结与下集预告今天我们为支付加上了“双保险”——主动查询与异步通知相结合确保支付结果不漏网实现了支付宝订单查询功能query_alipay_order改造同步回跳和异步通知均加入主动查询兜底在订单详情页提供“查询支付状态”按钮用户手动触发增加了状态流转日志方便追踪问题为后续 Celery 定时扫描未支付订单做好了铺垫第 23 篇会实现超时未支付自动取消。现在支付体系已经相当稳健。下一步我们要让用户能方便地管理自己的订单。第 22 篇我将实现“我的订单”列表与订单详情按状态分类展示支持查看物流模拟、确认收货等操作。想了解更多也可以去其它平台搜索「IT策士」一起升级 IT 思维 本文为《Django 从 0 到 1 打造完整电商平台》系列第 21 篇作者IT策士未经授权禁止转载。
Django 从 0 到 1 打造完整电商平台:支付结果处理与订单状态更新
IT策士 10余年一线大厂经验专注 IT 思维、架构、职场进阶。我也会在其它平台持续发布最新文章助你少走弯路。上一篇我们集成了支付宝沙箱支付用户从下单到付款整个交易链路已经跑通。但你有没有想过一个问题如果用户付了钱但异步通知因为网络波动没收到订单状态岂不是永远卡在“待支付”或者更糟用户支付后关了浏览器同步回跳根本没触发怎么办今天我们就来解决这些“支付后的麻烦事”。核心思路是不依赖单一回调主动查询 被动接收双管齐下确保支付结果万无一失。同时完善订单状态流转日志为后续的定时任务Celery打下基础。一、支付结果的不确定性分析在第 20 篇中我们使用了两个回调但异步通知也存在问题本地开发环境支付宝无法访问127.0.0.1必须依赖内网穿透网络抖动可能导致通知延迟几分钟甚至更久极端情况下支付宝重试机制也可能失败。因此一个健壮的支付系统应该具备主动查询能力——在同步回跳时、用户手动刷新订单页时主动向支付宝发起查询确认这笔订单的真实支付状态。二、实现支付宝订单查询python-alipay-sdk提供了api_alipay_trade_query方法可根据商户订单号查询交易状态。在apps/payment/views.py中添加查询函数from alipayimportAliPay, AliPayConfig from django.confimportsettingsimportlogging loggerlogging.getLogger(payment)def query_alipay_order(out_trade_no): 查询支付宝订单支付状态 返回(支付成功: bool, 支付宝交易号: str|None, 支付金额: str|None) alipayAliPay(appidsettings.ALIPAY_APPID,app_private_key_stringsettings.ALIPAY_APP_PRIVATE_KEY,alipay_public_key_stringsettings.ALIPAY_PUBLIC_KEY,sign_typeRSA2,debugsettings.ALIPAY_DEBUG,configAliPayConfig(timeout15),)try: resultalipay.api_alipay_trade_query(out_trade_noout_trade_no)except Exception as e: logger.error(f查询支付宝订单 {out_trade_no} 失败{e})returnFalse, None, None# 支付宝返回的 trade_statustrade_statusresult.get(trade_status,)total_amountresult.get(total_amount,)trade_noresult.get(trade_no,)# 成功状态TRADE_SUCCESS即时到账成功、TRADE_FINISHED交易完成不可退款iftrade_statusin(TRADE_SUCCESS,TRADE_FINISHED): logger.info(f订单 {out_trade_no} 支付成功支付宝交易号{trade_no})returnTrue, trade_no, total_amount else: logger.info(f订单 {out_trade_no} 支付状态为 {trade_status})returnFalse, 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_urlusers:login)def payment_return(request):支付完成后的同步回跳页面增强版验签 主动查询 alipayAliPay(appidsettings.ALIPAY_APPID,app_private_key_stringsettings.ALIPAY_APP_PRIVATE_KEY,alipay_public_key_stringsettings.ALIPAY_PUBLIC_KEY,sign_typeRSA2,debugsettings.ALIPAY_DEBUG,configAliPayConfig(timeout15),)datarequest.GET.dict()signdata.pop(sign, None)# 先验签ifnot sign or not alipay.verify(data, sign): logger.warning(同步回跳验签失败)messages.error(request,支付验证失败请联系客服。)returnredirect(home)out_trade_nodata.get(out_trade_no)# 主动查询支付宝确认支付结果is_success, trade_no, total_amountquery_alipay_order(out_trade_no)try: orderOrder.objects.get(order_noout_trade_no)paymentorder.paymentifis_success:# 支付成功更新订单状态iforder.status0: order.set_status(1)# 待支付 → 待发货ifpayment.status!1: payment.trade_notrade_no payment.status1payment.save(update_fields[trade_no,status,update_time])returnrender(request,payment/pay_success.html,{order:order,trade_no:trade_no,})else:# 支付未成功用户可能中途离开或支付失败returnrender(request,payment/pay_pending.html,{order:order,})except Order.DoesNotExist: messages.error(request,订单不存在。)returnredirect(home)四、改造异步通知视图加入主动查询兜底修改payment_notify视图验签后也执行一次主动查询确保和支付宝状态完全一致csrf_exempt def payment_notify(request):ifrequest.method!POST:returnHttpResponse(Method Not Allowed,status405)alipayAliPay(...)# 同上datarequest.POST.dict()signdata.pop(sign, None)ifnot sign or not alipay.verify(data, sign): logger.error(异步通知验签失败)returnHttpResponse(failure)out_trade_nodata.get(out_trade_no)# 主动查询支付宝确认is_success, trade_no, total_amountquery_alipay_order(out_trade_no)try: orderOrder.objects.select_for_update().get(order_noout_trade_no)ifis_success:iforder.status0: paymentorder.payment payment.trade_notrade_no payment.status1payment.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} 不存在)returnHttpResponse(failure)returnHttpResponse(success)设计思路异步通知和主动查询互为补充。即使通知延迟查询能填补空白即使查询暂时失败通知能最终一致。这叫对账思想。五、订单详情页增加“查询支付状态”按钮用户可能在支付后未看到成功页面进入订单详情页时仍显示“待支付”。我们可以给用户提供一个手动刷新支付状态的按钮。编辑apps/orders/templates/orders/order_detail.html第 19 篇创建在待支付订单上方增加{%iforder.status0%}divclassalert alert-warning该订单尚未支付。ahref{% url payment:payment_go order.pk %}classbtn btn-warning btn-sm ms-3去支付/aahref{% url payment:payment_query order.pk %}classbtn btn-outline-info btn-sm ms-2查询支付状态/a/div{% endif %}新增一个查询视图在apps/payment/views.py中login_required(login_urlusers:login)def payment_query(request, order_id):手动查询订单支付状态 orderget_object_or_404(Order,pkorder_id,userrequest.user)iforder.status!0: messages.info(request,订单已处理。)returnredirect(orders:order_detail,pkorder.pk)is_success, trade_no, _query_alipay_order(order.order_no)ifis_success: paymentorder.payment payment.trade_notrade_no payment.status1payment.save(update_fields[trade_no,status,update_time])order.set_status(1)messages.success(request,支付状态已更新该订单已支付成功。)else: messages.warning(request,该订单尚未支付请尽快完成付款。)returnredirect(orders:order_detail,pkorder.pk)在apps/payment/urls.py中添加路由path(query/int:order_id/, views.payment_query,namepayment_query),六、支付待处理页面模板创建apps/payment/templates/payment/pay_pending.html当支付结果不确定时展示友好提示{% extendsbase.html%}{% block title %}支付处理中{% endblock %}{% block content %}divclassrow justify-content-centerdivclasscol-md-6 text-centerdivclasscard shadow-smdivclasscard-body py-5h1classtext-warning mb-3⏳/h1h3支付处理中/h3pclasstext-muted您的支付正在处理请稍后查看订单状态。/phrpstrong订单号/strong{{order.order_no}}/ppstrong订单金额/strong¥{{order.total_amount|floatformat:2}}/pahref{% url payment:payment_query order.pk %}classbtn btn-primary mt-2刷新支付状态/aahref{% url orders:order_detail order.pk %}classbtn btn-outline-secondary mt-2查看订单/a/div/div/div/div{% endblock %}七、订单状态流转日志可选但推荐为方便追踪每一笔订单的状态变化我们可以在模型中增加一个日志记录。简单起见我们使用 Django 的logging模块记录关键状态变更。在apps/orders/models.py的Order.set_status方法中增加日志importlogging loggerlogging.getLogger(orders)def set_status(self, new_status):ifnot self.can_transition_to(new_status): raise ValueError(f不允许从状态 {self.status} 转换到 {new_status})old_statusself.status self.statusnew_statusifnew_status1: self.pay_timetimezone.now()self.save(update_fields[status,update_time,pay_time])logger.info(f订单 {self.order_no} 状态变更{old_status} → {new_status})这样在订单状态改变时控制台会输出类似[27/May/202610: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 runserver9.1 正常支付流程访问订单详情页点击“去支付”跳转支付宝沙箱。使用沙箱买家账号支付支付成功后自动回跳到payment_return。终端输出[27/May/202610:15:30]INFO[payment]订单 20260527101530X7K9M2 支付成功支付宝交易号2026052722001412345678901234[27/May/202610:15:30]INFO[orders]订单 20260527101530X7K9M2 状态变更0 →1[27/May/202610:15:30]GET /payment/return/?... HTTP/1.12005432页面显示“支付成功”。9.2 模拟支付后未回跳支付成功后立刻关闭浏览器模拟未触发 return_url。重新打开浏览器登录进入“我的订单”第 22 篇才会做列表目前直接访问订单详情。如果异步通知已到达需内网穿透订单状态为“待发货”。如果异步通知延迟点击“查询支付状态”按钮主动触发查询。终端输出[27/May/202610:20:00]INFO[payment]订单 20260527101530X7K9M2 支付成功支付宝交易号2026052722001412345678901234[27/May/202610:20:00]INFO[orders]订单 20260527101530X7K9M2 状态变更0 →1[27/May/202610:20:00]GET /payment/query/1/ HTTP/1.13020订单状态更新为“待发货”。9.3 支付失败或中途取消在支付宝收银台点击“取消支付”或关闭页面回跳到pay_pending.html显示“支付处理中”。9.4 防重复支付测试在订单已支付的情况下再次访问/payment/go/1/视图会判断order.status ! 0提示“该订单当前状态为「待发货」无法支付”并跳回订单详情。终端输出[27/May/202610:25:00]GET /payment/go/1/ HTTP/1.13020十、总结与下集预告今天我们为支付加上了“双保险”——主动查询与异步通知相结合确保支付结果不漏网实现了支付宝订单查询功能query_alipay_order改造同步回跳和异步通知均加入主动查询兜底在订单详情页提供“查询支付状态”按钮用户手动触发增加了状态流转日志方便追踪问题为后续 Celery 定时扫描未支付订单做好了铺垫第 23 篇会实现超时未支付自动取消。现在支付体系已经相当稳健。下一步我们要让用户能方便地管理自己的订单。第 22 篇我将实现“我的订单”列表与订单详情按状态分类展示支持查看物流模拟、确认收货等操作。想了解更多也可以去其它平台搜索「IT策士」一起升级 IT 思维 本文为《Django 从 0 到 1 打造完整电商平台》系列第 21 篇作者IT策士未经授权禁止转载。