如何在WooCommerce中创建类似Flat Rate那样的配送方式呢?答案是使用Shipping Method API,本文将代码简化,介绍一下在WooCommerce中创建配送选项的过程,基于WooCommerce 6.1.1。
目录
注册并创建运费选项的过程
- 定义运费类,要定义运费的名称、价格、计算运费的方式等属性和方法。
- 向WooCommerce注册这个运费类。
用代码来描述这个过程,如下:
add_action( 'woocommerce_shipping_init', 'define_your_shipping_method' );
add_filter( 'woocommerce_shipping_methods', 'add_your_shipping_method' );
function define_your_shipping_method(){
class Your_Shipping_Method extends WC_Shipping_Method {
// 定义运费名称、费用、计算运费方法等等的代码
}
}
function add_your_shipping_method( $methods ) {
$methods['your_shipping_method'] = 'Your_Shipping_Method';
return $methods;
}
如何定义一个运费类
class Your_Shipping_Method extends WC_Shipping_Method {
/**
* Constructor.
*
* @param int $instance_id Shipping method instance.
*/
public function __construct( $instance_id = 0 ) {
$this->id = 'your_shipping_method';
$this->instance_id = absint( $instance_id );
$this->method_title = '我的运费名称';
$this->method_description = '我的运费描述';
// 让运费支持shipping zones,而不是单独显示出来
$this->supports = array(
'shipping-zones',
'instance-settings',
'instance-settings-modal',
);
$this->init();
add_action( 'woocommerce_update_options_shipping_' . $this->id, array( $this, 'process_admin_options' ) );
}
/**
* Init user set variables.
*/
public function init() {
// 定义选项字段
$this->instance_form_fields = array(
'title' => array(
'title' => __( 'Title', 'woocommerce' ),
'type' => 'text',
'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce' ),
'default' => $this->method_title,
'desc_tip' => true,
),
'cost' => array(
'title' => __( 'Cost', 'woocommerce' ),
'type' => 'text',
'description' => '请输入价格',
'default' => '0',
'desc_tip' => true,
'sanitize_callback' => array( $this, 'sanitize_cost' ),
),
);
// 获取用户的选择
$this->title = $this->get_option( 'title' );
$this->cost = $this->get_option( 'cost' );
}
/**
* Calculate the shipping costs.
*
* @param array $package Package of items from cart.
*/
public function calculate_shipping( $package = array() ) {
$rate = array(
'id' => $this->get_rate_id(),
'label' => $this->get_option( 'title' ),
'cost' => $this->get_option( 'cost' ),
'package' => $package,
);
if( $rate['cost'] > 0 ){
$this->add_rate( $rate );
}
do_action( 'woocommerce_' . $this->id . '_shipping_add_rate', $this, $rate );
}
/**
* Sanitize the cost field.
*
*/
public function sanitize_cost( $value ) {
$value = is_null( $value ) ? '' : $value;
$value = wp_kses_post( trim( wp_unslash( $value ) ) );
$value = str_replace( array( get_woocommerce_currency_symbol(), html_entity_decode( get_woocommerce_currency_symbol() ) ), '', $value );
return $value;
}
}
简单讲解一下这段代码:
- 首先在类的构造函数里定义运费的名称、描述、唯一ID等基本属性。
- 在init()函数里定义运费的选项,这些选项的值会被保存到wp_options表里,再获取用户填写的值。
- 在calculate_shipping()函数里实现运费计算的逻辑。
- sanitize_cost()是价格字段的过滤函数,可有可无。
完整代码
放到插件或主题的functions.php中均可。
add_action( 'woocommerce_shipping_init', 'define_your_shipping_method' );
add_filter( 'woocommerce_shipping_methods', 'add_your_shipping_method' );
function define_your_shipping_method(){
class Your_Shipping_Method extends WC_Shipping_Method {
/**
* Constructor.
*
* @param int $instance_id Shipping method instance.
*/
public function __construct( $instance_id = 0 ) {
$this->id = 'your_shipping_method';
$this->instance_id = absint( $instance_id );
$this->method_title = '我的运费名称';
$this->method_description = '我的运费描述';
// 让运费支持shipping zones,而不是单独显示出来
$this->supports = array(
'shipping-zones',
'instance-settings',
'instance-settings-modal',
);
$this->init();
add_action( 'woocommerce_update_options_shipping_' . $this->id, array( $this, 'process_admin_options' ) );
}
/**
* Init user set variables.
*/
public function init() {
// 定义选项字段
$this->instance_form_fields = array(
'title' => array(
'title' => __( 'Title', 'woocommerce' ),
'type' => 'text',
'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce' ),
'default' => $this->method_title,
'desc_tip' => true,
),
'cost' => array(
'title' => __( 'Cost', 'woocommerce' ),
'type' => 'text',
'description' => '请输入价格',
'default' => '0',
'desc_tip' => true,
'sanitize_callback' => array( $this, 'sanitize_cost' ),
),
);
// 获取用户的选择
$this->title = $this->get_option( 'title' );
$this->cost = $this->get_option( 'cost' );
}
/**
* Calculate the shipping costs.
*
* @param array $package Package of items from cart.
*/
public function calculate_shipping( $package = array() ) {
$rate = array(
'id' => $this->get_rate_id(),
'label' => $this->get_option( 'title' ),
'cost' => $this->get_option( 'cost' ),
'package' => $package,
);
if( $rate['cost'] > 0 ){
$this->add_rate( $rate );
}
do_action( 'woocommerce_' . $this->id . '_shipping_add_rate', $this, $rate );
}
/**
* Sanitize the cost field.
*
*/
public function sanitize_cost( $value ) {
$value = is_null( $value ) ? '' : $value;
$value = wp_kses_post( trim( wp_unslash( $value ) ) );
$value = str_replace( array( get_woocommerce_currency_symbol(), html_entity_decode( get_woocommerce_currency_symbol() ) ), '', $value );
return $value;
}
}
}
function add_your_shipping_method( $methods ) {
$methods['your_shipping_method'] = 'Your_Shipping_Method';
return $methods;
}
效果展示



