Limit WooCommerce Payment Gateways Based on country

In some cases you would like to limit the payment gateways your WooCommerce store offers based on the customer’s billing country. The following code snippet uses the woocommerce_available_payment_gateways filter to accomplish this.

First, you will need to find and copy the payment gateway stub by going to: WP-Admin > WooCommerce > Settings > Payments > [select the gateway] then locate the stub in the address bar.

wp-admin/admin.php?page=wc-settings&tab=checkout&section=[Payment Gateway Stub]

Then you can modify the following code snippet to your the functions.php file of your child theme or the code snippets plugin. Replace both {Payment Gateway Stub} with what you copied in the step above.

This is set up to include this gateway option for customers with a United States or Canadian billing address only; but, it can be modified to include or exclude based on any parameter.

add_filter( 'woocommerce_available_payment_gateways', 'gmc_payment_gateway_disable_country' );
  
function gmc_payment_gateway_disable_country( $available_gateways ) {
    if ( is_admin() ) return $available_gateways;
    if ( isset( $available_gateways['sagepaymentsusaapi'] )){
		$billing_c = WC()->customer->get_billing_country() ?? array();
		if(!in_array($billing_c, array('US','CA'))) {
        	unset( $available_gateways['sagepaymentsusaapi'] );
		}
    } 
    return $available_gateways;
}

This code is here for reference purposes only and is not intended for use in a production environment. Modify and use this code at your own risk.

EDIT: added $billing_c to default to an array in the event that get_billing_country() returns null.

Disable WP Plugin using PhpMyAdmin

Sometimes there are plugin conflicts that can case fatal errors on your site and/or prevent users from logging into wp-admin.

This happened to me recently where a bug in a reCaptcha login plugin was preventing WC Shop managers from logging in. This bug was also preventing the admin, me, from authenticating as well.

In cases like these it is necessary to manually disable the plugin.

The easiest way is to disable them using a database editing tool like PhpMyAdmin. Note: use extreme caution when performing manual database edits on a live environment.

  1. Open the table wp_options
  2. Search for the key active_plugins
  3. If you have many plugins installed and only want to deactivate one, you can copy the value and then use this online Serialized Editor tool to remove the plugin from the list.
  4. Paste the result back into the value field and then save.
  5. Load your site to confirm there are no issues.

Exclude Product Tag from Coupons

This code snippet excludes product with a specific WooCommerce product_tag from being included in any coupon discount. Simply replace “kits” with the slug of the product_tag you want to exclude. has_term() also accepts an array in the first argument.

add_filter('woocommerce_coupon_is_valid_for_product', 'exclude_product_from_coupon_by_tag', 12, 4);
function exclude_product_from_coupon_by_tag($valid, $product, $coupon, $values ){
	
	// Check if the Product has the term
	if( $product->is_type( 'simple' ) ){
		$has_term = has_term('kits', 'product_tag', $product->get_id());
	} elseif( $product->is_type( 'variation' ) ){
		$has_term = has_term('kits', 'product_tag', $product->get_parent_id());
	}
	
    // Set the Return
	if($has_term === true){
		return false;
	}else{
		return true;
	}
}

Update: once again, variable products got the best of me. In WooCommerce, the product_tag is associated with the ‘top level’ product. For simple products, this would be the post itself. However, variations store the product_tag with the parent product. The code has been updated to account for this.

Change Number of Search Results for Specific User in Woocommerce

Normally Woocommerce pulls the number of results from the standard WordPress settings, usually set to 20-30 for most installs. For many environments it is helpful to keep this number relatively low for performance reasons. However, for specific logged in users, it can be helpful to set a higher default.

My client wanted his default set to 100. The following code snippet can be modified to fit your needs.

<?php
/**
 * Change number of products that are displayed per page (shop page)
 */
add_filter( 'loop_shop_per_page', 'custom_user_loop_shop_per_page', 20 );
function custom_user_loop_shop_per_page( $num_products) {
        $username = "test_user_name";
  // $num_products contains the current number of products per page based on the value stored on Options -> Reading
	$user = wp_get_current_user();
    if($user && isset($user->user_login) && $username == $user->user_login) {
        $num_products = 100;
    }
  return $parts;
}

Format Billing Phone Number on Woocommerce

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();

Display PayPal Fee on Woocommerce Order

<?php
    add_action( 'woocommerce_admin_order_totals_after_total', 'add_paypal_fee_order_admin' );
    
    function add_paypal_fee_order_admin( $order_id ){
            $paypalfee = get_post_meta( $order_id, 'PayPal Transaction Fee', true );
            
            if($paypalfee){
            $order = wc_get_order($order_id);
            ?>
            <table class="wc-order-totals" style="border-top: 1px solid #999; margin-top:12px; padding-top:12px">
            <tr>
                <td class="label"><?php esc_html_e( 'PayPal Fee', 'woocommerce' ); ?>:</td>
                <td width="1%"></td>
                <td class="total">-$<?php echo $paypalfee; ?></td>
            </tr>
            <tr>
                <td class="label"><?php esc_html_e( 'Amount Received', 'woocommerce' ); ?>:</td>
                <td width="1%"></td>
                <td class="total">
                    <?php echo wc_price( ($order->get_total() - $paypalfee), array( 'currency' => $order->get_currency() ) ); ?>
                </td>
            </tr>
            </table>
            <?php
            }
    }
?>

This code snippet works with the Paypal Standard Woocommerce Payment Gateway. It adds the PayPal fee and payout information to the WooCommerce Edit Order Screen.

If you are using WooCommerce PayPal Checkout Payment Gateway, this snippet is not needed. I am not sure about compatibility or necessity for any of the other many PayPal gateways.