Custom fields on the WooCommerce Checkout block: why your old snippet does nothing
If you added a checkout field with woocommerce_checkout_fields or printed one on woocommerce_after_order_notes, and it’s simply not there on a newer store, the store is probably using the Checkout block. Since WooCommerce 8.3 (November 2023), new installs get the Cart and Checkout blocks by default. The Checkout block builds its form in React and talks to the Store API. Most classic checkout hooks never run there.
如果您使用 woocommerce_checkout_fields 添加了结账字段,或者在 woocommerce_after_order_notes 上打印了字段,但它们在较新的商店中无法显示,那么该商店很可能正在使用“结账”(Checkout)区块。自 WooCommerce 8.3(2023 年 11 月)起,新安装的 WooCommerce 默认启用“购物车”和“结账”区块。结账区块使用 React 构建表单并与 Store API 进行交互,大多数传统的结账钩子(hooks)在此均不生效。
WooCommerce’s own hook compatibility table lists woocommerce_after_order_notes, woocommerce_before_order_notes and woocommerce_review_order_before_submit as not supported, and woocommerce_checkout_fields only partly. Validation on woocommerce_checkout_process doesn’t fire either, so an old snippet fails silently: no field, no error, no saved meta. The supported way is the Additional Checkout Fields API (stable since WooCommerce 8.9).
WooCommerce 官方的钩子兼容性列表显示 woocommerce_after_order_notes、woocommerce_before_order_notes 和 woocommerce_review_order_before_submit 均不受支持,而 woocommerce_checkout_fields 仅部分支持。woocommerce_checkout_process 上的验证也不会触发,因此旧的代码片段会静默失败:没有字段、没有错误提示、也没有保存元数据。目前官方支持的方法是使用“额外结账字段 API”(Additional Checkout Fields API,自 WooCommerce 8.9 起稳定)。
Register the field: Put this in a small plugin rather than functions.php, so the field survives a theme change.
注册字段:请将其放入一个小插件中,而不是 functions.php,这样即使更换主题,字段依然有效。
<?php
/**
* Plugin Name: My Checkout Fields
* Requires Plugins: woocommerce
*/
add_action( 'woocommerce_init', function () {
if ( ! function_exists( 'woocommerce_register_additional_checkout_field' ) ) {
return;
}
woocommerce_register_additional_checkout_field( array(
'id' => 'my-plugin/po-number',
'label' => __( 'Purchase order number', 'my-plugin' ),
'optionalLabel' => __( 'Purchase order number (optional)', 'my-plugin' ),
'location' => 'order',
'type' => 'text',
'attributes' => array(
'maxLength' => 20,
'autocomplete' => 'off'
),
'sanitize_callback' => function ( $value ) {
return strtoupper( str_replace( ' ', '', $value ) );
},
'validate_callback' => function ( $value ) {
if ( ! preg_match( '/^[A-Z0-9-]{4,20}$/', $value ) ) {
return new WP_Error( 'my-plugin-invalid-po', __( 'Use 4 to 20 letters, numbers or dashes.', 'my-plugin' ) );
}
},
) );
} );
Things that trip people up: The ID needs a namespace (namespace/name). Without the slash, registration fails with a _doing_it_wrong notice. location decides storage. contact saves to the customer account, address shows in both billing and shipping and saves two values, order saves to the order only. Types: text, select, checkbox, and date from WooCommerce 11.2. Register on woocommerce_init or later. Since 11.0, registering before after_setup_theme triggers a notice.
容易让人困惑的地方:ID 需要包含命名空间(namespace/name)。如果没有斜杠,注册会失败并触发 _doing_it_wrong 通知。location 参数决定了存储位置:contact 保存到客户账户,address 会同时显示在账单和收货地址中并保存两个值,order 仅保存到订单中。支持的类型包括:text、select、checkbox 以及 WooCommerce 11.2 引入的 date。请在 woocommerce_init 或之后进行注册。自 11.0 版本起,在 after_setup_theme 之前注册会触发通知。
Validate from somewhere else: validate_callback runs after sanitize_callback. For checks added by another plugin, use the action, and add to the WP_Error you’re given. Returning a new one does nothing.
从其他地方进行验证:validate_callback 在 sanitize_callback 之后运行。对于由其他插件添加的检查,请使用对应的 action,并将错误添加到给定的 WP_Error 对象中。返回一个新的 WP_Error 对象是无效的。
add_action( 'woocommerce_validate_additional_field', function ( WP_Error $errors, $key, $value ) {
if ( 'my-plugin/po-number' === $key && 0 === strpos( (string) $value, 'TEST' ) ) {
$errors->add( 'my-plugin-test-po', __( 'Test PO numbers are not accepted.', 'my-plugin' ) );
}
}, 10, 3 );
One naming trap: the docs call the order-location hook woocommerce_blocks_validate_location_other_fields, but the 11.2.1 source fires woocommerce_blocks_validate_location_order_fields. If a cross-field rule never runs, check which name your version fires.
一个命名陷阱:文档将订单位置钩子称为 woocommerce_blocks_validate_location_other_fields,但 11.2.1 版本的源代码触发的是 woocommerce_blocks_validate_location_order_fields。如果跨字段规则无法运行,请检查您当前版本触发的是哪个名称。
Read the value: The admin order screen, thank-you page and order emails show the field without extra code. In your own code, use the helper instead of raw meta keys.
读取数值:后台订单页面、感谢页面和订单邮件会自动显示该字段,无需额外代码。在您自己的代码中,请使用辅助工具(helper)而不是直接使用原始元数据键(meta keys)。
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
add_action( 'woocommerce_store_api_checkout_order_processed', function ( WC_Order $order ) {
$fields = Package::container()->get( CheckoutFields::class );
$po = $fields->get_field_from_object( 'my-plugin/po-number', $order, 'other' );
if ( $po ) {
$order->add_order_note( sprintf( 'PO number: %s', $po ) );
}
} );
Use the group ‘other’ for contact and order fields, and ‘billing’ or ‘shipping’ for address fields. And don’t wait for woocommerce_checkout_create_order: it doesn’t fire on the block checkout.
对于联系人和订单字段,请使用组名 ‘other’;对于地址字段,请使用 ‘billing’ 或 ‘shipping’。此外,不要等待 woocommerce_checkout_create_order,因为它不会在区块结账中触发。
When the classic checkout is still the right call: The API only adds fields. Keep the shortcode checkout if you need to change or remove core fields, use a type that isn’t listed (file upload, radio, textarea), show an address field in only one address, or run an extension that only supports the classic form. Fields registered with this API don’t appear on the shortcode checkout, so that route still needs classic hooks.
何时仍应使用经典结账:该 API 仅用于添加字段。如果您需要更改或删除核心字段、使用未列出的类型(如文件上传、单选按钮、文本域)、仅在其中一个地址中显示地址字段,或者运行仅支持经典表单的扩展插件,请保留短代码结账方式。通过此 API 注册的字段不会出现在短代码结账中,因此该路径仍需使用经典钩子。