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,无法解决支付网关没有正确返回数据的问题。
备注:内容仅供参考。
