Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/issue 2833 - Display a notice to use 9 digit zip code for more accurate tax calculation #2835

Open
wants to merge 11 commits into
base: trunk
Choose a base branch
from
3 changes: 2 additions & 1 deletion changelog.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
*** WooCommerce Shipping & Tax Changelog ***

= 2.8.8 - 2025-xx-xx =
= 2.9.0 - 2025-xx-xx =
* Add - Display a notice if the user does not use a 9-digit zip code.
* Tweak - WooCommerce 9.7 Compatibility.

= 2.8.7 - 2025-01-20 =
Expand Down
118 changes: 118 additions & 0 deletions classes/class-wc-connect-blocks-integration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php
/**
* Blocks Integration class.
*
* @package WooCommerce_Services
*/

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

use Automattic\WooCommerce\Blocks\Integrations\IntegrationInterface;

/**
* Class for WooCommerce Blocks integration.
*/
class WC_Connect_Blocks_Integration implements IntegrationInterface {

/**
* The name of the integration.
*
* @return string
*/
public function get_name(): string {
return 'woocommerce-services';
}

/**
* When called invokes any initialization/setup for the integratidon.
*/
public function initialize() {
$this->register_scripts();
}

/**
* Returns an array of script handles to enqueue in the frontend context.
*
* @return string[]
*/
public function get_script_handles(): array {
return array( 'woocommerce-services-checkout-' . WC_Connect_Loader::get_wcs_version() );
}

/**
* Returns an array of script handles to enqueue in the editor context.
*
* @return string[]
*/
public function get_editor_script_handles(): array {
return array();
}

/**
* An array of key, value pairs of data made available to the block on the client side.
*
* @return array
*/
public function get_script_data(): array {
return array();
}

/**
* Registers the scripts and styles for the integration.
*/
public function register_scripts() {

foreach ( $this->get_script_handles() as $handle ) {
$this->register_script( $handle );
}
}

/**
* Register a script for the integration.
*
* @param string $handle Script handle.
*/
protected function register_script( string $handle ) {
$script_path = $handle . '.js';
$script_url = WC_Connect_Loader::get_wc_connect_base_url() . $script_path;

$script_asset_path = WC_Connect_Loader::get_wc_connect_base_path() . $handle . '.asset.php';
$script_asset = file_exists( $script_asset_path )
? require $script_asset_path // nosemgrep: audit.php.lang.security.file.inclusion-arg --- This is a safe file inclusion.
: array(
'dependencies' => array(),
'version' => $this->get_file_version( WC_Connect_Loader::get_wc_connect_base_path() . $script_path ),
);

wp_register_script(
$handle,
$script_url,
$script_asset['dependencies'],
$script_asset['version'],
true
);

wp_set_script_translations(
$handle,
'woocommerce-services',
WC_Connect_Loader::get_wcs_abs_path() . 'i18n/languages'
);
}

/**
* Get the file modified time as a cache buster if we're in dev mode.
*
* @param string $file Local path to the file.
*
* @return string The cache buster value to use for the given file.
*/
protected function get_file_version( string $file ): string {
if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG && file_exists( $file ) ) {
return filemtime( $file );
}

return WC_Connect_Loader::get_wcs_version();
}
}
91 changes: 91 additions & 0 deletions classes/class-wc-connect-store-api-extension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php
/**
* Store_API_Extension class.
*
* A class to extend the store public API with WooCommerce Shipping & Tax Error/Notice Message functionality.
*
* @package WooCommerce_Services
*/

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

use Automattic\WooCommerce\StoreApi\StoreApi;
use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema;
use Automattic\WooCommerce\StoreApi\Schemas\V1\CartSchema;

