PAYATHON 2026

WordPress Coding:如何创建 custom action hook?

支付阿杰

结论

WordPress 不需要单独注册自定义 action hook。在支付状态确定后调用 do_action(),其他插件或主题就能通过 add_action() 监听该事件。

这两个 hook 应写在插件的支付处理逻辑中,不要放在主题模板里。这样无论使用哪个主题,只要插件的支付流程正常执行,hook 都能触发。

membership templates 可以默认放在插件的 templates/ 目录中,同时允许主题通过约定的目录覆盖模板。

创建并触发支付 hooks

先为 hook 添加插件专属前缀,避免与其他插件重名。例如:

  • my_membership_payment_success
  • my_membership_payment_failure

应在支付结果完成验证,并且会员状态和订单状态已经写入数据库后触发 hook:

<?php

function my_membership_process_payment( $order_id, $payment_result ) {
    $order = my_membership_get_order( $order_id );

    if ( ! $order ) {
        return;
    }

    if ( $payment_result->is_successful() ) {
        my_membership_update_order_status( $order_id, 'paid' );
        my_membership_activate_membership( $order->user_id, $order->membership_type_id );

        /**
         * Fires after a membership payment succeeds.
         *
         * @param int    $order_id      Order ID.
         * @param object $order         Membership order.
         * @param object $payment_result Payment gateway result.
         */
        do_action(
            'my_membership_payment_success',
            $order_id,
            $order,
            $payment_result
        );
    } else {
        my_membership_update_order_status( $order_id, 'failed' );

        /**
         * Fires after a membership payment fails.
         *
         * @param int    $order_id       Order ID.
         * @param object $order          Membership order.
         * @param object $payment_result Payment gateway result.
         */
        do_action(
            'my_membership_payment_failure',
            $order_id,
            $order,
            $payment_result
        );
    }
}

调用 do_action() 后,这个 action hook 就已经存在,无须再调用其他注册函数。

监听这些 hooks

其他插件或当前主题可以用 add_action() 添加处理逻辑:

<?php

add_action(
    'my_membership_payment_success',
    'send_membership_payment_success_email',
    10,
    3
);

function send_membership_payment_success_email(
    $order_id,
    $order,
    $payment_result
) {
    // Send a confirmation email or notify another system.
}

失败事件的监听方式相同:

<?php

add_action(
    'my_membership_payment_failure',
    'log_membership_payment_failure',
    10,
    3
);

function log_membership_payment_failure(
    $order_id,
    $order,
    $payment_result
) {
    // Log the failure or notify an administrator.
}

add_action() 的第四个参数表示回调接收的参数数量,必须与回调函数的需要一致。上例中的 do_action() 传递了三个参数,所以这里填写 3。

如果插件本身有默认处理程序,也可以在插件启动时直接添加监听:

add_action(
    'my_membership_payment_success',
    'my_membership_default_success_handler',
    10,
    3
);

插件可以借此提供默认行为,第三方也能继续添加自己的处理逻辑。

如何保证所有主题都能使用

不要把 do_action() 放进 header.php、footer.php 或其他主题文件。主题未必会调用某个特定模板,而且用户切换主题后,对原主题文件的修改也不会保留。

hook 应由插件自己的支付流程触发:

支付平台返回结果
    ↓
插件验证支付结果
    ↓
更新订单和会员状态
    ↓
触发 success 或 failure action

只要支付处理代码得到执行,hook 就会触发,不受当前主题影响。

action hook 只能通知当前请求中已经加载的代码。如果支付网关通过异步 webhook 返回支付结果,应在 webhook 处理程序完成签名验证和状态更新后调用 do_action(),不要等用户跳转到 支付成功 页面后再触发。

membership templates 放在哪里

插件可以采用下面的目录结构:

my-membership-plugin/
├── my-membership-plugin.php
├── includes/
│   ├── class-payment-handler.php
│   └── template-functions.php
└── templates/
    ├── membership-list.php
    ├── membership-single.php
    ├── payment-success.php
    └── payment-failure.php

把默认模板放在插件中,用户切换主题后页面仍然可用。为了让主题能够调整展示效果,可以约定一个主题覆盖目录,例如:

wp-content/themes/your-theme/my-membership/

模板加载函数可以这样写:

<?php

function my_membership_get_template_path( $template_name ) {
    $template_name = ltrim( $template_name, '/\\' );

    $theme_template = locate_template(
        'my-membership/' . $template_name,
        false,
        false
    );

    if ( $theme_template ) {
        return $theme_template;
    }

    return plugin_dir_path( __DIR__ ) . 'templates/' . $template_name;
}

如果这个函数位于插件根目录,而不是 includes/ 目录,需要根据实际文件位置调整 plugin_dir_path() 的参数。

加载模板时可以使用:

<?php

function my_membership_render_template( $template_name, array $args = array() ) {
    $template_path = my_membership_get_template_path( $template_name );

    if ( ! is_readable( $template_path ) ) {
        return;
    }

    if ( $args ) {
        extract( $args, EXTR_SKIP );
    }

    include $template_path;
}

调用示例:

my_membership_render_template(
    'payment-success.php',
    array(
        'order'           => $order,
        'membership_type' => $membership_type,
    )
);

主题要覆盖模板时,只需复制对应文件:

插件默认模板:
my-membership-plugin/templates/payment-success.php

主题覆盖模板:
your-theme/my-membership/payment-success.php

最好在子主题中覆盖模板,以免主题更新时丢失修改。

支付场景中的注意事项

先保存状态,再触发 hook

通常应先完成数据库更新,再调用 do_action()。监听方随后读取订单时,获得的就是最新状态。

如果业务需要让监听方阻止状态变化,仅使用 action 并不合适。可以在更新前增加 apply_filters(),允许其他代码修改或否决处理结果。

防止重复触发

支付网关可能重复发送 webhook。插件应先检查订单是否已经处理,避免重复开通会员、发送邮件或写入流水:

if ( 'paid' === $order->status ) {
    return;
}

更稳妥的实现还需要在数据库层进行原子状态更新,或者增加唯一交易号约束。具体方式取决于订单表结构。

不要通过成功页面判断付款结果

用户访问 payment-success.php,只能说明浏览器进入了这个页面,不能证明付款成功。支付结果应以完成验证的支付网关响应或 webhook 为准。

保持 hook 参数稳定

第三方开始监听这些 hooks 后,不要随意改变参数的顺序或含义。需要增加信息时,可以传递结构清晰的订单对象或上下文数组,并记录每个参数的用途。

不要传递敏感数据

传给 hook 的支付结果不应包含银行卡信息、完整凭据、签名密钥或其他敏感数据。第三方回调通常只需要订单 ID、用户 ID、会员类型、交易号和规范化后的支付状态。

备注:内容仅供参考。