diff --git a/includes/API/Orders_Controller.php b/includes/API/Orders_Controller.php index 34700d66..597dd163 100644 --- a/includes/API/Orders_Controller.php +++ b/includes/API/Orders_Controller.php @@ -86,7 +86,6 @@ public function wcpos_dispatch_request( $dispatch_result, WP_REST_Request $reque add_filter( 'woocommerce_order_get_items', array( $this, 'wcpos_order_get_items' ), 10, 3 ); add_action( 'woocommerce_before_order_object_save', array( $this, 'wcpos_before_order_object_save' ), 10, 2 ); add_filter( 'woocommerce_rest_shop_order_object_query', array( $this, 'wcpos_shop_order_query' ), 10, 2 ); - add_filter( 'option_woocommerce_tax_based_on', array( $this, 'wcpos_tax_based_on' ), 10, 2 ); /** * Check if the request is for all orders and if the 'posts_per_page' is set to -1. @@ -539,29 +538,6 @@ public function wcpos_before_order_object_save( WC_Abstract_Order $order ): void } } - /** - * Filters the value of the woocommerce_tax_based_on option. - * - * @param mixed $value Value of the option. - * @param string $option Option name. - */ - public function wcpos_tax_based_on( $value, $option ) { - $tax_based_on = 'base'; // default value is base - - // try to get POS tax settings from order meta - $raw_data = $this->wcpos_request->get_json_params(); - - if ( isset( $raw_data['meta_data'] ) ) { - foreach ( $raw_data['meta_data'] as $meta ) { - if ( '_woocommerce_pos_tax_based_on' == $meta['key'] ) { - $tax_based_on = $meta['value']; - } - } - } - - return $tax_based_on; - } - /** * Filter the order query. * diff --git a/includes/API/Stores.php b/includes/API/Stores.php index d04b9a96..2f45e672 100644 --- a/includes/API/Stores.php +++ b/includes/API/Stores.php @@ -1,4 +1,9 @@ namespace, @@ -44,6 +49,16 @@ public function register_routes(): void { 'permission_callback' => array( $this, 'check_permissions' ), ) ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\d]+)', + array( + 'methods' => 'GET', + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'check_permissions' ), + ) + ); } /** @@ -77,6 +92,28 @@ public function get_items( $request ) { } } + /** + * Retrieve a single store. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function get_item( $request ) { + $store = wcpos_get_store( $request['id'] ); + + if ( ! $store ) { + return new \WP_Error( + 'woocommerce_pos_store_not_found', + esc_html__( 'Store not found', 'woocommerce-pos' ), + array( 'status' => 404 ) + ); + } + + $data = $this->prepare_item_for_response( $store, $request ); + + return rest_ensure_response( $data ); + } + /** * Prepare a single product output for response. * diff --git a/includes/Abstracts/Store.php b/includes/Abstracts/Store.php index 68d1ba43..8264f162 100644 --- a/includes/Abstracts/Store.php +++ b/includes/Abstracts/Store.php @@ -7,6 +7,9 @@ namespace WCPOS\WooCommercePOS\Abstracts; +// use WC_Admin_Settings; // this messes up tests. +use function wc_format_country_state_string; + if ( ! defined( 'ABSPATH' ) ) { exit; } @@ -35,8 +38,10 @@ class Store extends \WC_Data { 'store_address' => '', 'store_address_2' => '', 'store_city' => '', - 'default_country' => '', 'store_postcode' => '', + 'store_state' => '', + 'store_country' => '', + 'default_country' => '', 'default_customer_address' => '', 'calc_taxes' => '', 'enable_coupons' => '', @@ -48,6 +53,12 @@ class Store extends \WC_Data { 'price_num_decimals' => '', 'prices_include_tax' => '', 'tax_based_on' => '', + 'tax_address' => array( + 'country' => '', + 'state' => '', + 'postcode' => '', + 'city' => '', + ), 'shipping_tax_class' => '', 'tax_round_at_subtotal' => '', 'tax_display_shop' => '', @@ -81,10 +92,15 @@ public function set_wordpress_settings() { * */ public function set_woocommerce_general_settings() { + $default_country = \WC_Admin_Settings::get_option( 'woocommerce_default_country' ); + $country_state_array = wc_format_country_state_string( $default_country ); + $this->set_prop( 'store_address', \WC_Admin_Settings::get_option( 'woocommerce_store_address' ) ); $this->set_prop( 'store_address_2', \WC_Admin_Settings::get_option( 'woocommerce_store_address_2' ) ); $this->set_prop( 'store_city', \WC_Admin_Settings::get_option( 'woocommerce_store_city' ) ); - $this->set_prop( 'default_country', \WC_Admin_Settings::get_option( 'woocommerce_default_country' ) ); + $this->set_prop( 'store_state', $country_state_array['state'] ); + $this->set_prop( 'store_country', $country_state_array['country'] ); + $this->set_prop( 'default_country', $default_country ); $this->set_prop( 'store_postcode', \WC_Admin_Settings::get_option( 'woocommerce_store_postcode' ) ); $this->set_prop( 'default_customer_address', \WC_Admin_Settings::get_option( 'woocommerce_default_customer_address' ) ); $this->set_prop( 'calc_taxes', \WC_Admin_Settings::get_option( 'woocommerce_calc_taxes' ) ); @@ -109,6 +125,19 @@ public function set_woocommerce_tax_settings() { $this->set_prop( 'tax_display_cart', \WC_Admin_Settings::get_option( 'woocommerce_tax_display_cart' ) ); $this->set_prop( 'price_display_suffix', \WC_Admin_Settings::get_option( 'woocommerce_price_display_suffix' ) ); $this->set_prop( 'tax_total_display', \WC_Admin_Settings::get_option( 'woocommerce_tax_total_display' ) ); + + // tax address is same as WooCommerce address by default. + $country_state_array = wc_format_country_state_string( \WC_Admin_Settings::get_option( 'woocommerce_default_country' ) ); + + $this->set_prop( + 'tax_address', + array( + 'country' => $country_state_array['country'], + 'state' => $country_state_array['state'], + 'postcode' => \WC_Admin_Settings::get_option( 'woocommerce_store_postcode' ), + 'city' => \WC_Admin_Settings::get_option( 'woocommerce_store_city' ), + ) + ); } /** @@ -180,7 +209,8 @@ public function get_store_postcode( $context = 'view' ) { } /** - * Get Store country. + * Get Store country, eg: US:AL. + * This is of the form COUNTRYCODE:STATECODE to be consistent with the WooCommerce settings. * * @param string $context What the value is for. Valid values are view and edit. * @return string @@ -189,6 +219,26 @@ public function get_default_country( $context = 'view' ) { return $this->get_prop( 'default_country', $context ); } + /** + * Get Store state code. + * + * @param string $context What the value is for. Valid values are view and edit. + * @return string + */ + public function get_store_state( $context = 'view' ) { + return $this->get_prop( 'store_state', $context ); + } + + /** + * Get Store country code. + * + * @param string $context What the value is for. Valid values are view and edit. + * @return string + */ + public function get_store_country( $context = 'view' ) { + return $this->get_prop( 'store_country', $context ); + } + /** * Get Store customer address. * @@ -299,6 +349,16 @@ public function get_tax_based_on( $context = 'view' ) { return $this->get_prop( 'tax_based_on', $context ); } + /** + * Get Store tax address. + * + * @param string $context What the value is for. Valid values are view and edit. + * @return array + */ + public function get_tax_address( $context = 'view' ) { + return $this->get_prop( 'tax_address', $context ); + } + /** * Get Store shipping tax class. * diff --git a/includes/Orders.php b/includes/Orders.php index dd2f1af7..0f3fdaf1 100644 --- a/includes/Orders.php +++ b/includes/Orders.php @@ -13,8 +13,17 @@ use WC_Abstract_Order; use WC_Product; use WC_Product_Simple; +use WC_Order; +/** + * Orders Class + * - runs for all life cycles + */ class Orders { + + /** + * Constructor + */ public function __construct() { $this->register_order_status(); add_filter( 'wc_order_statuses', array( $this, 'wc_order_statuses' ), 10, 1 ); @@ -24,6 +33,7 @@ public function __construct() { add_filter( 'woocommerce_payment_complete_order_status', array( $this, 'payment_complete_order_status' ), 10, 3 ); add_filter( 'woocommerce_hidden_order_itemmeta', array( $this, 'hidden_order_itemmeta' ) ); add_filter( 'woocommerce_order_item_product', array( $this, 'order_item_product' ), 10, 2 ); + add_filter( 'woocommerce_order_get_tax_location', array( $this, 'get_tax_location' ), 10, 2 ); // order emails $admin_emails = array( @@ -110,6 +120,8 @@ public function valid_order_statuses_for_payment_complete( array $order_statuses } /** + * + * * @param string $status * @param int $id * @param WC_Abstract_Order $order @@ -135,6 +147,9 @@ public function hidden_order_itemmeta( array $meta_keys ): array { return array_merge( $meta_keys, array( '_woocommerce_pos_uuid', '_woocommerce_pos_tax_status' ) ); } + /** + * + */ public function manage_admin_emails( $enabled, $order, $email_class ) { if ( ! woocommerce_pos_request() ) { return $enabled; @@ -143,6 +158,9 @@ public function manage_admin_emails( $enabled, $order, $email_class ) { return woocommerce_pos_get_settings( 'checkout', 'admin_emails' ); } + /** + * + */ public function manage_customer_emails( $enabled, $order, $email_class ) { if ( ! woocommerce_pos_request() ) { return $enabled; @@ -151,7 +169,9 @@ public function manage_customer_emails( $enabled, $order, $email_class ) { return woocommerce_pos_get_settings( 'checkout', 'customer_emails' ); } - + /** + * Register the POS order statuses. + */ private function register_order_status(): void { // Order status for open orders register_post_status( @@ -191,7 +211,7 @@ private function register_order_status(): void { } /** - * + * Filter the product object for an order item. * * @param WC_Product|bool $product The product object or false if not found * @param WC_Order_Item_Product $item The order item object @@ -207,4 +227,41 @@ public function order_item_product( $product, $item ) { return $product; } + + /** + * Get tax location for this order. + * + * @param array $args array Override the location. + * @param WC_Abstract_Order $order The order object. + * + * @return array + */ + public function get_tax_location( $args, WC_Abstract_Order $order ) { + if ( ! woocommerce_pos_is_pos_order( $order ) ) { + return $args; + } + + $tax_based_on = $order->get_meta( '_woocommerce_pos_tax_based_on' ); + + if ( $order instanceof WC_Order ) { + if ( 'billing' == $tax_based_on ) { + $args['country'] = $order->get_billing_country(); + $args['state'] = $order->get_billing_state(); + $args['postcode'] = $order->get_billing_postcode(); + $args['city'] = $order->get_billing_city(); + } elseif ( 'shipping' == $tax_based_on ) { + $args['country'] = $order->get_shipping_country(); + $args['state'] = $order->get_shipping_state(); + $args['postcode'] = $order->get_shipping_postcode(); + $args['city'] = $order->get_shipping_city(); + } else { + $args['country'] = WC()->countries->get_base_country(); + $args['state'] = WC()->countries->get_base_state(); + $args['postcode'] = WC()->countries->get_base_postcode(); + $args['city'] = WC()->countries->get_base_city(); + } + } + + return $args; + } } diff --git a/tests/includes/API/Test_Order_Taxes.php b/tests/includes/API/Test_Order_Taxes.php index e2ffc953..ef07d65b 100644 --- a/tests/includes/API/Test_Order_Taxes.php +++ b/tests/includes/API/Test_Order_Taxes.php @@ -4,6 +4,7 @@ use WCPOS\WooCommercePOS\API\Orders_Controller; use WCPOS\WooCommercePOS\Tests\Helpers\TaxHelper; +use WC_Admin_Settings; /** * @internal @@ -15,6 +16,7 @@ public function setup(): void { parent::setUp(); $this->endpoint = new Orders_Controller(); update_option( 'woocommerce_calc_taxes', 'yes' ); + update_option( 'woocommerce_tax_based_on', 'base' ); // Set default address // update_option( 'woocommerce_default_country', 'GB' ); @@ -88,6 +90,9 @@ public function tearDown(): void { * Create a new order. */ public function test_create_order_with_tax(): void { + $this->assertEquals( 'base', WC_Admin_Settings::get_option( 'woocommerce_tax_based_on' ) ); + $this->assertEquals( 'US:CA', WC_Admin_Settings::get_option( 'woocommerce_default_country' ) ); + $request = $this->wp_rest_post_request( '/wcpos/v1/orders' ); $request->set_body_params( array( @@ -135,6 +140,9 @@ public function test_create_order_with_tax(): void { * Create a new order with customer billing address as tax location. */ public function test_create_order_with_customer_billing_address_as_tax_location(): void { + $this->assertEquals( 'base', WC_Admin_Settings::get_option( 'woocommerce_tax_based_on' ) ); + $this->assertEquals( 'US:CA', WC_Admin_Settings::get_option( 'woocommerce_default_country' ) ); + $request = $this->wp_rest_post_request( '/wcpos/v1/orders' ); // Prepare your data as an array and then JSON-encode it $data = array( @@ -206,6 +214,9 @@ public function test_create_order_with_customer_billing_address_as_tax_location( * Create a new order with customer billing address as tax location. */ public function test_create_order_with_customer_shipping_address_as_tax_location(): void { + $this->assertEquals( 'base', WC_Admin_Settings::get_option( 'woocommerce_tax_based_on' ) ); + $this->assertEquals( 'US:CA', WC_Admin_Settings::get_option( 'woocommerce_default_country' ) ); + $request = $this->wp_rest_post_request( '/wcpos/v1/orders' ); // Prepare your data as an array and then JSON-encode it $data = array(