PAYATHON 2026

WordPress 中如何处理 PayBox 支付表单请求

支付小周

结论

这段 PHP 不应放在页面构建器、主题模板或单独的 payment.php 文件中。建议按以下方式处理:

  1. 在自定义 WordPress 插件中编写 PayBox 请求逻辑。
  2. 将页面构建器创建的表单提交到 WordPress 的 admin-post.php。
  3. 通过 admin_post_* 钩子接收表单、校验数据并生成 PayBox 签名。
  4. 将用户重定向到 PayBox 官方支付地址。
  5. 单独编写服务器回调处理器,验证 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 统一管理订单创建、库存、支付状态和退款流程。

备注:内容仅供参考。