计算运费时获取配送地址
calculate_shipping( $package = array() )
函数里的$package
包含所有信息,例如购物车里的产品数量、价格以及用户填写的配送地址等等。
如果用户填写了配送信息,$package['destination']
会包含这些信息,它是一个数组,结构如下:
Array
(
[country] => 'GB'
[state] => 'Lincolnshire'
[postcode] => 'LN12 1PB'
[city] => 'Mablethorpe'
[address] => 'Mill Rd'
[address_1] => 'Mill Rd'
[address_2] =>
)
全局模式或instance模式
上面介绍的方式是是instance模式,就是可以针对不同的shipping zone创建多个实例。如果想改成全局模式,需要修改两个地方。
首先,去掉下面的代码。
// 让运费支持shipping zones,而不是单独显示出来
$this->supports = array(
'shipping-zones',
'instance-settings',
'instance-settings-modal',
);
其次,初始化选项时要使用
$this->form_fields
instance的方式是使用$this->instance_form_fields
。
参考代码
add_rate()的使用方法
array(
'id' => $this->get_rate_id(), // ID for the rate. If not passed, this id:instance default will be used.
'label' => '', // Label for the rate.
'cost' => '0', // Amount or array of costs (per item shipping).
'taxes' => '', // Pass taxes, or leave empty to have it calculated for you, or 'false' to disable calculations.
'calc_tax' => 'per_order', // Calc tax per_order or per_item. Per item needs an array of costs.
'meta_data' => array(), // Array of misc meta data to store along with this rate - key value pairs.
'package' => false, // Package array this rate was generated for @since 2.6.0.
'price_decimals' => wc_get_price_decimals(),
)
创建表单字段的方法
源代码位于woocommerce/includes/abstracts/abstract-wc-settings-api.php
,下面列举了类型(例如type=’text’)和对应的参数。
text | price | decimal | password | color | textarea | checkbox
$defaults = array(
'title' => '',
'disabled' => false,
'class' => '',
'css' => '',
'placeholder' => '',
'type' => 'text',
'desc_tip' => false,
'description' => '',
'custom_attributes' => array(),
);
select
$defaults = array(
'title' => '',
'disabled' => false,
'class' => '',
'css' => '',
'placeholder' => '',
'type' => 'select',
'desc_tip' => false,
'description' => '',
'custom_attributes' => array(),
'options' => array(),
);
multiselect
$defaults = array(
'title' => '',
'disabled' => false,
'class' => '',
'css' => '',
'placeholder' => '',
'type' => 'multiselect',
'desc_tip' => false,
'description' => '',
'custom_attributes' => array(),
'select_buttons' => false,
'options' => array(),
);
title
$defaults = array(
'title' => '',
'class' => '',
);
扩展表单字段的方法
如果上面的字段类型不能满足需求,需要扩展一下怎么办?可以通过修改 Your_Shipping_Method 类来实现。
比如,创建一个类型为paragraph的字段,功能就是显示一段话,首先给Your_Shipping_Method添加一个新的方法,如下:
public function generate_paragraph_html( $key, $data ){
$field_key = $this->get_field_key( $key );
$defaults = array(
'title' => '',
'content' => '',
);
$data = wp_parse_args( $data, $defaults );
ob_start();
?>
<tr valign="top">
<th scope="row" class="titledesc">
<label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?> <?php echo $this->get_tooltip_html( $data ); // WPCS: XSS ok. ?></label>
</th>
<td class="forminp">
<p><?php echo wp_kses_post( $data['content'] ); ?> </p>
</td>
</tr>
<?php
return ob_get_clean();
}
然后这样定义一下字段
$this->instance_form_fields = array(
// Other options...
'custom_field' => array(
'title' => __( '扩展字段', 'woocommerce' ),
'type' => 'paragraph',
'content' => '这里随便写点东西'
),
);
效果如下图所示:

按照运费价格从低到高排序
add_filter( 'woocommerce_package_rates' , 'sola_sort_shipping_methods', 10, 2 );
function sola_sort_shipping_methods( $rates, $package ) {
if( $rates && is_array($rates) ){
uasort( $rates, function ( $a, $b ) {
if ( $a == $b ) return 0;
return ( $a->cost < $b->cost ) ? -1 : 1;
} );
}
return $rates;
}
刷新shipping rates缓存
运费不会重复计算,如果没有更新购物车内容、配送地址或后台的配送选项,则显示上一次缓存的结果。要刷新缓存,可以用以下操作:
- 更新购物车的产品或产品数量
- 更换配送地址
- 在后台保存一下配送设置
- 在后台woocommerce设置里的shipping options下打开debug mode
代码刷新缓存的方法如下:
WC_Cache_Helper::get_transient_version( 'shipping', true );
或者用下面这个函数,顺便解释了原理。
function purge_shipping_cache(){
if (!class_exists('WC_Cache_Helper') || !method_exists('WC_Cache_Helper', 'get_transient_version')) {
global $wpdb;
$transients = $wpdb->get_col("
SELECT SUBSTR(option_name, LENGTH('_transient_') + 1)
FROM `{$wpdb->options}`
WHERE option_name LIKE '_transient_wc_ship_%'
");
foreach ($transients as $transient) {
delete_transient($transient);
}
return;
}
WC_Cache_Helper::get_transient_version('shipping', true);
}
那么shipping缓存的原理是什么呢?简单的说,运费结果保存在woocommerce session里,session里记录了一个package_hash,每次显示shipping时,当根据购物车内容和配送地址计算出来的hash与session里的保存的不一样时,就要重算运费。
$package_hash = 'wc_ship_' . md5( wp_json_encode( $package_to_hash ) . WC_Cache_Helper::get_transient_version( 'shipping' ) );
上面是package_hash的计算方法,$package_to_hash
是根据购物车内容和配送地址得出的值,后面的shipping version则是我们用代码刷新的根据。它根据时间生成,可以理解为shipping的版本号,保存在WordPress瞬态缓存里面,只要刷新了版本号,就算购物车和地址都没变,shipping也会重新计算。
这个缓存刷新方法来自https://www.tollmanz.com/invalidation-schemes/。
购物车的shipping calculator在没有填地址时就显示shipping费用
这很正常,没填地址时会显示与配送地址无关的shipping选项,比如Flat Rate这种,计算的费用可能只跟重量有关。但这种逻辑可能有些问题,用户会奇怪我连地址都没写怎么可能知道运费呢,去美国和去中国一个费用?为了避免这个问题,可以修改成填写国家后再显示运费,方法如下:
function sola_hide_shipping_when_no_country(){
$country = WC()->cart->get_customer()->get_shipping_country();
return $country ? true : false;
}
add_filter( 'woocommerce_cart_ready_to_calc_shipping', 'sola_hide_shipping_when_no_country' );
woocommerce显示shipping calculator的函数是位于cart-totals.php
的wc_cart_totals_shipping_html()
,此函数调用前会有一个判断:
if ( WC()->cart->needs_shipping() && WC()->cart->show_shipping() )
代码中使用的filter就定义在WC()->cart->show_shipping()
方法中。
另外,可以在woocommerce设置的shipping options选项卡下打开“Hide shipping costs until an address is entered”这一项,这时用户必须在shipping calculator里填写完整的地址(国家、城市、具体地址)后才会显示运费。
写什么都不重要,因为写什么都有人看。贵在坚持,谢谢分享 https://chinatoday.news
WooCommerce现在国内用的人多吗?
做外贸的有可能用,别的不清楚,因为国内的外贸网站平台也很多。