Woocommerce Development
Woocommerce Development
WooCommerce is the most popular e-commerce platform for WordPress, powering over 30% of all
online stores worldwide. It's a flexible, open-source solution that can be customized to meet
any e-commerce requirement, from simple product catalogs to complex subscription services and
marketplace platforms.
: Simple, variable, grouped, and external products
: Customer purchases with statuses and fulfillment tracking
: User accounts with order history and billing information
Cart & Checkout
: Shopping cart system and checkout process
Payment Gateways
: Integration with payment processors
Shipping Methods
: Flat rate, free shipping, local pickup, zones
: Tax calculations based on location and product type
: Discount codes and promotional offers
WooCommerce extends WordPress with custom post types, taxonomies, and database tables:
Custom Post Types
: product, shop_order, shop_coupon
Custom Taxonomies
: product_cat, product_tag, product_type
Custom Tables
: Order items, tax rates, payment tokens, downloadable products
Session Handling
: Cart data stored in sessions and database
Template System
: Overridable templates in theme/woocommerce/ folder
WooCommerce products can be extensively customized with custom fields, product types, and
programmatic modifications.
Add Custom Product Fields
1<?php
2/**
3 * Add custom product fields
4 */
5
6// Add custom fields to product general tab
7function add_custom_product_fields() {
8 // Text field
9 woocommerce_wp_text_input( array(
10 'id' => '_manufacturer',
11 'label' => __( 'Manufacturer', 'textdomain' ),
12 'placeholder' => 'Enter manufacturer name',
13 'desc_tip' => true,
14 'description' => __( 'Product manufacturer or brand', 'textdomain' ),
15 ) );
16
17 // Number field
18 woocommerce_wp_text_input( array(
19 'id' => '_warranty_months',
20 'label' => __( 'Warranty (months)', 'textdomain' ),
21 'type' => 'number',
22 'custom_attributes' => array(
23 'step' => '1',
24 'min' => '0',
25 ),
26 ) );
27
28 // Select field
29 woocommerce_wp_select( array(
30 'id' => '_condition',
31 'label' => __( 'Condition', 'textdomain' ),
32 'options' => array(
33 'new' => __( 'New', 'textdomain' ),
34 'refurbished' => __( 'Refurbished', 'textdomain' ),
35 'used' => __( 'Used', 'textdomain' ),
36 ),
37 ) );
38
39 // Checkbox
40 woocommerce_wp_checkbox( array(
41 'id' => '_is_featured_deal',
42 'label' => __( 'Featured Deal', 'textdomain' ),
43 'description' => __( 'Mark as featured deal', 'textdomain' ),
44 ) );
45
46 // Textarea
47 woocommerce_wp_textarea_input( array(
48 'id' => '_technical_specs',
49 'label' => __( 'Technical Specifications', 'textdomain' ),
50 'placeholder' => 'Enter technical details',
51 'rows' => 5,
52 ) );
53}
54add_action( 'woocommerce_product_options_general_product_data', 'add_custom_product_fields' );
55
56/**
57 * Save custom product fields
58 */
59function save_custom_product_fields( $post_id ) {
60 // Save manufacturer
61 $manufacturer = isset( $_POST['_manufacturer'] ) ? sanitize_text_field( $_POST['_manufacturer'] ) : '';
62 update_post_meta( $post_id, '_manufacturer', $manufacturer );
63
64 // Save warranty
65 $warranty = isset( $_POST['_warranty_months'] ) ? absint( $_POST['_warranty_months'] ) : '';
66 update_post_meta( $post_id, '_warranty_months', $warranty );
67
68 // Save condition
69 $condition = isset( $_POST['_condition'] ) ? sanitize_text_field( $_POST['_condition'] ) : '';
70 update_post_meta( $post_id, '_condition', $condition );
71
72 // Save featured deal
73 $featured_deal = isset( $_POST['_is_featured_deal'] ) ? 'yes' : 'no';
74 update_post_meta( $post_id, '_is_featured_deal', $featured_deal );
75
76 // Save technical specs
77 $specs = isset( $_POST['_technical_specs'] ) ? sanitize_textarea_field( $_POST['_technical_specs'] ) : '';
78 update_post_meta( $post_id, '_technical_specs', $specs );
79}
80add_action( 'woocommerce_process_product_meta', 'save_custom_product_fields' );
81
82/**
83 * Display custom fields on product page
84 */
85function display_custom_product_fields() {
86 global $product;
87
88 $manufacturer = get_post_meta( $product->get_id(), '_manufacturer', true );
89 $warranty = get_post_meta( $product->get_id(), '_warranty_months', true );
90 $condition = get_post_meta( $product->get_id(), '_condition', true );
91 $specs = get_post_meta( $product->get_id(), '_technical_specs', true );
92
93 if ( $manufacturer || $warranty || $condition || $specs ) {
94 echo '<div class="custom-product-info">';
95
96 if ( $manufacturer ) {
97 echo '<p><strong>Manufacturer:</strong> ' . esc_html( $manufacturer ) . '</p>';
98 }
99
100 if ( $warranty ) {
101 echo '<p><strong>Warranty:</strong> ' . esc_html( $warranty ) . ' months</p>';
102 }
103
104 if ( $condition ) {
105 echo '<p><strong>Condition:</strong> ' . esc_html( ucfirst( $condition ) ) . '</p>';
106 }
107
108 if ( $specs ) {
109 echo '<div class="technical-specs">';
110 echo '<h3>Technical Specifications</h3>';
111 echo '<div>' . wp_kses_post( wpautop( $specs ) ) . '</div>';
112 echo '</div>';
113 }
114
115 echo '</div>';
116 }
117}
118add_action( 'woocommerce_single_product_summary', 'display_custom_product_fields', 25 );Create Custom Product Type
1<?php
2/**
3 * Register custom product type: Rental
4 */
5class WC_Product_Rental extends WC_Product {
6
7 public function __construct( $product ) {
8 $this->product_type = 'rental';
9 parent::__construct( $product );
10 }
11
12 /**
13 * Get rental duration
14 */
15 public function get_rental_duration() {
16 return get_post_meta( $this->get_id(), '_rental_duration', true );
17 }
18
19 /**
20 * Get rental period (daily, weekly, monthly)
21 */
22 public function get_rental_period() {
23 return get_post_meta( $this->get_id(), '_rental_period', true );
24 }
25
26 /**
27 * Get daily rental price
28 */
29 public function get_daily_price() {
30 return get_post_meta( $this->get_id(), '_daily_price', true );
31 }
32
33 /**
34 * Calculate rental price based on duration
35 */
36 public function get_rental_price( $duration = null ) {
37 if ( $duration === null ) {
38 $duration = $this->get_rental_duration();
39 }
40
41 $daily_price = $this->get_daily_price();
42 $period = $this->get_rental_period();
43
44 switch ( $period ) {
45 case 'daily':
46 return $daily_price * $duration;
47 case 'weekly':
48 return $daily_price * 7 * $duration;
49 case 'monthly':
50 return $daily_price * 30 * $duration;
51 default:
52 return $daily_price * $duration;
53 }
54 }
55
56 /**
57 * Check if product is in stock for rental dates
58 */
59 public function is_available_for_dates( $start_date, $end_date ) {
60 // Query existing orders for this product
61 $args = array(
62 'type' => 'shop_order',
63 'status' => array( 'wc-processing', 'wc-completed' ),
64 'limit' => -1,
65 );
66
67 $orders = wc_get_orders( $args );
68
69 foreach ( $orders as $order ) {
70 foreach ( $order->get_items() as $item ) {
71 if ( $item->get_product_id() === $this->get_id() ) {
72 $item_start = $item->get_meta( '_rental_start_date' );
73 $item_end = $item->get_meta( '_rental_end_date' );
74
75 // Check for date overlap
76 if ( $this->dates_overlap( $start_date, $end_date, $item_start, $item_end ) ) {
77 return false;
78 }
79 }
80 }
81 }
82
83 return true;
84 }
85
86 /**
87 * Check if date ranges overlap
88 */
89 private function dates_overlap( $start1, $end1, $start2, $end2 ) {
90 return ( $start1 <= $end2 ) && ( $end1 >= $start2 );
91 }
92}
93
94/**
95 * Register the rental product type
96 */
97function register_rental_product_type( $types ) {
98 $types['rental'] = __( 'Rental Product', 'textdomain' );
99 return $types;
100}
101add_filter( 'product_type_selector', 'register_rental_product_type' );
102
103/**
104 * Load rental product class
105 */
106function load_rental_product_class( $classname, $product_type ) {
107 if ( $product_type === 'rental' ) {
108 $classname = 'WC_Product_Rental';
109 }
110 return $classname;
111}
112add_filter( 'woocommerce_product_class', 'load_rental_product_class', 10, 2 );
113
114/**
115 * Add rental-specific fields
116 */
117function add_rental_product_fields() {
118 global $post;
119
120 echo '<div class="options_group show_if_rental">';
121
122 woocommerce_wp_text_input( array(
123 'id' => '_daily_price',
124 'label' => __( 'Daily Rental Price', 'textdomain' ),
125 'type' => 'number',
126 'custom_attributes' => array(
127 'step' => '0.01',
128 'min' => '0',
129 ),
130 ) );
131
132 woocommerce_wp_select( array(
133 'id' => '_rental_period',
134 'label' => __( 'Rental Period', 'textdomain' ),
135 'options' => array(
136 'daily' => __( 'Daily', 'textdomain' ),
137 'weekly' => __( 'Weekly', 'textdomain' ),
138 'monthly' => __( 'Monthly', 'textdomain' ),
139 ),
140 ) );
141
142 woocommerce_wp_text_input( array(
143 'id' => '_min_rental_duration',
144 'label' => __( 'Minimum Rental Duration', 'textdomain' ),
145 'type' => 'number',
146 'custom_attributes' => array(
147 'step' => '1',
148 'min' => '1',
149 ),
150 ) );
151
152 woocommerce_wp_text_input( array(
153 'id' => '_max_rental_duration',
154 'label' => __( 'Maximum Rental Duration', 'textdomain' ),
155 'type' => 'number',
156 'custom_attributes' => array(
157 'step' => '1',
158 'min' => '1',
159 ),
160 ) );
161
162 echo '</div>';
163}
164add_action( 'woocommerce_product_options_general_product_data', 'add_rental_product_fields' );
165
166/**
167 * Save rental product fields
168 */
169function save_rental_product_fields( $post_id ) {
170 $product = wc_get_product( $post_id );
171
172 if ( $product && $product->get_type() === 'rental' ) {
173 $daily_price = isset( $_POST['_daily_price'] ) ? sanitize_text_field( $_POST['_daily_price'] ) : '';
174 update_post_meta( $post_id, '_daily_price', $daily_price );
175
176 $period = isset( $_POST['_rental_period'] ) ? sanitize_text_field( $_POST['_rental_period'] ) : 'daily';
177 update_post_meta( $post_id, '_rental_period', $period );
178
179 $min_duration = isset( $_POST['_min_rental_duration'] ) ? absint( $_POST['_min_rental_duration'] ) : 1;
180 update_post_meta( $post_id, '_min_rental_duration', $min_duration );
181
182 $max_duration = isset( $_POST['_max_rental_duration'] ) ? absint( $_POST['_max_rental_duration'] ) : '';
183 update_post_meta( $post_id, '_max_rental_duration', $max_duration );
184 }
185}
186add_action( 'woocommerce_process_product_meta', 'save_rental_product_fields' );Working with Product Variations
1<?php
2/**
3 * Add custom variation fields
4 */
5function add_custom_variation_fields( $loop, $variation_data, $variation ) {
6 // Custom SKU for variation
7 woocommerce_wp_text_input( array(
8 'id' => 'custom_sku[' . $loop . ']',
9 'label' => __( 'Custom SKU', 'textdomain' ),
10 'value' => get_post_meta( $variation->ID, '_custom_sku', true ),
11 'wrapper_class' => 'form-row form-row-full',
12 ) );
13
14 // Manufacturer for variation
15 woocommerce_wp_text_input( array(
16 'id' => 'variation_manufacturer[' . $loop . ']',
17 'label' => __( 'Manufacturer', 'textdomain' ),
18 'value' => get_post_meta( $variation->ID, '_variation_manufacturer', true ),
19 'wrapper_class' => 'form-row form-row-full',
20 ) );
21
22 // Color hex code
23 woocommerce_wp_text_input( array(
24 'id' => 'color_hex[' . $loop . ']',
25 'label' => __( 'Color Hex Code', 'textdomain' ),
26 'value' => get_post_meta( $variation->ID, '_color_hex', true ),
27 'placeholder' => '#000000',
28 'wrapper_class' => 'form-row form-row-first',
29 ) );
30
31 // Additional shipping fee
32 woocommerce_wp_text_input( array(
33 'id' => 'additional_shipping_fee[' . $loop . ']',
34 'label' => __( 'Additional Shipping Fee', 'textdomain' ),
35 'type' => 'number',
36 'value' => get_post_meta( $variation->ID, '_additional_shipping_fee', true ),
37 'custom_attributes' => array(
38 'step' => '0.01',
39 'min' => '0',
40 ),
41 'wrapper_class' => 'form-row form-row-last',
42 ) );
43}
44add_action( 'woocommerce_variation_options_pricing', 'add_custom_variation_fields', 10, 3 );
45
46/**
47 * Save custom variation fields
48 */
49function save_custom_variation_fields( $variation_id, $i ) {
50 // Save custom SKU
51 if ( isset( $_POST['custom_sku'][$i] ) ) {
52 update_post_meta( $variation_id, '_custom_sku', sanitize_text_field( $_POST['custom_sku'][$i] ) );
53 }
54
55 // Save manufacturer
56 if ( isset( $_POST['variation_manufacturer'][$i] ) ) {
57 update_post_meta( $variation_id, '_variation_manufacturer', sanitize_text_field( $_POST['variation_manufacturer'][$i] ) );
58 }
59
60 // Save color hex
61 if ( isset( $_POST['color_hex'][$i] ) ) {
62 update_post_meta( $variation_id, '_color_hex', sanitize_text_field( $_POST['color_hex'][$i] ) );
63 }
64
65 // Save additional shipping fee
66 if ( isset( $_POST['additional_shipping_fee'][$i] ) ) {
67 update_post_meta( $variation_id, '_additional_shipping_fee', sanitize_text_field( $_POST['additional_shipping_fee'][$i] ) );
68 }
69}
70add_action( 'woocommerce_save_product_variation', 'save_custom_variation_fields', 10, 2 );
71
72/**
73 * Display variation custom data on frontend
74 */
75function display_variation_data() {
76 global $product;
77
78 if ( $product->is_type( 'variable' ) ) {
79 $variations = $product->get_available_variations();
80
81 foreach ( $variations as $variation ) {
82 $variation_id = $variation['variation_id'];
83 $color_hex = get_post_meta( $variation_id, '_color_hex', true );
84
85 if ( $color_hex ) {
86 ?>
87 <script>
88 jQuery(document).ready(function($) {
89 $('input[name="variation_id"][value="<?php echo $variation_id; ?>"]').closest('.variations_button')
90 .find('.single_variation_wrap')
91 .prepend('<div class="color-preview" style="width:50px;height:50px;background:<?php echo esc_attr( $color_hex ); ?>;border:1px solid #ccc;margin-bottom:10px;"></div>');
92 });
93 </script>
94 <?php
95 }
96 }
97 }
98}
99add_action( 'woocommerce_before_add_to_cart_button', 'display_variation_data' );Customize the shopping cart and checkout process to match your business requirements.
Modify Cart Behavior
1<?php
2/**
3 * Add custom data to cart items
4 */
5function add_custom_cart_item_data( $cart_item_data, $product_id, $variation_id ) {
6 // Add gift wrapping option
7 if ( isset( $_POST['gift_wrap'] ) && $_POST['gift_wrap'] === 'yes' ) {
8 $cart_item_data['gift_wrap'] = true;
9 $cart_item_data['gift_message'] = isset( $_POST['gift_message'] )
10 ? sanitize_textarea_field( $_POST['gift_message'] )
11 : '';
12 }
13
14 // Add personalization text
15 if ( isset( $_POST['personalization_text'] ) ) {
16 $cart_item_data['personalization_text'] = sanitize_text_field( $_POST['personalization_text'] );
17 }
18
19 // Make cart item unique to prevent merging
20 if ( isset( $cart_item_data['personalization_text'] ) || isset( $cart_item_data['gift_wrap'] ) ) {
21 $cart_item_data['unique_key'] = md5( microtime() . rand() );
22 }
23
24 return $cart_item_data;
25}
26add_filter( 'woocommerce_add_cart_item_data', 'add_custom_cart_item_data', 10, 3 );
27
28/**
29 * Display custom cart item data
30 */
31function display_custom_cart_item_data( $item_data, $cart_item ) {
32 if ( isset( $cart_item['gift_wrap'] ) && $cart_item['gift_wrap'] ) {
33 $item_data[] = array(
34 'name' => __( 'Gift Wrap', 'textdomain' ),
35 'value' => __( 'Yes (+$5.00)', 'textdomain' ),
36 );
37
38 if ( ! empty( $cart_item['gift_message'] ) ) {
39 $item_data[] = array(
40 'name' => __( 'Gift Message', 'textdomain' ),
41 'value' => esc_html( $cart_item['gift_message'] ),
42 );
43 }
44 }
45
46 if ( isset( $cart_item['personalization_text'] ) ) {
47 $item_data[] = array(
48 'name' => __( 'Personalization', 'textdomain' ),
49 'value' => esc_html( $cart_item['personalization_text'] ),
50 );
51 }
52
53 return $item_data;
54}
55add_filter( 'woocommerce_get_item_data', 'display_custom_cart_item_data', 10, 2 );
56
57/**
58 * Add custom price to cart items
59 */
60function add_custom_cart_item_price( $cart ) {
61 if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
62 return;
63 }
64
65 foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
66 // Add gift wrap fee
67 if ( isset( $cart_item['gift_wrap'] ) && $cart_item['gift_wrap'] ) {
68 $gift_wrap_fee = 5.00;
69 $cart_item['data']->set_price( $cart_item['data']->get_price() + $gift_wrap_fee );
70 }
71
72 // Add personalization fee
73 if ( isset( $cart_item['personalization_text'] ) ) {
74 $personalization_fee = 10.00;
75 $cart_item['data']->set_price( $cart_item['data']->get_price() + $personalization_fee );
76 }
77 }
78}
79add_action( 'woocommerce_before_calculate_totals', 'add_custom_cart_item_price' );
80
81/**
82 * Save custom cart item data to order
83 */
84function save_custom_order_item_meta( $item, $cart_item_key, $values, $order ) {
85 if ( isset( $values['gift_wrap'] ) && $values['gift_wrap'] ) {
86 $item->add_meta_data( '_gift_wrap', 'Yes' );
87
88 if ( ! empty( $values['gift_message'] ) ) {
89 $item->add_meta_data( '_gift_message', $values['gift_message'] );
90 }
91 }
92
93 if ( isset( $values['personalization_text'] ) ) {
94 $item->add_meta_data( '_personalization_text', $values['personalization_text'] );
95 }
96}
97add_action( 'woocommerce_checkout_create_order_line_item', 'save_custom_order_item_meta', 10, 4 );
98
99/**
100 * Set minimum order amount
101 */
102function set_minimum_order_amount() {
103 $minimum = 50;
104 $cart_total = WC()->cart->get_subtotal();
105
106 if ( $cart_total < $minimum ) {
107 wc_add_notice( sprintf(
108 __( 'Minimum order amount is %s. Current cart total is %s.', 'textdomain' ),
109 wc_price( $minimum ),
110 wc_price( $cart_total )
111 ), 'error' );
112
113 remove_action( 'woocommerce_proceed_to_checkout', 'woocommerce_button_proceed_to_checkout', 20 );
114 }
115}
116add_action( 'woocommerce_check_cart_items', 'set_minimum_order_amount' );
117
118/**
119 * Disable specific payment methods based on cart total
120 */
121function disable_payment_methods_by_total( $available_gateways ) {
122 if ( WC()->cart->get_total() > 1000 ) {
123 // Disable COD for orders over $1000
124 if ( isset( $available_gateways['cod'] ) ) {
125 unset( $available_gateways['cod'] );
126 }
127 }
128
129 return $available_gateways;
130}
131add_filter( 'woocommerce_available_payment_gateways', 'disable_payment_methods_by_total' );Custom Checkout Fields
1<?php
2/**
3 * Add custom checkout fields
4 */
5function add_custom_checkout_fields( $fields ) {
6 // Add delivery date field
7 $fields['order']['delivery_date'] = array(
8 'type' => 'date',
9 'label' => __( 'Preferred Delivery Date', 'textdomain' ),
10 'required' => true,
11 'class' => array( 'form-row-wide' ),
12 'placeholder' => __( 'Select a date', 'textdomain' ),
13 'priority' => 10,
14 );
15
16 // Add delivery time field
17 $fields['order']['delivery_time'] = array(
18 'type' => 'select',
19 'label' => __( 'Preferred Delivery Time', 'textdomain' ),
20 'required' => true,
21 'class' => array( 'form-row-wide' ),
22 'options' => array(
23 '' => __( 'Select a time', 'textdomain' ),
24 '9am-12pm' => __( '9:00 AM - 12:00 PM', 'textdomain' ),
25 '12pm-3pm' => __( '12:00 PM - 3:00 PM', 'textdomain' ),
26 '3pm-6pm' => __( '3:00 PM - 6:00 PM', 'textdomain' ),
27 '6pm-9pm' => __( '6:00 PM - 9:00 PM', 'textdomain' ),
28 ),
29 'priority' => 20,
30 );
31
32 // Add special instructions field
33 $fields['order']['special_instructions'] = array(
34 'type' => 'textarea',
35 'label' => __( 'Special Instructions', 'textdomain' ),
36 'required' => false,
37 'class' => array( 'form-row-wide' ),
38 'placeholder' => __( 'Any special delivery instructions?', 'textdomain' ),
39 'priority' => 30,
40 );
41
42 // Add company registration field for billing
43 $fields['billing']['billing_company_reg'] = array(
44 'type' => 'text',
45 'label' => __( 'Company Registration Number', 'textdomain' ),
46 'required' => false,
47 'class' => array( 'form-row-wide' ),
48 'priority' => 35,
49 );
50
51 // Add VAT number field
52 $fields['billing']['billing_vat_number'] = array(
53 'type' => 'text',
54 'label' => __( 'VAT Number', 'textdomain' ),
55 'required' => false,
56 'class' => array( 'form-row-wide' ),
57 'priority' => 36,
58 );
59
60 return $fields;
61}
62add_filter( 'woocommerce_checkout_fields', 'add_custom_checkout_fields' );
63
64/**
65 * Validate custom checkout fields
66 */
67function validate_custom_checkout_fields() {
68 // Validate delivery date
69 if ( isset( $_POST['delivery_date'] ) ) {
70 $delivery_date = sanitize_text_field( $_POST['delivery_date'] );
71 $date = strtotime( $delivery_date );
72 $today = strtotime( 'today' );
73
74 if ( $date < $today ) {
75 wc_add_notice( __( 'Delivery date cannot be in the past.', 'textdomain' ), 'error' );
76 }
77
78 $min_date = strtotime( '+2 days' );
79 if ( $date < $min_date ) {
80 wc_add_notice( __( 'Minimum 2 days required for delivery.', 'textdomain' ), 'error' );
81 }
82
83 // Check if date is weekend
84 $day_of_week = date( 'w', $date );
85 if ( $day_of_week == 0 || $day_of_week == 6 ) {
86 wc_add_notice( __( 'We do not deliver on weekends.', 'textdomain' ), 'error' );
87 }
88 }
89
90 // Validate VAT number format (EU format)
91 if ( ! empty( $_POST['billing_vat_number'] ) ) {
92 $vat = sanitize_text_field( $_POST['billing_vat_number'] );
93 if ( ! preg_match( '/^[A-Z]{2}[0-9A-Z]+$/', $vat ) ) {
94 wc_add_notice( __( 'Invalid VAT number format. Use format: GB123456789', 'textdomain' ), 'error' );
95 }
96 }
97}
98add_action( 'woocommerce_after_checkout_validation', 'validate_custom_checkout_fields', 10, 2 );
99
100/**
101 * Save custom checkout fields to order
102 */
103function save_custom_checkout_fields( $order_id ) {
104 // Save delivery date
105 if ( ! empty( $_POST['delivery_date'] ) ) {
106 update_post_meta( $order_id, '_delivery_date', sanitize_text_field( $_POST['delivery_date'] ) );
107 }
108
109 // Save delivery time
110 if ( ! empty( $_POST['delivery_time'] ) ) {
111 update_post_meta( $order_id, '_delivery_time', sanitize_text_field( $_POST['delivery_time'] ) );
112 }
113
114 // Save special instructions
115 if ( ! empty( $_POST['special_instructions'] ) ) {
116 update_post_meta( $order_id, '_special_instructions', sanitize_textarea_field( $_POST['special_instructions'] ) );
117 }
118
119 // Save company registration
120 if ( ! empty( $_POST['billing_company_reg'] ) ) {
121 update_post_meta( $order_id, '_billing_company_reg', sanitize_text_field( $_POST['billing_company_reg'] ) );
122 }
123
124 // Save VAT number
125 if ( ! empty( $_POST['billing_vat_number'] ) ) {
126 update_post_meta( $order_id, '_billing_vat_number', sanitize_text_field( $_POST['billing_vat_number'] ) );
127 }
128}
129add_action( 'woocommerce_checkout_update_order_meta', 'save_custom_checkout_fields' );
130
131/**
132 * Display custom fields in order admin
133 */
134function display_custom_fields_in_admin( $order ) {
135 $delivery_date = get_post_meta( $order->get_id(), '_delivery_date', true );
136 $delivery_time = get_post_meta( $order->get_id(), '_delivery_time', true );
137 $special_instructions = get_post_meta( $order->get_id(), '_special_instructions', true );
138 $company_reg = get_post_meta( $order->get_id(), '_billing_company_reg', true );
139 $vat_number = get_post_meta( $order->get_id(), '_billing_vat_number', true );
140
141 echo '<div class="order_data_column">';
142 echo '<h3>' . __( 'Delivery Information', 'textdomain' ) . '</h3>';
143
144 if ( $delivery_date ) {
145 echo '<p><strong>' . __( 'Delivery Date:', 'textdomain' ) . '</strong> ' . esc_html( $delivery_date ) . '</p>';
146 }
147
148 if ( $delivery_time ) {
149 echo '<p><strong>' . __( 'Delivery Time:', 'textdomain' ) . '</strong> ' . esc_html( $delivery_time ) . '</p>';
150 }
151
152 if ( $special_instructions ) {
153 echo '<p><strong>' . __( 'Special Instructions:', 'textdomain' ) . '</strong><br>' . esc_html( $special_instructions ) . '</p>';
154 }
155
156 if ( $company_reg ) {
157 echo '<p><strong>' . __( 'Company Registration:', 'textdomain' ) . '</strong> ' . esc_html( $company_reg ) . '</p>';
158 }
159
160 if ( $vat_number ) {
161 echo '<p><strong>' . __( 'VAT Number:', 'textdomain' ) . '</strong> ' . esc_html( $vat_number ) . '</p>';
162 }
163
164 echo '</div>';
165}
166add_action( 'woocommerce_admin_order_data_after_billing_address', 'display_custom_fields_in_admin' );Create custom payment gateways to integrate with any payment processor.
Custom Payment Gateway
1<?php
2/**
3 * Custom Payment Gateway
4 */
5class WC_Gateway_Custom extends WC_Payment_Gateway {
6
7 public function __construct() {
8 $this->id = 'custom_gateway';
9 $this->icon = '';
10 $this->has_fields = true;
11 $this->method_title = __( 'Custom Payment Gateway', 'textdomain' );
12 $this->method_description = __( 'Custom payment gateway integration', 'textdomain' );
13
14 // Load settings
15 $this->init_form_fields();
16 $this->init_settings();
17
18 // Get settings
19 $this->title = $this->get_option( 'title' );
20 $this->description = $this->get_option( 'description' );
21 $this->enabled = $this->get_option( 'enabled' );
22 $this->testmode = 'yes' === $this->get_option( 'testmode' );
23 $this->api_key = $this->testmode
24 ? $this->get_option( 'test_api_key' )
25 : $this->get_option( 'live_api_key' );
26
27 // Hooks
28 add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
29 add_action( 'woocommerce_api_' . $this->id, array( $this, 'webhook' ) );
30 }
31
32 /**
33 * Initialize gateway settings form fields
34 */
35 public function init_form_fields() {
36 $this->form_fields = array(
37 'enabled' => array(
38 'title' => __( 'Enable/Disable', 'textdomain' ),
39 'type' => 'checkbox',
40 'label' => __( 'Enable Custom Payment Gateway', 'textdomain' ),
41 'default' => 'yes',
42 ),
43 'title' => array(
44 'title' => __( 'Title', 'textdomain' ),
45 'type' => 'text',
46 'description' => __( 'This controls the title shown during checkout.', 'textdomain' ),
47 'default' => __( 'Credit Card', 'textdomain' ),
48 'desc_tip' => true,
49 ),
50 'description' => array(
51 'title' => __( 'Description', 'textdomain' ),
52 'type' => 'textarea',
53 'description' => __( 'Payment method description shown during checkout.', 'textdomain' ),
54 'default' => __( 'Pay securely using your credit card.', 'textdomain' ),
55 ),
56 'testmode' => array(
57 'title' => __( 'Test Mode', 'textdomain' ),
58 'type' => 'checkbox',
59 'label' => __( 'Enable Test Mode', 'textdomain' ),
60 'default' => 'yes',
61 ),
62 'test_api_key' => array(
63 'title' => __( 'Test API Key', 'textdomain' ),
64 'type' => 'text',
65 'description' => __( 'API key for test environment.', 'textdomain' ),
66 ),
67 'live_api_key' => array(
68 'title' => __( 'Live API Key', 'textdomain' ),
69 'type' => 'text',
70 'description' => __( 'API key for production environment.', 'textdomain' ),
71 ),
72 );
73 }
74
75 /**
76 * Payment fields on checkout
77 */
78 public function payment_fields() {
79 if ( $this->description ) {
80 echo wpautop( wp_kses_post( $this->description ) );
81 }
82
83 if ( $this->testmode ) {
84 echo '<p>' . __( 'TEST MODE ENABLED. Use test card: 4242424242424242', 'textdomain' ) . '</p>';
85 }
86
87 ?>
88 <fieldset>
89 <p class="form-row form-row-wide">
90 <label><?php _e( 'Card Number', 'textdomain' ); ?> <span class="required">*</span></label>
91 <input type="text" name="card_number" class="input-text"
92 placeholder="1234 5678 9012 3456" autocomplete="off" />
93 </p>
94 <p class="form-row form-row-first">
95 <label><?php _e( 'Expiry Date', 'textdomain' ); ?> <span class="required">*</span></label>
96 <input type="text" name="card_expiry" class="input-text"
97 placeholder="MM/YY" autocomplete="off" />
98 </p>
99 <p class="form-row form-row-last">
100 <label><?php _e( 'CVV', 'textdomain' ); ?> <span class="required">*</span></label>
101 <input type="text" name="card_cvv" class="input-text"
102 placeholder="123" autocomplete="off" />
103 </p>
104 <div class="clear"></div>
105 </fieldset>
106 <?php
107 }
108
109 /**
110 * Validate payment fields
111 */
112 public function validate_fields() {
113 if ( empty( $_POST['card_number'] ) ) {
114 wc_add_notice( __( 'Card number is required.', 'textdomain' ), 'error' );
115 return false;
116 }
117
118 $card_number = sanitize_text_field( $_POST['card_number'] );
119 $card_number = str_replace( ' ', '', $card_number );
120
121 if ( ! $this->validate_card_number( $card_number ) ) {
122 wc_add_notice( __( 'Invalid card number.', 'textdomain' ), 'error' );
123 return false;
124 }
125
126 if ( empty( $_POST['card_expiry'] ) ) {
127 wc_add_notice( __( 'Card expiry date is required.', 'textdomain' ), 'error' );
128 return false;
129 }
130
131 if ( empty( $_POST['card_cvv'] ) ) {
132 wc_add_notice( __( 'Card CVV is required.', 'textdomain' ), 'error' );
133 return false;
134 }
135
136 return true;
137 }
138
139 /**
140 * Validate card number using Luhn algorithm
141 */
142 private function validate_card_number( $number ) {
143 $sum = 0;
144 $alt = false;
145
146 for ( $i = strlen( $number ) - 1; $i >= 0; $i-- ) {
147 $n = substr( $number, $i, 1 );
148
149 if ( $alt ) {
150 $n *= 2;
151 if ( $n > 9 ) {
152 $n = ( $n % 10 ) + 1;
153 }
154 }
155
156 $sum += $n;
157 $alt = ! $alt;
158 }
159
160 return ( $sum % 10 == 0 );
161 }
162
163 /**
164 * Process payment
165 */
166 public function process_payment( $order_id ) {
167 $order = wc_get_order( $order_id );
168
169 // Get card details
170 $card_number = sanitize_text_field( $_POST['card_number'] );
171 $card_expiry = sanitize_text_field( $_POST['card_expiry'] );
172 $card_cvv = sanitize_text_field( $_POST['card_cvv'] );
173
174 // Process payment through API
175 $result = $this->process_payment_api( $order, $card_number, $card_expiry, $card_cvv );
176
177 if ( $result['success'] ) {
178 // Payment successful
179 $order->payment_complete( $result['transaction_id'] );
180
181 // Add order note
182 $order->add_order_note( sprintf(
183 __( 'Payment completed. Transaction ID: %s', 'textdomain' ),
184 $result['transaction_id']
185 ) );
186
187 // Empty cart
188 WC()->cart->empty_cart();
189
190 // Return success
191 return array(
192 'result' => 'success',
193 'redirect' => $this->get_return_url( $order ),
194 );
195 } else {
196 // Payment failed
197 wc_add_notice( __( 'Payment failed: ', 'textdomain' ) . $result['error'], 'error' );
198
199 $order->add_order_note( sprintf(
200 __( 'Payment failed: %s', 'textdomain' ),
201 $result['error']
202 ) );
203
204 return array(
205 'result' => 'failure',
206 );
207 }
208 }
209
210 /**
211 * Process payment through API
212 */
213 private function process_payment_api( $order, $card_number, $card_expiry, $card_cvv ) {
214 // Prepare API request
215 $api_url = $this->testmode
216 ? 'https://api.test.example.com/charge'
217 : 'https://api.example.com/charge';
218
219 $body = array(
220 'amount' => $order->get_total() * 100, // Amount in cents
221 'currency' => $order->get_currency(),
222 'card_number' => $card_number,
223 'card_expiry' => $card_expiry,
224 'card_cvv' => $card_cvv,
225 'description' => sprintf( __( 'Order #%s', 'textdomain' ), $order->get_order_number() ),
226 'metadata' => array(
227 'order_id' => $order->get_id(),
228 'customer_email' => $order->get_billing_email(),
229 ),
230 );
231
232 $response = wp_remote_post( $api_url, array(
233 'headers' => array(
234 'Authorization' => 'Bearer ' . $this->api_key,
235 'Content-Type' => 'application/json',
236 ),
237 'body' => json_encode( $body ),
238 'timeout' => 30,
239 ) );
240
241 if ( is_wp_error( $response ) ) {
242 return array(
243 'success' => false,
244 'error' => $response->get_error_message(),
245 );
246 }
247
248 $response_body = json_decode( wp_remote_retrieve_body( $response ), true );
249
250 if ( $response_body['status'] === 'success' ) {
251 return array(
252 'success' => true,
253 'transaction_id' => $response_body['transaction_id'],
254 );
255 } else {
256 return array(
257 'success' => false,
258 'error' => $response_body['message'],
259 );
260 }
261 }
262
263 /**
264 * Handle webhook
265 */
266 public function webhook() {
267 $payload = file_get_contents( 'php://input' );
268 $data = json_decode( $payload, true );
269
270 // Verify webhook signature
271 $signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
272 if ( ! $this->verify_webhook_signature( $payload, $signature ) ) {
273 status_header( 401 );
274 exit;
275 }
276
277 // Process webhook event
278 switch ( $data['event_type'] ) {
279 case 'payment.succeeded':
280 $this->handle_payment_succeeded( $data );
281 break;
282
283 case 'payment.failed':
284 $this->handle_payment_failed( $data );
285 break;
286
287 case 'refund.completed':
288 $this->handle_refund_completed( $data );
289 break;
290 }
291
292 status_header( 200 );
293 exit;
294 }
295
296 /**
297 * Verify webhook signature
298 */
299 private function verify_webhook_signature( $payload, $signature ) {
300 $expected_signature = hash_hmac( 'sha256', $payload, $this->api_key );
301 return hash_equals( $expected_signature, $signature );
302 }
303
304 /**
305 * Handle payment succeeded webhook
306 */
307 private function handle_payment_succeeded( $data ) {
308 $order_id = $data['metadata']['order_id'];
309 $order = wc_get_order( $order_id );
310
311 if ( $order && $order->get_status() === 'pending' ) {
312 $order->payment_complete( $data['transaction_id'] );
313 }
314 }
315
316 /**
317 * Handle payment failed webhook
318 */
319 private function handle_payment_failed( $data ) {
320 $order_id = $data['metadata']['order_id'];
321 $order = wc_get_order( $order_id );
322
323 if ( $order ) {
324 $order->update_status( 'failed', $data['message'] );
325 }
326 }
327
328 /**
329 * Handle refund completed webhook
330 */
331 private function handle_refund_completed( $data ) {
332 $order_id = $data['metadata']['order_id'];
333 $order = wc_get_order( $order_id );
334
335 if ( $order ) {
336 $order->add_order_note( sprintf(
337 __( 'Refund completed. Amount: %s', 'textdomain' ),
338 wc_price( $data['refund_amount'] / 100 )
339 ) );
340 }
341 }
342}
343
344/**
345 * Register custom payment gateway
346 */
347function add_custom_payment_gateway( $gateways ) {
348 $gateways[] = 'WC_Gateway_Custom';
349 return $gateways;
350}
351add_filter( 'woocommerce_payment_gateways', 'add_custom_payment_gateway' );Programmatically manage orders, customize order statuses, and automate order processing.
Custom Order Operations
1<?php
2/**
3 * Create order programmatically
4 */
5function create_custom_order( $customer_id, $products, $billing_data, $shipping_data ) {
6 // Create new order
7 $order = wc_create_order();
8
9 // Add products to order
10 foreach ( $products as $product_data ) {
11 $product = wc_get_product( $product_data['product_id'] );
12
13 if ( $product ) {
14 $order->add_product( $product, $product_data['quantity'], array(
15 'variation' => $product_data['variation'] ?? array(),
16 'totals' => array(
17 'subtotal' => $product->get_price() * $product_data['quantity'],
18 'total' => $product->get_price() * $product_data['quantity'],
19 ),
20 ) );
21 }
22 }
23
24 // Set addresses
25 $order->set_address( $billing_data, 'billing' );
26 $order->set_address( $shipping_data, 'shipping' );
27
28 // Set customer
29 $order->set_customer_id( $customer_id );
30
31 // Calculate totals
32 $order->calculate_totals();
33
34 // Set payment method
35 $order->set_payment_method( 'bacs' );
36 $order->set_payment_method_title( 'Bank Transfer' );
37
38 // Set order status
39 $order->update_status( 'processing', 'Order created programmatically' );
40
41 // Save order
42 $order->save();
43
44 return $order->get_id();
45}
46
47/**
48 * Register custom order status
49 */
50function register_custom_order_status() {
51 register_post_status( 'wc-awaiting-pickup', array(
52 'label' => __( 'Awaiting Pickup', 'textdomain' ),
53 'public' => true,
54 'show_in_admin_status_list' => true,
55 'show_in_admin_all_list' => true,
56 'exclude_from_search' => false,
57 'label_count' => _n_noop(
58 'Awaiting pickup <span class="count">(%s)</span>',
59 'Awaiting pickup <span class="count">(%s)</span>',
60 'textdomain'
61 ),
62 ) );
63}
64add_action( 'init', 'register_custom_order_status' );
65
66/**
67 * Add custom status to order statuses
68 */
69function add_custom_order_status( $order_statuses ) {
70 $order_statuses['wc-awaiting-pickup'] = __( 'Awaiting Pickup', 'textdomain' );
71 return $order_statuses;
72}
73add_filter( 'wc_order_statuses', 'add_custom_order_status' );
74
75/**
76 * Add custom order actions
77 */
78function add_custom_order_actions( $actions ) {
79 $actions['send_tracking_email'] = __( 'Send tracking email', 'textdomain' );
80 $actions['generate_invoice'] = __( 'Generate invoice PDF', 'textdomain' );
81 $actions['sync_to_erp'] = __( 'Sync to ERP system', 'textdomain' );
82 return $actions;
83}
84add_filter( 'woocommerce_order_actions', 'add_custom_order_actions' );
85
86/**
87 * Process custom order actions
88 */
89function process_custom_order_actions( $order ) {
90 $action = $_POST['wc_order_action'] ?? '';
91
92 switch ( $action ) {
93 case 'send_tracking_email':
94 send_tracking_email( $order );
95 break;
96
97 case 'generate_invoice':
98 generate_invoice_pdf( $order );
99 break;
100
101 case 'sync_to_erp':
102 sync_order_to_erp( $order );
103 break;
104 }
105}
106add_action( 'woocommerce_order_action_send_tracking_email', 'process_custom_order_actions' );
107add_action( 'woocommerce_order_action_generate_invoice', 'process_custom_order_actions' );
108add_action( 'woocommerce_order_action_sync_to_erp', 'process_custom_order_actions' );
109
110/**
111 * Send tracking email
112 */
113function send_tracking_email( $order ) {
114 $tracking_number = get_post_meta( $order->get_id(), '_tracking_number', true );
115
116 if ( ! $tracking_number ) {
117 return;
118 }
119
120 $to = $order->get_billing_email();
121 $subject = sprintf( __( 'Tracking Information for Order #%s', 'textdomain' ), $order->get_order_number() );
122
123 $message = sprintf(
124 __( 'Your order has been shipped. Tracking number: %s', 'textdomain' ),
125 $tracking_number
126 );
127
128 wp_mail( $to, $subject, $message );
129
130 $order->add_order_note( __( 'Tracking email sent to customer.', 'textdomain' ) );
131}
132
133/**
134 * Generate invoice PDF
135 */
136function generate_invoice_pdf( $order ) {
137 require_once( 'path/to/tcpdf/tcpdf.php' );
138
139 $pdf = new TCPDF();
140 $pdf->AddPage();
141
142 // Invoice header
143 $pdf->SetFont( 'helvetica', 'B', 16 );
144 $pdf->Cell( 0, 10, 'INVOICE', 0, 1, 'C' );
145
146 // Order details
147 $pdf->SetFont( 'helvetica', '', 10 );
148 $pdf->Cell( 0, 10, 'Order #' . $order->get_order_number(), 0, 1 );
149 $pdf->Cell( 0, 10, 'Date: ' . $order->get_date_created()->format( 'Y-m-d' ), 0, 1 );
150
151 // Customer details
152 $pdf->Cell( 0, 10, 'Customer: ' . $order->get_billing_first_name() . ' ' . $order->get_billing_last_name(), 0, 1 );
153
154 // Line items
155 $pdf->Ln( 10 );
156 $pdf->Cell( 100, 7, 'Product', 1 );
157 $pdf->Cell( 30, 7, 'Quantity', 1 );
158 $pdf->Cell( 30, 7, 'Total', 1, 1 );
159
160 foreach ( $order->get_items() as $item ) {
161 $pdf->Cell( 100, 7, $item->get_name(), 1 );
162 $pdf->Cell( 30, 7, $item->get_quantity(), 1 );
163 $pdf->Cell( 30, 7, wc_price( $item->get_total() ), 1, 1 );
164 }
165
166 // Total
167 $pdf->Ln( 5 );
168 $pdf->Cell( 130, 7, 'Total', 1 );
169 $pdf->Cell( 30, 7, wc_price( $order->get_total() ), 1, 1 );
170
171 // Save PDF
172 $upload_dir = wp_upload_dir();
173 $pdf_path = $upload_dir['basedir'] . '/invoices/';
174
175 if ( ! file_exists( $pdf_path ) ) {
176 wp_mkdir_p( $pdf_path );
177 }
178
179 $filename = 'invoice-' . $order->get_order_number() . '.pdf';
180 $pdf->Output( $pdf_path . $filename, 'F' );
181
182 // Add order note
183 $order->add_order_note( __( 'Invoice PDF generated.', 'textdomain' ) );
184
185 // Update order meta
186 update_post_meta( $order->get_id(), '_invoice_pdf', $filename );
187}
188
189/**
190 * Sync order to ERP
191 */
192function sync_order_to_erp( $order ) {
193 $erp_api_url = 'https://erp.example.com/api/orders';
194
195 $order_data = array(
196 'order_number' => $order->get_order_number(),
197 'customer_email' => $order->get_billing_email(),
198 'total' => $order->get_total(),
199 'items' => array(),
200 );
201
202 foreach ( $order->get_items() as $item ) {
203 $order_data['items'][] = array(
204 'product_id' => $item->get_product_id(),
205 'name' => $item->get_name(),
206 'quantity' => $item->get_quantity(),
207 'total' => $item->get_total(),
208 );
209 }
210
211 $response = wp_remote_post( $erp_api_url, array(
212 'headers' => array(
213 'Content-Type' => 'application/json',
214 'Authorization' => 'Bearer YOUR_ERP_API_KEY',
215 ),
216 'body' => json_encode( $order_data ),
217 'timeout' => 30,
218 ) );
219
220 if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200 ) {
221 $order->add_order_note( __( 'Order synced to ERP system.', 'textdomain' ) );
222 update_post_meta( $order->get_id(), '_synced_to_erp', 'yes' );
223 } else {
224 $order->add_order_note( __( 'Failed to sync order to ERP.', 'textdomain' ) );
225 }
226}Learn about Best Practices.
Always use WooCommerce's extensive hook system rather than modifying core files. This ensures
compatibility with updates and other plugins.
Always validate and sanitize payment data. Never store sensitive card information unless
PCI compliant. Use tokenization and secure payment APIs.
Test all customizations with various product types, payment methods, and order scenarios.
Use WooCommerce's test mode features extensively.
Properly manage order status transitions. Use appropriate statuses and send correct
notifications to customers at each stage.
Cache product queries, optimize database queries, and use transients for expensive
operations. WooCommerce sites can become slow with large product catalogs.
Encrypt sensitive customer data, use HTTPS, implement proper authentication, and follow
GDPR/privacy regulations for customer information.
Use WooCommerce's logging system to track payment gateway transactions, order processing,
and errors. Proper logging aids debugging and audit trails.
Customize email templates properly. Keep customers informed at every step. Use WooCommerce's
email system rather than creating custom emails.
Learn about Practical Exercises.
Add custom fields to products that allow store managers to specify manufacturer, warranty
information, and condition. Display this information on the product page.
Requirements:
Add manufacturer text field
Add warranty period (months) field
Add condition select (New/Refurbished/Used)
Save fields when product is updated
Display fields on product page frontend
Style the output appropriately
Create a custom checkout flow with delivery date selection, time slot booking, and special
instructions. Validate delivery dates and ensure no weekend deliveries.
Requirements:
Add delivery date picker (minimum 2 days ahead)
Add time slot selection dropdown
Add special instructions textarea
Validate no weekend deliveries
Save all data to order meta
Display delivery info in admin order page
Send delivery details in order email
Build a fully functional payment gateway integration with a payment processor API, including
payment processing, refunds, webhooks, and error handling.
Requirements:
Extend WC_Payment_Gateway class
Create admin settings page with API keys
Implement test mode and live mode
Add payment fields with validation
Process payment through external API
Handle success and failure responses
Implement webhook endpoint for async notifications
Add refund support through admin
Log all transactions
Display payment status in order admin
Learn about Knowledge Check.
Use woocommerce_wp_text_input() and save with woocommerce_process_product_meta hook
Directly modify WooCommerce template files
Add fields to database manually
Use WordPress custom fields only
WooCommerce provides helper functions like woocommerce_wp_text_input(), woocommerce_wp_select(),
and woocommerce_wp_checkbox() for adding fields to the product admin interface. These fields
should be saved using the woocommerce_process_product_meta hook and displayed using product
meta retrieval functions. This method ensures compatibility with WooCommerce updates and follows
best practices. Never modify core files or templates directly.
WP_Payment_Gateway
WC_Payment_Gateway
WooCommerce_Payment
WC_Gateway_Abstract
WC_Payment_Gateway is the base class for all WooCommerce payment gateways. When creating a
custom payment gateway, you extend this class and implement required methods like process_payment(),
init_form_fields(), and payment_fields(). The class provides the foundation for handling payments,
settings, and integration with WooCommerce's checkout process. You must also register your gateway
using the woocommerce_payment_gateways filter to make it available in the admin and checkout.
Use wc_create_order(), add products with add_product(), then calculate_totals()
Insert directly into wp_posts table
Use wp_insert_post() only
Create order through frontend checkout only
The proper way to create a WooCommerce order programmatically is to use wc_create_order() which
returns a WC_Order object. Then add products using the add_product() method, set billing and
shipping addresses with set_address(), assign a customer with set_customer_id(), and finally
call calculate_totals() to compute all order amounts including taxes and shipping. This approach
ensures all order data is properly structured and all WooCommerce hooks fire correctly. Never
insert directly into database tables.
woocommerce_checkout_fields
woocommerce_add_checkout_field
wc_checkout_custom_fields
checkout_form_fields
The woocommerce_checkout_fields filter is the correct hook for adding custom fields to the checkout
page. This filter receives an array of field groups (billing, shipping, account, order) and allows
you to add or modify fields in any group. Each field is defined with properties like type, label,
required, class, and priority. After adding fields, you must also use woocommerce_checkout_update_order_meta
to save the field values and woocommerce_after_checkout_validation to validate them.
Store encrypted in database
Never store; use tokenization and send directly to payment processor
Store in plain text for easy access
Store in session only
Sensitive payment data like credit card numbers should NEVER be stored on your server unless you
are PCI DSS Level 1 compliant (which requires extensive security measures and audits). The correct
approach is to send payment data directly to the payment processor using their API, which returns a
token representing the payment method. Store only this token, not the actual card details. Modern
payment gateways use JavaScript libraries that send card data directly to the processor, bypassing
your server entirely. This approach keeps you out of PCI compliance scope.
WooCommerce Code Reference
Official code documentation and API reference
WooCommerce Developer Wiki
Development guides and best practices
WooCommerce Plugin Development
Guide to creating WooCommerce extensions
Payment Gateway API Documentation
Complete guide to building payment gateways
WooCommerce Template Structure
Understanding and customizing WooCommerce templates
Your progress is saved in this browser, on this device only.