PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.17
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.17
1.10.25 1.10.24 1.10.23 1.10.22 1.10.21 1.10.20 1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 All 169 releases
← All changes | includes/Integrations/WooCommerce_Tax.php +79 -2 1.10.9 → 1.10.17 View file →
@@ -201,8 +201,9 @@
201 201 // plugin reads it for its VAT-exemption check.
202 202 if ( ! WC()->customer instanceof \WC_Customer ) {
203 203 wc_load_cart();
204 204 }
205 + add_filter( 'woocommerce_services_override_tax_rate', array( $this, 'preserve_tax_rate_order' ), PHP_INT_MAX, 3 );
205 206 if ( false === $taxjar->calculate_tax( $options ) ) {
206 207 \WCPOS\WooCommercePOS\Logger::log( 'WooCommerce Tax returned no rates for the POS order', array( 'order_id' => $order->get_id() ) );
207 208 }
208 209 } catch ( \Throwable $e ) {
@@ -212,12 +213,80 @@
212 213 'order_id' => $order->get_id(),
213 214 'error' => $e->getMessage(),
214 215 )
215 216 );
217 + } finally {
218 + remove_filter( 'woocommerce_services_override_tax_rate', array( $this, 'preserve_tax_rate_order' ), PHP_INT_MAX );
216 219 }
217 220 }
218 221
219 222 /**
223 + * Preserve WooCommerce rate IDs when TaxJar jurisdiction fields change order.
224 + *
225 + * WooCommerce Tax assigns rows by response position, not jurisdiction. Only
226 + * reorder an exact label bijection; new/renamed jurisdictions keep upstream
227 + * behaviour. Values are untouched, including genuine rate changes. This hook
228 + * exposes the mutable response object before the plugin writes its rate rows.
229 + *
230 + * @param mixed $rate Overall rate, returned unchanged.
231 + * @param object $tax TaxJar tax response.
232 + * @param array $body Normalized TaxJar request address.
233 + * @return mixed
234 + */
235 + public function preserve_tax_rate_order( $rate, $tax, $body ) {
236 + $lines = \is_array( $tax->breakdown->line_items ?? null ) ? $tax->breakdown->line_items : array();
237 + if ( isset( $tax->breakdown->shipping ) ) {
238 + $lines[] = $tax->breakdown->shipping;
239 + }
240 + foreach ( $lines as $line ) {
241 + if ( ! \is_object( $line ) ) {
242 + continue;
243 + }
244 + $keys = array();
245 + foreach ( $line as $key => $value ) {
246 + if ( 'combined_tax_rate' === $key || false === strpos( $key, '_tax_rate' ) ) {
247 + continue;
248 + }
249 + // Mirrors the plugin's private generate_itemized_tax_rate_name().
250 + $label = ucwords( str_replace( '_', ' ', str_replace( '_tax_rate', '', $key ) ) ) . ' ' . __( 'Tax', 'woocommerce-services' ); // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch -- Match the third-party rate labels.
251 + $place = trim( trim( $tax->jurisdictions->county ?? '' ) . ' ' . trim( $tax->jurisdictions->city ?? '' ) );
252 + $label = 'US' === $body['to_country'] ? ( '' === $place ? $label : $place . ' : ' . $label ) : strtoupper( $label );
253 + if ( isset( $keys[ $label ] ) ) {
254 + continue 2;
255 + }
256 + $keys[ $label ] = $key;
257 + }
258 + $product = wc_get_product( (int) ( $line->id ?? 0 ) );
259 + $rates = \WC_Tax::find_rates(
260 + array(
261 + 'country' => $body['to_country'],
262 + 'state' => $body['to_state'],
263 + 'postcode' => $body['to_zip'],
264 + 'city' => $body['to_city'],
265 + 'tax_class' => $product ? $product->get_tax_class() : '',
266 + )
267 + );
268 + if ( \count( $rates ) !== \count( $keys ) ) {
269 + continue;
270 + }
271 + $ordered = array();
272 + foreach ( $rates as $existing ) {
273 + if ( ! isset( $keys[ $existing['label'] ] ) ) {
274 + continue 2;
275 + }
276 + $key = $keys[ $existing['label'] ];
277 + $ordered[ $key ] = $line->$key;
278 + unset( $keys[ $existing['label'] ] );
279 + }
280 + foreach ( $ordered as $key => $value ) {
281 + unset( $line->$key );
282 + $line->$key = $value;
283 + }
284 + }
285 + return $rate;
286 + }
287 +
288 + /**
220 289 * Unhook the plugin's callbacks for an open POS order.
221 290 *
222 291 * @param array $args Args passed to calculate_taxes(). Unused.
223 292 * @param WC_Abstract_Order|null $order The order being recalculated.
@@ -278,10 +347,12 @@
278 347 * The street line that belongs to the address WooCommerce is taxing.
279 348 *
280 349 * The declared basis (the POS meta, else WooCommerce's setting) is tried first
281 350 * so two addresses that share a country, state, postcode and city are told
282 - * apart; the tuple check keeps the street consistent with the location that
283 - * was actually resolved, which a filter may have changed.
351 + * apart — including the store's own address, which a local customer's billing
352 + * or shipping address can match exactly; the tuple check keeps the street
353 + * consistent with the location that was actually resolved, which a filter may
354 + * have changed.
284 355 *
285 356 * @param WC_Abstract_Order $order The order.
286 357 * @param array $location Country, state, postcode and city from get_taxable_location().
287 358 *
@@ -292,11 +363,17 @@
292 363 $basis = (string) $order->get_meta( '_woocommerce_pos_tax_based_on' );
293 364 if ( '' === $basis ) {
294 365 $basis = (string) get_option( 'woocommerce_tax_based_on', 'shipping' );
295 366 }
367 + // The store address is a candidate too, but LAST unless it is the
368 + // declared basis: when a filter moves the taxed location to the other
369 + // customer address, that address must win over a store that happens
370 + // to share its country, state, postcode and city.
371 + $countries = WC()->countries;
296 372 $candidates = array(
297 373 'billing' => array( $order->get_billing_address_1(), array( $order->get_billing_country(), $order->get_billing_state(), $order->get_billing_postcode(), $order->get_billing_city() ) ),
298 374 'shipping' => array( $order->get_shipping_address_1(), array( $order->get_shipping_country(), $order->get_shipping_state(), $order->get_shipping_postcode(), $order->get_shipping_city() ) ),
375 + 'base' => array( $countries->get_base_address(), array( $countries->get_base_country(), $countries->get_base_state(), $countries->get_base_postcode(), $countries->get_base_city() ) ),
299 376 );
300 377 if ( isset( $candidates[ $basis ] ) ) {
301 378 $candidates = array( $basis => $candidates[ $basis ] ) + $candidates;
302 379 }