WooCommerce

WooCommerce自定义结账字段(2021)

WooCommerce自定义结账字段的插件有Custom Checkout Fields for WooCommerce等,如果你想要更多的自由,就用代码来解决吧。本文介绍一种用代码来自定义WooCommerce结账字段的方法。

WooCommerce自定义结账字段代码

代码单独保存一个文件引入,或直接放到子主题的functions.php中,第二种方式请将 defined('WPINC') or exit; 这一段删掉。

<?php
// Exit if accessed directly.
defined('WPINC') or exit;

/**
 * A class for creating custom checkout fields, please use the template below to add fields
 *
 * $field = array(
		// type support 
		// text,datetime,datetime-local,date,month,month,time,number,email,url,tel,checkbox,textarea,hidden,select,radio,state,country
		'type'                  => 'text', 
		'label'                 => '',
		'description'           => '',
		'placeholder'           => '',
		'maxlength'             => false,
		'required'              => false,
		'autocomplete'          => false,
		'id'                    => $key,
		'class'                 => array(),
		'label_class'           => array(),
		'input_class'           => array(),
		'return'                => false, // true to return the html string
		'options'               => array(),
		'custom_attributes'     => array(),
		'validate'              => array(), // add a validate-classname to the field, could be used to do custom validation
		'default'               => '',
		'autofocus'             => '',
		'priority'              => '',
		'save_usermeta'         => false, // Do not save this value to usermeta
		'show_in_email'         => true,
		'show_in_order_details' => true,
		'show_in_admin'         => true,
		'display_position'      => '1'
	);
 */
class Sola_Custom_WC_Checkout_Fields{

	private $field_names     = array( 'billing_fields','shipping_fields','special_fields');
	private $shipping_fields = false;
	private $billing_fields  = false;
	private $special_fields  = false;

	public function __construct( $billing_fields = false, $shipping_fields = false, $special_fields = false ){

		if( is_array($billing_fields) && sizeof($billing_fields) ){
			$this->billing_fields = $billing_fields;
		}

		if( is_array($shipping_fields) && sizeof($shipping_fields) ){
			$this->shipping_fields = $shipping_fields;
		}

		if( is_array($special_fields) && sizeof($special_fields) ){
			$this->special_fields = $special_fields;
		}

		if( $this->billing_fields || $this->shipping_fields || $this->special_fields ){
			$this->init();
		}
		
	}


	function init(){

		$this->show_fields_for_checkout();

		$this->validate_fields();

		/**
		 * Check if the fields need to be saved to usermeta
		 */
		add_action( 'woocommerce_checkout_update_customer',[$this,'may_update_customer'],10, 2 );

		$this->save_special_fields();

		$this->display_fields_for_order_details();

		$this->display_fields_for_admin();

		$this->display_fields_for_email();
	
	}


	function show_fields_for_checkout(){
		/**
		 * Define checkout fields
		 * Fields defined in checkout_fields array is automatically processed and saved to the order post meta
		 * 
		 */
		add_filter( 'woocommerce_checkout_fields' , [$this,'override_checkout_fields'] );


		/**
		 * Define special custom fields which are displayed after order notes
		 * 
		 */
		add_action( 'woocommerce_after_order_notes', [$this,'custom_special_fields'] );
	}

	function override_checkout_fields( $checkout_fields ) {

		if( $this->billing_fields ){
			$checkout_fields['billing'] += $this->billing_fields;
		}

		if( $this->shipping_fields ){
			$checkout_fields['shipping'] += $this->shipping_fields;
		}
		
	    return $checkout_fields;
	}


	function custom_special_fields( $checkout ) {

		if( ! $this->special_fields ){
			return $checkout;
		}

		echo '<div class="cp-checkout-special-fields">';
		foreach( $this->special_fields as $field_key => $field ){
			woocommerce_form_field( $field_key, $field, $checkout->get_value($field_key) );
		}
		echo '</div>';
	}


	/**
	 * Validate fields, both for normal fields and special fields
	 * 
	 */
	function validate_fields(){
		
		add_action( 'woocommerce_checkout_process', [$this,'validate_all_checkout_fields'] );

	}

