Django本地沙箱中支付宝未回调notify_url
明确结论
Postman 能访问 notify_url,说明公网地址、Django 路由和视图基本可用,但不能证明支付宝在创建支付订单时收到了这个地址。
最常见的原因是:settings.py 中虽然定义了 notify_url,创建支付宝支付请求时却没有把它作为 notify_url 参数传给支付宝。Django 配置项只是本地变量,支付宝不会主动读取。
另外,return_url 和 notify_url 的用途不同:
return_url:支付完成后,让用户的浏览器跳回网站,通常使用 GET 请求。notify_url:支付宝服务器主动向商户服务器发送异步 POST 请求。- 用户已经跳回网站,不代表异步通知一定送达。
- 只要下单时正确传递了
notify_url,用户关闭支付结果页也不会影响异步通知。
首先检查创建支付订单的代码
例如,在配置中定义:
# settings.py
ALIPAY_NOTIFY_URL = "https://example.cpolar.cn/alipay/notify/"
ALIPAY_RETURN_URL = "https://example.cpolar.cn/alipay/return/"
只有这段配置还不够。创建支付页面时,代码必须明确使用这些配置。
不同支付宝 SDK 的参数位置可能不同。以下是常见的 python-alipay-sdk 调用方式:
from django.conf import settings
from alipay import AliPay
alipay = AliPay(
appid=settings.ALIPAY_APP_ID,
app_notify_url=settings.ALIPAY_NOTIFY_URL,
app_private_key_string=settings.ALIPAY_APP_PRIVATE_KEY,
alipay_public_key_string=settings.ALIPAY_PUBLIC_KEY,
sign_type="RSA2",
debug=True, # 沙箱环境
)
也可以在调用支付接口时显式传递:
order_string = alipay.api_alipay_trade_page_pay(
out_trade_no=order.order_no,
total_amount=str(order.amount),
subject=order.product_name,
return_url=settings.ALIPAY_RETURN_URL,
notify_url=settings.ALIPAY_NOTIFY_URL,
)
然后跳转到沙箱网关:
pay_url = "https://openapi-sandbox.dl.alipaydev.com/gateway.do?" + order_string
具体网关地址和参数写法应以当前 SDK 的文档为准。需要确认的不是 URL 有没有写进 settings.py,而是它最终有没有进入提交给支付宝的支付请求。
可以直接查看生成的支付 URL 或请求参数,检查是否包含:
notify_url=https%3A%2F%2Fexample.cpolar.cn%2Falipay%2Fnotify%2F
如果支付请求中没有 notify_url,支付宝就不知道应该向哪里发送回调。
Django 回调视图的正确写法
支付宝的异步通知不会携带 Django CSRF Token,因此回调接口通常需要排除 CSRF 校验:
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def alipay_notify(request):
print("111111")
if request.method != "POST":
return HttpResponse("method not allowed", status=405)
notify_data = request.POST.dict()
print(notify_data)
# 实际项目中必须继续完成:
# 1. 验证支付宝签名
# 2. 校验 app_id、out_trade_no、total_amount、seller_id
# 3. 判断 trade_status
# 4. 幂等更新订单状态
return HttpResponse("success")
路由可以这样配置:
from django.urls import path
from . import views
urlpatterns = [
path("alipay/notify/", views.alipay_notify, name="alipay_notify"),
]
成功处理支付宝通知后,接口必须返回纯文本:
success
不要返回 JSON、HTML 页面或带引号的 "success"。如果返回内容不符合要求,支付宝通常会按通知机制重试。不过,即使后续业务校验失败,只要请求确实进入了视图,开头的 print("111111") 也应该执行。
为什么 Postman 正常,支付宝却没有回调
Postman 和支付宝验证的不是同一条请求链路。
Postman 的请求链路是:
Postman → cpolar → Django
支付宝异步通知的链路是:
创建支付请求
→ 支付宝是否收到 notify_url
→ 支付订单是否进入可通知状态
→ 支付宝服务器访问 cpolar
→ Django
所以,Postman 请求成功只能排除其中一部分问题。如果支付宝没有进入视图,可以按以下顺序检查。
1. notify_url 是否真正提交给支付宝
这一项应最先检查。常见问题包括:
- 只在
settings.py中定义了地址,没有传给 SDK。 - 创建支付请求时只传了
return_url。 - 参数拼写错误,例如写成
nofity_url。 - 页面调用的下单接口没有使用预期配置。
- 修改配置后,Django 服务没有重新加载。
- SDK 初始化时使用了旧地址,接口调用时又换成了另一个实例。
- 三个商品按钮分别调用了不同的下单逻辑,其中部分逻辑没有传入
notify_url。
可以在生成支付参数的位置打印实际值:
print("notify_url:", settings.ALIPAY_NOTIFY_URL)
print("order_string:", order_string)
检查重点应放在最终请求参数上,而不是只看配置文件。
2. 沙箱环境是否完全一致
以下配置必须来自同一套沙箱应用:
- 沙箱
app_id - 应用私钥
- 支付宝沙箱公钥
- 沙箱网关
- 沙箱买家账号
例如,使用沙箱 app_id 请求正式网关,或者误用了正式环境公钥,都可能引发异常。支付页面可以打开,也不能说明所有配置都正确。还需要检查支付宝侧的订单和接口调用记录。
3. 回调地址在支付期间是否一直有效
cpolar 临时域名可能会在隧道重启后发生变化。需要逐项确认:
- 下单时提交的地址与当前地址完全一致。
- 支付完成时,cpolar 隧道仍处于在线状态。
- 域名可以从公网访问。
- URL 不依赖登录状态。
- URL 不会重定向到登录页或其他地址。
- HTTP 与 HTTPS 之间没有异常跳转。
- 使用 HTTPS 时,证书有效。
- URL 中没有使用
localhost、127.0.0.1或内网 IP。
最好使用固定域名,以免每次启动内网穿透后地址发生变化。
4. Django 是否在进入视图前拒绝了请求
请求可能已经到达 Django,但被中间件拦截,没有进入视图,所以控制台不会打印 111111。首先检查:
ALLOWED_HOSTS = [
"example.cpolar.cn",
]
开发阶段可以临时验证问题是否与 Host 校验有关:
ALLOWED_HOSTS = ["*"]
确认问题后,应恢复为明确的域名列表。生产环境不宜长期使用 "*"。
还需要检查:
- CSRF 是否返回
403。 DisallowedHost是否返回400。- 路由是否触发了
301或302。 - Web 服务器或安全软件是否拦截了外部 POST 请求。
- cpolar 请求日志中是否有支付宝的访问记录。
如果 cpolar 日志中有请求,但 Django 控制台没有打印,问题通常出在反向代理、Host 校验、CSRF 或路由层。如果 cpolar 日志中完全没有请求,应先检查下单参数和支付宝侧的通知记录。
5. URL 尾部斜杠是否一致
例如,路由定义为:
path("alipay/notify/", views.alipay_notify)
提交给支付宝的地址最好也带上尾部斜杠:
https://example.cpolar.cn/alipay/notify/
不要依赖 Django 自动重定向下面这个 POST 请求:
https://example.cpolar.cn/alipay/notify
POST 请求经过 301 或 302 重定向后,请求方法和请求体可能无法按预期保留。
推荐的排查顺序
- 打印最终生成的支付宝支付请求参数。
- 确认参数中包含正确的
notify_url。 - 确认
notify_url与当前 cpolar 公网地址完全一致。 - 查看 cpolar 控制台是否收到支付宝发来的 POST 请求。
- 查看 Django 访问日志中的状态码,不要只看
print。 - 为回调视图添加
@csrf_exempt。 - 检查
ALLOWED_HOSTS和 URL 尾部斜杠。 - 检查沙箱
app_id、密钥、公钥和网关是否配套。 - 查看支付宝开放平台中是否有对应的交易和异步通知记录。
回调处理还需要注意安全性
确认能够收到通知后,不能只根据 trade_status 更新订单。至少要完成以下校验:
notify_data = request.POST.dict()
signature = notify_data.pop("sign", None)
verified = alipay.verify(notify_data, signature)
if not verified:
return HttpResponse("failure", status=400)
if notify_data.get("app_id") != settings.ALIPAY_APP_ID:
return HttpResponse("failure", status=400)
if notify_data.get("trade_status") not in {
"TRADE_SUCCESS",
"TRADE_FINISHED",
}:
return HttpResponse("success")
还要根据 out_trade_no 查询本地订单,并核对:
total_amountseller_id- 订单是否已经支付
- 支付宝交易号
trade_no
异步通知可能重复发送,因此订单更新逻辑必须具备幂等性。
根据目前的现象,应该先检查创建支付请求的代码,确认 settings 中的地址不只是完成了定义,还确实通过 notify_url 参数提交给了支付宝。Postman 测试成功不能替代这项检查。
备注:内容仅供参考。
