By default, Woocommerce does not format the billing phone number entered into the checkout field. However the user formats the number, that is how it gets entered into the database and how it is displayed throughout the Woocommerce Admin.
Unformatted phone numbers are difficult to read and can also create difficulties when trying to search for an order by phone number. If you can think of any other use cases, let me know in the comments!
For my client’s use, the main difficulty was trying to map orders to the proper customer in their accounting system. Their system uses the customer’s phone number the Customer ID. So, in order to create a match on order import, the billing phone number from Woo had to be formatted the same way as the Customer ID in their accounting software. The easiest path forward was to modify Woocommerce behavior instead of trying to alter their legacy system.
I use the Code Snippets plugin to run the following code.
Front-End / Preemptive
This snippet adds jQuery to the checkout page and formats the billing phone number according to the 10-digit format: XXX-XXX-XXXX. If 11 digits are provided, then the single digit country code prefix is removed. If more than 11 digits are entered (some international phone numbers) then it leaves the number unformulated.
<?php
add_action('wp_footer', 'jg_format_checkout_billing_phone');
function jg_format_checkout_billing_phone() {
if ( is_checkout() && ! is_wc_endpoint_url() ) :
?>
<script type="text/javascript">
jQuery( function($){
$('#billing_phone').focusout(function() {
var p = $(this).val();
p = p.replace(/[^0-9]/g,"");
var p_length = p.length;
if(p_length == 10){
p = p.replace(/(\d{3})(\d{3})(\d{4})/, "$1-$2-$3");
}else if(p_length == 11){
p = p.replace(/(\d{1})(\d{3})(\d{3})(\d{4})/, "$2-$3-$4");
}else{
p = p;
}
$(this).val(p);
});
});
</script>
<?php
endif;
}
Back-End / Preemptive
Update: It seems that .focusout is not triggered when a browser autofills form data. (If anyone knows of a dom event that catches that, let me know!) Since autofills are quite popular, we have to pull out the big guns and run our RegEx with PHP on the back-end. (I’m not a professional with RegEx, so let me know if there is a more efficient way in the comments.)
// Back-end Validation
add_action('woocommerce_checkout_process', 'custom_validate_billing_phone');
function custom_validate_billing_phone() {
// remove all non digits
$number = preg_replace("/[^\d]/","",$_POST['billing_phone']);
// get number of digits
$length = strlen($number);
if($length == 10) {
$_POST['billing_phone'] = preg_replace("/^(\d{3})(\d{3})(\d{4})$/", "$1-$2-$3", $number);
}else if($length == 11) {
$_POST['billing_phone'] = preg_replace("/^1?(\d{3})(\d{3})(\d{4})$/", "$1-$2-$3", $number);
}
}
Back-End / Retroactive
The snippet above does not help for phone numbers that have already been submitted. In order to retroactively format existing phone numbers, we need to update meta data stored in both the postmeta and usermeta tables.
The below snippets only need to be run once.
Woocommerce Order in postmeta
<?php
function retroactive_update_billing_phone_number(){
global $wpdb;
$results = $wpdb->get_results( $wpdb->prepare("SELECT * FROM $wpdb->postmeta WHERE meta_key = %s" , "_billing_phone") );
foreach($results as $meta){
$phone_number = preg_replace('/[^0-9]/', '', $meta->meta_value);
if(strlen($phone_number) == 10){
if(preg_match( '/^(\d{3})(\d{3})(\d{4})$/', $phone_number, $matches ) ){
$new_phone_number = $matches[1] . '-' .$matches[2] . '-' . $matches[3];
}
}
else if(strlen($phone_number) == 11){
if(preg_match( '/^(\d{1})(\d{3})(\d{3})(\d{4})$/', $phone_number, $matches ) ){
$new_phone_number = $matches[2] . '-' .$matches[3] . '-' . $matches[4];
}
}
else{
$new_phone_number = $phone_number;
}
$update_meta = update_post_meta($meta->post_id,"_billing_phone",$new_phone_number,$meta->meta_value);
}
}
retroactive_update_billing_phone_number();
Phone Number stored in usermeta
<?php
function retroactive_update_user_phone_number(){
global $wpdb;
$results = $wpdb->get_results( $wpdb->prepare("SELECT * FROM $wpdb->usermeta WHERE meta_key = %s" , "billing_phone") );
foreach($results as $meta){
$phone_number = preg_replace('/[^0-9]/', '', $meta->meta_value);
if(strlen($phone_number) == 10){
if(preg_match( '/^(\d{3})(\d{3})(\d{4})$/', $phone_number, $matches ) ){
$new_phone_number = $matches[1] . '-' .$matches[2] . '-' . $matches[3];
}
}
else if(strlen($phone_number) == 11){
if(preg_match( '/^(\d{1})(\d{3})(\d{3})(\d{4})$/', $phone_number, $matches ) ){
$new_phone_number = $matches[2] . '-' .$matches[3] . '-' . $matches[4];
}
}
else{
$new_phone_number = $phone_number;
}
$update_meta = update_user_meta($meta->user_id,"billing_phone",$new_phone_number,$meta->meta_value);
}
}
retroactive_update_user_phone_number();