diff --git a/fast.php b/fast.php index fc654151..d412cdbe 100644 --- a/fast.php +++ b/fast.php @@ -31,6 +31,8 @@ require_once FASTWC_PATH . 'includes/assets.php'; // Loads fast utilities. require_once FASTWC_PATH . 'includes/utilities.php'; + // Add suppot for multicurrency. + require_once FASTWC_PATH . 'includes/multicurrency.php'; // Adds Fast Checkout button to store. require_once FASTWC_PATH . 'includes/checkout.php'; // Adds Fast Login button to store. diff --git a/includes/checkout.php b/includes/checkout.php index c83bf1e2..21ec3c6f 100644 --- a/includes/checkout.php +++ b/includes/checkout.php @@ -150,10 +150,14 @@ function fastwc_woocommerce_before_checkout_form() { * * @param WC_Data $order Object object. * @param WP_REST_Request $request Request object. + * + * @return WC_Data */ function fastwc_woocommerce_rest_pre_insert_shop_order_object( $order, $request ) { - fastwc_log_debug( 'Request object: ' . print_r( $request, true ) ); // phpcs:ignore + $order = fastwc_maybe_update_order_for_multicurrency( $order, $request ); + + fastwc_log_debug( 'Request object: ' . print_r( $request, true ) ); fastwc_log_debug( 'fastwc_woocommerce_rest_pre_insert_shop_order_object ' . print_r( $order, true ) ); // phpcs:ignore diff --git a/includes/multicurrency.php b/includes/multicurrency.php new file mode 100644 index 00000000..98beb3fd --- /dev/null +++ b/includes/multicurrency.php @@ -0,0 +1,173 @@ +get_currency() : $wc_currency; +} + +/** + * Update the order for multicurrency. + * + * @param WC_Data $order The order to check. + * @param WP_REST_Request $request Request object. + * + * @return WC_Data + */ +function fastwc_update_order_for_multicurrency( $order, $request ) { + + /** + * Maybe update the order from the multicurrency plugin. + * + * @param WC_Data $order The order to check. + * @param WP_REST_Request $request Request object. + */ + $order = apply_filters( + 'fastwc_update_order_for_multicurrency', + $order, + $request + ); + + foreach ( $order->get_items() as $item_id => $item ) { + $product = method_exists( $item, 'get_product' ) ? $item->get_product() : null; + $quantity = method_exists( $item, 'get_quantity' ) ? (int) $item->get_quantity() : 0; + + if ( ! empty( $product ) ) { + /** + * Get the price from the multicurrency plugin. + * + * @param string $price Value of the price. + * @param WC_Product $product The product object. + * @param WC_Data $order The order to check. + * @param WC_Request $request Request object. + */ + $price = apply_filters( + 'fastwc_update_price_for_multicurrency', + $product->get_price(), + $product, + $order, + $request + ); + + // Calculate the price by multiplying by quantity. + $new_price = $price * $quantity; + + // Set the new price. + $item->set_subtotal( $new_price ); + $item->set_total( $new_price ); + + // Make new tax calculations. + $item->calculate_taxes(); + + // Save the item data. + $item->save(); + } + } + + $order->calculate_totals(); + + return $order; +} + +/** + * Maybe update shipping rates for multicurrency. + * + * @param array $rate_info The rate response information. + * @param string $wc_currency The default WooCommerce currency. + * @param string $currency The customer currency. + * @param WP_REST_Request $request The request object. + * + * @return array + */ +function fastwc_maybe_update_shipping_rate_for_multicurrency( $rate_info, $wc_currency, $currency, $request ) { + $multicurrency_disabled = fastwc_is_multicurrency_support_disabled(); + + // Do nothing if multicurrency is disabled. + if ( + false === $multicurrency_disabled + && ! empty( $currency ) // Make sure the customer currency is set. + && $wc_currency !== $currency // Make sure the customer currency is not the default currency. + ) { + /** + * Update shipping rates for multicurrency. + * + * @param array $rate_info The rate response information. + * @param string $currency The customer currency. + * @param WP_REST_Request $request The request object. + * + * @return array + */ + $rate_info = apply_filters( + 'fastwc_update_shipping_rate_for_multicurrency', + $rate_info, + $currency, + $request + ); + } + + return $rate_info; +} diff --git a/includes/multicurrency/class-base.php b/includes/multicurrency/class-base.php new file mode 100644 index 00000000..a82f05b6 --- /dev/null +++ b/includes/multicurrency/class-base.php @@ -0,0 +1,121 @@ +is_active() ) { + return $this->do_update_price( $price, $product, $order, $request ); + } + + return $price; + } + + /** + * Update the product price for multicurrency. + * + * @param string $price Value of the price. + * @param WC_Product $product The product object. + * @param WC_Data $order The order to check. + * @param WC_Request $request Request object. + * + * @return string + */ + protected function do_update_price( $price, $product, $order, $request ) { + return $price; + } + + /** + * Filter handler for updating the shipping rate for multicurrency. + * + * @param array $rate_info The rate response information. + * @param string $currency The customer currency. + * @param WP_REST_Request $request The request object. + * + * @return array + */ + public function update_shipping( $rate_info, $currency, $request ) { + + if ( $this->is_active() ) { + return $this->do_update_shipping( $rate_info, $currency, $request ); + } + + return $rate_info; + } + + /** + * Update the shipping rate for multicurrency. + * + * @param array $rate_info The rate response information. + * @param string $currency The customer currency. + * @param WP_REST_Request $request The request object. + * + * @return array + */ + protected function do_update_shipping( $rate_info, $currency, $request ) { + return $rate_info; + } + + /** + * Filter handler for updating the order for multicurrency. + * + * @param WC_Data $order The order to check. + * @param WP_REST_Request $request Request object. + */ + public function update_order( $order, $request ) { + + if ( $this->is_active() ) { + return $this->do_update_order( $order, $request ); + } + + return $order; + } + + /** + * Update the order for multicurrency. + * + * @param WC_Data $order The order to check. + * @param WP_REST_Request $request Request object. + */ + protected function do_update_order( $order, $request ) { + return $order; + } +} diff --git a/includes/multicurrency/class-currency-switcher-woocommerce.php b/includes/multicurrency/class-currency-switcher-woocommerce.php new file mode 100644 index 00000000..2b13abf9 --- /dev/null +++ b/includes/multicurrency/class-currency-switcher-woocommerce.php @@ -0,0 +1,70 @@ + $rate_tax ) { + $rate_info['taxes'][ $rate_tax_id ] = \alg_get_product_price_by_currency_global( $rate_tax, $currency ); + } + } + + return $rate_info; + } +} diff --git a/includes/multicurrency/class-woocommerce-currency-switcher.php b/includes/multicurrency/class-woocommerce-currency-switcher.php new file mode 100644 index 00000000..690123f1 --- /dev/null +++ b/includes/multicurrency/class-woocommerce-currency-switcher.php @@ -0,0 +1,71 @@ +raw_woocommerce_price( $price, $product ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase + } + + /** + * Update the shipping rate for multicurrency. + * + * @param array $rate_info The rate response information. + * @param string $currency The customer currency. + * @param WP_REST_Request $request The request object. + * + * @return array + */ + protected function do_update_shipping( $rate_info, $currency, $request ) { + global $WOOCS; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase + + $_REQUEST['woocs_raw_woocommerce_price_currency'] = $currency; + + $rate_info['price'] = $WOOCS->raw_woocommerce_price( $rate_info['price'] ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase + if ( ! empty( $rate_info['taxes'] ) ) { + $rate_taxes = $rate_info['taxes']; + + foreach ( $rate_taxes as $rate_tax_id => $rate_tax ) { + $rate_info['taxes'][ $rate_tax_id ] = $WOOCS->raw_woocommerce_price( $rate_tax ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase + } + } + + return $rate_info; + } +} diff --git a/includes/multicurrency/class-woocommerce-product-price-based-on-countries.php b/includes/multicurrency/class-woocommerce-product-price-based-on-countries.php new file mode 100644 index 00000000..4575e43f --- /dev/null +++ b/includes/multicurrency/class-woocommerce-product-price-based-on-countries.php @@ -0,0 +1,188 @@ +get_zone( $request ); + \fastwc_log_debug( 'Zone: ' . print_r( $zone, true ) ); // phpcs:ignore + + if ( ! empty( $zone ) ) { + \fastwc_log_debug( 'Setting price for zone' ); + $price = $zone->get_post_price( $product->get_id(), '_price' ); + } + + \fastwc_log_debug( 'Price After Conversion: ' . $price ); + + return $price; + } + + /** + * Update the shipping rate for multicurrency. + * + * @param array $rate_info The rate response information. + * @param string $currency The customer currency. + * @param WP_REST_Request $request The request object. + * + * @return array + */ + protected function do_update_shipping( $rate_info, $currency, $request ) { + $country = $this->get_country( $request ); + + \fastwc_log_debug( 'Price Based on Country multicurrency plugin - Update shipping' ); + \fastwc_log_debug( 'Shipping Before Conversion: ' . print_r( $rate_info, true ) ); // phpcs:ignore + + $zone = $this->get_zone( $request ); + \fastwc_log_debug( 'Zone: ' . print_r( $zone, true ) ); // phpcs:ignore + + if ( ! empty( $zone ) ) { + $rate_info['price'] = $zone->get_exchange_rate_price( $rate_info['price'] ); + + if ( ! empty( $rate_info['taxes'] ) ) { + $rate_taxes = $rate_info['taxes']; + + foreach ( $rate_taxes as $rate_tax_id => $rate_tax ) { + $rate_info['taxes'][ $rate_tax_id ] = $zone->get_exchange_rate_price( $rate_tax ); + } + } + } + + \fastwc_log_debug( 'Shipping After Conversion: ' . print_r( $rate_info, true ) ); // phpcs:ignore + + return $rate_info; + } + + /** + * Update the order for multicurrency. + * + * @param WC_Data $order The order to check. + * @param WP_REST_Request $request Request object. + */ + protected function do_update_order( $order, $request ) { + // Make sure the order currency is correct for the billing address zone. + $country = $this->get_country( $request ); + + if ( ! empty( $country ) ) { + $zone = $this->get_zone( $request, false ); + $order_currency = \fastwc_get_order_currency( $order ); + $zone_currency = $zone->get_currency(); + + // Change the order currency if it does not match the zone currenncy. + if ( ! empty( $zone ) && $order_currency !== $zone_currency ) { + $order->set_currency( $zone_currency ); + } + } + + return $order; + } + + /** + * Get the billing address country from the request. + * + * @param mixed $request The request object. + * + * @return string + */ + protected function get_country( $request ) { + $country = ''; + + $valid_based_on = array( 'billing', 'shipping' ); + $based_on = \get_option( 'wc_price_based_country_based_on', 'billing' ); + + // Make sure based on is billing or shipping. + if ( ! in_array( $based_on, $valid_based_on, true ) ) { + $based_on = 'billing'; + } + + if ( is_array( $request ) ) { + if ( ! empty( $request[ $based_on ]['country'] ) ) { + $country = $request[ $based_on ]['country']; + } + } elseif ( is_a( $request, 'WP_REST_Request' ) ) { + $params = $request->get_params(); + + if ( ! empty( $params[ $based_on ]['country'] ) ) { + $country = $params[ $based_on ]['country']; + } + } + + return $country; + } + + /** + * Get the pricing zone from the request. + * + * @param mixed $request The request object. + * @param bool $get_zone_by_currency Optional. Flag to get zone by currency. + * + * @return WCPBC_Pricing_Zone|WCPBC_Pricing_Zone_Pro + */ + protected function get_zone( $request, $get_zone_by_currency = true ) { + $country = $this->get_country( $request ); + + \fastwc_log_debug( 'Country: ' . $country ); + + $zone = false; + + if ( ! empty( $country ) ) { + $zone = \wcpbc_get_zone_by_country( $country ); + \fastwc_log_debug( 'Zone by country: ' . print_r( $zone, true ) ); // phpcs:ignore + } + + // Maybe get the zone by currency. + if ( + $get_zone_by_currency && + ( + empty( $zone ) || + $order_currency !== $zone->get_currency() + ) + ) { + $zones = \WCPBC_Pricing_Zones::get_zones(); + + // Loop through the zones and get a zone by the currency. + foreach ( $zones as $_zone ) { + if ( $order_currency === $_zone->get_currency() ) { + $zone = $_zone; + \fastwc_log_debug( 'Zone by currency: ' . print_r( $zone, true ) ); // phpcs:ignore + + break; + } + } + } + + return $zone; + } +} diff --git a/includes/routes/class-base.php b/includes/routes/class-base.php new file mode 100644 index 00000000..5fd21b86 --- /dev/null +++ b/includes/routes/class-base.php @@ -0,0 +1,95 @@ +init(); + $this->register(); + } + + /** + * Initialize the route arguments. + */ + protected function init() { + if ( empty( $this->callback ) ) { + $this->callback = array( $this, 'callback' ); + } + } + + /** + * Register the route. + */ + protected function register() { + register_rest_route( + $this->namespace, + $this->route, + array( + 'methods' => $this->methods, + 'callback' => $this->callback, + 'permission_callback' => $this->permission_callback, + ) + ); + } + + /** + * Route callback function. + * + * @param WP_REST_Request $request JSON request for shipping endpoint. + */ + abstract public function callback( $request ); +} diff --git a/includes/routes/class-shipping.php b/includes/routes/class-shipping.php index ce4b085f..49bd8332 100644 --- a/includes/routes/class-shipping.php +++ b/includes/routes/class-shipping.php @@ -10,7 +10,7 @@ /** * Fast shipping route object. */ -class Shipping extends Route { +class Shipping extends Base { /** * Route name. @@ -26,6 +26,20 @@ class Shipping extends Route { */ protected $methods = 'POST'; + /** + * Currency code. + * + * @var string + */ + protected $currency = ''; + + /** + * WooCommerce base currency code. + * + * @var string + */ + protected $wc_currency = ''; + /** * Given shipping address and product, attempts to calculate available shipping rates for the address. * Doesn't support shipping > 1 address. @@ -42,6 +56,8 @@ public function callback( $request ) { $params = $this->request->get_params(); $return = false; + $this->get_currency(); + // This is needed for session to work. \WC()->frontend_includes(); @@ -64,6 +80,34 @@ public function callback( $request ) { return $return; } + /** + * Get the currency from the request object. + */ + protected function get_currency() { + if ( empty( $this->request ) ) { + return; + } + + $params = $this->request->get_params(); + + // Maybe set the wc_currency parameter. + if ( empty( $this->wc_currency ) ) { + $this->wc_currency = \get_woocommerce_currency(); + } + + // Get the order ID from the request params. + $order_id = ! empty( $params['order_id'] ) ? $params['order_id'] : 0; + + if ( empty( $order_id ) ) { + $this->currency = ! empty( $params['currency'] ) ? $params['currency'] : $this->wc_currency; + } else { + $order = new \WC_Order( $order_id ); + $this->currency = \fastwc_get_order_currency( $order ); + } + + \add_filter( 'woocommerce_currency', array( $this, 'update_woocommerce_currency' ), PHP_INT_MAX ); + } + /** * Initialize the WC session. */ @@ -327,12 +371,38 @@ protected function get_rate_response( $rate ) { 'meta_data' => $this->get_rate_meta_data( $rate ), ); + $rate_info = \fastwc_maybe_update_shipping_rate_for_multicurrency( $rate_info, $this->wc_currency, $this->currency, $this->request ); + + $rate_info['price'] = \wc_format_decimal( $rate_info['price'], 2 ); + if ( ! empty( $rate_info['taxes'] ) ) { + $rate_taxes = $rate_info['taxes']; + + foreach ( $rate_taxes as $rate_tax_id => $rate_tax ) { + $rate_info['taxes'][ $rate_tax_id ] = \wc_format_decimal( $rate_tax, 2 ); + } + } + return array_merge( $rate_info, $this->get_store_currency_response() ); } + /** + * Update WC currency. + * + * @param string $currency The base currency. + * + * @return string + */ + public function update_woocommerce_currency( $currency ) { + if ( ! empty( $this->currency ) ) { + $currnecy = $this->currency; + } + + return $currency; + } + /** * Prepares a list of store currency data to return in responses. * @@ -340,6 +410,7 @@ protected function get_rate_response( $rate ) { */ protected function get_store_currency_response() { $position = \get_option( 'woocommerce_currency_pos' ); + $currency = ! empty( $this->currency ) ? $this->currency : \get_woocommerce_currency(); $symbol = html_entity_decode( \get_woocommerce_currency_symbol( $currency ) ); $prefix = ''; $suffix = ''; @@ -362,7 +433,7 @@ protected function get_store_currency_response() { } return array( - 'currency_code' => \get_woocommerce_currency(), + 'currency_code' => $currency, 'currency_symbol' => $symbol, 'currency_minor_unit' => \wc_get_price_decimals(), 'currency_decimal_separator' => \wc_get_price_decimal_separator(), diff --git a/templates/buttons/fast-checkout-button.php b/templates/buttons/fast-checkout-button.php index 67243019..cd609ad4 100644 --- a/templates/buttons/fast-checkout-button.php +++ b/templates/buttons/fast-checkout-button.php @@ -5,15 +5,20 @@ * @package Fast */ -$fastwc_app_id = fastwc_get_app_id(); -$product_id = ! empty( $args['product_id'] ) ? absint( $args['product_id'] ) : 0; -$variant_id = ! empty( $args['variant_id'] ) ? absint( $args['variant_id'] ) : 0; -$quantity = ! empty( $args['quantity'] ) ? absint( $args['quantity'] ) : 0; -$product_options = ! empty( $args['product_options'] ) ? fastwc_get_normalized_product_options( $args['product_options'] ) : ''; -$fastwc_use_dark_mode = fastwc_use_dark_mode( $product_id ); +$fastwc_app_id = fastwc_get_app_id(); +$currency = get_woocommerce_currency(); +$multicurrency_disabled = fastwc_is_multicurrency_support_disabled(); +$product_id = ! empty( $args['product_id'] ) ? absint( $args['product_id'] ) : 0; +$variant_id = ! empty( $args['variant_id'] ) ? absint( $args['variant_id'] ) : 0; +$quantity = ! empty( $args['quantity'] ) ? absint( $args['quantity'] ) : 0; +$product_options = ! empty( $args['product_options'] ) ? fastwc_get_normalized_product_options( $args['product_options'] ) : ''; +$fastwc_use_dark_mode = fastwc_use_dark_mode( $product_id ); ?> + currency="" + product_id="" diff --git a/templates/buttons/fast-checkout-cart-button.php b/templates/buttons/fast-checkout-cart-button.php index f28f65ee..c4b2367c 100644 --- a/templates/buttons/fast-checkout-cart-button.php +++ b/templates/buttons/fast-checkout-cart-button.php @@ -5,23 +5,24 @@ * @package Fast */ -$fastwc_app_id = fastwc_get_app_id(); -$fastwc_cart_data = fastwc_get_cart_data(); -$cart_data = wp_json_encode( array_values( $fastwc_cart_data ) ); -$applied_coupons = ! empty( WC()->cart ) ? WC()->cart->get_applied_coupons() : array(); -$applied_coupons_count = count( $applied_coupons ); -$fastwc_use_dark_mode = fastwc_use_dark_mode(); +$fastwc_app_id = fastwc_get_app_id(); +$fastwc_cart_data = fastwc_get_cart_data(); +$cart_data = wp_json_encode( array_values( $fastwc_cart_data ) ); +$applied_coupons = ! empty( WC()->cart ) ? WC()->cart->get_applied_coupons() : array(); +$applied_coupons_count = count( $applied_coupons ); +$currency = get_woocommerce_currency(); +$multicurrency_disabled = fastwc_is_multicurrency_support_disabled(); +$fastwc_use_dark_mode = fastwc_use_dark_mode(); ?> - coupon_code="" - + + currency="" + + + coupon_code="" + dark