Gateway response 在 WordPress 子目录安装中失效
结论
这通常不是 certificate 的问题,也不是因为 WordPress 安装在子目录后无法接收 POST。
更可能的情况是,gateway 提交 callback URL 后,该 URL 又触发了 301、302 或 canonical redirect。比如从根目录跳到子目录、从 HTTP 跳到 HTTPS、自动补上尾斜杠,或者被 WordPress 重定向到规范 URL。许多客户端跟随这类跳转时,会把原来的 POST 改成 GET。等插件代码真正执行时,$_POST 已经是空的。
另一个常见原因是,gateway 的服务器通知地址和用户付款后的返回地址使用了同一个 URL。用户浏览器会通过 GET 访问 callback,插件返回的:
REDIRECT=...
就会直接显示在页面上。REDIRECT= 并不是 HTTP 重定向,而是部分 gateway 约定的响应格式。只有 gateway 的服务器收到并识别这段内容后,才会继续处理跳转。
为什么子目录安装更容易出现这个问题
假设 WordPress 的实际安装地址是:
https://example.com/wordpress/
但提交给 gateway 的 callback 是:
https://example.com/?constrivPayer-api=Gateway_ConsTriv
服务器可能会把它重定向到:
https://example.com/wordpress/?constrivPayer-api=Gateway_ConsTriv
整个请求过程可能变成:
POST /?constrivPayer-api=Gateway_ConsTriv
302 Location: /wordpress/?constrivPayer-api=Gateway_ConsTriv
GET /wordpress/?constrivPayer-api=Gateway_ConsTriv
插件最终收到的是第二个请求,因此:
$_SERVER['REQUEST_METHOD'] === 'GET'
$_POST === []
其他类似情况还有:
http://自动跳转到https://www.example.com跳转到example.com- URL 自动添加或移除尾斜杠
- WordPress 的
redirect_canonical()改写地址 - 反向代理、CDN 或安全插件执行重定向
- callback 被配置成页面 URL,而不是专用通知端点
- gateway 的通知 URL 和用户返回 URL 配置颠倒
303 会明确要求客户端改用 GET。不少客户端处理 301 和 302 时,也会把 POST 改成 GET。callback 需要保留 POST 时,通常应彻底避免跳转,而不是依赖客户端正确处理 307 或 308。
先检查实际请求链
不要只看浏览器最终显示的 URL。gateway 的服务器通知和用户浏览器返回通常是两个相互独立的请求。
先从 Web 服务器访问日志中确认:
- gateway 是否真的发出了 POST
- POST 到达了哪个路径
- 服务器是否返回
301、302、403或404 - 跳转后的请求是否变成 GET
- POST 和浏览器 GET 是否来自不同 IP、不同 User-Agent
- gateway 收到的响应状态和响应正文是什么
也可以用 curl 模拟请求:
curl -i -X POST \
'https://example.com/wordpress/wp-admin/admin-post.php?action=constrivpayer_callback' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'result=CAPTURED&udf1=test&udf2=test&udf4=test'
检查它是直接返回 200,还是先返回 301/302。
如需查看完整跳转链,可以使用:
curl -i -L -X POST \
'https://example.com/wordpress/wp-admin/admin-post.php?action=constrivpayer_callback' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'result=CAPTURED&udf1=test&udf2=test&udf4=test'
不过,curl 的跳转行为未必与 gateway 完全一致。最终仍应以 gateway 日志和服务器访问日志为准。
使用 WordPress 生成 callback URL
不要手工拼接域名和子目录。可以把 WordPress 的 admin-post.php 用作独立的 callback 入口:
$callbackUrl = add_query_arg(
'action',
'constrivpayer_callback',
admin_url('admin-post.php')
);
WordPress 安装在子目录时,它会生成类似下面的地址:
https://example.com/wordpress/wp-admin/admin-post.php?action=constrivpayer_callback
把这个完整地址提交给 gateway,并确保它不会再次触发协议、域名、路径或尾斜杠重定向。
用户付款后的浏览器返回地址需要单独生成:
$returnUrl = home_url('/payment-result/');
两者的用途不同:
- callback/notify URL:gateway 服务器通过 POST 提交交易结果。
- return URL:gateway 将用户浏览器带回网站,通常使用 GET。
不要依赖 return URL 中的数据确认付款成功。最终支付状态应以经过签名验证的服务器 callback,或主动查询 gateway API 得到的结果为准。
更稳妥的 callback 示例
add_action(
'admin_post_nopriv_constrivpayer_callback',
'constrivpayer_handle_callback'
);
add_action(
'admin_post_constrivpayer_callback',
'constrivpayer_handle_callback'
);
function constrivpayer_handle_callback()
{
global $constrivPayer_options;
if (
!isset($_SERVER['REQUEST_METHOD']) ||
$_SERVER['REQUEST_METHOD'] !== 'POST'
) {
status_header(405);
header('Allow: POST');
exit('METHOD_NOT_ALLOWED');
}
$result = isset($_POST['result'])
? sanitize_text_field(wp_unslash($_POST['result']))
: '';
$udf1 = isset($_POST['udf1'])
? sanitize_text_field(wp_unslash($_POST['udf1']))
: '';
$udf2 = isset($_POST['udf2'])
? sanitize_text_field(wp_unslash($_POST['udf2']))
: '';
$udf4 = isset($_POST['udf4'])
? wp_unslash($_POST['udf4'])
: '';
if ($result === 'CAPTURED' || $result === 'APPROVED') {
$expectedSignature = md5(
$udf2 . CONSTRIVPAYER_MD5 . get_option('siteurl')
);
if (
$udf1 !== '' &&
hash_equals($expectedSignature, $udf1)
) {
$decoded = base64_decode($udf4, true);
$paymentData = $decoded !== false
? json_decode($decoded, true)
: null;
if (
is_array($paymentData) &&
isset($paymentData['amount'], $paymentData['payment_id'])
) {
$message = '1&amount=' .
rawurlencode((string) $paymentData['amount']) .
'&id=' .
rawurlencode((string) $paymentData['payment_id']);
} else {
$message = '4';
}
} else {
$message = '2';
}
} elseif (isset($_POST['Error'])) {
$message = '3';
} else {
$message = '4';
}
$pageUrl = $constrivPayer_options['page_url'];
$redirect = 'REDIRECT=' .
$pageUrl .
'?transaction_result&transaction_message=' .
$message;
nocache_headers();
header('Content-Type: text/plain; charset=UTF-8');
status_header(200);
echo $redirect;
exit;
}
这段代码会先检查请求方法以及字段是否存在,避免原代码在 POST 为空时直接访问:
$_POST["result"]
否则,PHP 会产生 undefined array key/undefined index 警告。如果错误信息被写入响应正文,还可能破坏 gateway 要求的精确响应格式。
检查签名中的 siteurl
原代码把下面的值加入了签名:
get_option('siteurl')
在子目录安装中,siteurl 很可能会从:
https://example.com
变成:
https://example.com/wordpress
这不会让 $_POST 变空,但可能导致签名校验失败。
需要确认生成付款请求时参与签名的 URL,与 callback 校验时使用的字符串完全一致,包括:
- HTTP 或 HTTPS
- 域名是否包含
www - 子目录
- 端口
- 尾斜杠
- 大小写和编码方式
如果 gateway 协议没有要求把 WordPress 的 siteurl 加入签名,最好不要自行添加。如果协议确实有此要求,创建付款请求和验证 callback 时应使用同一个已经保存并规范化的值,不要在两个阶段分别临时推导。
还要注意下面两个值的区别:
get_option('siteurl')
get_option('home')
siteurl 表示 WordPress 核心文件所在的位置,home 表示网站对外访问地址。WordPress 位于子目录、首页位于根目录时,两者可以不同。
检查 POST 的 Content-Type
只有请求使用常见的表单编码时,PHP 才会自动填充 $_POST,例如:
application/x-www-form-urlencoded
multipart/form-data
如果 gateway 发送的是 JSON:
application/json
那么 $_POST 为空是正常的。此时需要读取原始请求体:
$rawBody = file_get_contents('php://input');
$payload = json_decode($rawBody, true);
不过,如果同一个 gateway 在 WordPress 安装于根目录时可以正常填充 $_POST,Content-Type 通常不是最先需要怀疑的地方。除非子目录地址使用了不同的代理、WAF 或 callback 配置。
REDIRECT= 与 HTTP 重定向不是一回事
如果 gateway 文档明确要求 callback 返回:
REDIRECT=https://example.com/payment-result/
那就应继续返回纯文本,并确保响应正文中没有其他内容。
如果要让 PHP 重定向当前浏览器,则需要使用真正的 HTTP 重定向:
wp_safe_redirect($pageUrl);
exit;
但在没有确认 gateway 协议前,不能直接替换。服务器之间的 callback 即使收到 Location,执行跳转的也只是 gateway 的 HTTP 客户端,未必会影响用户浏览器。
建议的排查顺序
- 分开设置 gateway callback URL 和用户 return URL。
- 使用
admin_url()、home_url()等函数生成完整地址,不要手工拼接子目录。 - 确认 callback URL 从外部访问时不会触发任何
301/302/303。 - 检查访问日志,区分 gateway POST 和用户浏览器 GET。
- 查看 gateway 后台记录的通知状态、HTTP 状态码和响应正文。
- 检查请求的
Content-Type,确认数据是否应该出现在$_POST中。 - 检查安全插件、CDN、WAF、Basic Auth 和防火墙是否拦截服务器通知。
- 确认签名计算中的
siteurl与发起交易时使用的值完全一致。 - 确保 callback 只输出协议要求的纯文本,不要输出 PHP warning、HTML、BOM 或调试信息。
只有日志表明 gateway 根本无法建立 HTTPS 连接,或者明确报告 TLS 握手、证书链、主机名验证错误时,才应优先检查 certificate。对于“POST 变成 GET、REDIRECT 被打印”这一现象,URL 重定向或 callback/return URL 混用更符合实际表现。
备注:内容仅供参考。
