WordPress Coding:如何创建 custom action hook?
结论
WordPress 不需要单独注册自定义 action hook。在支付状态确定后调用 do_action(),其他插件或主题就能通过 add_action() 监听该事件。
这两个 hook 应写在插件的支付处理逻辑中,不要放在主题模板里。这样无论使用哪个主题,只要插件的支付流程正常执行,hook 都能触发。
membership templates 可以默认放在插件的 templates/ 目录中,同时允许主题通过约定的目录覆盖模板。
创建并触发支付 hooks
先为 hook 添加插件专属前缀,避免与其他插件重名。例如:
my_membership_payment_successmy_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、会员类型、交易号和规范化后的支付状态。
备注:内容仅供参考。