	function validate_all_checkout_fields(){

		if( $this->special_fields ){

			foreach( $this->special_fields as $field_key => $field ){

				if( $field['required'] && isset($_POST[$field_key]) && ! $_POST[$field_key] ){
					wc_add_notice( sprintf( __( '%s is a required field.', 'woocommerce' ), '<strong>' . esc_html( $field['label'] ) . '</strong>' ), 'error' );
				}
			}
		}
	}


	/**
	 * Wheather to save data to custom user meta or not
	 * If saved to user meta, the fields will be populated with saved data on checkout page
	 * 
	 */
	function may_update_customer( $customer, $data ){
		$this->maybe_not_save_to_usermeta( $this->billing_fields, $customer );
		$this->maybe_not_save_to_usermeta( $this->shipping_fields, $customer );
	}

	function maybe_not_save_to_usermeta( $fields, $customer ){

		if( $fields ){
			foreach( $fields as $field_key => $field ){
				if( isset($field['save_usermeta']) && ! $field['save_usermeta'] ){
					$customer->delete_meta_data($field_key);
				}
			}
		}	
	}


	/**
	 * Save the fields after order notes to order post meta
	 * 
	 */
	function save_special_fields(){
		
		add_action( 'woocommerce_checkout_update_order_meta', [$this,'save_speical_fields_to_order_meta'] );
	}

	function save_speical_fields_to_order_meta( $order_id ) {

		if( $this->special_fields ){

			foreach( $this->special_fields as $field_key => $field ){

				if ( ! empty( $_POST[$field_key] ) ) {
				    update_post_meta( $order_id, '_' . $field_key, sanitize_text_field( $_POST[$field_key] ) );
				}
			}
		}
	}


	/**
	 * Display fields in order details
	 * 
	 */
	function display_fields_for_order_details(){

		/**
		 * Position 1 - Display inside order total after shipping method
		 */
		add_filter( 'woocommerce_get_order_item_totals', [$this, 'display_after_shipping_method'], 10, 2 );

		/**
		 * Position 2 - Display after order total
		 */
		add_action( 'woocommerce_order_details_after_order_table', [$this,'display_after_order_details'] );
		
	}

	function display_after_shipping_method( $total_rows, $order ){

		$new_row = array();

		foreach( $this->field_names as $field_name ){

			if( $formatted = $this->get_field_values( $this->$field_name, $order ) ){
				
				foreach( $formatted as $field ){

					if( isset($field['display_position']) && ($field['display_position'] != '1')){
						continue;
					}

					$new_row[$field['field_key']] = array(
						'label' => $field['label'],
						'value' => $field['value']
					);
				}
			}
		}

		if( sizeof($new_row) ){
			$total_rows = array_merge( array_splice( $total_rows,0,2), $new_row, $total_rows );
		}
		
		
		return $total_rows;
	}

	function display_after_order_details( $order ){
		ob_start();
		$html = '';

		foreach( $this->field_names as $field_name ){
			if( $formatted = $this->get_field_values( $this->$field_name, $order ) ){

				foreach( $formatted as $field ){

					if( isset($field['display_position']) && $field['display_position'] != '2' ){
						continue;
					}

					if( isset($field['show_in_order_details']) && ! $field['show_in_order_details'] ){
						continue;
					}
					echo '<tr><th>', $field['label'], '</th><td>', $field['value'],'</td></tr>';
				}
			}
		}

		$html= ob_get_clean();

		if( $html ){
			echo '<table class="shop_table order_details order_extra"><tbody>', $html,'</tbody></table>';
		}
		
	}


	/**
	 * Display fields in user email
	 * 
	 */
	function display_fields_for_email(){

		add_filter( 'woocommerce_email_order_meta_fields', [$this,'display_after_email_item_details'],10, 3 );
	}

	function display_after_email_item_details( $fields, $sent_to_admin, $order ){

		$billing_fields  = $this->get_email_order_meta_keys( $this->billing_fields, $order );
		$shipping_fields = $this->get_email_order_meta_keys( $this->shipping_fields, $order );
		$special_fields  = $this->get_email_order_meta_keys( $this->special_fields, $order );
		
		return $billing_fields + $shipping_fields + $special_fields;
	}
	
	function get_email_order_meta_keys( $fields, $order ){

		$formatted = array();

		if( $fields ){

			$field_values = $this->get_field_values( $fields, $order );

			foreach( $field_values as $field ){

				if( isset($field['display_position']) && ($field['display_position'] != '2') ){
					continue;
				}

				if( isset($field['show_in_email']) && ! $field['show_in_email'] ){
					continue;
				}

				$value = $field['value'];
				$field_key = $field['field_key'];

				$formatted[$field_key] = array(
					'label' => $field['label'],
					'value' => $value
				);	
			}
		}
		return $formatted;
	}


	/**
	 * Display meta info in admin order after billing address
	 * 
	 */
	function display_fields_for_admin(){
		add_action( 'woocommerce_admin_order_data_after_billing_address', [$this,'display_in_admin_order_meta_billing'],20 );
		add_action( 'woocommerce_admin_order_data_after_shipping_address', [$this,'display_in_admin_order_meta_shipping'],20 );
	}

	function display_in_admin_order_meta_billing( $order ){

		$this->admin_show_fields( $this->special_fields, $order );

		$this->admin_show_fields( $this->billing_fields, $order );

	}

	function display_in_admin_order_meta_shipping( $order ){

		$this->admin_show_fields( $this->shipping_fields, $order );
	}

	function admin_show_fields( $fields, $order ){

		if( $formatted = $this->get_field_values( $fields, $order ) ){
			foreach( $formatted as $field ){
				if( isset($field['show_in_admin']) && ! $field['show_in_admin'] ){
					continue;
				}
				echo '<p><strong style="display:block">', $field['label'], ':</strong> ', $field['value'],'</p>';
			}
		}
	}


	function get_field_values( $fields, $order  ){
		$formatted = array();
		if( $fields ){
			foreach( $fields as $field_key => $field ){

				$saved_field_value = $this->get_order_meta( $field_key, $order );

				if( in_array($field['type'], array('select','radio','checkbox') ) ){
					$saved_field_value = $field['options'][$saved_field_value] ?? false;
				} 

				$formatted[] = array(
					'label'                 => esc_html($field['label']),
					'value'                 => $saved_field_value,
					'show_in_email'         => $field['show_in_email'] ?? true,
					'show_in_admin'         => $field['show_in_admin'] ?? true,
					'show_in_order_details' => $field['show_in_order_details'] ?? true,
					'field_key'             => $field_key,
					'display_position'	    => $field['display_position'] ?? '1'
				);
			}
			return $formatted;
		}
		return false;
	}


	function get_order_meta( $field_key, $order ){
		return get_post_meta( $order->get_id(), '_' . $field_key,true );
	}

}

代码如何使用

定义好 Sola_Custom_WC_Checkout_Fields 这个class后,我们来使用它。继续在functions.php里写。

add_action( 'init', function(){

  $billing_fields = array(
    'billing_field_company_type' => array(
      'type'          => 'select',
      'label'         => '公司类型',
      'placeholder'   => '',
      'required'      => false,
      'class'         => array('form-row-wide'),
      'label_class'   => array(),
      'clear'         => true,
      'priority'      => 20,
      'options'       => array('1'=>'有限责任公司','2' => '个人独资企业', '3' => '外商独资公司'),
      'default'       => '1',
      'display_position' => '1'
    ),
  );

  $shipping_fields = array();
  
  $special_fields = array(
    'order_dropshipping' => array(
      'type'          => 'radio',
      'label'         => '是否为Dropshipping订单',
      'placeholder'   => '',
      'required'      => false,
      'class'         => array('form-row-wide'),
      'label_class'   => array(),
      'clear'         => true,
      'priority'      => 1,
      'options'       => array('yes'=>'是','no' => '否'),
      'default'       => 'no',
      'display_position' => '2'
    ),
    
  );

  new Sola_Custom_WC_Checkout_Fields( $billing_fields,$shipping_fields, $special_fields );
});

定义了两个字段,billing表单里增加公司类型,order notes下面增加 是否为Dropshipping订单 。然后下单,结账,效果如下。

WooCommerce自定义结账字段
WooCommerce自定义结账字段

结算完成后,订单详情和后台订单详情显示自定义字段。

WooCommerce自定义结账字段 - 订单详情
WooCommerce自定义结账字段 - 后台订单详情

邮件里也会显示。

邮件自定义字段