PAYATHON 2026

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 重定向后,请求方法和请求体可能无法按预期保留。

推荐的排查顺序

  1. 打印最终生成的支付宝支付请求参数。
  2. 确认参数中包含正确的 notify_url。
  3. 确认 notify_url 与当前 cpolar 公网地址完全一致。
  4. 查看 cpolar 控制台是否收到支付宝发来的 POST 请求。
  5. 查看 Django 访问日志中的状态码,不要只看 print。
  6. 为回调视图添加 @csrf_exempt。
  7. 检查 ALLOWED_HOSTS 和 URL 尾部斜杠。
  8. 检查沙箱 app_id、密钥、公钥和网关是否配套。
  9. 查看支付宝开放平台中是否有对应的交易和异步通知记录。

回调处理还需要注意安全性

确认能够收到通知后,不能只根据 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_amount
  • seller_id
  • 订单是否已经支付
  • 支付宝交易号 trade_no

异步通知可能重复发送,因此订单更新逻辑必须具备幂等性。

根据目前的现象,应该先检查创建支付请求的代码,确认 settings 中的地址不只是完成了定义,还确实通过 notify_url 参数提交给了支付宝。Postman 测试成功不能替代这项检查。

备注:内容仅供参考。