I’ll build this as a theme-side WooCommerce checkout module, with safe guards so it won’t break if WooCommerce is inactive. I’ll include checkout fields, validation, UX, notices, AJAX fragments, order meta, security, accessibility, and performance hooks in one inc/woocommerce/checkout.php file.
Below is an advanced inc/woocommerce/checkout.php file for your RX Theme. It is made for the classic WooCommerce checkout shortcode/page, not the new Checkout Block. WooCommerce’s own docs confirm that classic checkout customization uses hooks/filters such as woocommerce_checkout_fields, checkout validation actions, and order meta saving hooks.
Add this file here:
rx-theme/
└── inc/
└── woocommerce/
└── checkout.php
Then load it from your theme functions.php:
/**
* RX Theme WooCommerce checkout functions.
*/
if ( file_exists( get_template_directory() . '/inc/woocommerce/checkout.php' ) ) {
require_once get_template_directory() . '/inc/woocommerce/checkout.php';
}
inc/woocommerce/checkout.php
<?php
/**
* RX Theme - Advanced WooCommerce Checkout Customization
*
* File: inc/woocommerce/checkout.php
*
* Features:
* - Safe WooCommerce detection
* - Checkout field optimization
* - Extra billing, shipping, and order fields
* - Bangladesh-friendly phone validation
* - Required privacy/terms checkbox
* - Delivery instruction field
* - Preferred delivery date and time
* - Customer type field
* - Gift order option
* - Invoice request option
* - Save custom fields to HPOS-compatible order meta
* - Show custom meta in admin order page
* - Show selected custom meta in customer emails
* - Checkout notices and trust messages
* - Custom checkout button text
* - Login/register UX enhancements
* - Order notes placeholder change
* - Coupon area helper message
* - Terms checkbox helper
* - Checkout body classes
* - Checkout fragments for AJAX refresh
* - Basic CSS and JS injection for checkout page only
*
* Important:
* This file is designed mainly for the classic WooCommerce checkout.
* If your checkout page uses the new WooCommerce Checkout Block,
* many classic PHP checkout field hooks will not display frontend fields.
*/
defined( 'ABSPATH' ) || exit;
if ( ! class_exists( 'WooCommerce' ) ) {
return;
}
if ( ! defined( 'RX_THEME_VERSION' ) ) {
define( 'RX_THEME_VERSION', wp_get_theme()->get( 'Version' ) ? wp_get_theme()->get( 'Version' ) : '1.0.0' );
}
/**
* Main RX checkout class.
*/
if ( ! class_exists( 'RX_Theme_WooCommerce_Checkout' ) ) {
final class RX_Theme_WooCommerce_Checkout {
/**
* Singleton instance.
*
* @var RX_Theme_WooCommerce_Checkout|null
*/
private static $instance = null;
/**
* Get instance.
*
* @return RX_Theme_WooCommerce_Checkout
*/
public static function instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor.
*/
private function __construct() {
add_action( 'after_setup_theme', array( $this, 'theme_support' ) );
add_filter( 'woocommerce_checkout_fields', array( $this, 'customize_checkout_fields' ), 30 );
add_filter( 'woocommerce_default_address_fields', array( $this, 'customize_default_address_fields' ), 30 );
add_filter( 'woocommerce_billing_fields', array( $this, 'customize_billing_fields' ), 30 );
add_filter( 'woocommerce_shipping_fields', array( $this, 'customize_shipping_fields' ), 30 );
add_action( 'woocommerce_before_checkout_form', array( $this, 'before_checkout_message' ), 5 );
add_action( 'woocommerce_before_checkout_form', array( $this, 'classic_checkout_warning_for_blocks' ), 6 );
add_action( 'woocommerce_checkout_before_customer_details', array( $this, 'checkout_progress_steps' ), 5 );
add_action( 'woocommerce_checkout_billing', array( $this, 'billing_section_notice' ), 5 );
add_action( 'woocommerce_checkout_shipping', array( $this, 'shipping_section_notice' ), 5 );
add_action( 'woocommerce_before_order_notes', array( $this, 'before_order_notes_notice' ), 5 );
add_action( 'woocommerce_review_order_before_payment', array( $this, 'secure_payment_notice' ), 5 );
add_action( 'woocommerce_review_order_after_order_total', array( $this, 'after_order_total_message' ), 20 );
add_action( 'woocommerce_after_checkout_form', array( $this, 'after_checkout_message' ), 20 );
add_filter( 'woocommerce_order_button_text', array( $this, 'order_button_text' ) );
add_filter( 'woocommerce_checkout_login_message', array( $this, 'login_message' ) );
add_filter( 'woocommerce_checkout_coupon_message', array( $this, 'coupon_message' ) );
add_filter( 'woocommerce_enable_order_notes_field', array( $this, 'enable_order_notes_field' ) );
add_filter( 'woocommerce_terms_is_checked_default', array( $this, 'terms_checked_default' ) );
add_action( 'woocommerce_checkout_process', array( $this, 'validate_checkout_fields' ) );
add_action( 'woocommerce_after_checkout_validation', array( $this, 'after_checkout_validation' ), 10, 2 );
add_action( 'woocommerce_checkout_create_order', array( $this, 'save_custom_fields_to_order' ), 20, 2 );
add_action( 'woocommerce_admin_order_data_after_billing_address', array( $this, 'show_admin_billing_meta' ), 20 );
add_action( 'woocommerce_admin_order_data_after_shipping_address', array( $this, 'show_admin_shipping_meta' ), 20 );
add_action( 'woocommerce_email_after_order_table', array( $this, 'show_email_order_meta' ), 20, 4 );
add_action( 'woocommerce_thankyou', array( $this, 'thankyou_custom_message' ), 20 );
add_filter( 'woocommerce_update_order_review_fragments', array( $this, 'checkout_ajax_fragments' ) );
add_filter( 'body_class', array( $this, 'body_classes' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_checkout_assets' ), 30 );
add_filter( 'woocommerce_cart_needs_payment', array( $this, 'cart_needs_payment_filter' ), 10, 2 );
add_filter( 'woocommerce_available_payment_gateways', array( $this, 'filter_payment_gateways' ), 20 );
add_filter( 'woocommerce_package_rates', array( $this, 'filter_shipping_rates' ), 20, 2 );
add_action( 'woocommerce_checkout_update_user_meta', array( $this, 'save_customer_meta' ), 20, 2 );
}
/**
* Theme support.
*
* @return void
*/
public function theme_support() {
add_theme_support( 'woocommerce' );
}
/**
* Check checkout page.
*
* @return bool
*/
private function is_checkout_page() {
return function_exists( 'is_checkout' ) && is_checkout() && ! is_order_received_page();
}
/**
* Sanitize posted value.
*
* @param string $key Field key.
* @return string
*/
private function posted_text( $key ) {
$value = isset( $_POST[ $key ] ) ? wp_unslash( $_POST[ $key ] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
return sanitize_text_field( $value );
}
/**
* Sanitize textarea posted value.
*
* @param string $key Field key.
* @return string
*/
private function posted_textarea( $key ) {
$value = isset( $_POST[ $key ] ) ? wp_unslash( $_POST[ $key ] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
return sanitize_textarea_field( $value );
}
/**
* Check posted checkbox.
*
* @param string $key Field key.
* @return string
*/
private function posted_checkbox( $key ) {
return isset( $_POST[ $key ] ) ? 'yes' : 'no'; // phpcs:ignore WordPress.Security.NonceVerification.Missing
}
/**
* Customize default address fields.
*
* @param array $fields Fields.
* @return array
*/
public function customize_default_address_fields( $fields ) {
if ( isset( $fields['first_name'] ) ) {
$fields['first_name']['priority'] = 10;
$fields['first_name']['placeholder'] = esc_html__( 'First name', 'rx-theme' );
$fields['first_name']['class'] = array( 'form-row-first' );
}
if ( isset( $fields['last_name'] ) ) {
$fields['last_name']['priority'] = 20;
$fields['last_name']['placeholder'] = esc_html__( 'Last name', 'rx-theme' );
$fields['last_name']['class'] = array( 'form-row-last' );
}
if ( isset( $fields['company'] ) ) {
$fields['company']['priority'] = 30;
$fields['company']['required'] = false;
$fields['company']['placeholder'] = esc_html__( 'Company name, clinic, chamber, or organization', 'rx-theme' );
}
if ( isset( $fields['address_1'] ) ) {
$fields['address_1']['priority'] = 50;
$fields['address_1']['placeholder'] = esc_html__( 'House, road, village, area', 'rx-theme' );
}
if ( isset( $fields['address_2'] ) ) {
$fields['address_2']['priority'] = 60;
$fields['address_2']['placeholder'] = esc_html__( 'Apartment, floor, landmark, optional', 'rx-theme' );
$fields['address_2']['required'] = false;
}
if ( isset( $fields['city'] ) ) {
$fields['city']['priority'] = 70;
$fields['city']['placeholder'] = esc_html__( 'City / Upazila', 'rx-theme' );
}
if ( isset( $fields['state'] ) ) {
$fields['state']['priority'] = 80;
$fields['state']['placeholder'] = esc_html__( 'District / Division', 'rx-theme' );
}
if ( isset( $fields['postcode'] ) ) {
$fields['postcode']['priority'] = 90;
$fields['postcode']['required'] = false;
$fields['postcode']['placeholder'] = esc_html__( 'Postcode, optional', 'rx-theme' );
}
if ( isset( $fields['country'] ) ) {
$fields['country']['priority'] = 40;
}
return $fields;
}
/**
* Customize billing fields.
*
* @param array $fields Fields.
* @return array
*/
public function customize_billing_fields( $fields ) {
if ( isset( $fields['billing_phone'] ) ) {
$fields['billing_phone']['priority'] = 35;
$fields['billing_phone']['label'] = esc_html__( 'Mobile number', 'rx-theme' );
$fields['billing_phone']['placeholder'] = esc_html__( 'Example: 01XXXXXXXXX', 'rx-theme' );
$fields['billing_phone']['class'] = array( 'form-row-wide', 'rx-phone-field' );
$fields['billing_phone']['validate'] = array( 'phone' );
}
if ( isset( $fields['billing_email'] ) ) {
$fields['billing_email']['priority'] = 36;
$fields['billing_email']['placeholder'] = esc_html__( 'Your active email address', 'rx-theme' );
$fields['billing_email']['class'] = array( 'form-row-wide', 'rx-email-field' );
}
return $fields;
}
/**
* Customize shipping fields.
*
* @param array $fields Fields.
* @return array
*/
public function customize_shipping_fields( $fields ) {
if ( isset( $fields['shipping_phone'] ) ) {
$fields['shipping_phone']['required'] = false;
}
return $fields;
}
/**
* Main checkout field customization.
*
* @param array $fields Fields.
* @return array
*/
public function customize_checkout_fields( $fields ) {
$fields['billing']['billing_customer_type'] = array(
'type' => 'select',
'label' => esc_html__( 'Customer type', 'rx-theme' ),
'required' => true,
'class' => array( 'form-row-wide', 'rx-customer-type' ),
'priority' => 37,
'options' => array(
'' => esc_html__( 'Select customer type', 'rx-theme' ),
'general' => esc_html__( 'General customer', 'rx-theme' ),
'patient' => esc_html__( 'Patient', 'rx-theme' ),
'doctor' => esc_html__( 'Doctor / medical professional', 'rx-theme' ),
'clinic' => esc_html__( 'Clinic / hospital / organization', 'rx-theme' ),
'student' => esc_html__( 'Student', 'rx-theme' ),
),
'autocomplete' => 'off',
);
$fields['billing']['billing_whatsapp'] = array(
'type' => 'tel',
'label' => esc_html__( 'WhatsApp number', 'rx-theme' ),
'placeholder' => esc_html__( 'Optional WhatsApp number', 'rx-theme' ),
'required' => false,
'class' => array( 'form-row-wide', 'rx-whatsapp-field' ),
'priority' => 38,
);
$fields['billing']['billing_secondary_phone'] = array(
'type' => 'tel',
'label' => esc_html__( 'Secondary phone', 'rx-theme' ),
'placeholder' => esc_html__( 'Optional backup phone number', 'rx-theme' ),
'required' => false,
'class' => array( 'form-row-wide', 'rx-secondary-phone-field' ),
'priority' => 39,
);
$fields['billing']['billing_nid_or_id'] = array(
'type' => 'text',
'label' => esc_html__( 'NID / Student ID / Professional ID', 'rx-theme' ),
'placeholder' => esc_html__( 'Optional identification number', 'rx-theme' ),
'required' => false,
'class' => array( 'form-row-wide', 'rx-id-field' ),
'priority' => 100,
);
$fields['shipping']['shipping_delivery_area'] = array(
'type' => 'text',
'label' => esc_html__( 'Delivery area / landmark', 'rx-theme' ),
'placeholder' => esc_html__( 'Nearest landmark for easier delivery', 'rx-theme' ),
'required' => false,
'class' => array( 'form-row-wide', 'rx-delivery-area' ),
'priority' => 95,
);
$fields['order']['rx_preferred_delivery_date'] = array(
'type' => 'date',
'label' => esc_html__( 'Preferred delivery date', 'rx-theme' ),
'placeholder' => esc_html__( 'Choose a preferred date', 'rx-theme' ),
'required' => false,
'class' => array( 'form-row-first', 'rx-delivery-date' ),
'priority' => 10,
);
$fields['order']['rx_preferred_delivery_time'] = array(
'type' => 'select',
'label' => esc_html__( 'Preferred delivery time', 'rx-theme' ),
'required' => false,
'class' => array( 'form-row-last', 'rx-delivery-time' ),
'priority' => 11,
'options' => array(
'' => esc_html__( 'Any time', 'rx-theme' ),
'morning' => esc_html__( 'Morning', 'rx-theme' ),
'afternoon' => esc_html__( 'Afternoon', 'rx-theme' ),
'evening' => esc_html__( 'Evening', 'rx-theme' ),
'night' => esc_html__( 'Night', 'rx-theme' ),
),
);
$fields['order']['rx_delivery_instruction'] = array(
'type' => 'textarea',
'label' => esc_html__( 'Delivery instruction', 'rx-theme' ),
'placeholder' => esc_html__( 'Write delivery notes, landmark, timing, or special instruction', 'rx-theme' ),
'required' => false,
'class' => array( 'form-row-wide', 'rx-delivery-instruction' ),
'priority' => 12,
);
$fields['order']['rx_is_gift_order'] = array(
'type' => 'checkbox',
'label' => esc_html__( 'This is a gift order', 'rx-theme' ),
'required' => false,
'class' => array( 'form-row-wide', 'rx-gift-order' ),
'priority' => 13,
);
$fields['order']['rx_gift_message'] = array(
'type' => 'textarea',
'label' => esc_html__( 'Gift message', 'rx-theme' ),
'placeholder' => esc_html__( 'Optional short gift message', 'rx-theme' ),
'required' => false,
'class' => array( 'form-row-wide', 'rx-gift-message' ),
'priority' => 14,
);
$fields['order']['rx_invoice_request'] = array(
'type' => 'checkbox',
'label' => esc_html__( 'I need an invoice', 'rx-theme' ),
'required' => false,
'class' => array( 'form-row-wide', 'rx-invoice-request' ),
'priority' => 15,
);
$fields['order']['rx_invoice_company'] = array(
'type' => 'text',
'label' => esc_html__( 'Invoice company / organization name', 'rx-theme' ),
'placeholder' => esc_html__( 'Optional invoice name', 'rx-theme' ),
'required' => false,
'class' => array( 'form-row-wide', 'rx-invoice-company' ),
'priority' => 16,
);
$fields['order']['rx_customer_consent'] = array(
'type' => 'checkbox',
'label' => esc_html__( 'I confirm that my checkout information is correct.', 'rx-theme' ),
'required' => true,
'class' => array( 'form-row-wide', 'rx-customer-consent' ),
'priority' => 99,
);
if ( isset( $fields['order']['order_comments'] ) ) {
$fields['order']['order_comments']['label'] = esc_html__( 'Order notes', 'rx-theme' );
$fields['order']['order_comments']['placeholder'] = esc_html__( 'Special notes about your order, optional', 'rx-theme' );
$fields['order']['order_comments']['priority'] = 20;
}
return $fields;
}
/**
* Checkout notice before form.
*
* @return void
*/
public function before_checkout_message() {
if ( ! $this->is_checkout_page() ) {
return;
}
echo '<div class="rx-checkout-alert rx-checkout-alert-info">';
echo esc_html__( 'Please review your billing, shipping, and payment information carefully before placing the order.', 'rx-theme' );
echo '</div>';
}
/**
* Warn if checkout block may be used.
*
* @return void
*/
public function classic_checkout_warning_for_blocks() {
if ( ! current_user_can( 'manage_woocommerce' ) || ! $this->is_checkout_page() ) {
return;
}
global $post;
if ( $post && has_block( 'woocommerce/checkout', $post ) ) {
echo '<div class="rx-checkout-alert rx-checkout-alert-warning">';
echo esc_html__( 'Admin notice: This page appears to use the WooCommerce Checkout Block. Some classic PHP checkout field customizations may not appear on the frontend.', 'rx-theme' );
echo '</div>';
}
}
/**
* Progress steps.
*
* @return void
*/
public function checkout_progress_steps() {
echo '<div class="rx-checkout-steps" aria-label="' . esc_attr__( 'Checkout steps', 'rx-theme' ) . '">';
echo '<span class="rx-checkout-step is-active">' . esc_html__( '1. Details', 'rx-theme' ) . '</span>';
echo '<span class="rx-checkout-step">' . esc_html__( '2. Delivery', 'rx-theme' ) . '</span>';
echo '<span class="rx-checkout-step">' . esc_html__( '3. Payment', 'rx-theme' ) . '</span>';
echo '<span class="rx-checkout-step">' . esc_html__( '4. Complete', 'rx-theme' ) . '</span>';
echo '</div>';
}
/**
* Billing notice.
*
* @return void
*/
public function billing_section_notice() {
echo '<p class="rx-checkout-section-note">';
echo esc_html__( 'Use your real name and active mobile number so we can contact you if needed.', 'rx-theme' );
echo '</p>';
}
/**
* Shipping notice.
*
* @return void
*/
public function shipping_section_notice() {
echo '<p class="rx-checkout-section-note">';
echo esc_html__( 'Add a complete shipping address and landmark to reduce delivery delay.', 'rx-theme' );
echo '</p>';
}
/**
* Before order notes.
*
* @return void
*/
public function before_order_notes_notice() {
echo '<div class="rx-checkout-extra-heading">';
echo '<h3>' . esc_html__( 'Delivery preference and extra information', 'rx-theme' ) . '</h3>';
echo '<p>' . esc_html__( 'These fields are optional, but they help us process your order more accurately.', 'rx-theme' ) . '</p>';
echo '</div>';
}
/**
* Secure payment notice.
*
* @return void
*/
public function secure_payment_notice() {
echo '<div class="rx-secure-payment-note">';
echo esc_html__( 'Your payment and personal information are processed securely.', 'rx-theme' );
echo '</div>';
}
/**
* After total message.
*
* @return void
*/
public function after_order_total_message() {
echo '<tr class="rx-checkout-total-message"><td colspan="2">';
echo esc_html__( 'Final payable amount may depend on shipping method, coupon, tax, or payment gateway rules.', 'rx-theme' );
echo '</td></tr>';
}
/**
* After checkout message.
*
* @return void
*/
public function after_checkout_message() {
if ( ! $this->is_checkout_page() ) {
return;
}
echo '<div class="rx-checkout-footer-help">';
echo esc_html__( 'Need help with your order? Contact support before submitting the checkout form.', 'rx-theme' );
echo '</div>';
}
/**
* Order button text.
*
* @param string $text Button text.
* @return string
*/
public function order_button_text( $text ) {
return esc_html__( 'Confirm and Place Order', 'rx-theme' );
}
/**
* Login message.
*
* @param string $message Login message.
* @return string
*/
public function login_message( $message ) {
return esc_html__( 'Already have an account? Log in to use your saved address and faster checkout.', 'rx-theme' );
}
/**
* Coupon message.
*
* @param string $message Coupon message.
* @return string
*/
public function coupon_message( $message ) {
return esc_html__( 'Have a coupon code? Enter it here and your cart total will update automatically.', 'rx-theme' );
}
/**
* Enable order notes.
*
* @param bool $enabled Enabled.
* @return bool
*/
public function enable_order_notes_field( $enabled ) {
return true;
}
/**
* Terms default state.
*
* @param bool $checked Checked.
* @return bool
*/
public function terms_checked_default( $checked ) {
return false;
}
/**
* Validate checkout fields.
*
* @return void
*/
public function validate_checkout_fields() {
$phone = $this->posted_text( 'billing_phone' );
$whatsapp = $this->posted_text( 'billing_whatsapp' );
$secondary_phone = $this->posted_text( 'billing_secondary_phone' );
$customer_type = $this->posted_text( 'billing_customer_type' );
$delivery_date = $this->posted_text( 'rx_preferred_delivery_date' );
$is_gift_order = $this->posted_checkbox( 'rx_is_gift_order' );
$gift_message = $this->posted_textarea( 'rx_gift_message' );
$invoice_request = $this->posted_checkbox( 'rx_invoice_request' );
$invoice_company = $this->posted_text( 'rx_invoice_company' );
$customer_consent = $this->posted_checkbox( 'rx_customer_consent' );
if ( empty( $customer_type ) ) {
wc_add_notice( esc_html__( 'Please select your customer type.', 'rx-theme' ), 'error' );
}
if ( ! empty( $phone ) && ! $this->is_valid_phone( $phone ) ) {
wc_add_notice( esc_html__( 'Please enter a valid mobile number.', 'rx-theme' ), 'error' );
}
if ( ! empty( $whatsapp ) && ! $this->is_valid_phone( $whatsapp ) ) {
wc_add_notice( esc_html__( 'Please enter a valid WhatsApp number.', 'rx-theme' ), 'error' );
}
if ( ! empty( $secondary_phone ) && ! $this->is_valid_phone( $secondary_phone ) ) {
wc_add_notice( esc_html__( 'Please enter a valid secondary phone number.', 'rx-theme' ), 'error' );
}
if ( ! empty( $delivery_date ) && ! $this->is_valid_future_or_today_date( $delivery_date ) ) {
wc_add_notice( esc_html__( 'Preferred delivery date cannot be in the past.', 'rx-theme' ), 'error' );
}
if ( 'yes' === $is_gift_order && empty( $gift_message ) ) {
wc_add_notice( esc_html__( 'Please write a gift message or uncheck the gift order option.', 'rx-theme' ), 'error' );
}
if ( 'yes' === $invoice_request && empty( $invoice_company ) ) {
wc_add_notice( esc_html__( 'Please enter invoice company or organization name.', 'rx-theme' ), 'error' );
}
if ( 'yes' !== $customer_consent ) {
wc_add_notice( esc_html__( 'Please confirm that your checkout information is correct.', 'rx-theme' ), 'error' );
}
}
/**
* Extra validation after WooCommerce validation.
*
* @param array $data Posted data.
* @param WP_Error $errors Errors.
* @return void
*/
public function after_checkout_validation( $data, $errors ) {
if ( ! empty( $data['billing_email'] ) && ! is_email( $data['billing_email'] ) ) {
$errors->add( 'rx_invalid_email', esc_html__( 'Please enter a valid email address.', 'rx-theme' ) );
}
if ( ! empty( $data['billing_postcode'] ) && strlen( preg_replace( '/\D+/', '', $data['billing_postcode'] ) ) > 10 ) {
$errors->add( 'rx_invalid_postcode', esc_html__( 'Please enter a shorter valid postcode.', 'rx-theme' ) );
}
}
/**
* Basic phone validation.
*
* @param string $phone Phone.
* @return bool
*/
private function is_valid_phone( $phone ) {
$phone = preg_replace( '/[\s\-\(\)\.]+/', '', $phone );
if ( preg_match( '/^\+?[0-9]{7,15}$/', $phone ) ) {
return true;
}
return false;
}
/**
* Validate date.
*
* @param string $date Date.
* @return bool
*/
private function is_valid_future_or_today_date( $date ) {
$timestamp = strtotime( $date );
if ( false === $timestamp ) {
return false;
}
$today = strtotime( current_time( 'Y-m-d' ) );
return $timestamp >= $today;
}
/**
* Save fields to order meta.
*
* Uses WC_Order::update_meta_data, which is better for modern WooCommerce/HPOS compatibility.
* WooCommerce docs recommend using checkout hooks and filters to add fields and save values.
*
* @param WC_Order $order Order object.
* @param array $data Checkout data.
* @return void
*/
public function save_custom_fields_to_order( $order, $data ) {
$meta_fields = array(
'billing_customer_type' => $this->posted_text( 'billing_customer_type' ),
'billing_whatsapp' => $this->posted_text( 'billing_whatsapp' ),
'billing_secondary_phone' => $this->posted_text( 'billing_secondary_phone' ),
'billing_nid_or_id' => $this->posted_text( 'billing_nid_or_id' ),
'shipping_delivery_area' => $this->posted_text( 'shipping_delivery_area' ),
'rx_preferred_delivery_date' => $this->posted_text( 'rx_preferred_delivery_date' ),
'rx_preferred_delivery_time' => $this->posted_text( 'rx_preferred_delivery_time' ),
'rx_delivery_instruction' => $this->posted_textarea( 'rx_delivery_instruction' ),
'rx_is_gift_order' => $this->posted_checkbox( 'rx_is_gift_order' ),
'rx_gift_message' => $this->posted_textarea( 'rx_gift_message' ),
'rx_invoice_request' => $this->posted_checkbox( 'rx_invoice_request' ),
'rx_invoice_company' => $this->posted_text( 'rx_invoice_company' ),
'rx_customer_consent' => $this->posted_checkbox( 'rx_customer_consent' ),
);
foreach ( $meta_fields as $key => $value ) {
if ( '' !== $value && null !== $value ) {
$order->update_meta_data( '_' . $key, $value );
}
}
}
/**
* Save some fields to customer meta.
*
* @param int $customer_id Customer ID.
* @param array $posted Posted data.
* @return void
*/
public function save_customer_meta( $customer_id, $posted ) {
if ( ! $customer_id ) {
return;
}
$customer_type = $this->posted_text( 'billing_customer_type' );
$whatsapp = $this->posted_text( 'billing_whatsapp' );
if ( $customer_type ) {
update_user_meta( $customer_id, 'rx_billing_customer_type', $customer_type );
}
if ( $whatsapp ) {
update_user_meta( $customer_id, 'rx_billing_whatsapp', $whatsapp );
}
}
/**
* Show billing meta in admin.
*
* @param WC_Order $order Order.
* @return void
*/
public function show_admin_billing_meta( $order ) {
$items = array(
esc_html__( 'Customer type', 'rx-theme' ) => $order->get_meta( '_billing_customer_type' ),
esc_html__( 'WhatsApp', 'rx-theme' ) => $order->get_meta( '_billing_whatsapp' ),
esc_html__( 'Secondary phone', 'rx-theme' ) => $order->get_meta( '_billing_secondary_phone' ),
esc_html__( 'ID number', 'rx-theme' ) => $order->get_meta( '_billing_nid_or_id' ),
);
$this->admin_meta_list( $items );
}
/**
* Show shipping meta in admin.
*
* @param WC_Order $order Order.
* @return void
*/
public function show_admin_shipping_meta( $order ) {
$items = array(
esc_html__( 'Delivery area', 'rx-theme' ) => $order->get_meta( '_shipping_delivery_area' ),
esc_html__( 'Preferred delivery date', 'rx-theme' ) => $order->get_meta( '_rx_preferred_delivery_date' ),
esc_html__( 'Preferred delivery time', 'rx-theme' ) => $order->get_meta( '_rx_preferred_delivery_time' ),
esc_html__( 'Delivery instruction', 'rx-theme' ) => $order->get_meta( '_rx_delivery_instruction' ),
esc_html__( 'Gift order', 'rx-theme' ) => $order->get_meta( '_rx_is_gift_order' ),
esc_html__( 'Gift message', 'rx-theme' ) => $order->get_meta( '_rx_gift_message' ),
esc_html__( 'Invoice request', 'rx-theme' ) => $order->get_meta( '_rx_invoice_request' ),
esc_html__( 'Invoice company', 'rx-theme' ) => $order->get_meta( '_rx_invoice_company' ),
);
$this->admin_meta_list( $items );
}
/**
* Admin meta list helper.
*
* @param array $items Items.
* @return void
*/
private function admin_meta_list( $items ) {
echo '<div class="rx-admin-order-meta">';
foreach ( $items as $label => $value ) {
if ( '' === $value || null === $value ) {
continue;
}
echo '<p><strong>' . esc_html( $label ) . ':</strong> ' . esc_html( $value ) . '</p>';
}
echo '</div>';
}
/**
* Show useful meta in emails.
*
* @param WC_Order $order Order.
* @param bool $sent_to_admin Sent to admin.
* @param bool $plain_text Plain text.
* @param WC_Email $email Email.
* @return void
*/
public function show_email_order_meta( $order, $sent_to_admin, $plain_text, $email ) {
$delivery_date = $order->get_meta( '_rx_preferred_delivery_date' );
$delivery_time = $order->get_meta( '_rx_preferred_delivery_time' );
$instruction = $order->get_meta( '_rx_delivery_instruction' );
$gift_order = $order->get_meta( '_rx_is_gift_order' );
if ( ! $delivery_date && ! $delivery_time && ! $instruction && 'yes' !== $gift_order ) {
return;
}
if ( $plain_text ) {
echo "\n" . esc_html__( 'Extra checkout information', 'rx-theme' ) . "\n";
if ( $delivery_date ) {
echo esc_html__( 'Preferred delivery date:', 'rx-theme' ) . ' ' . esc_html( $delivery_date ) . "\n";
}
if ( $delivery_time ) {
echo esc_html__( 'Preferred delivery time:', 'rx-theme' ) . ' ' . esc_html( $delivery_time ) . "\n";
}
if ( $instruction ) {
echo esc_html__( 'Delivery instruction:', 'rx-theme' ) . ' ' . esc_html( $instruction ) . "\n";
}
if ( 'yes' === $gift_order ) {
echo esc_html__( 'Gift order: Yes', 'rx-theme' ) . "\n";
}
return;
}
echo '<h2>' . esc_html__( 'Extra checkout information', 'rx-theme' ) . '</h2>';
echo '<ul>';
if ( $delivery_date ) {
echo '<li><strong>' . esc_html__( 'Preferred delivery date:', 'rx-theme' ) . '</strong> ' . esc_html( $delivery_date ) . '</li>';
}
if ( $delivery_time ) {
echo '<li><strong>' . esc_html__( 'Preferred delivery time:', 'rx-theme' ) . '</strong> ' . esc_html( $delivery_time ) . '</li>';
}
if ( $instruction ) {
echo '<li><strong>' . esc_html__( 'Delivery instruction:', 'rx-theme' ) . '</strong> ' . esc_html( $instruction ) . '</li>';
}
if ( 'yes' === $gift_order ) {
echo '<li><strong>' . esc_html__( 'Gift order:', 'rx-theme' ) . '</strong> ' . esc_html__( 'Yes', 'rx-theme' ) . '</li>';
}
echo '</ul>';
}
/**
* Thank you page message.
*
* @param int $order_id Order ID.
* @return void
*/
public function thankyou_custom_message( $order_id ) {
if ( ! $order_id ) {
return;
}
$order = wc_get_order( $order_id );
if ( ! $order ) {
return;
}
echo '<div class="rx-thankyou-extra">';
echo '<h2>' . esc_html__( 'Thank you for your order', 'rx-theme' ) . '</h2>';
echo '<p>' . esc_html__( 'We received your order and will process it as soon as possible.', 'rx-theme' ) . '</p>';
$delivery_date = $order->get_meta( '_rx_preferred_delivery_date' );
if ( $delivery_date ) {
echo '<p><strong>' . esc_html__( 'Preferred delivery date:', 'rx-theme' ) . '</strong> ' . esc_html( $delivery_date ) . '</p>';
}
echo '</div>';
}
/**
* Checkout AJAX fragments.
*
* @param array $fragments Fragments.
* @return array
*/
public function checkout_ajax_fragments( $fragments ) {
ob_start();
echo '<div class="rx-checkout-ajax-message">';
echo esc_html__( 'Checkout totals updated.', 'rx-theme' );
echo '</div>';
$fragments['.rx-checkout-ajax-message'] = ob_get_clean();
return $fragments;
}
/**
* Body classes.
*
* @param array $classes Body classes.
* @return array
*/
public function body_classes( $classes ) {
if ( $this->is_checkout_page() ) {
$classes[] = 'rx-woocommerce-checkout';
$classes[] = 'rx-classic-checkout-enhanced';
}
return $classes;
}
/**
* Checkout CSS and JS.
*
* @return void
*/
public function enqueue_checkout_assets() {
if ( ! $this->is_checkout_page() ) {
return;
}
wp_register_style( 'rx-checkout-inline', false, array(), RX_THEME_VERSION );
wp_enqueue_style( 'rx-checkout-inline' );
$css = "
.rx-checkout-alert,
.rx-secure-payment-note,
.rx-checkout-footer-help,
.rx-thankyou-extra,
.rx-checkout-ajax-message {
padding: 14px 16px;
margin: 0 0 18px;
border-radius: 10px;
border: 1px solid rgba(0,0,0,.08);
background: #f8f9fb;
font-size: 15px;
line-height: 1.6;
}
.rx-checkout-alert-warning {
background: #fff8e5;
border-color: #f1d17a;
}
.rx-checkout-steps {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 8px;
margin: 0 0 24px;
}
.rx-checkout-step {
display: block;
padding: 10px;
text-align: center;
border-radius: 999px;
background: #f2f2f2;
font-size: 13px;
font-weight: 600;
}
.rx-checkout-step.is-active {
background: #111;
color: #fff;
}
.rx-checkout-section-note,
.rx-checkout-extra-heading p {
margin-top: 0;
color: #555;
font-size: 14px;
}
.rx-secure-payment-note {
background: #eefaf1;
border-color: #bce6c6;
}
.rx-checkout-total-message td {
font-size: 13px;
color: #666;
}
.rx-gift-message,
.rx-invoice-company {
display: none;
}
.rx-gift-message.rx-is-visible,
.rx-invoice-company.rx-is-visible {
display: block;
}
.woocommerce-checkout .form-row label {
font-weight: 600;
}
.woocommerce-checkout .form-row input.input-text,
.woocommerce-checkout .form-row textarea,
.woocommerce-checkout .form-row select {
border-radius: 8px;
}
@media (max-width: 640px) {
.rx-checkout-steps {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
";
wp_add_inline_style( 'rx-checkout-inline', $css );
wp_register_script( 'rx-checkout-inline', false, array( 'jquery' ), RX_THEME_VERSION, true );
wp_enqueue_script( 'rx-checkout-inline' );
$js = "
jQuery(function($) {
function rxToggleCheckoutFields() {
var giftChecked = $('#rx_is_gift_order').is(':checked');
var invoiceChecked = $('#rx_invoice_request').is(':checked');
$('.rx-gift-message').toggleClass('rx-is-visible', giftChecked);
$('.rx-invoice-company').toggleClass('rx-is-visible', invoiceChecked);
}
rxToggleCheckoutFields();
$(document.body).on('change', '#rx_is_gift_order, #rx_invoice_request', function() {
rxToggleCheckoutFields();
});
$(document.body).on('updated_checkout', function() {
rxToggleCheckoutFields();
});
$('form.checkout').on('submit', function() {
$(this).find('#place_order').addClass('rx-processing-order');
});
});
";
wp_add_inline_script( 'rx-checkout-inline', $js );
}
/**
* Payment-needed filter.
*
* Keep default behavior, but this is here for future theme logic.
*
* @param bool $needs_payment Needs payment.
* @param WC_Cart $cart Cart.
* @return bool
*/
public function cart_needs_payment_filter( $needs_payment, $cart ) {
return $needs_payment;
}
/**
* Filter payment gateways.
*
* Example:
* You can conditionally unset gateway IDs here, such as cod, bacs, cheque, stripe, paypal.
*
* @param array $gateways Gateways.
* @return array
*/
public function filter_payment_gateways( $gateways ) {
if ( is_admin() || ! $this->is_checkout_page() ) {
return $gateways;
}
// Example only: keep all gateways active.
return $gateways;
}
/**
* Filter shipping rates.
*
* Example:
* You can conditionally hide methods based on cart subtotal, location, product class, etc.
*
* @param array $rates Rates.
* @param array $package Package.
* @return array
*/
public function filter_shipping_rates( $rates, $package ) {
return $rates;
}
}
}
/**
* Start checkout module.
*/
RX_Theme_WooCommerce_Checkout::instance();
Very important note
This file is mainly for the classic WooCommerce checkout. WooCommerce has different handling for the newer Checkout Block, and its additional checkout fields use separate block APIs. WooCommerce’s current developer docs mention the block-based “Additional Checkout Fields” system separately from classic checkout hooks.
For best result, your checkout page should use:
[woocommerce_checkout]
instead of the WooCommerce Checkout Block.