Skip to content

Instantly share code, notes, and snippets.

@psaikali
Created December 28, 2019 13:05
Show Gist options
  • Save psaikali/728be703ac016235bf587051882521c5 to your computer and use it in GitHub Desktop.
Save psaikali/728be703ac016235bf587051882521c5 to your computer and use it in GitHub Desktop.
Add custom WooCommerce checkout fields, add an extra fee to the cart, validate the fields before processing the order and save them on the order metadata.
<?php
/**
* Plugin Name: WooCommerce Custom Checkout Fields
* Description: Add some custom "emergency level" extra fields on the WooCommerce Checkout page. Save this custom data in each order metadata.
* Author: Pierre Saïkali
* Author URI: https://saika.li
* Text Domain: wc_ccf
* Domain Path: /languages/
* Version: 1.0.0
* Full Tutorial: https://mosaika.fr/personnaliser-tunnel-commande-woocommerce/
*/
namespace WC_CCF;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
//========================================
//
// ## ## ###### ## ## ####
// ## ## ## ## ## ##
// ## ## ## ## ## ###
// ## ## ## ## ## ##
// ##### ## ## ###### ####
//
//========================================
/**
* Get form posted data from WooCommerce: in some cases, it's serialized in a "post_data" key.
*
* @return array
*/
function get_wc_posted_data() {
$form_data = $_POST;
if ( isset( $_POST['post_data'] ) ) {
parse_str( $_POST['post_data'], $form_data );
}
return $form_data;
}
//==========================================================
//
// #### ## #### ##### ## ### ## ##
// ## ## ## ## ## ## ## ## ## ## ##
// ## ## ## ### ##### ## ## ## ####
// ## ## ## ## ## ## ####### ##
// #### ## #### ## ###### ## ## ##
//
//==========================================================
/**
* Display our custom extra fields: a checkbox and a select dropdown.
*
* @return void
*/
function display_custom_shipping_methods() {
?>
<fieldset class="extra-fields">
<legend><?php _e( 'Emergency', 'wc_ccf' ); ?></legend>
<p>
<label for="msk-urgent-order">
<input type="checkbox" name="msk-urgent-order" id="msk-urgent-order" value="on" class="msk-custom-field" />
<span><?php esc_html_e( 'This is an emergency, send me my order ASAP!', 'wc_ccf' ); ?></span>
</label>
</p>
<p>
<label for="msk-urgency-level">
<span><?php esc_html_e( 'When do you want this shipped?', 'wc_ccf' ); ?></span>
<select name="msk-urgency-level" id="msk-urgency-level" class="msk-custom-field">
<option value="_null"><?php esc_attr_e( '— Select an option', 'wc_ccf' ); ?></option>
<option value="next_hour"><?php esc_attr_e( 'In the next hour', 'wc_ccf' ); ?></option>
<option value="next_6hours"><?php esc_attr_e( 'In the next 6 hours', 'wc_ccf' ); ?></option>
<option value="next_12hours"><?php esc_attr_e( 'In the next 12 hours', 'wc_ccf' ); ?></option>
<option value="tomorrow"><?php esc_attr_e( 'By tomorrow', 'wc_ccf' ); ?></option>
</select>
</label>
</p>
</fieldset>
<script>
// When one of our custom field value changes, tell WC to update the checkout data (AJAX request to the back-end).
jQuery(document).ready(function($) {
$('form.checkout').on('change', '.msk-custom-field', function() {
$('body').trigger('update_checkout');
});
});
</script>
<?php
}
add_action( 'woocommerce_checkout_billing', __NAMESPACE__ . '\\display_custom_shipping_methods', 10 );
//==================================================================
//
// ## ## ### ## ## #### ### ###### #####
// ## ## ## ## ## ## ## ## ## ## ## ##
// ## ## ## ## ## ## ## ## ## ## ## #####
// ## ## ####### ## ## ## ## ####### ## ##
// ### ## ## ###### ## #### ## ## ## #####
//
//==================================================================
/**
* Validate checkout fields before processing the WooCommerce "order"
*
* @return void
*/
function validate_all_checkout_fields() {
$errors = [];
if ( isset( $_POST['msk-urgent-order'] ) && $_POST['msk-urgent-order'] === 'on' ) {
if ( ! isset( $_POST['msk-urgency-level'] ) || $_POST['msk-urgency-level'] === '_null' ) {
$errors[] = __( 'Please tell us the <strong>level of emergency</strong>.', 'wc_ccf' );
}
elseif ( isset( $_POST['msk-urgency-level'] ) && ! in_array( $_POST['msk-urgency-level'], [ 'next_hour', 'next_6hours', 'next_12hours', 'tomorrow' ], true ) ) {
$errors[] = __( 'This <strong>level of emergency</strong> is invalid.', 'wc_ccf' );
}
}
/**
* If we have errors,
*/
if ( ! empty( $errors ) ) {
foreach ( $errors as $error ) {
wc_add_notice( $error, 'error' );
}
}
}
add_action( 'woocommerce_checkout_process', __NAMESPACE__ . '\\validate_all_checkout_fields' );
//==============================================================================
//
// #### ### ##### ###### ##### ##### ## #### #####
// ## ## ## ## ## ## ## ## ## ## ## ## ##
// ## ## ## ##### ## ##### ##### ## ## #####
// ## ####### ## ## ## ## ## ## ## ## ##
// #### ## ## ## ## ## ## ## ## ## #### #####
//
//==============================================================================
/**
* Add emergency fee depending on the emergency level selected.
*
* @param WC_Cart $cart_object
* @return void
*/
function add_emergency_fee( $cart_object ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) || ! is_checkout() ) {
return;
}
// Only trigger this logic once.
if ( did_action( 'woocommerce_cart_calculate_fees' ) >= 2 ) {
return;
}
$form_data = get_wc_posted_data();
// Do not calculate anything if we do not have our emergency field checked or no emergency level is provided.
if ( ! isset( $form_data['msk-urgent-order'], $form_data['msk-urgency-level'] ) || $form_data['msk-urgent-order'] !== 'on' ) {
return;
}
// Store a mutiplier/coefficient to calculate the emergency fee.
$multipliers = [
'next_hour' => 2,
'next_6hours' => 1.5,
'next_12hours' => 1.25,
'tomorrow' => 1.1,
];
if ( ! array_key_exists( $form_data['msk-urgency-level'], $multipliers ) ) {
return;
}
// Add the extra fee to the user cart.
WC()->cart->add_fee(
__( 'Emergency processing fee', 'wc_ccf' ),
WC()->cart->subtotal * $multipliers[ $form_data['msk-urgency-level'] ],
false
);
}
add_action( 'woocommerce_cart_calculate_fees', __NAMESPACE__ . '\\add_emergency_fee' );
//========================================================================================
//
// ##### ##### #### ###### ##### ##### #### ##### #####
// ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
// ##### ## ## ### ## ######## ## ## ##### ## ## ##### #####
// ## ## ## ## ## ## ## ## ## ## ## ## ## ##
// ## ##### #### ## ##### ## ## #### ##### ## ##
//
//========================================================================================
/**
* For tutorial sake, enable the order process without payment.
*
* @param boolean $needed
* @return boolean
*/
function woocommerce_order_needs_payment( $needed ) {
return false;
}
add_filter( 'woocommerce_cart_needs_payment', __NAMESPACE__ . '\\woocommerce_order_needs_payment' );
/**
* Allow our custom checkout data (emergency checkbox & level) to be included in order data array.
*
* @param array $data
* @return array
*/
function add_custom_checkout_data_to_order_data_array( $data ) {
$custom_keys = [
'msk-urgent-order',
'msk-urgency-level',
];
foreach ( $custom_keys as $key ) {
if ( isset( $_POST[ $key ] ) ) {
$data[ $key ] = sanitize_text_field( $_POST[ $key ] );
}
}
return $data;
}
add_filter( 'woocommerce_checkout_posted_data', __NAMESPACE__ . '\\add_custom_checkout_data_to_order_data_array', 10, 2 );
/**
* Save our custom checkout data on the order metadata.
*
* @param integer $order_id
* @param array $data Posted data.
* @return void
*/
function save_custom_checkout_data_in_order_metadata( $order_id, $data ) {
$custom_keys = [
'msk-urgent-order',
'msk-urgency-level',
];
$order = wc_get_order( $order_id );
foreach ( $custom_keys as $key ) {
if ( isset( $data[ $key ] ) ) {
$order->add_meta_data( $key, $data[ $key ] );
}
}
$order->save();
}
add_action( 'woocommerce_checkout_update_order_meta', __NAMESPACE__ . '\\save_custom_checkout_data_in_order_metadata', 10, 2 );
@ninazadrin
Copy link

Hi, how do you add that field in emails?

@airkhu
Copy link

airkhu commented Dec 13, 2022

Thanks! Working!
But I want make 2 such blocks.
How to do it?
1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment