diff --git a/changelog.txt b/changelog.txt index c59bb5d4e..1ed60205e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,8 @@ *** WooCommerce Tax Changelog *** += 3.3.0 - 2025-xx-xx = +* Add - Jurisdiction information to generated US tax rate names to improve tax analytics accuracy. + = 3.2.3 - 2025-11-17 = * Fix - Resolved issue where shipping features loaded despite the site being set to tax-only mode. diff --git a/classes/class-wc-connect-taxjar-integration.php b/classes/class-wc-connect-taxjar-integration.php index e2178effc..0dc4a429a 100644 --- a/classes/class-wc-connect-taxjar-integration.php +++ b/classes/class-wc-connect-taxjar-integration.php @@ -65,6 +65,11 @@ class WC_Connect_TaxJar_Integration { */ private $response_line_items; + /** + * @var bool + */ + private $is_itemized_tax_display; + /** * Backend tax classes. * @@ -103,6 +108,8 @@ public function __construct( // Cache error response for 5 minutes. $this->error_cache_time = MINUTE_IN_SECONDS * 5; + + $this->is_itemized_tax_display = ( 'itemized' === get_option( 'woocommerce_tax_total_display' ) ); } /** @@ -110,22 +117,55 @@ public function __construct( * * @param string $taxjar_rate_name The tax rate name from TaxJar, typically including '_tax_rate'. * @param string $to_country The destination country for the tax calculation. + * @param array $jurisdictions Tax jurisdictions. * * @return string The formatted and localized tax rate name. */ - private static function generate_itemized_tax_rate_name( string $taxjar_rate_name, string $to_country ) { - $rate_name = str_replace( '_tax_rate', '', $taxjar_rate_name ); - if ( 'country' === $rate_name && in_array( $to_country, WC()->countries->get_vat_countries(), true ) ) { - $rate_name = 'VAT'; - } elseif ( 'US' === $to_country ) { - $rate_name = str_replace( '_', ' ', $rate_name ); - $rate_name = ucwords( $rate_name ) . ' ' . __( 'Tax', 'woocommerce-services' ); + private static function generate_itemized_tax_rate_name( string $taxjar_rate_name, string $to_country, array $jurisdictions ) { + // Normalize the base key by stripping the trailing "_tax_rate" and converting underscores to spaces. + $base_key = str_replace( '_tax_rate', '', $taxjar_rate_name ); + $label_core = ucwords( str_replace( '_', ' ', $base_key ) ); + $rate_name = $label_core . ' ' . __( 'Tax', 'woocommerce-services' ); - } else { - $rate_name = strtoupper( $rate_name ); + // Handle VAT countries where country-level tax should be labeled as VAT. + $is_vat_country = false; + if ( function_exists( 'WC' ) && WC() && isset( WC()->countries ) && method_exists( WC()->countries, 'get_vat_countries' ) ) { + $is_vat_country = in_array( $to_country, WC()->countries->get_vat_countries(), true ); } - return $rate_name; + if ( 'country' === $base_key && $is_vat_country ) { + return 'VAT'; + } + + // Default, country-agnostic formatting for non‑US destinations. + if ( 'US' !== $to_country ) { + // Preserve previous behavior of uppercasing for non‑US. + return strtoupper( $rate_name ); + } + + // United States specific naming enhancements. + $county = isset( $jurisdictions['county'] ) ? trim( (string) $jurisdictions['county'] ) : ''; + $city = isset( $jurisdictions['city'] ) ? trim( (string) $jurisdictions['city'] ) : ''; + + switch ( $taxjar_rate_name ) { + case 'city_tax_rate': + case 'special_tax_rate': + // Example: "Some County Some City : City Tax". + $prefix = trim( $county . ' ' . $city ); + return ( '' !== $prefix ? $prefix . ' : ' : '' ) . $rate_name; + + case 'county_tax_rate': + // Example: "Some County : County Tax". + return ( '' !== $county ? $county . ' : ' : '' ) . $rate_name; + + case 'state_sales_tax_rate': + // Example: "State Sales Tax" (no jurisdiction prefix). + return $rate_name; + + default: + // Fallback for any other US rate types. + return $rate_name; + } } public function init() { @@ -179,6 +219,8 @@ public function init() { add_filter( 'woocommerce_calc_tax', array( $this, 'override_woocommerce_tax_rates' ), 10, 3 ); add_filter( 'woocommerce_matched_rates', array( $this, 'allow_street_address_for_matched_rates' ), 10, 2 ); + add_filter( 'woocommerce_rate_label', array( $this, 'cleanup_tax_label' ) ); + WC_Connect_Custom_Surcharge::init(); } @@ -662,6 +704,18 @@ public function allow_street_address_for_matched_rates( $matched_tax_rates, $tax return $matched_tax_rates; } + public function cleanup_tax_label( $rate_name ) { + + if ( ! $this->is_itemized_tax_display ) { + return $rate_name; + } + + $label_parts = explode( ' : ', $rate_name ); + $clean_label = ! empty( $label_parts[1] ) ? $label_parts[1] : $label_parts[0]; + + return $clean_label; + } + /** * Get taxable address. * @@ -1209,7 +1263,11 @@ private function get_itemized_tax_rates( $taxes, $taxjar_taxes, $options ): arra if ( $taxes['has_nexus'] ) { // Use Woo core to find matching rates for taxable address. - $location = array( + $jurisdictions = array( + 'county' => $taxjar_taxes->jurisdictions->county ?? null, + 'city' => $taxjar_taxes->jurisdictions->city ?? null, + ); + $location = array( 'from_country' => $from_country, 'from_state' => $from_state, 'to_country' => $to_country, @@ -1243,7 +1301,7 @@ private function get_itemized_tax_rates( $taxes, $taxjar_taxes, $options ): arra $tax_class, $taxes['freight_taxable'], $priority, - self::generate_itemized_tax_rate_name( $tax_rate_name, $to_country ) + self::generate_itemized_tax_rate_name( $tax_rate_name, $to_country, $jurisdictions ) ); ++$priority; @@ -1263,7 +1321,7 @@ private function get_itemized_tax_rates( $taxes, $taxjar_taxes, $options ): arra '', $taxes['freight_taxable'], $priority, - self::generate_itemized_tax_rate_name( $tax_rate_name, $to_country ) + self::generate_itemized_tax_rate_name( $tax_rate_name, $to_country, $jurisdictions ) ); ++$priority; diff --git a/readme.txt b/readme.txt index 69a600ab6..b7a21381a 100644 --- a/readme.txt +++ b/readme.txt @@ -70,6 +70,9 @@ This plugin relies on the following external services: == Changelog == += 3.3.0 - 2025-xx-xx = +* Add - Jurisdiction information to generated US tax rate names to improve tax analytics accuracy. + = 3.2.3 - 2025-11-17 = * Fix - Resolved issue where shipping features loaded despite the site being set to tax-only mode.