PAYATHON 2026

Django 使用 zeep 时出现 AttributeError

支付阿杰

结论

当前的 client 指向 zeep.client 模块,并不是通过 WSDL 创建的 zeep.Client 实例。模块没有 service 属性,所以会报错:

AttributeError: module 'zeep.client' has no attribute 'service'

需要先实例化 Client,再通过实例的 service 调用 SOAP 方法。

正确初始化 zeep

原来的导入方式可能类似:

from zeep import client

这里的 client 是模块。可以改为:

from zeep import Client

soap_client = Client(wsdl=PAYMENT_WSDL)

调用方法时使用这个实例:

result = soap_client.service.PaymentVerification(
    MERCHANT,
    authority,
    amount,
)

也可以这样写:

import zeep

soap_client = zeep.Client(wsdl=PAYMENT_WSDL)

service 属于加载了 WSDL 的客户端实例,不属于 zeep.client 模块,也不属于尚未实例化的 Client 类。

Django 中的完整示例

from django.conf import settings
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render
from zeep import Client

from . import models


PAYMENT_WSDL = settings.PAYMENT_WSDL
MERCHANT = settings.PAYMENT_MERCHANT

soap_client = Client(wsdl=PAYMENT_WSDL)


def callback(request):
    callback_status = request.GET.get('Status')

    # 这里应根据支付网关文档判断成功状态。
    # 如果 NOK 表示失败,则不应该在这个分支中执行支付验证。
    if callback_status != 'OK':
        return HttpResponse('Payment was not successful.', status=400)

    authority = request.GET.get('Authority')
    if not authority:
        return HttpResponse('Missing authority.', status=400)

    invoice = get_object_or_404(
        models.Invoice,
        authority=authority,
    )

    amount = sum(
        item.product_cost
        for item in models.OrderItem.objects.filter(order=invoice.order)
    )

    try:
        result = soap_client.service.PaymentVerification(
            MERCHANT,
            authority,
            amount,
        )
    except Exception:
        # 实际项目中应通过 logging.exception() 记录完整异常,
        # 不要把网关或服务器的异常详情直接返回给用户。
        return HttpResponse(
            'Payment verification failed.',
            status=502,
        )

    if result.Status == 100:
        return render(
            request,
            'callback.html',
            {'invoice': invoice},
        )

    return HttpResponse(
        'Payment verification failed: ' + str(result.Status),
        status=400,
    )

Status、Authority、成功状态值和 SOAP 参数顺序,都要以实际支付网关文档及 WSDL 为准。原代码使用了小写的 authority,但部分网关返回的参数名可能是 Authority。Django 的 request.GET 参数名区分大小写。

如果 WSDL 要求命名参数

有些 WSDL 生成的方法签名要求传入字段名,不能使用位置参数。例如:

result = soap_client.service.PaymentVerification(
    MerchantID=MERCHANT,
    Authority=authority,
    Amount=amount,
)

字段名必须与 WSDL 中的定义完全一致,不要根据示例猜测名称或大小写。可以通过客户端检查操作签名:

for service in soap_client.wsdl.services.values():
    for port in service.ports.values():
        operations = port.binding._operations
        for operation_name, operation in operations.items():
            print(operation_name, operation.input.signature())

如果 WSDL 定义了多个 service 或 port,也可以显式绑定:

payment_service = soap_client.bind(
    'ServiceName',
    'PortName',
)

result = payment_service.PaymentVerification(
    MERCHANT,
    authority,
    amount,
)

ServiceName 和 PortName 也需要从实际 WSDL 中获取。

还需要检查的两个问题

回调状态判断可能写反

原代码只在以下条件成立时执行验证:

request.GET.get('Status') == 'NOK'

按照不少支付接口的惯例,NOK 表示支付失败,成功回调可能使用 OK。这个问题不会导致当前的 zeep 报错,但修复 client 后,它仍可能让支付验证无法正常完成。

具体状态值要以当前支付网关的接口文档为准。

不要只依赖回调参数确认付款

不能单独根据回调中的 Status、Authority 或金额判断支付成功。服务器仍需调用 PaymentVerification,并检查:

  • 订单是否存在且尚未支付;
  • 验证结果是否为网关规定的成功状态;
  • 验证金额是否与订单最终应付金额一致;
  • 同一笔交易是否已经处理过;
  • 返回值是对象、字典还是整数。

有些 WSDL 会直接返回整数状态码,而不是带有 Status 属性的对象。如果实际返回值是整数,应写成:

if result == 100:
    ...

不能写成:

if result.Status == 100:
    ...

当前异常可以直接通过创建 Client(wsdl=...) 实例解决。之后还要根据实际 WSDL 核对方法签名和响应结构。

备注:内容仅供参考。