PAYATHON 2026

Django后端无法调用AlipayTradePagePayModel

支付小周

结论

AlipayTradePagePayModel 是封装业务参数的数据模型,不能直接用于调用支付接口。网页支付需要创建 AlipayTradePagePayRequest,把 AlipayTradePagePayModel 赋给请求对象的 biz_model,再通过 AlipayClient.page_execute() 或当前 SDK 提供的等价方法生成支付页面。

目前没有具体代码和报错文本,无法确定唯一原因。排查时可以先检查以下内容:

  • 安装的支付宝 Python SDK 是否与示例代码使用的是同一个库
  • AlipayTradePagePayModel 的导入路径是否正确
  • 是否误把 Model 当作 Request 执行
  • 沙箱网关、应用 ID、支付宝公钥和应用私钥是否配套
  • out_trade_no、total_amount、product_code 等参数是否符合要求

正确的调用关系

支付宝电脑网站支付通常涉及三个对象:

AlipayClient
    └── AlipayTradePagePayRequest
            └── AlipayTradePagePayModel

它们各自负责不同的工作:

  • AlipayTradePagePayModel 保存订单号、金额、标题等业务参数。
  • AlipayTradePagePayRequest 表示 alipay.trade.page.pay 请求。
  • AlipayClient 负责签名、组装参数并生成支付页面。

因此,下面的写法并不完整:

model = AlipayTradePagePayModel()
client.execute(model)

客户端需要执行 Request,而不是 Model。

先确认使用的是哪个 SDK

Python 中有多个名称相近的支付宝库,但它们的导入路径和调用方式并不通用。先查看当前环境实际安装了哪些包:

python -m pip list | grep -i alipay
python -m pip show alipay-sdk-python

还要确认 Django 运行时使用的 Python,与安装 SDK 时使用的是同一个环境:

python -c "import sys; print(sys.executable)"
python -c "import alipay; print(alipay.__file__)"

如果参考的是官方 alipay-sdk-python 风格的代码,常见导入方式如下:

from alipay.aop.api.AlipayClientConfig import AlipayClientConfig
from alipay.aop.api.DefaultAlipayClient import DefaultAlipayClient
from alipay.aop.api.domain.AlipayTradePagePayModel import AlipayTradePagePayModel
from alipay.aop.api.request.AlipayTradePagePayRequest import AlipayTradePagePayRequest

如果导入时出现 ModuleNotFoundError 或 ImportError,常见原因有:

  1. SDK 没有安装到 Django 当前使用的虚拟环境;
  2. 安装的是另一个同名或名称相近的支付宝库;
  3. 示例代码与已安装的 SDK 版本不匹配。

python-alipay-sdk、alipay-sdk-python 等不同库的示例不能混用。

Django 调用示例

下面是官方 AOP SDK 常见的调用结构。不同版本的 SDK 在方法签名上可能略有差别,具体应以当前安装包提供的接口为准。

from decimal import Decimal, InvalidOperation

from django.conf import settings
from django.http import HttpResponse, JsonResponse
from django.views.decorators.http import require_POST

from alipay.aop.api.AlipayClientConfig import AlipayClientConfig
from alipay.aop.api.DefaultAlipayClient import DefaultAlipayClient
from alipay.aop.api.domain.AlipayTradePagePayModel import AlipayTradePagePayModel
from alipay.aop.api.request.AlipayTradePagePayRequest import AlipayTradePagePayRequest


def create_alipay_client():
    config = AlipayClientConfig()

    # 沙箱环境。部分 SDK 版本的配置字段可能不同。
    config.server_url = "https://openapi-sandbox.dl.alipaydev.com/gateway.do"
    config.app_id = settings.ALIPAY_APP_ID
    config.app_private_key = settings.ALIPAY_APP_PRIVATE_KEY
    config.alipay_public_key = settings.ALIPAY_PUBLIC_KEY

    return DefaultAlipayClient(alipay_client_config=config)


@require_POST
def create_payment(request):
    product_id = request.POST.get("product_id")

    if not product_id:
        return JsonResponse({"message": "缺少 product_id"}, status=400)

    # 必须从数据库读取商品和真实价格,不要采用前端提交的金额。
    try:
        product = Product.objects.get(pk=product_id, is_active=True)
    except Product.DoesNotExist:
        return JsonResponse({"message": "商品不存在"}, status=404)

    try:
        total_amount = Decimal(str(product.price)).quantize(Decimal("0.01"))
    except (InvalidOperation, TypeError):
        return JsonResponse({"message": "商品价格无效"}, status=500)

    # 应使用数据库唯一字段或可靠的订单号生成器。
    order = Order.objects.create(
        product=product,
        amount=total_amount,
        status="pending",
    )

    model = AlipayTradePagePayModel()
    model.out_trade_no = str(order.order_no)
    model.total_amount = format(total_amount, ".2f")
    model.subject = product.name
    model.product_code = "FAST_INSTANT_TRADE_PAY"

    pay_request = AlipayTradePagePayRequest(biz_model=model)
    pay_request.notify_url = settings.ALIPAY_NOTIFY_URL
    pay_request.return_url = settings.ALIPAY_RETURN_URL

    try:
        client = create_alipay_client()
        payment_html = client.page_execute(pay_request, http_method="POST")
    except Exception:
        # 生产环境应通过日志记录完整异常,但不要把私钥等配置返回给前端。
        return JsonResponse({"message": "创建支付宝支付请求失败"}, status=502)

    return HttpResponse(payment_html, content_type="text/html; charset=utf-8")

