diff --git a/modules/shipping/commerce_currency_resolver_shipping.services.yml b/modules/shipping/commerce_currency_resolver_shipping.services.yml index 535afa7..302de59 100644 --- a/modules/shipping/commerce_currency_resolver_shipping.services.yml +++ b/modules/shipping/commerce_currency_resolver_shipping.services.yml @@ -1,6 +1,11 @@ services: - commerce_currency_resolver.shipping: + commerce_currency_resolver_shipping.rates: class: Drupal\commerce_currency_resolver_shipping\EventSubscriber\CommerceShippingCurrency arguments: ['@commerce_currency_resolver.current_currency','@commerce_currency_resolver.calculator', '@current_route_match'] tags: - { name: event_subscriber, priority: 200 } + commerce_currency_resolver_shipping.order_processor: + class: Drupal\commerce_currency_resolver_shipping\ShippingCurrencyOrderProcessor + arguments: ['@commerce_shipping.order_manager'] + tags: + - { name: commerce_order.order_processor, priority: 999 } diff --git a/modules/shipping/src/ShippingCurrencyOrderProcessor.php b/modules/shipping/src/ShippingCurrencyOrderProcessor.php new file mode 100644 index 0000000..b383d3b --- /dev/null +++ b/modules/shipping/src/ShippingCurrencyOrderProcessor.php @@ -0,0 +1,57 @@ +shippingOrderManager = $shipping_order_manager; + } + + /** + * {@inheritdoc} + */ + public function process(OrderInterface $order) { + // No shipment, skip order. + if (!$this->shippingOrderManager->hasShipments($order)) { + return; + } + + // No need to trigger this processor. + if (!$order->getData(CurrencyHelper::CURRENCY_ORDER_REFRESH)) { + return; + } + + // Unset flag. + $order->unsetData(CurrencyHelper::CURRENCY_ORDER_REFRESH); + + // If we don't have already this flag, trigger it. + // Otherwise amount on shipment is gonna be on old currency. + if (!$order->getData(ShippingOrderManagerInterface::FORCE_REFRESH)) { + $order->setData(ShippingOrderManagerInterface::FORCE_REFRESH, TRUE); + } + + } + +} diff --git a/modules/shipping/tests/src/FunctionalJavascript/ShippingIntegrationTest.php b/modules/shipping/tests/src/FunctionalJavascript/ShippingIntegrationTest.php index 1ea63b2..7dc3c2f 100644 --- a/modules/shipping/tests/src/FunctionalJavascript/ShippingIntegrationTest.php +++ b/modules/shipping/tests/src/FunctionalJavascript/ShippingIntegrationTest.php @@ -7,15 +7,21 @@ use Drupal\commerce_order\Entity\OrderType; use Drupal\commerce_payment\Entity\PaymentGateway; use Drupal\commerce_product\Entity\ProductVariationType; use Drupal\Tests\commerce\FunctionalJavascript\CommerceWebDriverTestBase; +use Drupal\Tests\commerce_currency_resolver\Traits\CurrentCurrencyTrait; /** * Tests integration with the shipping module. * * @coversDefaultClass \Drupal\commerce_currency_resolver_shipping\EventSubscriber\CommerceShippingCurrency + * @covers \Drupal\commerce_currency_resolver_shipping\Plugin\Commerce\ShippingMethod\FlatRateCurrency + * @covers \Drupal\commerce_currency_resolver_shipping\Plugin\Commerce\ShippingMethod\FlatRatePerItemCurrency + * @covers \Drupal\commerce_currency_resolver_shipping\ShippingCurrencyOrderProcessor * @group commerce_currency_resolver */ class ShippingIntegrationTest extends CommerceWebDriverTestBase { + use CurrentCurrencyTrait; + /** * First sample product. * @@ -201,6 +207,7 @@ class ShippingIntegrationTest extends CommerceWebDriverTestBase { * Test for recalculating shipping trough cart/checkout steps. * * @covers ::shippingCurrency + * @covers \Drupal\commerce_currency_resolver_shipping\ShippingCurrencyOrderProcessor::process */ public function testRecalculateShippingPricing() { // Create a flat rate. @@ -291,8 +298,22 @@ class ShippingIntegrationTest extends CommerceWebDriverTestBase { $this->assertSession()->pageTextContains('Shipping method'); $this->assertSession()->pageTextContains('Shipping $1.00'); + // Switch currency default to HRK. + $this->store->setDefaultCurrencyCode('HRK'); + $this->store->save(); + $this->reloadEntity($this->store); + $this->getSession()->getPage()->findButton('Continue to review')->click(); - $this->assertSession()->pageTextContains('Shipping $1.00'); + $this->assertSession()->pageTextContains('Shipping HRK0.00'); + + $this->drupalGet('cart'); + $this->assertSession()->pageTextContains('Shipping HRK0.00'); + $this->getSession()->getPage()->fillField('edit_quantity[0]', 3); + $this->getSession()->getPage()->findButton('Update cart')->click(); + $this->assertSession()->pageTextContains('Shipping HRK30.00'); + + $this->drupalGet('checkout/1'); + $this->assertSession()->pageTextContains('Shipping HRK30.00'); } diff --git a/src/CurrencyHelper.php b/src/CurrencyHelper.php index 5e4f71a..7588a33 100644 --- a/src/CurrencyHelper.php +++ b/src/CurrencyHelper.php @@ -21,6 +21,11 @@ class CurrencyHelper implements CurrencyHelperInterface { use StringTranslationTrait; + /** + * Use as flag for out other order processor. + */ + const CURRENCY_ORDER_REFRESH = 'currency_order_refresh'; + /** * Current request. * diff --git a/src/CurrencyOrderProcessor.php b/src/CurrencyOrderProcessor.php index b22e238..fd80dbe 100644 --- a/src/CurrencyOrderProcessor.php +++ b/src/CurrencyOrderProcessor.php @@ -4,14 +4,13 @@ namespace Drupal\commerce_currency_resolver; use Drupal\commerce_exchanger\ExchangerCalculatorInterface; use Drupal\commerce_order\Adjustment; -use Drupal\commerce_order\Entity\Order; use Drupal\commerce_order\Entity\OrderInterface; use Drupal\commerce_order\OrderProcessorInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Session\AccountInterface; /** - * Applies taxes to orders during the order refresh process. + * Apply currency changes during the order refresh process. */ class CurrencyOrderProcessor implements OrderProcessorInterface { @@ -134,9 +133,8 @@ class CurrencyOrderProcessor implements OrderProcessorInterface { $order = $order->recalculateTotalPrice(); } - // Refresh order on load. Shipping fix. Probably all other potential - // unlocked adjustments which are not set correctly. - $order->setRefreshState(Order::REFRESH_ON_LOAD); + // Use as flag for our submodules order processors. + $order->setData(CurrencyHelper::CURRENCY_ORDER_REFRESH, TRUE); // Save order. $order->save(); diff --git a/src/EventSubscriber/CurrencyOrderRefresh.php b/src/EventSubscriber/CurrencyOrderRefresh.php index 9857e0d..fef5a03 100644 --- a/src/EventSubscriber/CurrencyOrderRefresh.php +++ b/src/EventSubscriber/CurrencyOrderRefresh.php @@ -79,10 +79,8 @@ class CurrencyOrderRefresh implements EventSubscriberInterface { /** @var \Drupal\commerce_order\Entity\OrderInterface $order */ $order = $event->getOrder(); - if (!isset($orders[$order->id()])) { - $orders[$order->id()] = TRUE; - } - else { + // We already have fired this event. + if (isset($orders[$order->id()])) { return; } @@ -97,6 +95,9 @@ class CurrencyOrderRefresh implements EventSubscriberInterface { // correction of total order price and currency. if ($order_currency !== $resolved_currency && $this->shouldCurrencyRefresh($order)) { + // Set as flag to trigger this even once. + $orders[$order->id()] = TRUE; + // Check if we can refresh order. $this->orderRefresh->refresh($order); } diff --git a/tests/src/Functional/CurrentCurrencyCartTest.php b/tests/src/Functional/CurrentCurrencyCartTest.php new file mode 100644 index 0000000..b11c897 --- /dev/null +++ b/tests/src/Functional/CurrentCurrencyCartTest.php @@ -0,0 +1,149 @@ +container->get('commerce_price.currency_importer'); + $currency_importer->import('HRK'); + + // Create new exchange rates. + $exchange_rates = ExchangeRates::create([ + 'id' => 'testing', + 'label' => 'Manual', + 'plugin' => 'manual', + 'status' => TRUE, + 'configuration' => [ + 'cron' => FALSE, + 'use_cross_sync' => FALSE, + 'demo_amount' => 100, + 'base_currency' => 'USD', + 'mode' => 'live', + ], + ] + ); + $exchange_rates->save(); + + $this->config($exchange_rates->getExchangerConfigName())->setData([ + 'rates' => [ + 'HRK' => [ + 'USD' => [ + 'value' => 0.15, + 'sync' => 0, + ], + ], + 'USD' => [ + 'HRK' => [ + 'value' => 6.85, + 'sync' => 0, + ], + ], + ], + ])->save(); + + $this->config('commerce_currency_resolver.settings') + ->set('currency_exchange_rates', 'testing') + ->save(); + + $this->store->setDefaultCurrencyCode('HRK'); + $this->store->save(); + $this->reloadEntity($this->store); + + $this->currentCurrency = $this->container->get('commerce_currency_resolver.current_currency'); + + $variation_display = commerce_get_entity_display('commerce_product_variation', 'default', 'view'); + $variation_display->setComponent('price', [ + 'label' => 'above', + 'type' => 'commerce_price_calculated', + 'settings' => [], + ]); + $variation_display->save(); + } + + /** + * Test adding a product to the cart. + * + * @covers ::checkCurrency + * @covers ::shouldCurrencyRefresh + * @covers ::checkOrderOwner + * @covers ::checkAdminRoute + * @covers \Drupal\commerce_currency_resolver\CurrencyOrderProcessor::process + * @covers \Drupal\commerce_currency_resolver\Resolver\CommerceCurrencyResolver::resolve + */ + public function testProductAddToCartForm() { + $this->assertEqual('USD', $this->variation->getPrice()->getCurrencyCode()); + $this->assertEqual('999', $this->variation->getPrice()->getNumber()); + $this->assertEqual('HRK', $this->currentCurrency->getCurrency()); + + // Confirm that the initial add to cart submit works. + $this->postAddToCart($this->variation->getProduct()); + $this->assertSession()->pageTextContains('HRK6,843.15'); + $this->cart = Order::load($this->cart->id()); + + $this->drupalGet('cart'); + $this->assertEqual(999 * 6.85, $this->cart->getTotalPrice()->getNumber()); + $this->assertEqual('HRK', $this->cart->getTotalPrice()->getCurrencyCode()); + + // Check product display. And check current currency. + $this->drupalGet('product/1'); + $this->assertSession()->pageTextContains('HRK6,843.15'); + $this->assertEqual('HRK', $this->currentCurrency->getCurrency()); + + // Switch currency back to USD. + $this->store->setDefaultCurrencyCode('USD'); + $this->store->save(); + $this->reloadEntity($this->store); + $this->resetCurrencyContainer(); + + $this->assertEqual('USD', $this->currentCurrency->getCurrency()); + $this->postAddToCart($this->variation->getProduct()); + + $order = Order::load($this->cart->id()); + $this->drupalGet('cart'); + $this->assertEqual(2 * 999, $order->getTotalPrice()->getNumber()); + $this->assertEqual('USD', $order->getTotalPrice()->getCurrencyCode()); + + } + +} diff --git a/tests/src/Kernel/CurrentCurrencyTest.php b/tests/src/Kernel/CurrentCurrencyTest.php new file mode 100644 index 0000000..3cdfc9f --- /dev/null +++ b/tests/src/Kernel/CurrentCurrencyTest.php @@ -0,0 +1,154 @@ +installConfig('system'); + $this->installConfig('language'); + + // Add additional language. + ConfigurableLanguage::create(['id' => 'hr'])->save(); + + // Ensure we are building a new Language object for each test. + $this->languageManager = $this->container->get('language_manager'); + $this->languageDefault = $this->container->get('language.default'); + $language = ConfigurableLanguage::load('hr'); + $this->languageDefault->set($language); + $this->config('system.site')->set('default_langcode', $language->getId())->save(); + $this->languageManager->reset(); + + // Add additional currency. + // The parent has already imported USD. + $currency_importer = $this->container->get('commerce_price.currency_importer'); + $currency_importer->import('HRK'); + + // Resolver configuration specifics. + $this->installConfig(['commerce_currency_resolver']); + $this->config('commerce_currency_resolver.settings') + ->set('currency_default', 'HRK')->save(); + $this->currentCurrency = $this->container->get('commerce_currency_resolver.current_currency'); + + // Prepare mapping for language test. + $this->config('commerce_currency_resolver.currency_mapping')->setData([ + 'domicile_currency' => NULL, + 'logic' => NULL, + 'matrix' => [ + 'en' => 'USD', + 'hr' => 'HRK', + ], + + ])->save(); + + } + + /** + * Test current currency. + * + * @covers ::getCurrency + */ + public function testDefault() { + $this->assertInstanceOf( StoreInterface::class, $this->store); + $this->assertEqual($this->store->getDefaultCurrencyCode(), $this->currentCurrency->getCurrency()); + $this->assertEqual('USD', $this->store->getDefaultCurrencyCode()); + $this->assertEqual('USD', $this->currentCurrency->getCurrency()); + $this->assertEqual('HRK', $this->config('commerce_currency_resolver.settings') + ->get('currency_default')); + } + + /** + * Tests current currency with no store. + * + * @covers ::getCurrency + */ + public function testNoStore() { + $this->assertEqual('HRK', $this->config('commerce_currency_resolver.settings') + ->get('currency_default')); + $this->assertEqual('USD', $this->store->getDefaultCurrencyCode()); + $this->assertEqual('USD', $this->currentCurrency->getCurrency()); + $this->assertEqual(1, $this->store->id()); + $store = Store::load($this->store->id()); + $store->delete(); + $this->assertEmpty(Store::load(1)); + $this->resetCurrencyContainer(); + $this->assertEqual('HRK', $this->currentCurrency->getCurrency()); + } + + /** + * Tests language based current currency. + * + * @covers ::getCurrency + */ + public function testLanguage() { + $this->assertEqual('USD', $this->currentCurrency->getCurrency()); + + // Change mapping from store to language. + $this->config('commerce_currency_resolver.settings') + ->set('currency_mapping', 'lang')->save(); + + // Validate default language. + $this->assertEqual('hr', $this->languageManager->getCurrentLanguage()->getId()); + + // Rebuild container and recheck currency. + $this->resetCurrencyContainer(); + $this->assertEqual('HRK', $this->currentCurrency->getCurrency()); + + // Change language back to english. + $this->config('system.site')->set('default_langcode', 'en')->save(); + $this->resetCurrencyContainer(); + $this->assertEqual('USD', $this->currentCurrency->getCurrency()); + } + +} diff --git a/tests/src/Kernel/OrderIntegrationTest.php b/tests/src/Kernel/OrderIntegrationTest.php new file mode 100644 index 0000000..96c0e57 --- /dev/null +++ b/tests/src/Kernel/OrderIntegrationTest.php @@ -0,0 +1,134 @@ +container->get('commerce_price.currency_importer'); + $currency_importer->import('HRK'); + + $this->installConfig(['commerce_currency_resolver']); + $user = $this->createUser(['mail' => $this->randomString() . '@example.com']); + + // Create new exchange rates. + $exchange_rates = ExchangeRates::create([ + 'id' => 'testing', + 'label' => 'Manual', + 'plugin' => 'manual', + 'status' => TRUE, + 'configuration' => [ + 'cron' => FALSE, + 'use_cross_sync' => FALSE, + 'demo_amount' => 100, + 'base_currency' => 'USD', + 'mode' => 'live', + ], + ] + ); + $exchange_rates->save(); + + $this->config($exchange_rates->getExchangerConfigName())->setData([ + 'rates' => [ + 'HRK' => [ + 'USD' => [ + 'value' => 0.15, + 'sync' => 0, + ], + ], + 'USD' => [ + 'HRK' => [ + 'value' => 6.85, + 'sync' => 0, + ], + ], + ], + ])->save(); + + $this->config('commerce_currency_resolver.settings') + ->set('currency_exchange_rates', 'testing') + ->save(); + + $order = Order::create([ + 'type' => 'default', + 'store_id' => $this->store->id(), + 'state' => 'draft', + 'mail' => $user->getEmail(), + 'uid' => $user->id(), + 'ip_address' => '127.0.0.1', + 'order_number' => '6', + ]); + $order->save(); + $this->order = $this->reloadEntity($order); + + $this->currentCurrency = $this->container->get('commerce_currency_resolver.current_currency'); + } + + /** + * Tests the refresh of orders loading in CLI mode. + * + * @covers ::checkCurrency + * @covers ::shouldCurrencyRefresh + */ + public function testCliMode() { + $order_item = OrderItem::create([ + 'type' => 'test', + 'quantity' => '1', + 'unit_price' => new Price('12.00', 'HRK'), + ]); + $order_item->save(); + $this->order->addItem($order_item); + $this->order->save(); + + $order = Order::load(1); + $this->assertInstanceOf(OrderInterface::class, $order); + $this->assertEqual('cli', PHP_SAPI); + $this->assertEqual('HRK', $this->order->getTotalPrice()->getCurrencyCode()); + $this->assertEqual('USD', $this->currentCurrency->getCurrency()); + } + +} diff --git a/tests/src/Traits/CurrentCurrencyTrait.php b/tests/src/Traits/CurrentCurrencyTrait.php new file mode 100644 index 0000000..405768b --- /dev/null +++ b/tests/src/Traits/CurrentCurrencyTrait.php @@ -0,0 +1,18 @@ +container = $this->container->get('kernel')->rebuildContainer(); + $this->currentCurrency = $this->container->get('commerce_currency_resolver.current_currency'); + } + +}