/**
* Store API Extension.
*/
class WC_Connect_Store_API_Extension {
/**
* Stores Rest Extending instance.
*
* @var ExtendSchema
*/
private static $extend;

/**
* Plugin Identifier, unique to each plugin.
*
* @var string
*/
const IDENTIFIER = 'woocommerce_services';

/**
* Bootstraps the class and hooks required data.
*
* @since 1.0.0
*/
public static function init() {
self::$extend = StoreApi::container()->get( ExtendSchema::class );
self::extend_store();
}

/**
* Registers the data into each endpoint.
*/
public static function extend_store() {

self::$extend->register_endpoint_data(
array(
'endpoint' => CartSchema::IDENTIFIER,
'namespace' => self::IDENTIFIER,
'data_callback' => array( static::class, 'data' ),
'schema_callback' => array( static::class, 'schema' ),
'schema_type' => ARRAY_A,
)
);
}

/**
* Store API extension data callback.
*
* @return array
*/
public static function data() {
$notices = WC()->session->get( WC_Connect_TaxJar_Integration::NOTICE_KEY );
$notices = is_array( $notices ) ? $notices : array();

return array(
'error_notices' => $notices,
);
}

/**
* Store API extension schema callback.
*
* @return array Registered schema.
*/
public static function schema() {
return array(
'error_notices' => array(
'description' => __( 'Error notices from TaxJar operation.', 'woocommerce-services' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
);
}
}
77 changes: 69 additions & 8 deletions classes/class-wc-connect-taxjar-integration.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class WC_Connect_TaxJar_Integration {
const PROXY_PATH = 'taxjar/v2';
const OPTION_NAME = 'wc_connect_taxes_enabled';
const SETUP_WIZARD_OPTION_NAME = 'woocommerce_setup_automated_taxes';
const NOTICE_KEY = 'woocommerce_services_notices';

public function __construct(
WC_Connect_API_Client $api_client,
Expand Down Expand Up @@ -400,10 +401,31 @@ public function _log( $message ) {
}

/**
* @param $message
* Display notice in cart or checkout page.
*
* @param string|array $message Error message.
*/
public function _notice( $message ) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for the function _ prefix?

$formatted_message = is_scalar( $message ) ? $message : wp_json_encode( $message );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to accept scalar values for the notice message? Or should we only be accepting strings?


// if on checkout page load (not ajax), don't set an error as it prevents checkout page from displaying
if (
( is_cart() || ( is_checkout() && is_ajax() ) ) ||
( WC_Connect_Functions::has_cart_or_checkout_block() || WC_Connect_functions::is_store_api_call() )
) {
$this->maybe_add_error_notice( $message, 'notice' );
}

return;
}

/**
* Display error on cart or checkout page.
*
* @param string|array $message Error message.
*/
public function _error( $message ) {
$formatted_message = is_scalar( $message ) ? $message : json_encode( $message );
$formatted_message = is_scalar( $message ) ? $message : wp_json_encode( $message );

// ignore error messages caused by customer input
$state_zip_mismatch = false !== strpos( $formatted_message, 'to_zip' ) && false !== strpos( $formatted_message, 'is not used within to_state' );
Expand All @@ -425,13 +447,11 @@ public function _error( $message ) {
}

// if on checkout page load (not ajax), don't set an error as it prevents checkout page from displaying
if ( (
( is_cart() || ( is_checkout() && is_ajax() ) ) ||
( WC_Connect_Functions::has_cart_or_checkout_block() || WC_Connect_functions::is_store_api_call() )
)
&& ! wc_has_notice( $message, 'error' )
if (
( is_cart() || ( is_checkout() && is_ajax() ) ) ||
( WC_Connect_Functions::has_cart_or_checkout_block() || WC_Connect_functions::is_store_api_call() )
) {
wc_add_notice( $message, 'error' );
$this->maybe_add_error_notice( $message, 'error' );
}

return;
Expand Down Expand Up @@ -1061,6 +1081,9 @@ public function maybe_apply_taxjar_nexus_addresses_workaround( $body ) {
*/
public function calculate_tax( $options = array() ) {
$this->_log( ':::: TaxJar Plugin requested ::::' );

// Unset the error notice.
WC()->session->set( self::NOTICE_KEY, array() );

// Process $options array and turn them into variables
$options = is_array( $options ) ? $options : array();
Expand Down Expand Up @@ -1135,6 +1158,8 @@ public function calculate_tax( $options = array() ) {
$body['line_items'] = $line_items;
}

$this->maybe_add_taxjar_suggestion( $body );

$response = $this->smartcalcs_cache_request( wp_json_encode( $body ) );

// if no response, no need to keep going - bail early
Expand Down Expand Up @@ -1301,6 +1326,22 @@ public function create_or_update_tax_rate( $taxjar_response, $location, $rate, $
return $rate_id;
}

/**
* Add suggestion for a better TaxJar result.
*
* @param array $body_request Body TaxJar request.
*/
public function maybe_add_taxjar_suggestion( $body_request ) {
if (
! empty( $body_request['to_country'] ) &&
! empty( $body_request['to_zip'] ) &&
'US' === $body_request['to_country'] &&
! ( (bool) preg_match( '/^([0-9]{5})([-]?)([0-9]{4})$/', $body_request['to_zip'] ) )
) {
$this->_notice( sprintf( __( 'Use 9 digits zip code for more accurate tax calculation. %1$sClick here for zip code look up%2$s.', 'woocommerce-services' ), '<a href="https://tools.usps.com/zip-code-lookup.htm?byaddress" target="_blank">', '</a>' ) );
}
}

/**
* Validate TaxJar API request json value and add the error to log.
*
Expand Down Expand Up @@ -1429,6 +1470,26 @@ public function smartcalcs_request( $json ) {
}
}

public function maybe_add_error_notice( $message, $type = 'error' ) {
if ( ! wc_has_notice( $message, $type ) ) {
wc_add_notice( $message, $type );
}

$this->add_error_notice( $message, $type );
}

public function add_error_notice( $message, $type = 'error' ) {
$notices = WC()->session->get( self::NOTICE_KEY );

if ( ! is_array( $notices ) ) {
$notices = array();
}

$notices[ $type ] = $message;

WC()->session->set( self::NOTICE_KEY, $notices );
}

/**
* Exports existing tax rates to a CSV and clears the table.
*
Expand Down
Loading
Loading