woocommerce-eu-vat-number/includes/class-wc-eu-vat-number.php
2026-03-18 10:49:43 +00:00

1492 lines
51 KiB
PHP

<?php
/**
* EU VAT Number Plugin class
*
* @package woocommerce-eu-vat-number
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
require_once __DIR__ . '/vies/class-vies-client.php';
require_once __DIR__ . '/class-wc-eu-vat-uk-number-api.php';
require_once __DIR__ . '/class-wc-eu-vat-admin.php';
/**
* WC_EU_VAT_Number class.
*/
class WC_EU_VAT_Number {
/**
* Stores an array of EU country codes.
*
* @var array
*/
private static $eu_countries = array();
/**
* Stores an array of RegEx patterns for country codes.
*
* @var array
*/
private static $country_codes_patterns = array(
'AT' => 'U[A-Z\d]{8}',
'BE' => '[01]\d{9}',
'BG' => '\d{9,10}',
'CY' => '\d{8}[A-Z]',
'CZ' => '\d{8,10}',
'DE' => '\d{9}',
'DK' => '(\d{2} ?){3}\d{2}',
'EE' => '\d{9}',
'EL' => '\d{9}',
'ES' => '[A-Z]\d{7}[A-Z]|\d{8}[A-Z]|[A-Z]\d{8}',
'FI' => '\d{8}',
'FR' => '([A-Z]{2}|[A-Z0-9]{2})\d{9}',
'GB' => '(\d{9}|\d{12}|(GD|HA)\d{3})',
'XI' => '(\d{9}|\d{12}|(GD|HA)\d{3})',
'HR' => '\d{11}',
'HU' => '\d{8}',
'IE' => '[A-Z\d]{8,10}',
'IT' => '\d{11}',
'LT' => '(\d{9}|\d{12})',
'LU' => '\d{8}',
'LV' => '\d{11}',
'MT' => '\d{8}',
'NL' => '\d{9}B\d{2}',
'PL' => '\d{10}',
'PT' => '\d{9}',
'RO' => '\d{2,10}',
'SE' => '\d{12}',
'SI' => '\d{8}',
'SK' => '\d{10}',
);
/**
* VAT Number data.
*
* @var array
*/
private static $data = array(
'vat_number' => false,
'validation' => array(
'valid' => null,
'error' => false,
),
);
/**
* Stores the current IP Address' country code after geolocation.
*
* @var boolean
*/
private static $ip_country = false;
/**
* Init.
*/
public static function init() {
// Add fields to checkout process.
add_action( 'wp_enqueue_scripts', array( __CLASS__, 'load_scripts' ) );
add_filter( 'woocommerce_billing_fields', array( __CLASS__, 'vat_number_field' ) );
if ( wc_eu_vat_use_shipping_country() ) {
add_filter( 'woocommerce_shipping_fields', array( __CLASS__, 'shipping_vat_number_field' ) );
}
add_action( 'woocommerce_checkout_process', array( __CLASS__, 'process_checkout' ) );
add_action( 'woocommerce_checkout_update_order_review', array( __CLASS__, 'ajax_update_checkout_totals' ) );
add_action( 'woocommerce_review_order_before_submit', array( __CLASS__, 'location_confirmation' ) );
add_action( 'woocommerce_deposits_after_scheduled_order_props_set', array( __CLASS__, 'set_vat_details_for_scheduled_orders' ), 10, 2 );
add_action( 'woocommerce_checkout_create_order', array( __CLASS__, 'set_order_data' ) );
add_action( 'woocommerce_checkout_update_customer', array( __CLASS__, 'set_customer_data' ) );
add_action( 'woocommerce_create_refund', array( __CLASS__, 'set_refund_data' ) );
add_filter( 'woocommerce_customer_get_billing_vat_number', array( __CLASS__, 'filter_customer_meta_vat_number' ) );
add_filter( 'woocommerce_customer_get_shipping_vat_number', array( __CLASS__, 'filter_customer_meta_vat_number' ) );
// Add VAT to addresses.
add_filter( 'woocommerce_order_formatted_billing_address', array( __CLASS__, 'formatted_billing_address' ), 10, 2 );
add_filter( 'woocommerce_order_formatted_shipping_address', array( __CLASS__, 'formatted_shipping_address' ), 10, 2 );
add_filter( 'woocommerce_formatted_address_replacements', array( __CLASS__, 'output_company_vat_number' ), 10, 2 );
add_filter( 'woocommerce_localisation_address_formats', array( __CLASS__, 'localisation_address_formats' ), 10, 2 );
// Digital goods taxable location.
add_filter( 'woocommerce_get_tax_location', array( __CLASS__, 'woocommerce_get_tax_location' ), 10, 2 );
// Add VAT Number in order endpoint (REST API).
add_filter( 'woocommerce_api_order_response', array( __CLASS__, 'add_vat_number_to_order_response' ) );
add_filter( 'woocommerce_rest_prepare_shop_order', array( __CLASS__, 'add_vat_number_to_order_response' ) );
// Logs API errors generated by this plugin.
add_action( 'wp_error_added', array( __CLASS__, 'log_api_errors' ), 10, 2 );
// Add support for subscriptions.
add_filter( 'wcs_renewal_order_created', array( __CLASS__, 'vat_on_creating_renewal_order' ), 999, 2 );
}
/**
* Logs API errors.
*
* @param string $code Error code.
* @param string $message Error message.
*/
public static function log_api_errors( $code, $message ) {
if ( 'wc-eu-vat-api-error' !== $code ) {
return;
}
$logger = wc_get_logger();
$logger->info(
$message,
array(
'source' => 'woocommerce-eu-vat-number',
)
);
}
/**
* Load scripts used on the checkout.
*/
public static function load_scripts() {
if ( is_checkout() ) {
$asset_file = require_once WC_EU_ABSPATH . '/build/eu-vat.asset.php';
if ( ! is_array( $asset_file ) ) {
return;
}
wp_enqueue_script( 'wc-eu-vat', WC_EU_VAT_PLUGIN_URL . '/build/eu-vat.js', array( 'jquery', 'wc-checkout' ), $asset_file['version'], true );
self::localize_wc_eu_vat_params( 'wc-eu-vat' );
}
}
/**
* Localise data for the `wc_eu_vat_params` script.
*
* @param string $script_handle Script handle.
*/
public static function localize_wc_eu_vat_params( $script_handle ) {
$vat_number = WC() && WC()->session ? WC()->session->get( 'vat_number' ) : '';
$customer_id = get_current_user_id();
if ( $customer_id && empty( $vat_number ) ) {
$vat_number = get_user_meta( $customer_id, 'vat_number', true );
}
wp_localize_script(
$script_handle,
'wc_eu_vat_params',
array(
'eu_countries' => self::get_eu_countries(),
'b2b_required' => get_option( 'woocommerce_eu_vat_number_b2b', 'false' ),
'input_label' => WC_EU_VAT_Admin::get_vat_number_field_label(),
'input_description' => get_option( 'woocommerce_eu_vat_number_field_description', '' ),
'failure_handler' => get_option( 'woocommerce_eu_vat_number_failure_handling', 'reject' ),
'use_shipping_country' => wc_eu_vat_use_shipping_country(),
'country_codes' => self::get_country_code_patterns(),
'saved_vat_number' => $vat_number,
)
);
}
/**
* Get EU Country codes.
*
* @return array
*/
public static function get_eu_countries() {
if ( empty( self::$eu_countries ) ) {
self::$eu_countries = include 'data/eu-country-codes.php';
}
return self::$eu_countries;
}
/**
* Reset number.
*/
public static function reset() {
WC()->customer->set_is_vat_exempt( false );
self::$data = array(
'vat_number' => false,
'validation' => array(
'valid' => null,
'error' => false,
'code' => false,
),
);
}
/**
* Add VAT Number field wrapper on the shipping fields.
*
* @since 2.9.4
*
* @param array $fields Shipping Fields.
* @return array
*/
public static function shipping_vat_number_field( $fields ) {
$user_id = get_current_user_id();
$vat_number = WC() && WC()->session ? WC()->session->get( 'vat_number' ) : '';
if ( empty( $vat_number ) && $user_id > 0 ) {
$vat_number = get_user_meta( $user_id, 'vat_number', true );
}
// If on edit address page, unset vat number field.
if ( is_wc_endpoint_url( 'edit-address' ) ) {
if ( isset( $fields['shipping_vat_number'] ) ) {
unset( $fields['shipping_vat_number'] );
}
return $fields;
}
$fields['shipping_vat_number'] = array(
'label' => get_option( 'woocommerce_eu_vat_number_field_label', __( 'VAT number', 'woocommerce-eu-vat-number' ) ),
'default' => $vat_number,
'required' => false,
'class' => array(
'form-row-wide',
),
'description' => get_option( 'woocommerce_eu_vat_number_field_description', '' ),
'id' => 'woocommerce_eu_vat_number_shipping',
'priority' => 120,
);
return $fields;
}
/**
* Show the VAT field on the checkout.
*
* @since 1.0.0
* @version 2.3.1
* @param array $fields Billing Fields.
* @return array
*/
public static function vat_number_field( $fields ) {
$user_id = get_current_user_id();
$vat_number = WC() && WC()->session ? WC()->session->get( 'vat_number' ) : '';
if ( empty( $vat_number ) && $user_id > 0 ) {
$vat_number = get_user_meta( $user_id, 'vat_number', true );
}
// If on edit address page, unset vat number field.
if ( is_wc_endpoint_url( 'edit-address' ) ) {
if ( isset( $fields['billing_vat_number'] ) ) {
unset( $fields['billing_vat_number'] );
}
return $fields;
}
$fields['billing_vat_number'] = array(
'label' => WC_EU_VAT_Admin::get_vat_number_field_label(),
'default' => $vat_number,
'required' => false,
'class' => array(
'form-row-wide',
),
'description' => get_option( 'woocommerce_eu_vat_number_field_description', '' ),
'id' => 'woocommerce_eu_vat_number',
'priority' => 120,
);
return $fields;
}
/**
* Return the vat number prefix.
*
* @param string $country Country Code.
* @return string
*/
public static function get_vat_number_prefix( $country ) {
switch ( $country ) {
case 'GR':
$vat_prefix = 'EL';
break;
case 'MC':
$vat_prefix = 'FR';
break;
case 'IM':
$vat_prefix = 'GB';
break;
default:
$vat_prefix = $country;
break;
}
/**
* Filter the VAT prefix.
*
* @since 3.0.0
*
* @hook woocommerce_eu_vat_number_get_vat_number_prefix
*
* @param {string} $vat_prefix VAT prefix.
*
* @return {string} Filtered VAT prefix.
*/
return apply_filters( 'woocommerce_eu_vat_number_get_vat_number_prefix', $vat_prefix );
}
/**
* Normalize a VAT number by cleaning it and ensuring the correct country prefix.
*
* Strips unwanted characters, uppercases, and fixes the prefix for countries
* where the VAT prefix differs from the country code (e.g., Greece uses 'EL'
* as VAT prefix but 'GR' as country code).
*
* @since 3.1.0
*
* @param string $vat_number The raw VAT number.
* @param string $country The country code.
* @return string The normalized VAT number with the correct prefix.
*/
public static function get_normalized_vat_number( $vat_number, $country ) {
$vat_number = strtoupper( str_replace( array( ' ', '.', '-', ',', ', ' ), '', $vat_number ) );
$vat_number_formatted = self::get_formatted_vat_number( $vat_number );
$vat_prefix = self::get_vat_number_prefix( $country );
// If the VAT number starts with the country code (e.g., GR) instead of the VAT prefix (e.g., EL),
// replace it with the correct VAT prefix. This handles cases like Greece where GR != EL.
if ( $vat_prefix !== $country && str_starts_with( $vat_number, $country ) ) {
$vat_number = $vat_prefix . $vat_number_formatted;
} elseif ( ! str_starts_with( $vat_number, $vat_prefix ) && $vat_number_formatted === $vat_number ) {
// No recognized prefix was provided, add the correct VAT prefix.
$vat_number = $vat_prefix . $vat_number;
}
return $vat_number;
}
/**
* Extract the 2-letter country code from a VAT number.
*
* @param string $vat_number The VAT number to extract the country code from.
*
* @return string|null The 2-letter country code if found, or null if not.
*/
public static function get_country_code_from_vat( $vat_number ) {
// Define the regex pattern.
$pattern = '/^[A-Z]{2}/';
$vat_number = strtoupper( $vat_number );
// Perform the regex match.
if ( preg_match( $pattern, $vat_number, $matches ) ) {
return $matches[0]; // Return the matched country code.
}
// Return null if no match is found.
return null;
}
/**
* Remove unwanted chars and the prefix from a VAT number.
*
* @param string $vat VAT Number.
* @return string
*/
public static function get_formatted_vat_number( $vat ) {
$vat = strtoupper( str_replace( array( ' ', '.', '-', ',', ', ' ), '', $vat ) );
if ( in_array( substr( $vat, 0, 2 ), array_merge( self::get_eu_countries(), array( 'EL', 'XI' ) ), true ) ) {
$vat = substr( $vat, 2 );
}
return $vat;
}
/**
* Get IP address country for user.
*
* @return string
*/
public static function get_ip_country() {
if ( false === self::$ip_country ) {
$geoip = WC_Geolocation::geolocate_ip();
self::$ip_country = $geoip['country'];
}
return self::$ip_country;
}
/**
* Validate a number.
*
* @param string $vat_number VAT Number.
* @param string $country CountryCode.
* @param string $postcode Postcode.
*
* @return bool|WP_Error if valid/not valid, WP_ERROR if validation failed
*/
public static function vat_number_is_valid( $vat_number, $country, $postcode = '' ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
// The StoreAPI will set $vat_number to null if the user does not enter it. We should show an error in this case.
if ( null === $vat_number ) {
return false;
}
// Replace unwanted chars on VAT Number.
$vat_number = $vat_number ? $vat_number : '';
$vat_number = strtoupper( str_replace( array( ' ', '.', '-', ',', ', ' ), '', $vat_number ) );
$vat_prefix = self::get_vat_number_prefix( $country );
$vat_number_formatted = self::get_formatted_vat_number( $vat_number );
// Assume country prefix based on address if not provided and permitted by merchant.
if ( ( ! self::is_country_prefix_required() ) && $vat_number_formatted === $vat_number ) {
$vat_number = $vat_prefix . $vat_number_formatted;
}
$transient_name = 'vat_number_' . $vat_prefix . $vat_number_formatted;
$cached_result = get_transient( $transient_name );
// Keep supporting prefix 'XI' for Northern Ireland.
if ( 'GB' === $country && 'XI' === substr( $vat_number, 0, 2 ) ) {
$vat_prefix = 'XI';
}
if ( empty( $vat_number_formatted ) ) {
return new WP_Error( 'wc-eu-vat-validation-error', __( 'The VAT number is incomplete.', 'woocommerce-eu-vat-number' ) );
}
$country_codes_patterns = self::get_country_code_patterns();
// Return error if VAT Country Code doesn't match or exist.
if ( ! isset( $country_codes_patterns[ $vat_prefix ] ) || ( $vat_prefix . $vat_number_formatted !== $vat_number ) ) {
// translators: %1$s - VAT number field label, %2$s - VAT Number from user, %3$s - Billing country.
return new WP_Error( 'wc-eu-vat-validation-error', sprintf( __( 'You have entered an invalid country code for %1$s (%2$s) for your country (%3$s).', 'woocommerce-eu-vat-number' ), get_option( 'woocommerce_eu_vat_number_field_label', 'VAT number' ), $vat_number, $country ) );
}
if ( ! empty( $cached_result ) ) {
return 'yes' === $cached_result;
}
$is_valid = false;
if ( in_array( $country, array( 'GB', 'IM' ), true ) ) {
// For United Kingdom (UK) (Isle of Man included) check VAT number with UK VAT Number API.
try {
$uk_vat_api = new WC_EU_VAT_UK_Number_API();
$is_valid = $uk_vat_api->check_vat_number( $vat_number_formatted );
} catch ( Exception $e ) {
return new WP_Error( 'wc-eu-vat-api-error', __( 'Error communicating with the VAT validation server - please try again.', 'woocommerce-eu-vat-number' ) );
}
} else {
// Check rest of EU countries with VIES.
$vies = new VIES_Client();
$soap_client = $vies->get_soap_client();
// Return error if any error occurs in getting the SOAP client.
if ( is_wp_error( $soap_client ) ) {
return $soap_client;
}
if ( $soap_client ) {
try {
$vies_req = $vies->check_vat( $vat_prefix, $vat_number_formatted );
$is_valid = $vies_req->is_valid();
} catch ( SoapFault $e ) {
$fault_string = $e->getMessage();
// Check for specific VIES error codes that indicate member state unavailability.
if ( stripos( $fault_string, 'MS_UNAVAILABLE' ) !== false ) {
return new WP_Error(
'wc-eu-vat-api-error',
__( 'The VAT validation service for this country is temporarily unavailable. Please try again later.', 'woocommerce-eu-vat-number' )
);
}
if ( stripos( $fault_string, 'SERVICE_UNAVAILABLE' ) !== false || stripos( $fault_string, 'MS_MAX_CONCURRENT_REQ' ) !== false ) {
return new WP_Error(
'wc-eu-vat-api-error',
__( 'The VAT validation service is temporarily unavailable due to high load. Please try again later.', 'woocommerce-eu-vat-number' )
);
}
if ( stripos( $fault_string, 'TIMEOUT' ) !== false ) {
return new WP_Error(
'wc-eu-vat-api-error',
__( 'The VAT validation service timed out. Please try again later.', 'woocommerce-eu-vat-number' )
);
}
return new WP_Error( 'wc-eu-vat-api-error', __( 'Error communicating with the VAT validation server - please try again.', 'woocommerce-eu-vat-number' ) );
} catch ( Exception $e ) {
return new WP_Error( 'wc-eu-vat-api-error', __( 'Error communicating with the VAT validation server - please try again.', 'woocommerce-eu-vat-number' ) );
}
}
}
/**
* Filter whether the VAT number is valid or not.
*
* @since 2.4.2
* @hook woocommerce_eu_vat_number_is_valid
*
* @param {boolean} $is_valid Whether the VAT number is valid or not.
* @param {string} $vat_number VAT number.
* @param {string} $country Country.
*
* @return {boolean}
*/
$is_valid = apply_filters( 'woocommerce_eu_vat_number_is_valid', $is_valid, $vat_number, $country );
set_transient( $transient_name, $is_valid ? 'yes' : 'no', DAY_IN_SECONDS );
return $is_valid;
}
/**
* Returns array of country code patterns.
*
* @since 2.9.0
*
* @return array
*/
public static function get_country_code_patterns() {
/**
* Filter to support more countries.
*
* @since 3.0.0
*
* @param array $code_patterns Country code patterns.
*
* @return array Associative array of country codes and their regex patterns.
*/
$code_patterns = apply_filters( 'woocommerce_eu_vat_country_code_patterns', self::$country_codes_patterns );
$result = array();
// Just in-case the filtered value is not an array.
if ( ! is_array( $code_patterns ) ) {
return $result;
}
foreach ( $code_patterns as $country_code => $regex ) {
if ( false === @preg_match( '#' . $regex . '#', '' ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
$logger = wc_get_logger();
$logger->log(
'warning',
sprintf(
/* translators: %1$s - regex pattern, $2$s - country code. */
__( 'The regex pattern %1$s for the country code %2$s is invalid.', 'woocommerce-eu-vat-number' ),
$regex,
$country_code
)
);
continue;
}
$result[ $country_code ] = $regex;
}
return $result;
}
/**
* Validate a number and store the result.
*
* @param string $vat_number VAT Number.
* @param string $country Billing CountryCode.
* @param string $postcode Billing PostCode.
* @return void
*/
public static function validate( $vat_number, $country, $postcode = '' ) {
$valid = self::vat_number_is_valid( $vat_number, $country, $postcode );
$vat_number_formatted = self::get_formatted_vat_number( $vat_number );
if ( is_wp_error( $valid ) ) {
self::$data['vat_number'] = $vat_number;
self::$data['validation'] = array(
'valid' => false,
'error' => $valid->get_error_message(),
'code' => $valid->get_error_code(),
);
} else {
self::$data['vat_number'] = $valid ? self::get_vat_number_prefix( $country ) . $vat_number_formatted : $vat_number;
self::$data['validation'] = array(
'valid' => $valid,
'error' => false,
'code' => false,
);
}
}
/**
* Whether the base country match with the billing/shipping country.
*
* @param string $billing_country Billing country of customer.
* @param string $shipping_country Shipping country of customer.
* @return bool
*/
public static function is_base_country_match( $billing_country, $shipping_country ) {
/*
* Special handling needs to be done
* for Isle of Man. Technically Isle of Man
* is separate from UK however in the context
* of VAT, it is considered within UK.
* Ref: https://www.gov.im/categories/tax-vat-and-your-money/customs-and-excise/international-trade-and-the-isle-of-man-requirements-and-standards/
*/
$base_country = WC()->countries->get_base_country();
$tax_based_on = get_option( 'woocommerce_tax_based_on', 'billing' );
$base_country_is_uk = in_array( $base_country, array( 'GB', 'IM' ), true );
$base_country_is_fr = in_array( $base_country, array( 'FR', 'MC' ), true );
// Greece uses both GR and EL country codes.
$base_country_is_gr = in_array( $base_country, array( 'GR', 'EL' ), true );
if ( 'billing' === $tax_based_on ) {
if ( $base_country_is_uk && in_array( $billing_country, array( 'GB', 'IM' ), true ) ) {
return true;
}
if ( $base_country_is_fr && in_array( $billing_country, array( 'FR', 'MC' ), true ) ) {
return true;
}
if ( $base_country_is_gr && in_array( $billing_country, array( 'GR', 'EL' ), true ) ) {
return true;
}
return ( $base_country === $billing_country );
} elseif ( 'shipping' === $tax_based_on ) {
// Set shipping country from billing country if shipping country is empty (handling digital goods).
if ( empty( $shipping_country ) ) {
$shipping_country = $billing_country;
}
if ( $base_country_is_uk && in_array( $shipping_country, array( 'GB', 'IM' ), true ) ) {
return true;
}
if ( $base_country_is_fr && in_array( $shipping_country, array( 'FR', 'MC' ), true ) ) {
return true;
}
if ( $base_country_is_gr && in_array( $shipping_country, array( 'GR', 'EL' ), true ) ) {
return true;
}
return ( $base_country === $shipping_country );
}
return in_array( $base_country, array( $billing_country, $shipping_country ), true );
}
/**
* Set tax exception based on the vat number and store country.
*
* @param string $vat_number VAT Number.
* @param string $exempt Boolean to exempt VAT.
*
* @return void
*/
public static function maybe_apply_vat_exemption( $vat_number = '', $exempt = null ) {
if ( empty( $vat_number ) ) {
return;
}
$vat_country_code = self::get_country_code_from_vat( $vat_number );
if ( ! $vat_country_code ) {
return;
}
$store_country_code = WC()->countries->get_base_country();
/**
* Filters the VAT exception.
*
* @since 2.9.14
*
* @param bool $exempt Is VAT exempt?.
* @param bool $store_country_code Store's location country code.
* @param string $vat_country_code Country code from the VAT number.
*/
$exempt = apply_filters( 'woocommerce_eu_vat_maybe_apply_vat_exemption', $exempt, $store_country_code, $vat_country_code );
$is_valid_condition = $exempt;
$should_deduct_in_base = 'yes' === get_option( 'woocommerce_eu_vat_number_deduct_in_base', 'yes' );
$base_country_match = self::is_base_country_match( $vat_country_code, $vat_country_code );
if ( $base_country_match && $should_deduct_in_base ) {
$is_valid_condition = true;
} elseif ( $base_country_match ) {
$is_valid_condition = false;
}
if ( $is_valid_condition ) {
WC()->customer->set_is_vat_exempt( $exempt );
}
}
/**
* Set tax exception based on countries.
*
* @param bool $exempt Are they exempt?.
* @param string $billing_country Billing country of customer.
* @param string $shipping_country Shipping country of customer.
*/
public static function maybe_set_vat_exempt( $exempt, $billing_country, $shipping_country ) {
$base_country_match = self::is_base_country_match( $billing_country, $shipping_country );
if ( ( $base_country_match && 'yes' === get_option( 'woocommerce_eu_vat_number_deduct_in_base', 'yes' ) ) || ! $base_country_match ) {
/**
* Filters the VAT exception.
*
* @since 2.3.6
*
* @param bool $exempt Are they exempt?.
* @param bool $base_country_match Is Base coutry match?.
* @param string $billing_country Billing country of customer.
* @param string $shipping_country Shipping country of customer.
*/
$exempt = apply_filters( 'woocommerce_eu_vat_number_set_is_vat_exempt', $exempt, $base_country_match, $billing_country, $shipping_country );
WC()->customer->set_is_vat_exempt( $exempt );
}
}
/**
* Validate the VAT number when the checkout form is processed.
*
* For B2C transactions, validate the IP only if this is a digital order.
*/
public static function process_checkout() {
self::reset();
self::validate_checkout( $_POST, true ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
}
/**
* Determine if the country prefix is required.
*
* Helper function for the woocommerce_eu_vat_number_require_country_code
* option to determine if the country prefix is required by the merchant.
*
* @return bool Whether the country prefix is required or not.
*/
public static function is_country_prefix_required() {
$option = get_option( 'woocommerce_eu_vat_number_require_country_code', 'yes' );
/*
* This is backward to ensure the default is `yes`.
*
* If the option returns anything other than `no`, the country
* prefix is assumed to be required. This is to allow for direct
* database manipulation that does not use the expected values.
*/
$required = 'no' !== $option;
/**
* Filter whether the country prefix is required or not.
*
* The result of this filter is cast to a boolean.
*
* @since 3.0.3
*
* @param bool $required Whether the country prefix is required (true) or not (false).
*/
return (bool) apply_filters( 'woocommerce_eu_vat_number_require_country_code', $required );
}
/**
* See if we need the user to self-declare location.
*
* This is needed when:
* The IP country cannot be detected
* The IP country is inside the EU OR
* The Billing country is inside the EU AND
* The IP doesn't match the billing country.
*
* @param string $ip_country IP Country of customer.
* @param string $billing_country Billig Country code.
* @return boolean
*/
public static function is_self_declaration_required( $ip_country = null, $billing_country = null ) {
if ( is_null( $ip_country ) ) {
$ip_country = self::get_ip_country();
}
if ( is_null( $billing_country ) ) {
$billing_country = is_callable( array( WC()->customer, 'get_billing_country' ) ) ? WC()->customer->get_billing_country() : WC()->customer->get_country();
}
return ( empty( $ip_country ) || in_array( $ip_country, self::get_eu_countries(), true ) || in_array( $billing_country, self::get_eu_countries(), true ) ) && $ip_country !== $billing_country;
}
/**
* Show checkbox for customer to confirm their location (location evidence for B2C)
*/
public static function location_confirmation() {
$should_validate_ip = 'yes' === get_option( 'woocommerce_eu_vat_number_validate_ip', 'no' );
$should_self_declare = self::is_self_declaration_required();
$has_digital_goods = self::cart_has_digital_goods();
$is_vat_required = 'yes' === get_option( 'woocommerce_eu_vat_number_b2b', 'no' );
$accept_if_invalid = 'reject' !== get_option( 'woocommerce_eu_vat_number_failure_handling', 'reject' );
$vat_number = self::$data['vat_number'];
$is_valid = $vat_number && self::$data['validation']['valid'];
/**
* Do nothing if VAT is required as location confirmation is
* only required for B2C transactions and if vat is required,
* that indicates site transacts purely from are B2B.
*/
if ( $is_vat_required ) {
return;
}
// Do nothing if IP validation is not enabled.
if ( ! $should_validate_ip ) {
return;
}
/**
* Do nothing if user doesn't have to self-declare the location.
* This indicates that the billing country matches the customer's
* country detected via their IP address.
*/
if ( ! $should_self_declare ) {
return;
}
/**
* Do nothing if cart does not have digital goods as
* location confirmation is only required for digital goods.
*/
if ( ! $has_digital_goods ) {
return;
}
/**
* If the VAT number entered is valid, then we don't need to
* show the location confirmation field as this indicates a
* B2B transaction.
*/
if ( ! empty( $vat_number ) && $is_valid ) {
return;
}
/**
* If the Checkout experience is set to reject VAT if wrong, then customer won't be able to
* place an order anyway, so we don't need to show the location confirmation field.
*/
if ( empty( $vat_number ) || ( $vat_number && ! $is_valid && $accept_if_invalid ) ) {
wc_get_template(
'location-confirmation-field.php',
array(
'location_confirmation_is_checked' => isset( $_POST['location_confirmation'] ), // phpcs:ignore WordPress.Security.NonceVerification.Missing
'countries' => WC()->countries->get_countries(),
),
'woocommerce-eu-vat-number',
untrailingslashit( plugin_dir_path( WC_EU_VAT_FILE ) ) . '/templates/'
);
}
}
/**
* Support method for WooCommerce Deposits.
*
* Sets the VAT related meta whenever a new scheduled order is created.
*
* @param WC_Order $new_order The scheduled order object.
* @param WC_Order $original_order The original order object.
*/
public static function set_vat_details_for_scheduled_orders( $new_order, $original_order ) {
$vat_number = $original_order->get_meta( '_billing_vat_number' );
$is_vat_exempt = $original_order->get_meta( 'is_vat_exempt' );
$is_vat_validated = $original_order->get_meta( '_vat_number_is_validated' );
$is_vat_valid = $original_order->get_meta( '_vat_number_is_valid' );
if ( ! empty( $vat_number ) ) {
$new_order->update_meta_data( '_billing_vat_number', $vat_number );
}
if ( ! empty( $is_vat_exempt ) ) {
$new_order->update_meta_data( 'is_vat_exempt', $is_vat_exempt );
}
if ( ! empty( $is_vat_validated ) ) {
$new_order->update_meta_data( '_vat_number_is_validated', $is_vat_validated );
}
if ( ! empty( $is_vat_valid ) ) {
$new_order->update_meta_data( '_vat_number_is_valid', $is_vat_valid );
}
}
/**
* Triggered when the totals are updated on the checkout.
*
* @since 1.0.0
* @version 2.3.1
* @param array $form_data Checkout Form data.
*/
public static function ajax_update_checkout_totals( $form_data ) {
parse_str( $form_data, $form_data );
self::reset();
if ( ( empty( $form_data['billing_country'] ) && empty( $form_data['shipping_country'] ) ) || ( ( empty( $form_data['billing_vat_number'] ) && empty( $form_data['shipping_vat_number'] ) ) ) ) {
return;
}
self::validate_checkout( $form_data );
}
/**
* Sees if a cart contains anything non-shippable. Thanks EU, I hate you.
*
* @return bool
*/
public static function cart_has_digital_goods() {
$has_digital_goods = false;
if ( WC()->cart->get_cart() ) {
foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) {
$_product = $values['data'];
if ( ! $_product->needs_shipping() ) {
$has_digital_goods = true;
}
}
}
/**
* Filters if cart has digital goods.
*
* @since 2.1.2
*
* @param bool $has_digital_goods Is it Digital good?
*/
return apply_filters( 'woocommerce_cart_has_digital_goods', $has_digital_goods );
}
/**
* Add VAT ID to the formatted address array
*
* @param array $address Address Array.
* @param WC_Order $order WC Order Object.
* @return array
*/
public static function formatted_billing_address( $address, $order ) {
if ( $order->has_shipping_address() && $order->needs_shipping_address() && wc_eu_vat_use_shipping_country() ) {
return $address;
}
$vat_id = wc_eu_vat_get_vat_from_order( $order );
if ( $vat_id ) {
$address['vat_id'] = $vat_id;
}
return $address;
}
/**
* Add VAT ID to the formatted shipping address array
*
* @param array $address Address Array.
* @param WC_Order $order WC Order Object.
* @return array
*/
public static function formatted_shipping_address( $address, $order ) {
if ( ! $order->has_shipping_address() || ! wc_eu_vat_use_shipping_country() ) {
return $address;
}
$vat_id = wc_eu_vat_get_vat_from_order( $order );
if ( $vat_id ) {
$address['vat_id'] = $vat_id;
}
return $address;
}
/**
* Add {vat_id} placeholder
*
* @param array $formats Address formats.
* @param array $args Arguments.
* @return array
*/
public static function output_company_vat_number( $formats, $args ) {
if ( isset( $args['vat_id'] ) ) {
/* translators: %s: VAT Number */
$formats['{vat_id}'] = sprintf( __( 'VAT Number: %s', 'woocommerce-eu-vat-number' ), $args['vat_id'] );
} else {
$formats['{vat_id}'] = '';
}
return $formats;
}
/**
* Address formats.
*
* @param array $formats Address formats.
* @return array
*/
public static function localisation_address_formats( $formats ) {
foreach ( $formats as $key => $format ) {
if ( 'default' === $key || in_array( $key, self::get_eu_countries(), true ) ) {
$formats[ $key ] .= "\n{vat_id}";
}
}
return $formats;
}
/**
* Force Digital Goods tax class to use billing address
*
* @param array $location Location.
* @param string $tax_class Tax Class.
* @return array
*/
public static function woocommerce_get_tax_location( $location, $tax_class = '' ) {
if ( ! empty( WC()->customer ) && ! empty( $tax_class ) && in_array( sanitize_title( $tax_class ), get_option( 'woocommerce_eu_vat_number_digital_tax_classes', array() ), true ) ) {
return array(
WC()->customer->get_billing_country(),
WC()->customer->get_billing_state(),
WC()->customer->get_billing_postcode(),
WC()->customer->get_billing_city(),
);
}
return $location;
}
/**
* Add VAT Number to order endpoint response.
*
* @since 2.1.12
*
* @param WP_REST_Response $response The response object.
*
* @return WP_REST_Response The response object with VAT number
*/
public static function add_vat_number_to_order_response( $response ) {
if ( is_a( $response, 'WP_REST_Response' ) ) {
$order = wc_get_order( (int) $response->data['id'] );
$response->data['vat_number'] = wc_eu_vat_get_vat_from_order( $order );
} elseif ( is_array( $response ) && ! empty( $response['id'] ) ) {
// Legacy endpoint.
$order = wc_get_order( (int) $response['id'] );
$response['vat_number'] = wc_eu_vat_get_vat_from_order( $order );
}
return $response;
}
/**
* Save VAT Number to the order during checkout (WC 2.7.x).
*
* @param WC_Order $order WC Order.
*/
public static function set_order_data( $order ) {
$order->update_meta_data( '_billing_vat_number', self::$data['vat_number'] );
$order->update_meta_data( '_vat_number_is_validated', ! is_null( self::$data['validation']['valid'] ) ? 'true' : 'false' );
$order->update_meta_data( '_vat_number_is_valid', true === self::$data['validation']['valid'] ? 'true' : 'false' );
if ( false !== self::get_ip_country() ) {
$order->update_meta_data( '_customer_ip_country', self::get_ip_country() );
$order->update_meta_data( '_customer_self_declared_country', ! empty( $_POST['location_confirmation'] ) ? 'true' : 'false' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
}
// Add detailed order note about VAT validation result.
wc_eu_vat_maybe_add_order_note(
$order,
self::$data['vat_number'],
self::$data['validation']['valid'] ?? null
);
}
/**
* Save VAT Number to the customer during checkout (WC 2.7.x).
*
* @param WC_Customer $customer Customer Object.
*/
public static function set_customer_data( $customer ) {
$customer->update_meta_data( 'vat_number', self::$data['vat_number'] );
}
/**
* Save VAT Number to the customer during checkout (WC 2.7.x).
*
* @param WC_Order $refund Refund Order.
*/
public static function set_refund_data( $refund ) {
$order = wc_get_order( $refund->get_parent_id() );
$refund->update_meta_data( '_billing_vat_number', wc_eu_vat_get_vat_from_order( $order ) );
}
/**
* Validate the VAT number format.
*
* @param string $vat_number VAT Number.
* @param string $address_country_code Purchaser's Shipping or Billing Country Code.
*
* @return bool|WP_Error
*/
public static function validate_vat_format( $vat_number = '', $address_country_code = '' ) {
$country_code_part = self::get_country_code_from_vat( $vat_number );
$country_code = self::get_vat_number_prefix( $country_code_part );
if ( empty( $country_code ) && ( ! self::is_country_prefix_required() ) ) {
$country_code = self::get_vat_number_prefix( $address_country_code );
$vat_number = $country_code . $vat_number;
}
// If country prefix is required but not provided, show a specific error.
if ( empty( $country_code ) && self::is_country_prefix_required() ) {
return new WP_Error( 'wc-eu-vat-country-code-required', __( 'A valid two character country code is required at the start of the VAT number.', 'woocommerce-eu-vat-number' ) );
}
$vat_number = self::get_formatted_vat_number( $vat_number );
$regex_patterns = self::get_country_code_patterns();
if ( ! in_array( $country_code, array_keys( $regex_patterns ), true ) ) {
return new WP_Error( 'wc-eu-vat-unsupported-country-error', __( 'The country code provided in the VAT Number is not supported.', 'woocommerce-eu-vat-number' ) );
}
$regex_pattern = $regex_patterns[ $country_code ];
$regex_pattern = '/^' . $regex_pattern . '$/';
$is_a_match = preg_match( $regex_pattern, $vat_number, $matches );
if ( $is_a_match ) {
return true;
}
return new WP_Error( 'wc-eu-vat-format-error', __( 'The VAT number format is incorrect.', 'woocommerce-eu-vat-number' ) );
}
/**
* Validate AJAX Order Review / Checkout & add errors if any.
*
* @param array $data Checkout field data.
* @param boolean $doing_checkout True if doing checkout. False if AJAX order review.
*/
public static function validate_checkout( $data, $doing_checkout = false ) {
$use_shipping_country = wc_eu_vat_use_shipping_country();
$b2b_vat_enabled = get_option( 'woocommerce_eu_vat_number_b2b', 'no' );
$fail_handler = get_option( 'woocommerce_eu_vat_number_failure_handling', 'reject' );
$billing_country = wc_clean( $data['billing_country'] );
$shipping_country = wc_clean( ! empty( $data['shipping_country'] ) && ! empty( $data['ship_to_different_address'] ) ? $data['shipping_country'] : $data['billing_country'] );
$billing_vat_number = wc_clean( $data['billing_vat_number'] );
$billing_postcode = wc_clean( $data['billing_postcode'] );
$shipping_postcode = wc_clean( ( ! empty( $data['ship_to_different_address'] ) && isset( $data['shipping_postcode'] ) ) ? $data['shipping_postcode'] : $data['billing_postcode'] );
$ship_to_different = ! empty( $data['ship_to_different_address'] ) ? true : false;
// If using shipping country, use shipping VAT number.
if ( ! empty( $data['shipping_country'] ) && $use_shipping_country && $ship_to_different ) {
$billing_vat_number = wc_clean( $data['shipping_vat_number'] );
}
$vat_country = $billing_country;
$postcode = $billing_postcode;
if ( $use_shipping_country && $ship_to_different ) {
$vat_country = $shipping_country;
$postcode = $shipping_postcode;
}
if ( in_array( $vat_country, self::get_eu_countries(), true ) && ! empty( $billing_vat_number ) ) {
$billing_vat_number = self::get_normalized_vat_number( $billing_vat_number, $vat_country );
$is_format_valid = self::validate_vat_format( $billing_vat_number, $vat_country );
if ( is_wp_error( $is_format_valid ) ) {
wc_add_notice( $is_format_valid->get_error_message(), 'error' );
WC()->session->set( 'vat_number', null );
}
self::validate( $billing_vat_number, $vat_country, $postcode );
if ( true === (bool) self::$data['validation']['valid'] ) {
// VAT number validated successfully.
self::maybe_apply_vat_exemption( $billing_vat_number, true );
WC()->session->set( 'vat_number', $billing_vat_number );
} else {
// Validation failed (either API error or invalid number).
// Handle according to fail_handler setting.
switch ( $fail_handler ) {
case 'accept_with_vat':
// Accept the order but keep VAT charged.
self::maybe_apply_vat_exemption( $billing_vat_number, false );
break;
case 'accept':
// Accept the order and remove VAT.
self::maybe_apply_vat_exemption( $billing_vat_number, true );
break;
default:
// 'reject' - show error and block the order.
if ( ! empty( self::$data['validation']['error'] ) ) {
wc_add_notice( self::$data['validation']['error'], 'error' );
} elseif ( false === self::$data['validation']['valid'] ) {
wc_add_notice(
sprintf(
/* translators: 1: VAT number field label, 2: VAT Number, 3: Address type, 4: Country */
__( 'You have entered an invalid %1$s (%2$s) for your %3$s country (%4$s).', 'woocommerce-eu-vat-number' ),
get_option( 'woocommerce_eu_vat_number_field_label', __( 'VAT number', 'woocommerce-eu-vat-number' ) ),
self::$data['vat_number'],
( $use_shipping_country && $ship_to_different ) ? __( 'shipping', 'woocommerce-eu-vat-number' ) : __( 'billing', 'woocommerce-eu-vat-number' ),
$vat_country
),
'error'
);
}
WC()->session->set( 'vat_number', null );
break;
}
}
} elseif ( $doing_checkout ) {
// If doing checkout, check for additional conditions.
if ( in_array( $vat_country, self::get_eu_countries(), true ) && empty( $billing_vat_number ) && ( 'yes' === $b2b_vat_enabled ) ) {
wc_add_notice(
sprintf(
/* translators: 1: VAT number field label, 2: Address type, 3: Billing country */
__( '%1$s is a required field for your %2$s country (%3$s).', 'woocommerce-eu-vat-number' ),
'<strong>' . get_option( 'woocommerce_eu_vat_number_field_label', __( 'VAT number', 'woocommerce-eu-vat-number' ) ) . '</strong>',
( $use_shipping_country && $ship_to_different ) ? __( 'shipping', 'woocommerce-eu-vat-number' ) : __( 'billing', 'woocommerce-eu-vat-number' ),
$billing_country
),
'error'
);
}
if (
'yes' === get_option( 'woocommerce_eu_vat_number_validate_ip', 'no' ) &&
self::cart_has_digital_goods() &&
self::is_self_declaration_required( self::get_ip_country(), $billing_country ) &&
empty( $data['location_confirmation'] )
) {
/**
* Filters the self declared IP address.
*
* @since 2.1.10
*/
$ip_address = apply_filters( 'wc_eu_vat_self_declared_ip_address', WC_Geolocation::get_ip_address() );
/* translators: 1: Ip Address. */
wc_add_notice( sprintf( __( 'Your IP Address (%1$s) does not match your billing country (%2$s). European VAT laws require your IP address to match your billing country when purchasing digital goods in the EU. Please confirm you are located within your billing country using the checkbox below.', 'woocommerce-eu-vat-number' ), $ip_address, $billing_country ), 'error' );
}
}
}
/**
* Load script with all required dependencies.
*
* @param string $handler Script handler.
* @param string $script Script name.
* @param array $dependencies Additional dependencies.
*
* @throws Exception If the script file could not be read.
* @return void
*/
public static function register_script_with_dependencies( string $handler, string $script, array $dependencies = array() ) {
// Validate that file exists and is readable.
if ( ! is_readable( WC_EU_ABSPATH . $script . '.js' ) ) {
throw new Exception( esc_html( __( 'The script file could not be read or does not exist at the specified location. Please verify the file path and permissions.', 'woocommerce-eu-vat-number' ) ) );
}
$script_file = $script . '.js';
$script_src_url = plugins_url( $script_file, __DIR__ );
$script_asset_path = WC_EU_ABSPATH . $script . '.asset.php';
$script_asset = file_exists( $script_asset_path ) ? require $script_asset_path : array( 'dependencies' => array() ); // nosemgrep: audit.php.lang.security.file.inclusion-arg -- already checked above.
$script_asset['dependencies'] = array_merge( $script_asset['dependencies'], $dependencies );
wp_register_script(
$handler,
$script_src_url,
$script_asset['dependencies'],
self::get_file_version( $script_file ),
true
);
}
/**
* Gets the file modified time as a cache buster if we're in dev mode, or the plugin version otherwise.
*
* @param string $file Local path to the file.
* @return string The cache buster value to use for the given file.
*/
public static function get_file_version( $file ): string {
if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG && file_exists( WC_EU_ABSPATH . $file ) ) {
return (string) filemtime( WC_EU_ABSPATH . trim( $file, '/' ) );
}
return WC_EU_VAT_VERSION;
}
/**
* Handles legcy meta value for VAT number.
*
* The legacy vat number was stored automatically by WooCommerce
* in the user meta under the billing_vat_number and shipping_vat_number keys.
* The values under these keys are used to pre-fill the VAT number field on the
* checkout page for logged in users, which is incorrect as the legacy keys are
* not used anymore.
*
* We instead return the value stored under the vat_number key.
*
* @param string $value The meta value.
* @return string The filtered meta value.
*/
public static function filter_customer_meta_vat_number( $value ) {
if ( ! is_checkout() ) {
return $value;
}
$customer_id = get_current_user_id();
if ( ! $customer_id ) {
return $value;
}
$vat_number = get_user_meta( $customer_id, 'vat_number', true );
if ( $vat_number ) {
return $vat_number;
}
return $value;
}
/**
* Performs VAT validation during Subscription renewal and charges/exempt VAT
* based on the current tax settings.
*
* @param WC_Order $renewal The renewal order.
* @param WC_Subscription $subscription Subscription.
* @return WC_Order The renewal order.
*/
public static function vat_on_creating_renewal_order( WC_Order $renewal, $subscription ) {
// Make sure the renewal and subscription are not empty.
if ( empty( $renewal ) || empty( $subscription ) ) {
return $renewal;
}
/**
* Filter whether to validate VAT on subscription renewal. By default, it is true.
*
* @since 3.0.3
*
* @param bool $validate_vat Whether to validate VAT on subscriptions renewal.
* @param WC_Order $renewal The renewal order.
* @param WC_Subscription $subscription The subscription.
* @return bool Whether to validate VAT on subscriptions renewal.
*/
if ( ! apply_filters( 'woocommerce_eu_vat_number_validate_vat_on_subscription_renewal', true, $renewal, $subscription ) ) {
return $renewal;
}
$vat_number = wc_eu_vat_get_vat_from_order( $renewal );
if ( empty( $vat_number ) ) {
return $renewal;
}
// Bail if the subscription is manual, not editable, or doesn't support amount changes.
if ( $subscription->is_manual() || ! $subscription->is_editable() || ! $subscription->payment_method_supports( 'subscription_amount_changes' ) ) {
return $renewal;
}
try {
$vat_country_code = self::get_country_code_from_vat( $vat_number );
$fail_handler = get_option( 'woocommerce_eu_vat_number_failure_handling', 'reject' );
$country = $renewal->get_billing_country();
if ( wc_eu_vat_use_shipping_country() && $renewal->has_shipping_address() && $renewal->get_shipping_country() ) {
$country = $renewal->get_shipping_country();
}
// Assign the country code to the VAT number if it is not set, this is to handle VAT number without country code prefix.
if ( empty( $vat_country_code ) ) {
$vat_country_code = $country;
}
$exempt_in_base = 'yes' === get_option( 'woocommerce_eu_vat_number_deduct_in_base', 'yes' );
$should_exempt_vat = false;
$is_registered_valid = self::vat_number_is_valid( $vat_number, $country );
$order_note = '';
// If VAT validation failed due to any error (like VIES API error etc...), avoid updating renewal totals.
if ( is_wp_error( $is_registered_valid ) ) {
/* translators: %s is the error message. */
$renewal->add_order_note( sprintf( __( 'VAT validation skipped on this renewal order due to the error: %s', 'woocommerce-eu-vat-number' ), $is_registered_valid->get_error_message() ) );
return $renewal;
}
/*
* At this point, we see that the VAT number is valid.
* But should we exempt VAT though? What if the tax settings have
* changed after the subscription order was created?
*/
if ( true === $is_registered_valid ) {
$should_exempt_vat = true;
$base_country_match = self::is_base_country_match( $vat_country_code, $vat_country_code );
/*
* If the "Remove VAT for Businesses in Your Base Country" setting was updated,
* then we might still have to charge VAT depending on the value.
*/
if ( $exempt_in_base && $base_country_match ) {
$should_exempt_vat = true;
$order_note = sprintf(
/* translators: %s is the VAT number. */
__( 'VAT: %1$s validation successful. VAT was exempted for the renewal order due to the current "Remove VAT for Businesses in Your Base Country" setting.', 'woocommerce-eu-vat-number' ),
$vat_number
);
} elseif ( ! $exempt_in_base && $base_country_match ) {
$should_exempt_vat = false;
$order_note = sprintf(
/* translators: %s is the VAT number. */
__( 'VAT: %1$s validation successful. VAT was charged for the renewal order due to the current "Remove VAT for Businesses in Your Base Country" setting.', 'woocommerce-eu-vat-number' ),
$vat_number
);
}
} else {
/*
* If the VAT registration validation fails, then we need to rely on the
* `Failed Validation Handling` setting to decide whether we should charge
* or exempt vat.
*/
switch ( $fail_handler ) {
case 'reject':
case 'accept_with_vat':
$should_exempt_vat = false;
$order_note = sprintf(
/* translators: %s is the VAT number. */
__( 'VAT: %1$s validation failed. VAT was charged to the renewal order due to the current "Failed Validation Handling" setting.', 'woocommerce-eu-vat-number' ),
$vat_number,
);
break;
case 'accept':
$should_exempt_vat = true;
$order_note = sprintf(
/* translators: %s is the VAT number. */
__( 'VAT: %1$s validation failed. VAT was not charged to the renewal order due to the current "Failed Validation Handling" setting.', 'woocommerce-eu-vat-number' ),
$vat_number,
);
break;
default:
break;
}
}
// Bail if the current VAT exempt status is the same as the should exempt VAT status.
$current_vat_exempt = 'yes' === $subscription->get_meta( 'is_vat_exempt' );
if ( $current_vat_exempt === $should_exempt_vat ) {
return $renewal;
}
// Update the VAT exempt status of the renewal order and subscription.
if ( $should_exempt_vat ) {
$subscription->update_meta_data( 'is_vat_exempt', 'yes' );
$renewal->update_meta_data( 'is_vat_exempt', 'yes' );
} else {
$subscription->update_meta_data( 'is_vat_exempt', 'no' );
$renewal->update_meta_data( 'is_vat_exempt', 'no' );
}
// Recalculate Totals of renewal order and subscription after updating the VAT exempt status.
$renewal->calculate_totals( true );
$renewal->save();
$subscription->calculate_totals( true );
$subscription->save();
$subscription->add_order_note( __( 'Subscription VAT exempt status updated and totals recalculated due to the VAT validation on the renewal order.', 'woocommerce-eu-vat-number' ) );
if ( ! empty( $order_note ) ) {
$renewal->add_order_note( $order_note );
}
return $renewal;
} catch ( Exception $e ) {
/* translators: %s is the error message. */
$renewal->add_order_note( sprintf( __( 'VAT validation failed for the renewal order due to the error: %s', 'woocommerce-eu-vat-number' ), $e->getMessage() ) );
return $renewal;
}
}
}
WC_EU_VAT_Number::init();