WordPress 中如何处理 PayBox 支付表单请求
结论
这段 PHP 不应放在页面构建器、主题模板或单独的 payment.php 文件中。建议按以下方式处理:
- 在自定义 WordPress 插件中编写 PayBox 请求逻辑。
- 将页面构建器创建的表单提交到 WordPress 的
admin-post.php。 - 通过
admin_post_*钩子接收表单、校验数据并生成 PayBox 签名。 - 将用户重定向到 PayBox 官方支付地址。
- 单独编写服务器回调处理器,验证 PayBox 返回的支付结果。
这种做法可以使用 WordPress 自带的安全机制,也能避免主题更新导致代码丢失。
为什么不能直接放进页面
页面构建器通常只生成 HTML。PHP 代码粘贴到页面内容中后一般不会执行。即使某些插件支持执行页面中的 PHP,这样做也有明显的安全风险。
另外,下面的代码必须在 WordPress 输出任何 HTML 之前运行:
header('Location: ...');
否则可能出现:
Cannot modify header information - headers already sent
使用 admin_post_* 钩子处理请求,可以让代码在正确的阶段执行,减少这类问题。
创建 PayBox 处理插件
在 wp-content/plugins/ 中创建以下目录:
paybox-payment-handler
然后创建文件:
paybox-payment-handler.php
文件内容如下:
<?php
/**
* Plugin Name: PayBox Payment Handler
* Description: Handles PayBox payment form submissions.
* Version: 1.0.0
*/
defined('ABSPATH') || exit;
/*
* 建议将这些值放进 wp-config.php 或服务器环境变量,
* 不要把真实密钥提交到公开代码仓库。
*/
defined('PAYBOX_MERCHANT_ID') || define('PAYBOX_MERCHANT_ID', '111');
defined('PAYBOX_SECRET_KEY') || define('PAYBOX_SECRET_KEY', 'replace_with_real_secret');
/*
* 必须替换为 PayBox 文档中适用于当前账户和环境的真实 payment.php 地址。
*/
defined('PAYBOX_PAYMENT_URL') || define(
'PAYBOX_PAYMENT_URL',
'https://PAYBOX-HOST/payment.php'
);
add_action('admin_post_paybox_create_payment', 'my_paybox_create_payment');
add_action('admin_post_nopriv_paybox_create_payment', 'my_paybox_create_payment');
function my_paybox_create_payment(): void
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
wp_die('不支持的请求方法。', 'PayBox', ['response' => 405]);
}
if (
!isset($_POST['paybox_nonce']) ||
!wp_verify_nonce(
sanitize_text_field(wp_unslash($_POST['paybox_nonce'])),
'paybox_create_payment'
)
) {
wp_die('表单验证失败,请刷新页面后重试。', 'PayBox', ['response' => 403]);
}
$product_id = isset($_POST['product_id'])
? absint($_POST['product_id'])
: 0;
/*
* 金额必须由服务器根据商品、订单或套餐计算。
* 不要直接信任浏览器提交的 amount 字段。
*/
$products = [
101 => [
'amount' => '25.00',
'description' => '示例商品',
],
];
if (!isset($products[$product_id])) {
wp_die('无效的商品。', 'PayBox', ['response' => 400]);
}
$order_id = wp_generate_uuid4();
$pg_salt = wp_generate_password(32, false, false);
$request = [
'pg_merchant_id' => PAYBOX_MERCHANT_ID,
'pg_amount' => $products[$product_id]['amount'],
'pg_salt' => $pg_salt,
'pg_order_id' => $order_id,
'pg_description' => $products[$product_id]['description'],
'pg_result_url' => admin_url(
'admin-post.php?action=paybox_payment_result'
),
];
/*
* 测试环境是否使用此参数,以及参数允许的值,
* 应以当前 PayBox 文档和商户配置为准。
*/
// $request['pg_testing_mode'] = 1;
ksort($request, SORT_STRING);
/*
* 按示例中的 PayBox 规则生成签名:
* payment.php + 排序后的参数值 + secret key,
* 各部分使用分号连接。
*
* 如果当前 PayBox 接口文档规定了不同的脚本名、
* 编码或签名算法,应严格以当前文档为准。
*/
$signature_parts = array_merge(
['payment.php'],
array_values($request),
[PAYBOX_SECRET_KEY]
);
$request['pg_sig'] = md5(implode(';', $signature_parts));
$payment_url = add_query_arg($request, PAYBOX_PAYMENT_URL);
/*
* wp_safe_redirect() 默认可能拒绝未列入白名单的外部域名。
* 此处地址是服务器端固定配置,而不是来自用户输入。
*/
wp_redirect($payment_url, 302, 'PayBox Payment Handler');
exit;
}
保存文件后,在 WordPress 后台的“插件”页面启用该插件。
必须将 PAYBOX_PAYMENT_URL 中的占位地址替换为 PayBox 当前文档提供的真实支付入口。不要继续使用示例地址:
http://core.local/payment.php
这通常只是本地地址或示例地址。
连接页面构建器中的表单
将表单提交地址设置为:
https://example.com/wp-admin/admin-post.php
提交方法设置为:
POST
表单至少要包含以下字段:
<input type="hidden" name="action" value="paybox_create_payment">
<input type="hidden" name="product_id" value="101">
action 的值必须与插件钩子中的名称一致:
admin_post_paybox_create_payment
admin_post_nopriv_paybox_create_payment
其中,第二个钩子用于允许未登录的访客发起支付。
添加 WordPress nonce
表单还要提交 paybox_nonce。如果页面构建器不能直接生成 WordPress nonce,可以在插件中添加一个短代码:
add_shortcode('paybox_nonce', function (): string {
return wp_nonce_field(
'paybox_create_payment',
'paybox_nonce',
true,
false
);
});
然后在页面构建器支持短代码的位置插入:
[paybox_nonce]
最终生成的表单结构应类似于:
<form method="post" action="https://example.com/wp-admin/admin-post.php">
<input type="hidden" name="action" value="paybox_create_payment">
<input type="hidden" name="product_id" value="101">
<!-- 这里应包含 [paybox_nonce] 生成的隐藏字段 -->
<button type="submit">立即支付</button>
</form>
如果页面构建器会拦截表单并通过 AJAX 提交,需要关闭 AJAX,或者使用该构建器提供的“提交后重定向/自定义 PHP 钩子”。普通 AJAX 请求收到 PHP 返回的 Location 响应后,不会自动将整个浏览器页面跳转到 PayBox。
支付结果回调不能省略
pg_result_url 通常用于接收 PayBox 服务器发送的支付结果通知。不能因为用户回到了某个页面,就直接认定支付成功。
上面的代码已将它指向:
/wp-admin/admin-post.php?action=paybox_payment_result
插件中还需要注册相应的处理器:
add_action('admin_post_paybox_payment_result', 'my_paybox_payment_result');
add_action('admin_post_nopriv_paybox_payment_result', 'my_paybox_payment_result');
function my_paybox_payment_result(): void
{
/*
* 1. 读取 PayBox 发来的参数。
* 2. 按 PayBox 的回调签名规则重新计算 pg_sig。
* 3. 使用 hash_equals() 比较签名。
* 4. 核对订单号、金额、币种和当前订单状态。
* 5. 以幂等方式更新订单,避免重复通知造成重复入账。
* 6. 按 PayBox 文档要求返回确认响应。
*/
status_header(501);
exit('PayBox callback handler is not implemented.');
}
这段示例没有假设 PayBox 回调使用哪种请求方法、哪个签名脚本名或什么确认响应格式,因为这些细节必须按照当前 PayBox 接口文档实现。在回调处理完成之前,不应投入真实交易。
重要注意事项
- 商户密钥只能保存在服务器端,绝不能通过表单隐藏字段或 JavaScript 变量发送给浏览器。
- 金额、订单描述和商品信息应由服务器根据商品 ID 或订单记录确定。
pg_order_id应对应数据库中的真实订单,并且必须唯一。- 生产环境必须使用 HTTPS。
- 不能只凭“用户回到了成功页面”就将订单标记为已支付。
- 回调必须验证
pg_sig,并核对订单金额、订单号和订单当前状态。 - PayBox 可能重复发送回调,因此订单更新逻辑必须具有幂等性。
- 如果网站使用 WooCommerce,更合适的做法是编写 WooCommerce Payment Gateway,而不是维护独立表单。这样可以由 WooCommerce 统一管理订单创建、库存、支付状态和退款流程。
备注:内容仅供参考。
