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 核对方法签名和响应结构。
备注:内容仅供参考。
