PAYATHON 2026

SecurePay 无响应并触发 PHP non-object 错误

支付老李

结论

$this->ResponseTree->Status->statusCode 本身不是问题所在。错误出现在更早的一步:_Dispatch() 没有返回预期的 XML,导致:

simplexml_load_string($this->ResponseXml)

返回 false。此时 $this->ResponseTree 不是对象,继续访问 ->Status->statusCode,就会触发 Trying to get property of non-object。

应先检查 SecurePay 返回的原始内容和传输层错误,再解析 XML。常见原因有 SecurePay 服务异常、测试端点变化、TLS 或 CA 证书问题、DNS 或防火墙限制、请求超时,以及服务器返回 HTML 错误页而非 XML。

先修正错误处理

当前的 TestConnection() 假定响应一定是有效 XML,这会掩盖真正的通信错误。可以改成:

function TestConnection()
{
    $this->RequestXml = $this->_ComposeEcho();
    $this->ResponseXml = $this->_Dispatch($this->RequestXml);

    if (!is_string($this->ResponseXml) || trim($this->ResponseXml) === '') {
        log_message('error', 'SecurePay returned an empty response');
        return false;
    }

    libxml_use_internal_errors(true);
    $this->ResponseTree = simplexml_load_string($this->ResponseXml);

    if ($this->ResponseTree === false) {
        $errors = array();

        foreach (libxml_get_errors() as $error) {
            $errors[] = trim($error->message);
        }

        libxml_clear_errors();

        log_message(
            'error',
            'SecurePay returned invalid XML: ' . implode('; ', $errors)
        );

        return false;
    }

    if (
        !isset($this->ResponseTree->Status) ||
        !isset($this->ResponseTree->Status->statusCode)
    ) {
        log_message('error', 'SecurePay response does not contain Status/statusCode');
        return false;
    }

    return (
        $this->_TranslateServerCode(
            (string) $this->ResponseTree->Status->statusCode
        ) === SECUREPAY_STATUS_OK
    );
}

调用处也要检查连接测试的返回值:

$sp = new SecurePay('ABC0001', 'abc123', true);
$sp->TestMode();

if (!$sp->TestConnection()) {
    die('Unable to communicate with SecurePay');
}

这项修改不能恢复网络连接,但可以避免二次报错,同时保留真正的故障线索。

检查 _Dispatch() 的传输结果

如果 _Dispatch() 使用 cURL,应记录这些信息:

  • curl_errno() 和 curl_error()
  • HTTP 状态码
  • 实际请求的 URL
  • 请求耗时以及是否超时
  • 响应的 Content-Type
  • 原始响应是空内容、XML、HTML,还是代理服务器错误页

可以加入以下诊断逻辑:

$response = curl_exec($ch);

if ($response === false) {
    $errorNumber = curl_errno($ch);
    $errorMessage = curl_error($ch);

    log_message(
        'error',
        'SecurePay cURL error ' . $errorNumber . ': ' . $errorMessage
    );

    curl_close($ch);
    return false;
}

$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);

log_message(
    'error',
    'SecurePay HTTP status: ' . $httpCode .
    '; Content-Type: ' . $contentType
);

curl_close($ch);

return $response;

如果运行的是较老版本的 PHP,还需根据实际环境确认是否支持 CURLINFO_CONTENT_TYPE。

不同结果对应的排查方向也不同:

  • HTTP 200 但响应为空:检查中间代理、服务端是否关闭了连接,以及库中的读取逻辑。
  • HTTP 301、302:端点可能发生了跳转。支付接口通常不应盲目跟随跳转。
  • HTTP 401、403:检查商户凭据、IP 白名单,以及当前使用的环境是否匹配。
  • HTTP 404、410:检查接口 URL 是否已经变更或停用。
  • HTTP 500、502、503、504:可能是网关服务或网络中间层发生故障。
  • 响应以 <html 开头:返回的不是 SecurePay XML,而是 Web 服务器、代理或防火墙生成的错误页。
  • cURL 报告证书或握手错误:检查 TLS、CA 证书和系统时间。

逐项排查

1. 保存原始响应

可以在非生产环境中检查:

var_dump($sp->ResponseXml);

生产环境中不要把响应直接输出给用户。可以记录响应长度、HTTP 状态和脱敏后的内容。响应为空,说明问题发生在 XML 解析之前;响应有内容,则要确认它是不是 SecurePay 预期的 XML。

2. 核对测试与生产环境

示例先传入了:

new SecurePay('ABC0001', 'abc123', true);

随后又调用:

$sp->TestMode();

需要查看该库对第三个参数和 TestMode() 的定义,确认两者不会把端点或凭据切换到不同环境。测试账号通常不能直接用于生产端点,生产账号同样不能直接用于测试端点。

3. 检查 TLS 和 CA 证书

如果程序之前正常、后来突然停止工作,原因经常与服务器环境或远端证书变化有关,例如:

  • 本机 CA 证书包已经过期;
  • PHP、cURL 或 OpenSSL 版本太旧,不支持服务端要求的 TLS 配置;
  • 服务器系统时间不正确;
  • 主机商修改了出站 HTTPS 策略;
  • 中间代理替换了证书,导致校验失败。

不要用以下设置长期绕过证书错误:

curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

关闭证书校验会降低支付连接的安全性。应更新 CA 证书、PHP、cURL 或 OpenSSL,或者修复代理使用的证书链。

4. 检查网络限制

需要确认运行 PHP 的服务器能够访问库中配置的 SecurePay 主机。开发者自己的电脑可以访问,并不代表服务器也可以访问。重点检查:

  • DNS 能否正常解析;
  • 出站 443 端口是否开放;
  • 防火墙、WAF 或代理是否拦截请求;
  • SecurePay 是否要求配置 IP 白名单;
  • 请求能否在超时时间内完成。

5. 核对当前接口端点

查看 securepay.php 中 _Dispatch() 最终使用的 URL,并与当前 SecurePay 接口文档或商户后台配置进行核对。

仅凭这个 PHP Notice,无法判断 SecurePay 是否修改了端点,或者是否已经停用某个旧接口。还要结合 HTTP 状态码、cURL 错误和官方服务状态判断。

示例数据中的另一个问题

示例使用的有效期是:

$sp->ExpiryDate = '07/20';

该日期已经过期,即使连接正常,也不应将其用于实际交易。不过,它无法解释 TestConnection() 中的 non-object 错误,因为连接测试发生在设置卡号和有效期之前。

日志中也不要记录完整卡号、CVV、密码或未经脱敏的支付 XML。尤其不能持久化保存 CVV。

最终处理顺序

先让 _Dispatch() 输出 cURL 错误和 HTTP 状态,再检查原始响应是否为空,或者是否根本不是 XML。接着核对端点、测试与生产环境、TLS 证书,以及服务器的出站网络。最后,为 simplexml_load_string()、Status 和 statusCode 保留防御性检查。

只有修复导致响应无效的通信问题,SecurePay 调用才能恢复。单独在第 479 行增加 isset() 只能消除 Notice,无法解决支付网关没有正确返回数据的问题。

备注:内容仅供参考。