From b6a1bc96ce73b9e8f541d717be4e61cce3051bb0 Mon Sep 17 00:00:00 2001 From: dallendalton Date: Wed, 19 Jan 2022 14:17:51 -0700 Subject: [PATCH] Calculation/Transaction Sync Status Meta Box (#214) * Save Calculation Results to Cart and Order (#207) * Persist calculation results on cart and order * Ensure correct tax results get set on subscription * Admin Order Meta Box (#208) * Add meta box to orders * Change copied notification from alert to tool tip * INT 2155 Transaction Sync Data in Metabox (#211) * Add meta box to orders * Change copied notification from alert to tool tip * Add transaction sync data to order metabox * Refactor sync queue link method for better readability. * Remove usage of deprecated function * Remove storing of raw request and response and section for them in the order meta box (#213) * Version 4.1.0 --- CHANGELOG.md | 6 + ...cart-tax-calculation-result-data-store.php | 48 ++++ ...rder-tax-calculation-result-data-store.php | 48 ++++ .../class-tax-calculator-builder.php | 27 ++ .../TaxCalculation/class-tax-calculator.php | 51 +++- includes/abstract-class-taxjar-record.php | 12 +- includes/admin/class-admin-meta-boxes.php | 43 ++++ includes/admin/class-order-meta-box.php | 231 ++++++++++++++++++ includes/admin/views/html-order-meta-box.php | 117 +++++++++ includes/class-taxjar-customer-record.php | 7 +- includes/class-taxjar-order-record.php | 26 +- includes/class-taxjar-refund-record.php | 23 +- includes/class-taxjar-tax-calculation.php | 14 +- includes/class-wc-taxjar-integration.php | 13 +- includes/css/admin.css | 191 +++++++++++++-- ...lass-tax-calculation-result-data-store.php | 25 ++ readme.txt | 14 +- taxjar-woocommerce.php | 16 +- .../test-cart-tax-result-data-store.php | 26 ++ .../test-order-tax-calculator.php | 3 + .../test-order-tax-result-data-store.php | 20 ++ tests/specs/test-transaction-sync.php | 1 + 22 files changed, 893 insertions(+), 69 deletions(-) create mode 100644 includes/TaxCalculation/class-cart-tax-calculation-result-data-store.php create mode 100644 includes/TaxCalculation/class-order-tax-calculation-result-data-store.php create mode 100644 includes/admin/class-admin-meta-boxes.php create mode 100644 includes/admin/class-order-meta-box.php create mode 100644 includes/admin/views/html-order-meta-box.php create mode 100644 includes/interfaces/class-tax-calculation-result-data-store.php create mode 100644 tests/specs/tax-calculation/test-cart-tax-result-data-store.php create mode 100644 tests/specs/tax-calculation/test-order-tax-result-data-store.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 88695ee1..4a039c7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 4.1.0 (2022-01-19) +* Add order meta box to give details of calculation and sync statuses +* Remove usage of deprecated function is_ajax +* WooCommerce tested up to 6.1 +* Update minimum WordPress version to 5.6 + # 4.0.2 (2021-12-20) * Filter invalid PTCs before creating transactions in TaxJar. * Fix floating point precision issue causing transactions to be rejected by the TaxJar API. diff --git a/includes/TaxCalculation/class-cart-tax-calculation-result-data-store.php b/includes/TaxCalculation/class-cart-tax-calculation-result-data-store.php new file mode 100644 index 00000000..6c022777 --- /dev/null +++ b/includes/TaxCalculation/class-cart-tax-calculation-result-data-store.php @@ -0,0 +1,48 @@ +cart = $cart; + } + + /** + * Persist results on the cart + * + * @param Tax_Calculation_Result $calculation_result Result of tax calculation. + */ + public function update( Tax_Calculation_Result $calculation_result ) { + $calculation_result->set_raw_request(''); + $calculation_result->set_raw_response(''); + $this->cart->tax_calculation_results = $calculation_result->to_json(); + } + +} diff --git a/includes/TaxCalculation/class-order-tax-calculation-result-data-store.php b/includes/TaxCalculation/class-order-tax-calculation-result-data-store.php new file mode 100644 index 00000000..99959aa4 --- /dev/null +++ b/includes/TaxCalculation/class-order-tax-calculation-result-data-store.php @@ -0,0 +1,48 @@ +order = $order; + } + + /** + * Persist results on the order + * + * @param Tax_Calculation_Result $calculation_result Result of tax calculation. + */ + public function update( Tax_Calculation_Result $calculation_result ) { + $calculation_result->set_raw_request(''); + $calculation_result->set_raw_response(''); + $this->order->update_meta_data( '_taxjar_tax_result', $calculation_result->to_json() ); + } + +} diff --git a/includes/TaxCalculation/class-tax-calculator-builder.php b/includes/TaxCalculation/class-tax-calculator-builder.php index f2daed5f..ee05c666 100644 --- a/includes/TaxCalculation/class-tax-calculator-builder.php +++ b/includes/TaxCalculation/class-tax-calculator-builder.php @@ -8,6 +8,7 @@ namespace TaxJar; use WC_Cart; +use WC_Order; use WC_Taxjar_Nexus; use TaxJar_Settings; @@ -101,6 +102,7 @@ private function setup_api_tax_calculator( $order ) { $this->set_order_applicator( $order ); $this->set_order_validator( $order ); $this->set_context( 'api_order' ); + $this->set_order_tax_result_data_store( $order ); } /** @@ -114,6 +116,7 @@ private function setup_order_calculator( $order ) { $this->set_order_applicator( $order ); $this->set_order_validator( $order ); $this->set_context( 'order' ); + $this->set_order_tax_result_data_store( $order ); } /** @@ -270,6 +273,7 @@ private function setup_admin_order_calculator( $order ) { $this->set_order_applicator( $order ); $this->set_order_validator( $order ); $this->set_context( 'admin_order' ); + $this->set_order_tax_result_data_store( $order ); } /** @@ -294,6 +298,7 @@ public function build_subscription_order_calculator( $subscription ) { $this->set_order_applicator( $subscription ); $this->set_order_validator( $subscription ); $this->set_context( 'subscription_order' ); + $this->set_order_tax_result_data_store( $subscription ); return $this->calculator; } @@ -310,9 +315,20 @@ public function build_renewal_order_calculator( $renewal ): Tax_Calculator { $this->set_order_applicator( $renewal ); $this->set_order_validator( $renewal ); $this->set_context( 'renewal_order' ); + $this->set_order_tax_result_data_store( $renewal ); return $this->calculator; } + /** + * Set the tax result data store when building an order calculator. + * + * @param WC_Order $order Order whose tax is being calculated. + */ + private function set_order_tax_result_data_store( WC_Order $order ) { + $result_data_store = new Order_Tax_Calculation_Result_Data_Store( $order ); + $this->calculator->set_result_data_store( $result_data_store ); + } + /** * Build cart tax calculator. * @@ -326,9 +342,20 @@ public function build_cart_calculator( WC_Cart $cart ): Tax_Calculator { $this->set_cart_tax_applicator( $cart ); $this->set_cart_validator( $cart ); $this->set_context( 'cart' ); + $this->set_cart_tax_result_data_store( $cart ); return $this->calculator; } + /** + * Set the tax result data store when building a cart calculator. + * + * @param WC_Cart $cart Cart whose tax is being calculated. + */ + private function set_cart_tax_result_data_store( WC_Cart $cart ) { + $result_data_store = new Cart_Tax_Calculation_Result_Data_Store( $cart ); + $this->calculator->set_result_data_store( $result_data_store ); + } + /** * Sets the logger for cart tax calculation. */ diff --git a/includes/TaxCalculation/class-tax-calculator.php b/includes/TaxCalculation/class-tax-calculator.php index ce77acac..d21f5945 100644 --- a/includes/TaxCalculation/class-tax-calculator.php +++ b/includes/TaxCalculation/class-tax-calculator.php @@ -82,6 +82,20 @@ class Tax_Calculator { */ private $tax_details; + /** + * Persists the tax calculation results on the object having tax calculated. + * + * @var Tax_Calculation_Result_Data_Store + */ + private $result_data_store; + + /** + * Result of tax calculation. + * + * @var Tax_Calculation_Result + */ + private $result; + /** * Sets the logger. * @@ -154,6 +168,15 @@ public function get_context() { return $this->context; } + /** + * Set the result data store. + * + * @param Tax_Calculation_Result_Data_Store $data_store Result data store. + */ + public function set_result_data_store( Tax_Calculation_Result_Data_Store $data_store ) { + $this->result_data_store = $data_store; + } + /** * Calculates and applies tax if possible and necessary. */ @@ -166,6 +189,8 @@ public function maybe_calculate_and_apply_tax() { $this->success(); } catch ( Exception $exception ) { $this->failure( $exception ); + } finally { + $this->result_data_store->update( $this->result ); } } @@ -250,12 +275,12 @@ public function apply_tax() { * Logs success details. */ private function success() { - $result = new Tax_Calculation_Result(); - $result->set_success( true ); - $result->set_context( $this->get_context() ); - $result->set_raw_request( $this->request_body->to_json() ); - $result->set_raw_response( wp_json_encode( $this->tax_details->get_raw_response() ) ); - $this->logger->log_success( $result ); + $this->result = new Tax_Calculation_Result(); + $this->result->set_success( true ); + $this->result->set_context( $this->get_context() ); + $this->result->set_raw_request( $this->request_body->to_json() ); + $this->result->set_raw_response( wp_json_encode( $this->tax_details->get_raw_response() ) ); + $this->logger->log_success( $this->result ); } /** @@ -264,16 +289,16 @@ private function success() { * @param Exception $exception Exception that occurred during tax calculation. */ private function failure( Exception $exception ) { - $result = new Tax_Calculation_Result(); - $result->set_success( false ); - $result->set_context( $this->get_context() ); - $result->set_raw_request( $this->request_body->to_json() ); + $this->result = new Tax_Calculation_Result(); + $this->result->set_success( false ); + $this->result->set_context( $this->get_context() ); + $this->result->set_raw_request( $this->request_body->to_json() ); if ( $this->tax_details ) { - $result->set_raw_response( wp_json_encode( $this->tax_details->get_raw_response() ) ); + $this->result->set_raw_response( wp_json_encode( $this->tax_details->get_raw_response() ) ); } - $result->set_error_message( $exception->getMessage() ); - $this->logger->log_failure( $result, $exception ); + $this->result->set_error_message( $exception->getMessage() ); + $this->logger->log_failure( $this->result, $exception ); } } diff --git a/includes/abstract-class-taxjar-record.php b/includes/abstract-class-taxjar-record.php index 91511e1d..bc1b4c33 100644 --- a/includes/abstract-class-taxjar-record.php +++ b/includes/abstract-class-taxjar-record.php @@ -289,12 +289,13 @@ public function sync_success() { $this->save(); } - public function add_object_sync_metadata() { + public function update_object_sync_success_meta_data() { $data = $this->get_data(); $data_hash = hash( 'md5', serialize( $data ) ); $sync_datetime = $this->get_processed_datetime(); $this->object->update_meta_data( '_taxjar_last_sync', $sync_datetime ); $this->object->update_meta_data( '_taxjar_hash', $data_hash ); + $this->object->delete_meta_data( '_taxjar_sync_last_error' ); $this->object->save(); } @@ -331,6 +332,13 @@ public function sync_failure( $error_message ) { $this->save(); } + public function update_object_sync_failure_meta_data( $error_message ) { + if ( $this->object ) { + $this->object->update_meta_data( '_taxjar_sync_last_error', $error_message ); + $this->object->save(); + } + } + abstract function create_in_taxjar(); abstract function update_in_taxjar(); abstract function delete_in_taxjar(); @@ -555,4 +563,4 @@ public function set_last_error( $last_error ) { public function get_plugin_parameter() { return 'woo'; } -} \ No newline at end of file +} diff --git a/includes/admin/class-admin-meta-boxes.php b/includes/admin/class-admin-meta-boxes.php new file mode 100644 index 00000000..0041cea1 --- /dev/null +++ b/includes/admin/class-admin-meta-boxes.php @@ -0,0 +1,43 @@ +ID; + $order = wc_get_order( $order_id ); + $metadata = self::get_order_tax_calculation_metadata( $order ); + wp_enqueue_script( 'accordion' ); + + include_once dirname( __FILE__ ) . '/views/html-order-meta-box.php'; + } + + /** + * Get the metadata to display in the meta box. + * + * @param \WC_Order $order Order object. + * + * @return array + */ + private static function get_order_tax_calculation_metadata( \WC_Order $order ): array { + $metadata = array(); + $raw_calculation_result = $order->get_meta( '_taxjar_tax_result' ); + + if ( ! empty( $raw_calculation_result ) ) { + $result = Tax_Calculation_Result::from_json_string( $raw_calculation_result ); + $metadata['calculation_status'] = self::get_calculation_status( $result ); + $metadata['calculation_status_description'] = self::get_calculation_status_description( $result ); + } else { + $metadata['calculation_status'] = 'unknown'; + $metadata['calculation_status_description'] = 'No TaxJar calculation data is present on the order. This may indicate that TaxJar was not enabled when this order was placed, that tax calculation has not yet occurred (if creating the order manually through admin) or that the tax was calculated prior to TaxJar version 4.1.0 which introduced this status feature.'; + } + + return $metadata; + } + + /** + * Get the calculation status of the result. + * + * @param Tax_Calculation_Result $result Tax calculation result. + * + * @return string + */ + private static function get_calculation_status( Tax_Calculation_Result $result ): string { + if ( $result->get_success() ) { + return 'success'; + } else { + return 'fail'; + } + } + + /** + * Get the calculation status description. + * + * @param Tax_Calculation_Result $result Tax calculation result. + * + * @return string + */ + private static function get_calculation_status_description( Tax_Calculation_Result $result ): string { + if ( $result->get_success() ) { + return 'Tax was calculated in realtime through the TaxJar API.'; + } else { + return 'TaxJar did not or was unable to perform a tax calculation on this order.
Reason: ' . $result->get_error_message(); + } + } + + /** + * Get the sync status of the order. + * + * @param \WC_Order $order Order object. + * + * @return string + */ + public static function get_order_sync_accordion_content( \WC_Order $order ): string { + $last_sync_timestamp = $order->get_meta( '_taxjar_last_sync' ); + $last_error = $order->get_meta( '_taxjar_sync_last_error' ); + + if ( empty( $last_sync_timestamp ) ) { + if ( empty( $last_error ) ) { + $queue_id = TaxJar_Order_Record::find_active_in_queue( $order->get_id() ); + + if ( $queue_id ) { + return 'Order is currently in the sync queue. ' . self::get_sync_queue_link( $order ); + } + + $record = new TaxJar_Order_Record( $order->get_id(), true ); + $record->load_object( $order ); + $can_sync = $record->should_sync(); + + if ( $can_sync ) { + return 'Order is ready to sync to TaxJar but has not yet been added to the sync queue.'; + } else { + return 'Order cannot sync to TaxJar. ' . $record->get_error()['message']; + } + } else { + return 'Order failed to sync to TaxJar. ' . $last_error; + } + } else { + $sync_date = wp_date( wc_date_format(), wc_string_to_timestamp( $last_sync_timestamp ) ); + $sync_time = wp_date( wc_time_format(), wc_string_to_timestamp( $last_sync_timestamp ) ); + return 'Order successfully synced to TaxJar.
Last Synced on: ' . $sync_date . ' ' . $sync_time; + } + } + + /** + * Get accordion content for a refund. + * + * @param \WC_Order_Refund $refund Refund. + * + * @return string + */ + public static function get_refund_sync_accordion_content( \WC_Order_Refund $refund ): string { + $last_sync_timestamp = $refund->get_meta( '_taxjar_last_sync' ); + $last_error = $refund->get_meta( '_taxjar_sync_last_error' ); + + if ( empty( $last_sync_timestamp ) ) { + if ( empty( $last_error ) ) { + $queue_id = TaxJar_Refund_Record::find_active_in_queue( $refund->get_id() ); + + if ( $queue_id ) { + return 'Refund is currently in the sync queue. ' . self::get_sync_queue_link( $refund ); + } + + $record = new TaxJar_Refund_Record( $refund->get_id(), true ); + $record->load_object(); + $can_sync = $record->should_sync(); + + if ( $can_sync ) { + return 'Refund is ready to sync to TaxJar but has not yet been added to the sync queue.'; + } else { + return 'Refund cannot sync to TaxJar. ' . $record->get_error()['message']; + } + } else { + return 'Refund failed to sync to TaxJar. ' . $last_error; + } + } else { + $sync_date = wp_date( wc_date_format(), wc_string_to_timestamp( $last_sync_timestamp ) ); + $sync_time = wp_date( wc_time_format(), wc_string_to_timestamp( $last_sync_timestamp ) ); + return 'Refund successfully synced to TaxJar.
Last Synced on: ' . $sync_date . ' ' . $sync_time; + } + } + + /** + * Get the link to the sync queue search for a particular order. + * + * @param \WC_Abstract_Order $order Order or Refund to get sync queue link for. + * + * @return string + */ + private static function get_sync_queue_link( \WC_Abstract_Order $order ): string { + $link = add_query_arg( + array( + 'page' => 'wc-settings', + 'tab' => 'taxjar-integration', + 'section' => 'sync_queue', + 's' => $order->get_id(), + 'paged' => 1, + ), + admin_url( 'admin.php' ) + ); + + return 'View Progress'; + } + + /** + * Get the sync status of an order or refund. + * + * @param \WC_Abstract_Order $order Order or Refund. + * + * @return string + */ + public static function get_sync_status( \WC_Abstract_Order $order ): string { + $last_sync_timestamp = $order->get_meta( '_taxjar_last_sync' ); + $last_error = $order->get_meta( '_taxjar_sync_last_error' ); + + if ( empty( $last_sync_timestamp ) ) { + if ( empty( $last_error ) ) { + return 'not-synced'; + } else { + return 'failed'; + } + } else { + return 'synced'; + } + } + + /** + * Get the text description of an order or refund sync status. + * + * @param string $status Sync status of order or refund. + * @param string $type Type, either order or refund. + * + * @return string + */ + public static function get_sync_status_tip( string $status, string $type ): string { + $tips = array( + 'order-synced' => __( 'Order successfully synced to TaxJar.', 'taxjar' ), + 'order-not-synced' => __( 'Order has not been synced to TaxJar.', 'taxjar' ), + 'order-failed' => __( 'Order failed to sync to TaxJar.', 'taxjar' ), + 'refund-synced' => __( 'Refund successfully synced to TaxJar.', 'taxjar' ), + 'refund-not-synced' => __( 'Refund has not been synced to TaxJar.', 'taxjar' ), + 'refund-failed' => __( 'Refund failed to sync to TaxJar.', 'taxjar' ), + ); + + return $tips[ $type . '-' . $status ]; + } +} diff --git a/includes/admin/views/html-order-meta-box.php b/includes/admin/views/html-order-meta-box.php new file mode 100644 index 00000000..8ccad8ed --- /dev/null +++ b/includes/admin/views/html-order-meta-box.php @@ -0,0 +1,117 @@ + + +
+
+ +
+

+ + array() ) ); ?> +