如果当前 SDK 不接受构造参数 biz_model=model,可以改成属性赋值:

pay_request = AlipayTradePagePayRequest()
pay_request.biz_model = model

应根据已安装 SDK 中 AlipayTradePagePayRequest 的定义选择写法。

如果需要返回支付地址

不同 SDK 可能提供 page_execute()、execute() 或其他方法。有些版本的 page_execute() 返回完整的 HTML 表单,另一些调用方式会生成网关 URL。

如果返回 HTML 表单,可以由 Django 直接响应:

return HttpResponse(payment_html, content_type="text/html; charset=utf-8")

如果返回 URL,可以进行重定向:

from django.shortcuts import redirect

return redirect(payment_url)

在确认返回值类型之前,不能直接把结果当作 JSON 或支付状态处理。

沙箱配置检查

1. 使用沙箱网关

沙箱应用不能使用生产环境网关。网关地址应以当前支付宝开放平台的沙箱配置为准,不要直接复制已经过期的示例。

还需要逐项确认:

  • app_id 是否属于当前沙箱应用
  • 应用私钥是否与上传公钥时使用的密钥对一致
  • alipay_public_key 是否为支付宝公钥,而不是应用公钥
  • 密钥格式是否符合 SDK 的要求

这部分很容易配错。应用私钥、应用公钥和支付宝公钥是三个不同的概念。

2. 检查密钥换行

通过环境变量读取 PEM 私钥时,换行符可能会被保存为字面量 \n:

private_key = settings.ALIPAY_APP_PRIVATE_KEY.replace("\\n", "\n")

只有在确认环境变量采用这种存储方式时才需要转换。已经包含正常换行的密钥不要重复处理。

3. 检查回调地址

notify_url 是支付宝服务器主动请求的异步通知地址,必须能从公网访问。本机地址通常无法直接接收通知,例如:

http://127.0.0.1:8000/alipay/notify/

return_url 是用户完成支付后,浏览器跳转到的地址,不能代替异步通知。

参数检查

电脑网站支付需要重点检查以下字段:

model.out_trade_no = "唯一商户订单号"
model.total_amount = "0.01"
model.subject = "订单标题"
model.product_code = "FAST_INSTANT_TRADE_PAY"

常见问题包括:

  • out_trade_no 重复
  • 金额使用 float 后出现精度问题
  • total_amount 为空、为负数或格式不合法
  • subject 为空
  • product_code 拼写错误
  • 请求对象没有设置 biz_model

金额建议使用 Decimal,最后转换成保留两位小数的字符串:

amount = Decimal("12.30")
model.total_amount = format(amount, ".2f")

根据报错类型定位

ModuleNotFoundError

通常说明包没有安装、使用了错误的虚拟环境,或者导入路径与 SDK 不匹配。

TypeError: 'module' object is not callable

通常是导入了模块,而不是模块中的类。检查是否使用了完整的类导入路径:

from alipay.aop.api.domain.AlipayTradePagePayModel import AlipayTradePagePayModel

AttributeError

如果错误提示客户端没有 page_execute,说明示例代码与当前 SDK 不匹配。此时应检查安装包版本,以及客户端实际提供的方法,不能直接在 execute() 和 page_execute() 之间反复替换。

签名验证失败

先检查以下配置:

  • 应用私钥是否正确
  • 支付宝公钥是否正确
  • 字符集是否为 utf-8
  • 签名算法是否与应用配置一致
  • 是否混用了沙箱与生产环境的密钥或应用 ID

网关返回业务错误

需要记录支付宝返回的完整错误码、sub_code 和 sub_msg。只有“调用失败”这一条信息,无法判断问题出在权限、参数、订单还是签名。

import logging

logger = logging.getLogger(__name__)

try:
    result = client.page_execute(pay_request, http_method="POST")
except Exception:
    logger.exception("支付宝支付请求创建失败")
    raise

日志中不要记录应用私钥、完整签名串或用户敏感信息。

安全与业务注意事项

不能把前端提交的金额直接作为最终支付金额。正确流程如下:

  1. 前端只提交商品 ID、数量等必要信息;
  2. Django 从数据库查询商品价格;
  3. 后端重新计算订单金额;
  4. 先将订单写入数据库,再生成支付宝请求;
  5. 收到异步通知后进行验签;
  6. 验证通知中的订单号、金额、商户身份和支付状态;
  7. 验证通过后更新订单,并保证更新操作具有幂等性。

浏览器跳转到 return_url 并不能证明支付成功。订单的最终状态应以通过验签和业务校验的异步通知为准,也可以由后端主动查询支付结果。

要进一步确定当前报错,还需要提供完整异常堆栈、SDK 包名和版本、相关导入语句及客户端调用代码。私钥、签名、应用密钥等内容必须先脱敏。

备注:内容仅供参考。