diff --git a/multi-currency/src/Compatibility.php b/multi-currency/src/Compatibility.php
index 1a4d23af909..87e10cbde78 100644
--- a/multi-currency/src/Compatibility.php
+++ b/multi-currency/src/Compatibility.php
@@ -39,7 +39,7 @@ class Compatibility extends BaseCompatibility {
*
* @return void
*/
- protected function init() {
+ public function init() {
add_action( 'init', [ $this, 'init_compatibility_classes' ], 11 );
if ( defined( 'DOING_CRON' ) ) {
diff --git a/multi-currency/src/Compatibility/BaseCompatibility.php b/multi-currency/src/Compatibility/BaseCompatibility.php
index a98073fd113..3e7d1a67a20 100644
--- a/multi-currency/src/Compatibility/BaseCompatibility.php
+++ b/multi-currency/src/Compatibility/BaseCompatibility.php
@@ -46,5 +46,5 @@ public function __construct( MultiCurrency $multi_currency, Utils $utils ) {
*
* @return void
*/
- abstract protected function init();
+ abstract public function init();
}
diff --git a/multi-currency/src/Compatibility/WooCommerceBookings.php b/multi-currency/src/Compatibility/WooCommerceBookings.php
index 5a99534e5d6..756e4eef355 100644
--- a/multi-currency/src/Compatibility/WooCommerceBookings.php
+++ b/multi-currency/src/Compatibility/WooCommerceBookings.php
@@ -39,7 +39,7 @@ public function __construct( MultiCurrency $multi_currency, Utils $utils, Fronte
*
* @return void
*/
- protected function init() {
+ public function init() {
// Add needed actions and filters if Bookings is active.
if ( class_exists( 'WC_Bookings' ) ) {
if ( ! is_admin() || wp_doing_ajax() ) {
diff --git a/multi-currency/src/Compatibility/WooCommerceDeposits.php b/multi-currency/src/Compatibility/WooCommerceDeposits.php
index f92819785c5..e2ffa89d441 100644
--- a/multi-currency/src/Compatibility/WooCommerceDeposits.php
+++ b/multi-currency/src/Compatibility/WooCommerceDeposits.php
@@ -20,7 +20,7 @@ class WooCommerceDeposits extends BaseCompatibility {
*
* @return void
*/
- protected function init() {
+ public function init() {
if ( class_exists( 'WC_Deposits' ) ) {
/*
* Multi-currency support was added to WooCommerce Deposits in version 2.0.1.
diff --git a/multi-currency/src/Compatibility/WooCommerceFedEx.php b/multi-currency/src/Compatibility/WooCommerceFedEx.php
index 738e738150f..8a38d058e40 100644
--- a/multi-currency/src/Compatibility/WooCommerceFedEx.php
+++ b/multi-currency/src/Compatibility/WooCommerceFedEx.php
@@ -20,7 +20,7 @@ class WooCommerceFedEx extends BaseCompatibility {
*
* @return void
*/
- protected function init() {
+ public function init() {
// Add needed actions and filters if FedEx is active.
if ( class_exists( 'WC_Shipping_Fedex_Init' ) ) {
add_filter( MultiCurrency::FILTER_PREFIX . 'should_return_store_currency', [ $this, 'should_return_store_currency' ] );
diff --git a/multi-currency/src/Compatibility/WooCommerceNameYourPrice.php b/multi-currency/src/Compatibility/WooCommerceNameYourPrice.php
index 155f99e1a4d..fad352e1d1f 100644
--- a/multi-currency/src/Compatibility/WooCommerceNameYourPrice.php
+++ b/multi-currency/src/Compatibility/WooCommerceNameYourPrice.php
@@ -21,7 +21,7 @@ class WooCommerceNameYourPrice extends BaseCompatibility {
*
* @return void
*/
- protected function init() {
+ public function init() {
// Add needed actions and filters if Name Your Price is active.
if ( class_exists( 'WC_Name_Your_Price' ) ) {
// Convert meta prices.
diff --git a/multi-currency/src/Compatibility/WooCommercePointsAndRewards.php b/multi-currency/src/Compatibility/WooCommercePointsAndRewards.php
index 9d78886eb41..38819d15322 100644
--- a/multi-currency/src/Compatibility/WooCommercePointsAndRewards.php
+++ b/multi-currency/src/Compatibility/WooCommercePointsAndRewards.php
@@ -33,7 +33,7 @@ class WooCommercePointsAndRewards extends BaseCompatibility {
*
* @return void
*/
- protected function init() {
+ public function init() {
// Add needed filters if Points & Rewards is active and it's not an admin request.
if ( is_admin() || ! class_exists( 'WC_Points_Rewards' ) ) {
return;
diff --git a/multi-currency/src/Compatibility/WooCommercePreOrders.php b/multi-currency/src/Compatibility/WooCommercePreOrders.php
index 3c3fe9d5efc..b16dd91b646 100644
--- a/multi-currency/src/Compatibility/WooCommercePreOrders.php
+++ b/multi-currency/src/Compatibility/WooCommercePreOrders.php
@@ -20,7 +20,7 @@ class WooCommercePreOrders extends BaseCompatibility {
*
* @return void
*/
- protected function init() {
+ public function init() {
// Add needed actions and filters if Pre-Orders is active.
if ( class_exists( 'WC_Pre_Orders' ) ) {
add_filter( 'wc_pre_orders_fee', [ $this, 'wc_pre_orders_fee' ] );
diff --git a/multi-currency/src/Compatibility/WooCommerceProductAddOns.php b/multi-currency/src/Compatibility/WooCommerceProductAddOns.php
index 7d245cd4e4f..add583059f8 100644
--- a/multi-currency/src/Compatibility/WooCommerceProductAddOns.php
+++ b/multi-currency/src/Compatibility/WooCommerceProductAddOns.php
@@ -23,7 +23,7 @@ class WooCommerceProductAddOns extends BaseCompatibility {
*
* @return void
*/
- protected function init() {
+ public function init() {
// Add needed actions and filters if Product Add Ons is active.
if ( class_exists( 'WC_Product_Addons' ) ) {
if ( ! is_admin() && ! defined( 'DOING_CRON' ) ) {
diff --git a/multi-currency/src/Compatibility/WooCommerceSubscriptions.php b/multi-currency/src/Compatibility/WooCommerceSubscriptions.php
index eb11007fb91..6701f6739dd 100644
--- a/multi-currency/src/Compatibility/WooCommerceSubscriptions.php
+++ b/multi-currency/src/Compatibility/WooCommerceSubscriptions.php
@@ -59,7 +59,7 @@ class WooCommerceSubscriptions extends BaseCompatibility {
*
* @return void
*/
- protected function init() {
+ public function init() {
// Add needed actions and filters if WC Subscriptions or WCPay Subscriptions are active.
if ( class_exists( 'WC_Subscriptions' ) || class_exists( 'WC_Payments_Subscriptions' ) ) {
if ( ! is_admin() && ! defined( 'DOING_CRON' ) ) {
diff --git a/multi-currency/src/Compatibility/WooCommerceUPS.php b/multi-currency/src/Compatibility/WooCommerceUPS.php
index 6a53f47bff3..427aa060d52 100644
--- a/multi-currency/src/Compatibility/WooCommerceUPS.php
+++ b/multi-currency/src/Compatibility/WooCommerceUPS.php
@@ -20,7 +20,7 @@ class WooCommerceUPS extends BaseCompatibility {
*
* @return void
*/
- protected function init() {
+ public function init() {
// Add needed actions and filters if UPS is active.
if ( class_exists( 'WC_Shipping_UPS_Init' ) ) {
add_filter( MultiCurrency::FILTER_PREFIX . 'should_return_store_currency', [ $this, 'should_return_store_currency' ] );
diff --git a/multi-currency/src/Logger.php b/multi-currency/src/Logger.php
index 769d57c4844..d9f6865c5b7 100644
--- a/multi-currency/src/Logger.php
+++ b/multi-currency/src/Logger.php
@@ -12,6 +12,11 @@
*/
class Logger {
+ /**
+ * Log source identifier.
+ */
+ const LOG_FILE = 'woopayments-multi-currency';
+
/**
* The WooCommerce logger instance.
*
@@ -19,11 +24,6 @@ class Logger {
*/
private $logger;
- /**
- * Log source identifier.
- */
- const LOG_FILE = 'woopayments-multi-currency';
-
/**
* Log a debug message.
*
diff --git a/multi-currency/src/MultiCurrency.php b/multi-currency/src/MultiCurrency.php
index 009af60e653..4caab08a113 100644
--- a/multi-currency/src/MultiCurrency.php
+++ b/multi-currency/src/MultiCurrency.php
@@ -601,94 +601,6 @@ public function maybe_update_customer_currencies_option( $order_id ) {
update_option( self::CUSTOMER_CURRENCIES_KEY, $currencies );
}
- /**
- * Sets up the available currencies, which are alphabetical by name.
- *
- * @return void
- */
- private function initialize_available_currencies() {
- // Add default store currency with a rate of 1.0.
- $woocommerce_currency = get_woocommerce_currency();
- $this->available_currencies[ $woocommerce_currency ] = new Currency( $this->localization_service, $woocommerce_currency, 1.0 );
-
- $available_currencies = [];
-
- $currencies = $this->get_account_available_currencies();
- $cache_data = $this->get_cached_currencies();
-
- foreach ( $currencies as $currency_code ) {
- $currency_rate = $cache_data['currencies'][ $currency_code ] ?? 1.0;
- $update_time = $cache_data['updated'] ?? null;
- $new_currency = new Currency( $this->localization_service, $currency_code, $currency_rate, $update_time );
-
- // Add this to our list of available currencies.
- $available_currencies[ $new_currency->get_name() ] = $new_currency;
- }
-
- ksort( $available_currencies );
-
- foreach ( $available_currencies as $currency ) {
- $this->available_currencies[ $currency->get_code() ] = $currency;
- }
- }
-
- /**
- * Sets up the enabled currencies.
- *
- * @return void
- */
- private function initialize_enabled_currencies() {
- $available_currencies = $this->get_available_currencies();
- $enabled_currency_codes = get_option( $this->id . '_enabled_currencies', [] );
- $enabled_currency_codes = is_array( $enabled_currency_codes ) ? $enabled_currency_codes : [];
- $default_code = $this->get_default_currency()->get_code();
- $default = [];
- $enabled_currency_codes[] = $default_code;
-
- // This allows to keep the alphabetical sorting by name.
- $enabled_currencies = array_filter(
- $available_currencies,
- function ( $currency ) use ( $enabled_currency_codes ) {
- return in_array( $currency->get_code(), $enabled_currency_codes, true );
- }
- );
-
- $this->enabled_currencies = [];
-
- foreach ( $enabled_currencies as $enabled_currency ) {
- // Get the charm and rounding for each enabled currency and add the currencies to the object property.
- $currency = clone $enabled_currency;
- $charm = get_option( $this->id . '_price_charm_' . $currency->get_id(), 0.00 );
- $rounding = get_option( $this->id . '_price_rounding_' . $currency->get_id(), $currency->get_is_zero_decimal() ? '100' : '1.00' );
- $currency->set_charm( $charm );
- $currency->set_rounding( $rounding );
-
- // If the currency is set to be manual, set the rate to the stored manual rate.
- $type = get_option( $this->id . '_exchange_rate_' . $currency->get_id(), 'automatic' );
- if ( 'manual' === $type ) {
- $manual_rate = get_option( $this->id . '_manual_rate_' . $currency->get_id(), $currency->get_rate() );
- $currency->set_rate( $manual_rate );
- }
-
- $this->enabled_currencies[ $currency->get_code() ] = $currency;
- }
-
- // Set default currency to the top of the list.
- $default[ $default_code ] = $this->enabled_currencies[ $default_code ];
- unset( $this->enabled_currencies[ $default_code ] );
- $this->enabled_currencies = array_merge( $default, $this->enabled_currencies );
- }
-
- /**
- * Sets the default currency.
- *
- * @return void
- */
- private function set_default_currency() {
- $available_currencies = $this->get_available_currencies();
- $this->default_currency = $available_currencies[ get_woocommerce_currency() ] ?? null;
- }
-
/**
* Gets the currencies available. Initializes it if needed.
*
@@ -1080,355 +992,588 @@ public static function remove_woo_admin_notes() {
}
/**
- * Gets the price after adjusting it with the rounding and charm settings.
- *
- * @param float $price The price to be adjusted.
- * @param bool $apply_charm_pricing Whether charm pricing should be applied.
- * @param Currency $currency The currency to be used when adjusting.
+ * Checks if the merchant has enabled automatic currency switching and geolocation.
*
- * @return float The adjusted price.
+ * @return bool
*/
- protected function get_adjusted_price( $price, $apply_charm_pricing, $currency ): float {
- $price = $this->ceil_price( $price, (float) $currency->get_rounding() );
-
- if ( $apply_charm_pricing ) {
- $price += (float) $currency->get_charm();
- }
-
- // Do not return negative prices (possible because of $currency->get_charm()).
- return max( 0, $price );
+ public function is_using_auto_currency_switching(): bool {
+ return 'yes' === get_option( $this->id . '_enable_auto_currency', 'no' );
}
/**
- * Ceils the price to the next number based on the rounding value.
- *
- * @param float $price The price to be ceiled.
- * @param float $rounding The rounding option.
+ * Checks if the merchant has enabled the currency switcher widget.
*
- * @return float The ceiled price.
+ * @return bool
*/
- protected function ceil_price( float $price, float $rounding ): float {
- if ( 0.00 === $rounding ) {
- return $price;
- }
- return ceil( $price / $rounding ) * $rounding;
+ public function is_using_storefront_switcher(): bool {
+ return 'yes' === get_option( $this->id . '_enable_storefront_switcher', 'no' );
}
/**
- * Returns the currency code stored for the user or in the session.
+ * Gets the store settings.
*
- * @return string|null Currency code.
+ * @return array The store settings.
*/
- private function get_stored_currency_code() {
- $user_id = get_current_user_id();
-
- if ( $user_id ) {
- return get_user_meta( $user_id, self::CURRENCY_META_KEY, true );
- }
-
- WC()->initialize_session();
- $currency_code = WC()->session->get( self::CURRENCY_SESSION_KEY );
-
- return is_string( $currency_code ) ? $currency_code : null;
+ public function get_settings() {
+ return [
+ $this->id . '_enable_auto_currency' => $this->is_using_auto_currency_switching(),
+ $this->id . '_enable_storefront_switcher' => $this->is_using_storefront_switcher(),
+ 'site_theme' => wp_get_theme()->get( 'Name' ),
+ 'date_format' => esc_attr( get_option( 'date_format', 'F j, Y' ) ),
+ 'time_format' => esc_attr( get_option( 'time_format', 'g:i a' ) ),
+ 'store_url' => esc_attr( get_page_uri( wc_get_page_id( 'shop' ) ) ),
+ ];
}
/**
- * Checks to see if the store currency has changed. If it has, this will
- * also update the option containing the store currency.
+ * Updates the store settings
*
- * @return bool
+ * @param array $params Update requested values.
+ *
+ * @return void
*/
- private function check_store_currency_for_change(): bool {
- $last_known_currency = get_option( $this->id . '_store_currency', false );
- $woocommerce_currency = get_woocommerce_currency();
-
- // If the last known currency was not set, update the option to set it and return false.
- if ( ! $last_known_currency ) {
- update_option( $this->id . '_store_currency', $woocommerce_currency );
- return false;
- }
+ public function update_settings( $params ) {
+ $updateable_options = [
+ 'wcpay_multi_currency_enable_auto_currency',
+ 'wcpay_multi_currency_enable_storefront_switcher',
+ ];
- if ( $last_known_currency !== $woocommerce_currency ) {
- update_option( $this->id . '_store_currency', $woocommerce_currency );
- return true;
+ foreach ( $updateable_options as $key ) {
+ if ( isset( $params[ $key ] ) ) {
+ update_option( $key, sanitize_text_field( $params[ $key ] ) );
+ }
}
-
- return false;
}
/**
- * Called when the store currency has changed. Puts any manual rate currencies into an option for a notice to display.
+ * Apply client order currency format and reduces the rounding precision to 2.
*
- * @return void
+ * @return void
*/
- private function update_manual_rate_currencies_notice_option() {
- $enabled_currencies = $this->get_enabled_currencies();
- $manual_currencies = [];
-
- // Check enabled currencies for manual rates.
- foreach ( $enabled_currencies as $currency ) {
- $rate_type = get_option( $this->id . '_exchange_rate_' . $currency->get_id(), false );
- if ( 'manual' === $rate_type ) {
- $manual_currencies[] = $currency->get_name();
+ public function set_client_format_and_rounding_precision() {
+ $screen = get_current_screen();
+ if ( in_array( $screen->id, [ 'shop_order', 'woocommerce_page_wc-orders' ], true ) ) :
+ $order = wc_get_order();
+ if ( ! $order ) {
+ return;
}
- }
+ $currency = $order->get_currency();
+ $currency_format_num_decimals = $this->backend_currencies->get_price_decimals( $currency );
+ $currency_format_decimal_sep = $this->backend_currencies->get_price_decimal_separator( $currency );
+ $currency_format_thousand_sep = $this->backend_currencies->get_price_thousand_separator( $currency );
+ $currency_format = str_replace( [ '%1$s', '%2$s', ' ' ], [ '%s', '%v', ' ' ], $this->backend_currencies->get_woocommerce_price_format( $currency ) );
- if ( 0 < count( $manual_currencies ) ) {
- update_option( $this->id . '_show_store_currency_changed_notice', $manual_currencies );
- }
+ $rounding_precision = wc_get_price_decimals() ?? wc_get_rounding_precision();
+ ?>
+
+ gateway_context['plugin_file_path'] );
+ $script_asset_path = plugin_dir_path( $this->gateway_context['plugin_file_path'] ) . $script . '.asset.php';
+ $script_asset = file_exists( $script_asset_path ) ? require $script_asset_path : [ 'dependencies' => [] ];
+ $all_dependencies = array_merge( $script_asset['dependencies'], $additional_dependencies );
- foreach ( $currencies as $currency ) {
- $this->remove_currency_settings( $currency );
- }
+ wp_register_script(
+ $handler,
+ $script_src_url,
+ $all_dependencies,
+ $this->get_file_version( $script_file ),
+ true
+ );
}
/**
- * Will remove a currency's settings if it is not enabled.
+ * Get the file modified time as a cache buster if we're in dev mode.
*
- * @param mixed $currency Currency object or 3 letter currency code.
+ * @param string $file Local path to the file.
*
- * @return void
+ * @return string
*/
- private function remove_currency_settings( $currency ) {
- $code = is_a( $currency, Currency::class ) ? $currency->get_code() : strtoupper( $currency );
+ public function get_file_version( $file ) {
+ $plugin_path = plugin_dir_path( $this->gateway_context['plugin_file_path'] );
- // Bail if the currency code passed is not 3 characters, or if the currency is presently enabled.
- if ( 3 !== strlen( $code ) || isset( $this->get_enabled_currencies()[ $code ] ) ) {
- return;
+ if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG && file_exists( $plugin_path . $file ) ) {
+ return (string) filemtime( $plugin_path . trim( $file, '/' ) );
}
- $settings = [
- 'price_charm',
- 'price_rounding',
- 'manual_rate',
- 'exchange_rate',
+ return $this->gateway_context['plugin_version'];
+ }
+
+ /**
+ * Validates the given currency code.
+ *
+ * @param string $currency_code The currency code to check validity.
+ *
+ * @return string|false Returns back the currency code in uppercase letters if it's valid, or `false` if not.
+ */
+ public function validate_currency_code( $currency_code ) {
+ return array_key_exists( strtoupper( $currency_code ), $this->available_currencies )
+ ? strtoupper( $currency_code )
+ : false;
+ }
+
+ /**
+ * Get simulation params from querystring and activate when needed
+ *
+ * @return void
+ */
+ public function possible_simulation_activation() {
+ // This is required in the MC onboarding simulation iframe.
+ $this->simulation_params = $this->get_multi_currency_onboarding_simulation_variables();
+ if ( ! $this->is_simulation_enabled() ) {
+ return;
+ }
+ // Modify the page links to deliver required params in the simulation.
+ $this->add_simulation_params_to_preview_urls();
+ $this->simulate_client_currency();
+ }
+
+ /**
+ * Returns whether the simulation querystring param is set and active
+ *
+ * @return bool Whether the simulation is enabled or not
+ */
+ public function is_simulation_enabled() {
+ return 0 < count( $this->simulation_params );
+ }
+
+ /**
+ * Gets the Multi-Currency onboarding preview overrides from the querystring.
+ *
+ * @return array Override variables
+ */
+ public function get_multi_currency_onboarding_simulation_variables() {
+
+ $parameters = $_GET; // phpcs:ignore WordPress.Security.NonceVerification
+ // Check if we are in a preview session, don't interfere with the main session.
+ if ( ! isset( $parameters['is_mc_onboarding_simulation'] ) || ! (bool) $parameters['is_mc_onboarding_simulation'] ) {
+ // Check if the page referer has the variables.
+ $server = $_SERVER; // phpcs:ignore WordPress.Security.NonceVerification
+ // Check if we are coming from a simulation session (if we don't have the necessary query strings).
+ if ( isset( $server['HTTP_REFERER'] ) && 0 < strpos( $server['HTTP_REFERER'], 'is_mc_onboarding_simulation' ) ) {
+ wp_parse_str( wp_parse_url( $server['HTTP_REFERER'], PHP_URL_QUERY ), $parameters );
+ if ( ! isset( $parameters['is_mc_onboarding_simulation'] ) || ! (bool) $parameters['is_mc_onboarding_simulation'] ) {
+ return [];
+ }
+ } else {
+ return [];
+ }
+ }
+
+ // Define variables which can be overridden inside the preview session, with their sanitization methods.
+ $possible_variables = [
+ 'enable_storefront_switcher' => 'wp_validate_boolean',
+ 'enable_auto_currency' => 'wp_validate_boolean',
];
- // Go through each setting and remove them.
- foreach ( $settings as $setting ) {
- delete_option( $this->id . '_' . $setting . '_' . strtolower( $code ) );
+ // Define the defaults if the parameter is missing in the request.
+ $defaults = [
+ 'enable_storefront_switcher' => false,
+ 'enable_auto_currency' => false,
+ ];
+
+ // Prepare the params array.
+ $values = [];
+
+ // Walk through the querystring parameter possibilities, and prepare the params.
+ foreach ( $possible_variables as $possible_variable => $sanitization_callback ) {
+ // phpcs:disable WordPress.Security.NonceVerification
+ if ( isset( $parameters[ $possible_variable ] ) ) {
+ $values[ $possible_variable ] = $sanitization_callback( $parameters[ $possible_variable ] );
+ } else {
+ // Append the default, the param is missing in the querystring.
+ $values [ $possible_variable ] = $defaults[ $possible_variable ];
+ }
}
+
+ return $values;
}
/**
- * Returns the currencies enabled for the payment provider account that are
- * also available in WC.
+ * Checks if the currently displayed page is the WooCommerce Payments
+ * settings page for the Multi-Currency settings.
*
- * Can be filtered with the 'wcpay_multi_currency_available_currencies' hook.
+ * @return bool
+ */
+ public function is_multi_currency_settings_page(): bool {
+ global $current_screen, $current_tab;
+ return (
+ is_admin()
+ && $current_tab && $current_screen
+ && 'wcpay_multi_currency' === $current_tab
+ && 'woocommerce_page_wc-settings' === $current_screen->base
+ );
+ }
+
+ /**
+ * Get all the currencies that have been used in the store.
*
- * @return array Array with the available currencies' codes.
+ * @return array
*/
- private function get_account_available_currencies(): array {
- // If the payment provider is not connected, return an empty array. This prevents using MC without being connected to the payment provider.
- if ( ! $this->payments_account->is_provider_connected() ) {
- return [];
+ public function get_all_customer_currencies(): array {
+ global $wpdb;
+
+ $currencies = get_option( self::CUSTOMER_CURRENCIES_KEY );
+
+ if ( self::is_customer_currencies_data_valid( $currencies ) ) {
+ return array_map( 'strtoupper', $currencies );
}
- $wc_currencies = array_keys( get_woocommerce_currencies() );
- $account_currencies = $wc_currencies;
+ $currencies = $this->get_available_currencies();
+ $query_union = [];
- $account = $this->payments_account->get_cached_account_data();
- $supported_currencies = $this->payments_account->get_account_customer_supported_currencies();
- if ( $account && ! empty( $supported_currencies ) ) {
- $account_currencies = array_map( 'strtoupper', $supported_currencies );
+ if ( class_exists( 'Automattic\WooCommerce\Utilities\OrderUtil' ) &&
+ \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled() ) {
+ foreach ( $currencies as $currency ) {
+ $query_union[] = $wpdb->prepare(
+ "SELECT %s AS currency_code, EXISTS(SELECT currency FROM {$wpdb->prefix}wc_orders WHERE currency=%s LIMIT 1) AS exists_in_orders",
+ $currency->code,
+ $currency->code
+ );
+ }
+ } else {
+ foreach ( $currencies as $currency ) {
+ $query_union[] = $wpdb->prepare(
+ "SELECT %s AS currency_code, EXISTS(SELECT meta_value FROM {$wpdb->postmeta} WHERE meta_key=%s AND meta_value=%s LIMIT 1) AS exists_in_orders",
+ $currency->code,
+ '_order_currency',
+ $currency->code
+ );
+ }
}
- /**
- * Filter the available currencies for WooCommerce Multi-Currency.
- *
- * This filter can be used to modify the currencies available for WC Pay
- * Multi-Currency. Currencies have to be added in uppercase and should
- * also be available in `get_woocommerce_currencies` for them to work.
- *
- * @since 2.8.0
- *
- * @param array $available_currencies Current available currencies. Calculated based on
- * WC Pay's account currencies and WC currencies.
- */
- return apply_filters( self::FILTER_PREFIX . 'available_currencies', array_intersect( $account_currencies, $wc_currencies ) );
+ $sub_query = implode( ' UNION ALL ', $query_union );
+ $query = "SELECT currency_code FROM ( $sub_query ) as subquery WHERE subquery.exists_in_orders=1 ORDER BY currency_code ASC";
+ $currencies = $wpdb->get_col( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
+
+ if ( self::is_customer_currencies_data_valid( $currencies ) ) {
+ update_option( self::CUSTOMER_CURRENCIES_KEY, $currencies );
+ return array_map( 'strtoupper', $currencies );
+ }
+
+ return [];
}
/**
- * Checks if the merchant has enabled automatic currency switching and geolocation.
+ * Checks if there are additional currencies enabled beyond the store's default one.
*
* @return bool
*/
- public function is_using_auto_currency_switching(): bool {
- return 'yes' === get_option( $this->id . '_enable_auto_currency', 'no' );
+ public function has_additional_currencies_enabled(): bool {
+ $enabled_currencies = $this->get_enabled_currencies();
+ return count( $enabled_currencies ) > 1;
}
/**
- * Checks if the merchant has enabled the currency switcher widget.
+ * Returns if the currency initializations are completed.
*
- * @return bool
+ * @return bool If the initializations have been completed.
*/
- public function is_using_storefront_switcher(): bool {
- return 'yes' === get_option( $this->id . '_enable_storefront_switcher', 'no' );
+ public function is_initialized(): bool {
+ return static::$is_initialized;
}
/**
- * Gets the store settings.
+ * Gets the price after adjusting it with the rounding and charm settings.
*
- * @return array The store settings.
+ * @param float $price The price to be adjusted.
+ * @param bool $apply_charm_pricing Whether charm pricing should be applied.
+ * @param Currency $currency The currency to be used when adjusting.
+ *
+ * @return float The adjusted price.
*/
- public function get_settings() {
- return [
- $this->id . '_enable_auto_currency' => $this->is_using_auto_currency_switching(),
- $this->id . '_enable_storefront_switcher' => $this->is_using_storefront_switcher(),
- 'site_theme' => wp_get_theme()->get( 'Name' ),
- 'date_format' => esc_attr( get_option( 'date_format', 'F j, Y' ) ),
- 'time_format' => esc_attr( get_option( 'time_format', 'g:i a' ) ),
- 'store_url' => esc_attr( get_page_uri( wc_get_page_id( 'shop' ) ) ),
- ];
+ protected function get_adjusted_price( $price, $apply_charm_pricing, $currency ): float {
+ $price = $this->ceil_price( $price, (float) $currency->get_rounding() );
+
+ if ( $apply_charm_pricing ) {
+ $price += (float) $currency->get_charm();
+ }
+
+ // Do not return negative prices (possible because of $currency->get_charm()).
+ return max( 0, $price );
}
/**
- * Updates the store settings
+ * Ceils the price to the next number based on the rounding value.
*
- * @param array $params Update requested values.
+ * @param float $price The price to be ceiled.
+ * @param float $rounding The rounding option.
*
- * @return void
+ * @return float The ceiled price.
*/
- public function update_settings( $params ) {
- $updateable_options = [
- 'wcpay_multi_currency_enable_auto_currency',
- 'wcpay_multi_currency_enable_storefront_switcher',
- ];
+ protected function ceil_price( float $price, float $rounding ): float {
+ if ( 0.00 === $rounding ) {
+ return $price;
+ }
+ return ceil( $price / $rounding ) * $rounding;
+ }
- foreach ( $updateable_options as $key ) {
- if ( isset( $params[ $key ] ) ) {
- update_option( $key, sanitize_text_field( $params[ $key ] ) );
- }
+ /**
+ * Sets up the available currencies, which are alphabetical by name.
+ *
+ * @return void
+ */
+ private function initialize_available_currencies() {
+ // Add default store currency with a rate of 1.0.
+ $woocommerce_currency = get_woocommerce_currency();
+ $this->available_currencies[ $woocommerce_currency ] = new Currency( $this->localization_service, $woocommerce_currency, 1.0 );
+
+ $available_currencies = [];
+
+ $currencies = $this->get_account_available_currencies();
+ $cache_data = $this->get_cached_currencies();
+
+ foreach ( $currencies as $currency_code ) {
+ $currency_rate = $cache_data['currencies'][ $currency_code ] ?? 1.0;
+ $update_time = $cache_data['updated'] ?? null;
+ $new_currency = new Currency( $this->localization_service, $currency_code, $currency_rate, $update_time );
+
+ // Add this to our list of available currencies.
+ $available_currencies[ $new_currency->get_name() ] = $new_currency;
+ }
+
+ ksort( $available_currencies );
+
+ foreach ( $available_currencies as $currency ) {
+ $this->available_currencies[ $currency->get_code() ] = $currency;
}
}
/**
- * Apply client order currency format and reduces the rounding precision to 2.
+ * Sets up the enabled currencies.
*
- * @return void
+ * @return void
*/
- public function set_client_format_and_rounding_precision() {
- $screen = get_current_screen();
- if ( in_array( $screen->id, [ 'shop_order', 'woocommerce_page_wc-orders' ], true ) ) :
- $order = wc_get_order();
- if ( ! $order ) {
- return;
+ private function initialize_enabled_currencies() {
+ $available_currencies = $this->get_available_currencies();
+ $enabled_currency_codes = get_option( $this->id . '_enabled_currencies', [] );
+ $enabled_currency_codes = is_array( $enabled_currency_codes ) ? $enabled_currency_codes : [];
+ $default_code = $this->get_default_currency()->get_code();
+ $default = [];
+ $enabled_currency_codes[] = $default_code;
+
+ // This allows to keep the alphabetical sorting by name.
+ $enabled_currencies = array_filter(
+ $available_currencies,
+ function ( $currency ) use ( $enabled_currency_codes ) {
+ return in_array( $currency->get_code(), $enabled_currency_codes, true );
}
- $currency = $order->get_currency();
- $currency_format_num_decimals = $this->backend_currencies->get_price_decimals( $currency );
- $currency_format_decimal_sep = $this->backend_currencies->get_price_decimal_separator( $currency );
- $currency_format_thousand_sep = $this->backend_currencies->get_price_thousand_separator( $currency );
- $currency_format = str_replace( [ '%1$s', '%2$s', ' ' ], [ '%s', '%v', ' ' ], $this->backend_currencies->get_woocommerce_price_format( $currency ) );
+ );
- $rounding_precision = wc_get_price_decimals() ?? wc_get_rounding_precision();
- ?>
-
- enabled_currencies = [];
+
+ foreach ( $enabled_currencies as $enabled_currency ) {
+ // Get the charm and rounding for each enabled currency and add the currencies to the object property.
+ $currency = clone $enabled_currency;
+ $charm = get_option( $this->id . '_price_charm_' . $currency->get_id(), 0.00 );
+ $rounding = get_option( $this->id . '_price_rounding_' . $currency->get_id(), $currency->get_is_zero_decimal() ? '100' : '1.00' );
+ $currency->set_charm( $charm );
+ $currency->set_rounding( $rounding );
+
+ // If the currency is set to be manual, set the rate to the stored manual rate.
+ $type = get_option( $this->id . '_exchange_rate_' . $currency->get_id(), 'automatic' );
+ if ( 'manual' === $type ) {
+ $manual_rate = get_option( $this->id . '_manual_rate_' . $currency->get_id(), $currency->get_rate() );
+ $currency->set_rate( $manual_rate );
+ }
+
+ $this->enabled_currencies[ $currency->get_code() ] = $currency;
+ }
+
+ // Set default currency to the top of the list.
+ $default[ $default_code ] = $this->enabled_currencies[ $default_code ];
+ unset( $this->enabled_currencies[ $default_code ] );
+ $this->enabled_currencies = array_merge( $default, $this->enabled_currencies );
}
/**
- * Register the CSS and JS admin scripts.
+ * Sets the default currency.
*
* @return void
*/
- private function register_admin_scripts() {
- $this->register_script_with_dependencies( 'WCPAY_MULTI_CURRENCY_SETTINGS', 'dist/multi-currency', [ 'WCPAY_ADMIN_SETTINGS' ] );
+ private function set_default_currency() {
+ $available_currencies = $this->get_available_currencies();
+ $this->default_currency = $available_currencies[ get_woocommerce_currency() ] ?? null;
+ }
- wp_register_style(
- 'WCPAY_MULTI_CURRENCY_SETTINGS',
- plugins_url( 'dist/multi-currency.css', $this->gateway_context['plugin_file_path'] ),
- [ 'wc-components', 'WCPAY_ADMIN_SETTINGS' ],
- $this->get_file_version( 'dist/multi-currency.css' ),
- 'all'
- );
+ /**
+ * Returns the currency code stored for the user or in the session.
+ *
+ * @return string|null Currency code.
+ */
+ private function get_stored_currency_code() {
+ $user_id = get_current_user_id();
+
+ if ( $user_id ) {
+ return get_user_meta( $user_id, self::CURRENCY_META_KEY, true );
+ }
+
+ WC()->initialize_session();
+ $currency_code = WC()->session->get( self::CURRENCY_SESSION_KEY );
+
+ return is_string( $currency_code ) ? $currency_code : null;
}
/**
- * Load script with all required dependencies.
+ * Checks to see if the store currency has changed. If it has, this will
+ * also update the option containing the store currency.
+ *
+ * @return bool
+ */
+ private function check_store_currency_for_change(): bool {
+ $last_known_currency = get_option( $this->id . '_store_currency', false );
+ $woocommerce_currency = get_woocommerce_currency();
+
+ // If the last known currency was not set, update the option to set it and return false.
+ if ( ! $last_known_currency ) {
+ update_option( $this->id . '_store_currency', $woocommerce_currency );
+ return false;
+ }
+
+ if ( $last_known_currency !== $woocommerce_currency ) {
+ update_option( $this->id . '_store_currency', $woocommerce_currency );
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Called when the store currency has changed. Puts any manual rate currencies into an option for a notice to display.
+ *
+ * @return void
+ */
+ private function update_manual_rate_currencies_notice_option() {
+ $enabled_currencies = $this->get_enabled_currencies();
+ $manual_currencies = [];
+
+ // Check enabled currencies for manual rates.
+ foreach ( $enabled_currencies as $currency ) {
+ $rate_type = get_option( $this->id . '_exchange_rate_' . $currency->get_id(), false );
+ if ( 'manual' === $rate_type ) {
+ $manual_currencies[] = $currency->get_name();
+ }
+ }
+
+ if ( 0 < count( $manual_currencies ) ) {
+ update_option( $this->id . '_show_store_currency_changed_notice', $manual_currencies );
+ }
+ }
+
+ /**
+ * Accepts an array of currencies that should have their settings removed.
*
- * @param string $handler Script handler.
- * @param string $script Script name relative to the plugin root.
- * @param array $additional_dependencies Additional dependencies.
+ * @param array $currencies Array of Currency objects or 3 letter currency codes.
*
* @return void
*/
- public function register_script_with_dependencies( string $handler, string $script, array $additional_dependencies = [] ) {
- $script_file = $script . '.js';
- $script_src_url = plugins_url( $script_file, $this->gateway_context['plugin_file_path'] );
- $script_asset_path = plugin_dir_path( $this->gateway_context['plugin_file_path'] ) . $script . '.asset.php';
- $script_asset = file_exists( $script_asset_path ) ? require $script_asset_path : [ 'dependencies' => [] ];
- $all_dependencies = array_merge( $script_asset['dependencies'], $additional_dependencies );
+ private function remove_currencies_settings( array $currencies ) {
- wp_register_script(
- $handler,
- $script_src_url,
- $all_dependencies,
- $this->get_file_version( $script_file ),
- true
- );
+ foreach ( $currencies as $currency ) {
+ $this->remove_currency_settings( $currency );
+ }
}
/**
- * Get the file modified time as a cache buster if we're in dev mode.
+ * Will remove a currency's settings if it is not enabled.
*
- * @param string $file Local path to the file.
+ * @param mixed $currency Currency object or 3 letter currency code.
*
- * @return string
+ * @return void
*/
- public function get_file_version( $file ) {
- $plugin_path = plugin_dir_path( $this->gateway_context['plugin_file_path'] );
+ private function remove_currency_settings( $currency ) {
+ $code = is_a( $currency, Currency::class ) ? $currency->get_code() : strtoupper( $currency );
- if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG && file_exists( $plugin_path . $file ) ) {
- return (string) filemtime( $plugin_path . trim( $file, '/' ) );
+ // Bail if the currency code passed is not 3 characters, or if the currency is presently enabled.
+ if ( 3 !== strlen( $code ) || isset( $this->get_enabled_currencies()[ $code ] ) ) {
+ return;
}
- return $this->gateway_context['plugin_version'];
+ $settings = [
+ 'price_charm',
+ 'price_rounding',
+ 'manual_rate',
+ 'exchange_rate',
+ ];
+
+ // Go through each setting and remove them.
+ foreach ( $settings as $setting ) {
+ delete_option( $this->id . '_' . $setting . '_' . strtolower( $code ) );
+ }
}
/**
- * Validates the given currency code.
+ * Returns the currencies enabled for the payment provider account that are
+ * also available in WC.
*
- * @param string $currency_code The currency code to check validity.
+ * Can be filtered with the 'wcpay_multi_currency_available_currencies' hook.
*
- * @return string|false Returns back the currency code in uppercase letters if it's valid, or `false` if not.
+ * @return array Array with the available currencies' codes.
*/
- public function validate_currency_code( $currency_code ) {
- return array_key_exists( strtoupper( $currency_code ), $this->available_currencies )
- ? strtoupper( $currency_code )
- : false;
+ private function get_account_available_currencies(): array {
+ // If the payment provider is not connected, return an empty array. This prevents using MC without being connected to the payment provider.
+ if ( ! $this->payments_account->is_provider_connected() ) {
+ return [];
+ }
+
+ $wc_currencies = array_keys( get_woocommerce_currencies() );
+ $account_currencies = $wc_currencies;
+
+ $account = $this->payments_account->get_cached_account_data();
+ $supported_currencies = $this->payments_account->get_account_customer_supported_currencies();
+ if ( $account && ! empty( $supported_currencies ) ) {
+ $account_currencies = array_map( 'strtoupper', $supported_currencies );
+ }
+
+ /**
+ * Filter the available currencies for WooCommerce Multi-Currency.
+ *
+ * This filter can be used to modify the currencies available for WC Pay
+ * Multi-Currency. Currencies have to be added in uppercase and should
+ * also be available in `get_woocommerce_currencies` for them to work.
+ *
+ * @since 2.8.0
+ *
+ * @param array $available_currencies Current available currencies. Calculated based on
+ * WC Pay's account currencies and WC currencies.
+ */
+ return apply_filters( self::FILTER_PREFIX . 'available_currencies', array_intersect( $account_currencies, $wc_currencies ) );
}
/**
- * Get simulation params from querystring and activate when needed
+ * Register the CSS and JS admin scripts.
*
- * @return void
+ * @return void
*/
- public function possible_simulation_activation() {
- // This is required in the MC onboarding simulation iframe.
- $this->simulation_params = $this->get_multi_currency_onboarding_simulation_variables();
- if ( ! $this->is_simulation_enabled() ) {
- return;
- }
- // Modify the page links to deliver required params in the simulation.
- $this->add_simulation_params_to_preview_urls();
- $this->simulate_client_currency();
+ private function register_admin_scripts() {
+ $this->register_script_with_dependencies( 'WCPAY_MULTI_CURRENCY_SETTINGS', 'dist/multi-currency', [ 'WCPAY_ADMIN_SETTINGS' ] );
+
+ wp_register_style(
+ 'WCPAY_MULTI_CURRENCY_SETTINGS',
+ plugins_url( 'dist/multi-currency.css', $this->gateway_context['plugin_file_path'] ),
+ [ 'wc-components', 'WCPAY_ADMIN_SETTINGS' ],
+ $this->get_file_version( 'dist/multi-currency.css' ),
+ 'all'
+ );
}
/**
@@ -1477,67 +1622,6 @@ function ( $selected_country ) use ( $simulation_country ) {
remove_action( 'wp_loaded', [ $this, 'recalculate_cart' ] );
}
- /**
- * Returns whether the simulation querystring param is set and active
- *
- * @return bool Whether the simulation is enabled or not
- */
- public function is_simulation_enabled() {
- return 0 < count( $this->simulation_params );
- }
-
- /**
- * Gets the Multi-Currency onboarding preview overrides from the querystring.
- *
- * @return array Override variables
- */
- public function get_multi_currency_onboarding_simulation_variables() {
-
- $parameters = $_GET; // phpcs:ignore WordPress.Security.NonceVerification
- // Check if we are in a preview session, don't interfere with the main session.
- if ( ! isset( $parameters['is_mc_onboarding_simulation'] ) || ! (bool) $parameters['is_mc_onboarding_simulation'] ) {
- // Check if the page referer has the variables.
- $server = $_SERVER; // phpcs:ignore WordPress.Security.NonceVerification
- // Check if we are coming from a simulation session (if we don't have the necessary query strings).
- if ( isset( $server['HTTP_REFERER'] ) && 0 < strpos( $server['HTTP_REFERER'], 'is_mc_onboarding_simulation' ) ) {
- wp_parse_str( wp_parse_url( $server['HTTP_REFERER'], PHP_URL_QUERY ), $parameters );
- if ( ! isset( $parameters['is_mc_onboarding_simulation'] ) || ! (bool) $parameters['is_mc_onboarding_simulation'] ) {
- return [];
- }
- } else {
- return [];
- }
- }
-
- // Define variables which can be overridden inside the preview session, with their sanitization methods.
- $possible_variables = [
- 'enable_storefront_switcher' => 'wp_validate_boolean',
- 'enable_auto_currency' => 'wp_validate_boolean',
- ];
-
- // Define the defaults if the parameter is missing in the request.
- $defaults = [
- 'enable_storefront_switcher' => false,
- 'enable_auto_currency' => false,
- ];
-
- // Prepare the params array.
- $values = [];
-
- // Walk through the querystring parameter possibilities, and prepare the params.
- foreach ( $possible_variables as $possible_variable => $sanitization_callback ) {
- // phpcs:disable WordPress.Security.NonceVerification
- if ( isset( $parameters[ $possible_variable ] ) ) {
- $values[ $possible_variable ] = $sanitization_callback( $parameters[ $possible_variable ] );
- } else {
- // Append the default, the param is missing in the querystring.
- $values [ $possible_variable ] = $defaults[ $possible_variable ];
- }
- }
-
- return $values;
- }
-
/**
* Adds the required querystring parameters to all urls in preview pages.
*
@@ -1577,90 +1661,6 @@ function () use ( $params ) {
);
}
- /**
- * Checks if the currently displayed page is the WooCommerce Payments
- * settings page for the Multi-Currency settings.
- *
- * @return bool
- */
- public function is_multi_currency_settings_page(): bool {
- global $current_screen, $current_tab;
- return (
- is_admin()
- && $current_tab && $current_screen
- && 'wcpay_multi_currency' === $current_tab
- && 'woocommerce_page_wc-settings' === $current_screen->base
- );
- }
-
- /**
- * Get all the currencies that have been used in the store.
- *
- * @return array
- */
- public function get_all_customer_currencies(): array {
- global $wpdb;
-
- $currencies = get_option( self::CUSTOMER_CURRENCIES_KEY );
-
- if ( self::is_customer_currencies_data_valid( $currencies ) ) {
- return array_map( 'strtoupper', $currencies );
- }
-
- $currencies = $this->get_available_currencies();
- $query_union = [];
-
- if ( class_exists( 'Automattic\WooCommerce\Utilities\OrderUtil' ) &&
- \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled() ) {
- foreach ( $currencies as $currency ) {
- $query_union[] = $wpdb->prepare(
- "SELECT %s AS currency_code, EXISTS(SELECT currency FROM {$wpdb->prefix}wc_orders WHERE currency=%s LIMIT 1) AS exists_in_orders",
- $currency->code,
- $currency->code
- );
- }
- } else {
- foreach ( $currencies as $currency ) {
- $query_union[] = $wpdb->prepare(
- "SELECT %s AS currency_code, EXISTS(SELECT meta_value FROM {$wpdb->postmeta} WHERE meta_key=%s AND meta_value=%s LIMIT 1) AS exists_in_orders",
- $currency->code,
- '_order_currency',
- $currency->code
- );
- }
- }
-
- $sub_query = implode( ' UNION ALL ', $query_union );
- $query = "SELECT currency_code FROM ( $sub_query ) as subquery WHERE subquery.exists_in_orders=1 ORDER BY currency_code ASC";
- $currencies = $wpdb->get_col( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
-
- if ( self::is_customer_currencies_data_valid( $currencies ) ) {
- update_option( self::CUSTOMER_CURRENCIES_KEY, $currencies );
- return array_map( 'strtoupper', $currencies );
- }
-
- return [];
- }
-
- /**
- * Checks if there are additional currencies enabled beyond the store's default one.
- *
- * @return bool
- */
- public function has_additional_currencies_enabled(): bool {
- $enabled_currencies = $this->get_enabled_currencies();
- return count( $enabled_currencies ) > 1;
- }
-
- /**
- * Returns if the currency initializations are completed.
- *
- * @return bool If the initializations have been completed.
- */
- public function is_initialized(): bool {
- return static::$is_initialized;
- }
-
/**
* Logs a message and throws InvalidCurrencyException.
*