+ get_id(), $order ); ?> +
+
+ +

+ get_refunds(); + $order_sync_status = Order_Meta_Box::get_sync_status( $order ); + ?> + +
+
+

+ Order #get_id() ); ?> + +

+
+

+ array(), + 'a' => array( 'href' => array() ), + ) + ); + ?> +

+
+
+ +
+

+ Refund #get_id() ); ?> + +

+
+

+ array(), + 'a' => array( 'href' => array() ), + ) + ); + ?> +

+
+
+ +
+ + + + get_id(), $order ); ?> +
+ get_id(), $order ); ?> +
+
diff --git a/includes/class-taxjar-customer-record.php b/includes/class-taxjar-customer-record.php index 930f5a69..d4c25720 100644 --- a/includes/class-taxjar-customer-record.php +++ b/includes/class-taxjar-customer-record.php @@ -79,7 +79,12 @@ public function should_sync( $ignore_status = false ) { */ public function sync_success() { parent::sync_success(); - $this->add_object_sync_metadata(); + $this->update_object_sync_success_meta_data(); + } + + public function sync_failure( $error_message ) { + parent::sync_failure( $error_message ); + $this->update_object_sync_failure_meta_data( $error_message ); } /** diff --git a/includes/class-taxjar-order-record.php b/includes/class-taxjar-order-record.php index d7f28548..f5179561 100644 --- a/includes/class-taxjar-order-record.php +++ b/includes/class-taxjar-order-record.php @@ -30,7 +30,7 @@ public function get_record_type() { public function should_sync( $ignore_status = false ) { if ( ! isset( $this->object ) ) { - $this->add_error( __( 'Order failed validation - order object not loaded to record before syncing.', 'wc-taxjar' ) ); + $this->add_error( __( 'Order object not loaded to record before syncing.', 'wc-taxjar' ) ); return false; } @@ -38,13 +38,13 @@ public function should_sync( $ignore_status = false ) { $status = $this->object->get_status(); $valid_statuses = apply_filters( 'taxjar_valid_order_statuses_for_sync', array( 'completed', 'refunded' ) ); if ( ! in_array( $status, $valid_statuses ) ) { - $this->add_error( __( 'Order failed validation - invalid order status.', 'wc-taxjar' ) ); + $this->add_error( __( 'Order has an invalid status. Only orders with the following statuses can sync to TaxJar: ', 'wc-taxjar' ) . implode( ", ", $valid_statuses) ); return false; } if ( WC_Taxjar_Transaction_Sync::should_validate_order_completed_date() ) { if ( empty( $this->object->get_date_completed() ) ) { - $this->add_error( __( 'Order failed validation - order has no completed date', 'wc-taxjar' ) ); + $this->add_error( __( 'Order does not have a completed date. Only orders that have been completed can sync to TaxJar.', 'wc-taxjar' ) ); return false; } } @@ -52,7 +52,7 @@ public function should_sync( $ignore_status = false ) { if ( ! $this->get_force_push() ) { if ( hash( 'md5', serialize( $this->get_data() ) ) === $this->get_object_hash() ) { - $this->add_error( __( 'Order failed validation, order data not different than previous sync.', 'wc-taxjar' ) ); + $this->add_error( __( 'Order data is not different from previous sync, re-syncing the transaction is not necessary.', 'wc-taxjar' ) ); return false; } } @@ -60,22 +60,22 @@ public function should_sync( $ignore_status = false ) { $order_data = $this->get_data(); if ( ! in_array( $order_data[ 'to_country' ], TaxJar_Record::allowed_countries() ) ) { - $this->add_error( __( 'Order failed validation, ship to country did not pass validation', 'wc-taxjar' ) ); + $this->add_error( __( 'Order ship to country is not supported for reporting and filing. Only orders shipped to the following countries will sync to TaxJar: ', 'wc-taxjar' ) . implode( ", ", TaxJar_Record::allowed_countries() ) ); return false; } if ( ! in_array( $this->object->get_currency(), TaxJar_Record::allowed_currencies() ) ) { - $this->add_error( __( 'Order failed validation, currency did not pass validation.', 'wc-taxjar' ) ); + $this->add_error( __( 'Order currency is not supported. Only orders with the following currencies will sync to TaxJar: ', 'wc-taxjar' ) . implode( ", ", TaxJar_Record::allowed_currencies() ) ); return false; } if ( ! $this->has_valid_ship_from_address() ) { - $this->add_error( __( 'Order failed validation, missing required ship from field', 'wc-taxjar' ) ); + $this->add_error( __( 'Order is missing required ship from field.', 'wc-taxjar' ) ); return false; } if ( empty( $order_data[ 'to_country' ] ) || empty( $order_data[ 'to_state' ] ) || empty( $order_data[ 'to_zip' ] ) || empty( $order_data[ 'to_city' ] ) ) { - $this->add_error( __( 'Order failed validation, missing required ship to field.', 'wc-taxjar' ) ); + $this->add_error( __( 'Order is missing required ship to field.', 'wc-taxjar' ) ); return false; } @@ -87,7 +87,15 @@ public function sync_success() { // prevent creating new record in queue when updating a successfully synced order remove_action( 'woocommerce_update_order', array( 'WC_Taxjar_Transaction_Sync', 'order_updated' ) ); - $this->add_object_sync_metadata(); + $this->update_object_sync_success_meta_data(); + add_action( 'woocommerce_update_order', array( 'WC_Taxjar_Transaction_Sync', 'order_updated' ) ); + } + + public function sync_failure( $error_message ) { + parent::sync_failure( $error_message ); + + remove_action( 'woocommerce_update_order', array( 'WC_Taxjar_Transaction_Sync', 'order_updated' ) ); + $this->update_object_sync_failure_meta_data( $error_message ); add_action( 'woocommerce_update_order', array( 'WC_Taxjar_Transaction_Sync', 'order_updated' ) ); } diff --git a/includes/class-taxjar-refund-record.php b/includes/class-taxjar-refund-record.php index 3a7477e1..2527c86a 100644 --- a/includes/class-taxjar-refund-record.php +++ b/includes/class-taxjar-refund-record.php @@ -23,47 +23,47 @@ public function load_object() { public function should_sync() { $data = $this->get_data(); if ( empty( $data ) ) { - $this->add_error( __( 'Refund failed validation - refund object not loaded to record before syncing.', 'wc-taxjar' ) ); + $this->add_error( __( 'Refund object not loaded to record before syncing.', 'wc-taxjar' ) ); return false; } if ( WC_Taxjar_Transaction_Sync::should_validate_order_completed_date() ) { if ( empty( $this->order_completed_date ) ) { - $this->add_error( __( 'Refund failed validation - parent order has no completed date.', 'wc-taxjar' ) ); + $this->add_error( __( 'Parent order does not have a completed date. Only refunds of orders that have been completed can sync to TaxJar.', 'wc-taxjar' ) ); return false; } } $valid_order_statuses = apply_filters( 'taxjar_valid_order_statuses_for_sync', array( 'completed', 'refunded' ) ); if ( empty( $this->order_status ) || ! in_array( $this->order_status, $valid_order_statuses ) ) { - $this->add_error( __( 'Refund failed validation - parent order does not have valid status.', 'wc-taxjar' ) ); + $this->add_error( __( 'Parent order has an invalid status. Only refunds of orders with the following statuses can sync to TaxJar: ', 'wc-taxjar' ) . implode( ", ", $valid_order_statuses ) ); return false; } if ( ! $this->get_force_push() ) { if ( hash( 'md5', serialize( $this->get_data() ) ) === $this->get_object_hash() ) { - $this->add_error( __( 'Refund failed validation - not updated since last sync.', 'wc-taxjar' ) ); + $this->add_error( __( 'Refund data is not different from previous sync, re-syncing the transaction is not necessary.', 'wc-taxjar' ) ); return false; } } if ( ! in_array( $data[ 'to_country' ], TaxJar_Record::allowed_countries() ) ) { - $this->add_error( __( 'Refund failed validation, ship to country did not pass validation', 'wc-taxjar' ) ); + $this->add_error( __( 'Parent order ship to country is not supported for reporting and filing. Only orders shipped to the following countries will sync to TaxJar: ', 'wc-taxjar' ) . implode( ", ", TaxJar_Record::allowed_countries()) ); return false; } if ( ! in_array( $this->object->get_currency(), TaxJar_Record::allowed_currencies() ) ) { - $this->add_error( __( 'Refund failed validation, currency did not pass validation.', 'wc-taxjar' ) ); + $this->add_error( __( 'Refund currency is not supported. Only refunds with the following currencies will sync to TaxJar: ', 'wc-taxjar' ) . implode( ", ", TaxJar_Record::allowed_currencies() ) ); return false; } if ( ! $this->has_valid_ship_from_address() ) { - $this->add_error( __( 'Refund failed validation - missing required ship from field on parent order', 'wc-taxjar' ) ); + $this->add_error( __( 'Parent order is missing required ship from field.', 'wc-taxjar' ) ); return false; } if ( empty( $data[ 'to_country' ] ) || empty( $data[ 'to_state' ] ) || empty( $data[ 'to_zip' ] ) || empty( $data[ 'to_city' ] ) ) { - $this->add_error( __( 'Refund failed validation - missing required ship to field on parent order.', 'wc-taxjar' ) ); + $this->add_error( __( 'Parent order is missing required ship to field', 'wc-taxjar' ) ); return false; } @@ -72,7 +72,12 @@ public function should_sync() { public function sync_success() { parent::sync_success(); - $this->add_object_sync_metadata(); + $this->update_object_sync_success_meta_data(); + } + + public function sync_failure( $error_message ) { + parent::sync_failure( $error_message ); + $this->update_object_sync_failure_meta_data( $error_message ); } public function get_data_from_object() { diff --git a/includes/class-taxjar-tax-calculation.php b/includes/class-taxjar-tax-calculation.php index 9bd7598d..73ef69dd 100644 --- a/includes/class-taxjar-tax-calculation.php +++ b/includes/class-taxjar-tax-calculation.php @@ -38,6 +38,18 @@ public function init_hooks() { add_filter( 'wcs_new_order_created', array( $this, 'calculate_renewal_order_totals' ), 10, 3 ); add_action( 'woocommerce_order_after_calculate_totals', array( $this, 'maybe_calculate_order_taxes' ), 10, 2 ); + add_action( 'woocommerce_checkout_create_order', array( $this, 'persist_cart_calculation_results_to_order'), 10, 1 ); + add_action( 'woocommerce_checkout_create_subscription', array( $this, 'persist_recurring_cart_calculation_results_to_subscription'), 10, 4 ); + } + + public function persist_cart_calculation_results_to_order( $order ) { + $calculation_results = WC()->cart->tax_calculation_results; + $order->update_meta_data( '_taxjar_tax_result', $calculation_results ); + } + + public function persist_recurring_cart_calculation_results_to_subscription( $subscription, $posted_data, $order, $recurring_cart ) { + $calculation_results = $recurring_cart->tax_calculation_results; + $subscription->update_meta_data( '_taxjar_tax_result', $calculation_results ); } public function maybe_calculate_order_taxes( $and_taxes, $order ) { @@ -132,7 +144,7 @@ private function is_cart_or_checkout_page() { * @return bool */ private function is_mini_cart() { - return is_cart() && is_ajax(); + return is_cart() && wp_doing_ajax(); } /** diff --git a/includes/class-wc-taxjar-integration.php b/includes/class-wc-taxjar-integration.php index 61b3f413..6a6f482f 100644 --- a/includes/class-wc-taxjar-integration.php +++ b/includes/class-wc-taxjar-integration.php @@ -45,7 +45,7 @@ public function __construct() { TaxJar_Settings::init(); $this->tax_calculations = new TaxJar_Tax_Calculation(); - if ( $this->on_settings_page() ) { + if ( is_admin() ) { add_action( 'admin_enqueue_scripts', array( $this, 'load_taxjar_admin_assets' ) ); } @@ -53,6 +53,8 @@ public function __construct() { // Scripts / Stylesheets add_action( 'admin_enqueue_scripts', array( $this, 'load_taxjar_admin_new_order_assets' ) ); } + + new \TaxJar\Admin_Meta_Boxes(); } /** @@ -217,15 +219,6 @@ private function is_order_post_type( $post_type ) { return in_array( $post_type, $allowed_post_types, true ); } - /** - * Checks if currently on the TaxJar settings page - * - * @return boolean - */ - public function on_settings_page() { - return ( isset( $_GET['page'] ) && 'wc-settings' === $_GET['page'] && isset( $_GET['tab'] ) && 'taxjar-integration' === $_GET['tab'] ); - } - /** * Admin Assets */ diff --git a/includes/css/admin.css b/includes/css/admin.css index 43e2c0e4..a2499b71 100644 --- a/includes/css/admin.css +++ b/includes/css/admin.css @@ -1,17 +1,3 @@ -/* Remove Calculate Taxes button */ -#poststuff #woocommerce-order-totals .buttons .calc_line_taxes { - display: none !important; -} - -/* Remove Add Tax Row Button */ -.add_total_row { - display: none !important; -} - -.add-order-tax { - display: none !important; -} - .widefat .column-taxjar_actions a.button { display: block; text-indent: -9999px; @@ -90,4 +76,179 @@ div.taxjar-connected p { .woocommerce table.form-table th label[for="woocommerce_taxjar-integration_settings[api_token]"] { display: none; -} \ No newline at end of file +} + +#taxjar_order_data.panel-wrap { + overflow: hidden; +} + +#taxjar_order_data ul.wc-tabs { + margin: 0; + width: 20%; + float: left; + line-height: 1em; + padding: 0 0 10px; + position: relative; + background-color: #fafafa; + border-right: 1px solid #eee; + box-sizing: border-box; +} + +#taxjar_order_data ul.wc-tabs li { + margin: 0; + padding: 0; + display: block; + position: relative; +} + +#taxjar_order_data ul.wc-tabs li a { + margin: 0; + padding: 10px; + display: block; + box-shadow: none; + text-decoration: none; + line-height: 20px!important; + border-bottom: 1px solid #eee; +} + +#taxjar_order_data ul.wc-tabs li.active a { + color: #555; + position: relative; + background-color: #eee; +} + +#taxjar_order_data ul.wc-tabs li a::before { + font-family: Dashicons; + speak: never; + font-weight: 400; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; + content: ""; + text-decoration: none; +} + +#taxjar_order_data .wc-metaboxes-wrapper, #taxjar_order_data .woocommerce_options_panel { + float: left; + width: 80%; +} + +#taxjar.postbox .inside { + margin: 0; + padding: 0; +} + +#taxjar_order_data ul.wc-tabs li.calculation_status_tab a::before, #taxjar_order_data ul.wc-tabs li.sync_status_tab a::before { + content: "\f226"; +} + +#taxjar_order_data ul.wc-tabs li.advanced_tab a::before { + content: "\f111"; +} + +#taxjar_order_data ul.wc-tabs li a span { + margin-left: 0.618em; + margin-right: 0.618em; +} + +#taxjar_order_data ul.wc-tabs::after { + content: ""; + display: block; + width: 100%; + height: 9999em; + position: absolute; + bottom: -9999em; + left: 0; + background-color: #fafafa; + border-right: 1px solid #eee; +} + +#calculation_status_order_data label span.calculation-status-icon::before { + font-family: Dashicons; + speak: never; + font-weight: 400; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; + content: ""; + text-decoration: none; +} + +#calculation_status_order_data label span.calculation-status-icon.success::before { + content: "\f159"; + color: #5b841b; + background-color: #5b841b; + border-radius: 50%; +} + +#calculation_status_order_data label span.calculation-status-icon.fail::before { + content: "\f153"; + color: #d63638; +} + +#calculation_status_order_data label span.calculation-status-icon.unknown::before { + content: "\f348"; + color: #dba617; +} + +#advanced_order_data pre { + margin: 0 0 9px; + font-size: 12px; + padding: 5px 9px; + line-height: 24px; + max-width: 95%; +} + +#calculation_status_order_data .description { + font-style: italic; + margin: 0px; +} + +#advanced_order_data .accordion-section-title { + font-size: 12px; +} + +#advanced_order_data span.copy-button.dashicons.dashicons-clipboard { + float: right; + margin-right: 20px; + font-size: 14px; + vertical-align: middle; + margin-top: 2px; +} + +#sync_status_order_data span.sync-status::before { + font-family: Dashicons; + speak: never; + font-weight: 400; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; + content: ""; + text-decoration: none; + margin-left: 15px; + vertical-align: middle; +} + +#sync_status_order_data span.sync-status.synced::before { + content: "\f159"; + color: #5b841b; + background-color: #5b841b; + border-radius: 50%; +} + +#sync_status_order_data span.sync-status.not-synced::before { + content: "\f159"; + color: #dcdcde; + background-color: #dcdcde; + border-radius: 50%; +} + +#sync_status_order_data span.sync-status.failed::before { + content: "\f159"; + color: #d63638; + background-color: #d63638; + border-radius: 50%; +} diff --git a/includes/interfaces/class-tax-calculation-result-data-store.php b/includes/interfaces/class-tax-calculation-result-data-store.php new file mode 100644 index 00000000..8742a920 --- /dev/null +++ b/includes/interfaces/class-tax-calculation-result-data-store.php @@ -0,0 +1,25 @@ +cart->empty_cart(); + unset( WC()->cart->tax_calculation_results ); + parent::tearDown(); + } + + function test_result_is_stored_on_cart() { + $test_json = 'test'; + $cart = WC()->cart; + $result_stub = $this->createMock( Tax_Calculation_Result::class ); + $result_stub->method( 'to_json' )->willReturn( $test_json ); + $cart_result_data_store = new Cart_Tax_Calculation_Result_Data_Store( $cart ); + + $cart_result_data_store->update( $result_stub ); + + $this->assertEquals( $test_json, $cart->tax_calculation_results ); + } +} diff --git a/tests/specs/tax-calculation/test-order-tax-calculator.php b/tests/specs/tax-calculation/test-order-tax-calculator.php index 18e6384f..3fbe5509 100644 --- a/tests/specs/tax-calculation/test-order-tax-calculator.php +++ b/tests/specs/tax-calculation/test-order-tax-calculator.php @@ -17,6 +17,7 @@ class Test_Order_Tax_Calculator extends WP_UnitTestCase { private $mock_tax_details; private $mock_order; private $mock_validator; + private $mock_tax_result_data_store; public function setUp() { $this->mock_request_body = $this->createMock( Tax_Request_Body::class ); @@ -33,6 +34,7 @@ public function setUp() { $this->mock_logger = $this->createMock( Tax_Calculation_Logger::class ); $this->mock_cache = $this->createMock( Cache_Interface::class ); $this->mock_applicator = $this->createMock( Tax_Applicator_Interface::class ); + $this->mock_tax_result_data_store = $this->createMock( Order_Tax_Calculation_Result_Data_Store::class ); $this->mock_order = $this->createMock( WC_Order::class ); $this->mock_validator = $this->createMock( Tax_Calculation_Validator_Interface::class ); @@ -63,6 +65,7 @@ private function build_calculator() { $calculator->set_applicator( $this->mock_applicator ); $calculator->set_validator( $this->mock_validator ); $calculator->set_context( 'test' ); + $calculator->set_result_data_store( $this->mock_tax_result_data_store ); return $calculator; } } diff --git a/tests/specs/tax-calculation/test-order-tax-result-data-store.php b/tests/specs/tax-calculation/test-order-tax-result-data-store.php new file mode 100644 index 00000000..f278d289 --- /dev/null +++ b/tests/specs/tax-calculation/test-order-tax-result-data-store.php @@ -0,0 +1,20 @@ +createMock( Tax_Calculation_Result::class ); + $result_stub->method( 'to_json' )->willReturn( $test_json ); + $cart_result_data_store = new Order_Tax_Calculation_Result_Data_Store( $order ); + + $cart_result_data_store->update( $result_stub ); + + $this->assertEquals( $test_json, $order->get_meta( '_taxjar_tax_result' ) ); + } +} diff --git a/tests/specs/test-transaction-sync.php b/tests/specs/test-transaction-sync.php index b8e067ae..5b5cf92b 100644 --- a/tests/specs/test-transaction-sync.php +++ b/tests/specs/test-transaction-sync.php @@ -280,6 +280,7 @@ function test_order_record_sync_failure() { $record->sync_failure( "Last Error" ); $updated_record = new TaxJar_Order_Record( $order->get_id() ); + $updated_record->load_object(); $updated_record->set_queue_id( $record->get_queue_id() ); $updated_record->read();