如何在 WordPress 中创建支付网关 return URL?
结论
可以实现,但不建议在 init 中直接比较 $_SERVER['REQUEST_URI']。更合适的做法是在插件中注册 WordPress 重写规则,将这些虚拟 URL 映射到自定义 query var,再到 template_redirect 阶段处理请求并立即 exit。
这种方式不需要创建真实的 PHP 文件,也不会进入主题模板或最终返回 404。例如:
/get-return.php→ 支付完成后的用户返回页/get-error.php→ 支付失败后的用户返回页/get-receipt.php→ 网关发送支付结果的回调接口
插件实现示例
创建一个插件,加入以下代码:
<?php
/**
* Plugin Name: Payment Gateway Endpoints
*/
defined( 'ABSPATH' ) || exit;
/**
* 注册虚拟 URL。
*/
function my_gateway_add_rewrite_rules() {
add_rewrite_rule(
'^get-return\.php/?$',
'index.php?gateway_endpoint=return',
'top'
);
add_rewrite_rule(
'^get-error\.php/?$',
'index.php?gateway_endpoint=error',
'top'
);
add_rewrite_rule(
'^get-receipt\.php/?$',
'index.php?gateway_endpoint=receipt',
'top'
);
}
add_action( 'init', 'my_gateway_add_rewrite_rules' );
/**
* 声明允许使用的 query var。
*/
function my_gateway_register_query_vars( $vars ) {
$vars[] = 'gateway_endpoint';
return $vars;
}
add_filter( 'query_vars', 'my_gateway_register_query_vars' );
/**
* 处理虚拟端点。
*/
function my_gateway_handle_endpoint() {
$endpoint = get_query_var( 'gateway_endpoint' );
if ( ! $endpoint ) {
return;
}
switch ( $endpoint ) {
case 'return':
my_gateway_handle_return();
break;
case 'error':
my_gateway_handle_error();
break;
case 'receipt':
my_gateway_handle_receipt();
break;
default:
status_header( 404 );
exit;
}
}
add_action( 'template_redirect', 'my_gateway_handle_endpoint' );
接着实现各个端点的业务逻辑:
/**
* 用户支付成功后由浏览器返回。
*/
function my_gateway_handle_return() {
status_header( 200 );
nocache_headers();
// 根据订单号查询支付状态。
// 不要仅凭 URL 参数将订单标记为已支付。
wp_safe_redirect( home_url( '/payment-success/' ) );
exit;
}
/**
* 用户支付失败或取消后返回。
*/
function my_gateway_handle_error() {
status_header( 200 );
nocache_headers();
wp_safe_redirect( home_url( '/payment-failed/' ) );
exit;
}
/**
* 接收支付网关的服务器回调。
*/
function my_gateway_handle_receipt() {
nocache_headers();
if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ?? '' ) ) {
status_header( 405 );
header( 'Allow: POST' );
exit;
}
$raw_body = file_get_contents( 'php://input' );
// 以下函数需要根据支付网关的签名规范自行实现。
if ( ! my_gateway_verify_signature( $raw_body, $_SERVER ) ) {
status_header( 401 );
exit;
}
// 按网关实际格式解析数据,例如 JSON。
$payload = json_decode( $raw_body, true );
if ( ! is_array( $payload ) ) {
status_header( 400 );
exit;
}
// 校验订单号、支付金额、币种、商户号和交易状态。
// 更新 orders 表时必须保证重复回调不会重复记账。
my_gateway_process_payment_notification( $payload );
status_header( 200 );
header( 'Content-Type: text/plain; charset=utf-8' );
// 返回内容必须符合支付网关规定,例如 OK、SUCCESS 或特定 JSON。
echo 'OK';
exit;
}
my_gateway_verify_signature() 和 my_gateway_process_payment_notification() 只是示意函数,具体参数和实现必须遵循实际支付网关的协议,不能直接照搬。
刷新重写规则
添加重写规则后,需要刷新 WordPress 的 rewrite rules。建议在插件启用和停用时执行:
function my_gateway_activate_plugin() {
my_gateway_add_rewrite_rules();
flush_rewrite_rules();
}
register_activation_hook( __FILE__, 'my_gateway_activate_plugin' );
function my_gateway_deactivate_plugin() {
flush_rewrite_rules();
}
register_deactivation_hook( __FILE__, 'my_gateway_deactivate_plugin' );
不要在每次 init 时执行 flush_rewrite_rules(),这个操作开销较大。
如果插件已经启用,也可以进入 WordPress 后台的“设置 → 固定链接”,保存一次固定链接设置。
为什么不直接在 init 中判断 URL
从技术上说,也可以这样处理:
add_action( 'init', function () {
$path = wp_parse_url( $_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH );
if ( '/get-return.php' === $path ) {
// 处理请求。
exit;
}
} );
不过,这种写法可能受到以下情况影响:
- WordPress 安装在子目录中;
- URL 带有尾部斜杠或存在编码差异;
- 站点使用了反向代理;
- 多站点配置改变了请求路径;
- 代码绕过了 WordPress 正常的请求解析流程;
- 其他插件需要识别或过滤该请求。
使用重写规则后,WordPress 可以正常识别这些地址。template_redirect 又在主题输出之前执行,适合返回响应、执行跳转并阻止模板继续加载。
Web 服务器必须把请求交给 WordPress
这个方案有一个前提:Web 服务器必须把不存在的 /get-return.php 转发给 WordPress 的 index.php。
常见的 WordPress Apache 重写配置通常会转发不存在的文件,所以虚拟 .php 地址一般可以正常工作。如果服务器、CDN 或安全规则会直接拦截所有 .php 路径,可以改用不带扩展名的地址,例如:
https://www.mysite.com/payment/return
https://www.mysite.com/payment/error
https://www.mysite.com/payment/receipt
对应的规则可以改为:
add_rewrite_rule(
'^payment/(return|error|receipt)/?$',
'index.php?gateway_endpoint=$matches[1]',
'top'
);
这类 URL 更符合 WordPress 的路由方式,通常也较少受到服务器安全策略的影响。
支付回调的注意事项
不能只根据用户浏览器访问的 return URL 判断支付成功。用户可能关闭页面、伪造参数,也可能在付款后没有成功跳转。订单状态应以网关的服务器回调或主动查询结果为准。
处理回调时至少需要做到:
- 使用 HTTPS;
- 按照网关规范验证签名,不能只检查订单号或 status code;
- 核对商户号、订单号、金额和币种;
- 使用网关交易号,避免同一笔交易重复入账;
- 将处理逻辑设计为幂等操作,因为网关通常会重试回调;
- 完成可靠的数据库更新后,再返回网关要求的成功响应;
- 不要强制外部支付回调使用 WordPress nonce,外部网关通常无法生成 nonce;
- 验证失败时不要返回成功,否则网关可能停止重试;
- 使用
$wpdb->prepare()处理数据库参数,并在更新前确认订单当前状态。
如果支付网关允许配置任意路径,也可以考虑通过 WordPress REST API 创建 /wp-json/... 回调端点。如果网关明确要求使用 get-return.php 这类固定路径,自定义 rewrite rule 更合适。
备注:内容仅供参考。
