Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions Classes/Domain/Model/Cart/Cart.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@
* LICENSE file that was distributed with this source code.
*/

use Extcode\Cart\Service\CurrencyTranslationServiceInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;

class Cart implements AdditionalDataInterface
{
use AdditionalDataTrait;

private ?CurrencyTranslationServiceInterface $currencyTranslationService = null;

protected float $net;

protected float $gross;
Expand Down Expand Up @@ -73,6 +78,8 @@ public function __construct(
protected string $currencySign = '€',
protected float $currencyTranslation = 1.00
) {
$this->currencyTranslationService = GeneralUtility::makeInstance(CurrencyTranslationServiceInterface::class);

$this->net = 0.0;
$this->gross = 0.0;
$this->count = 0;
Expand Down Expand Up @@ -1078,11 +1085,10 @@ public function setCurrencySign(string $currencySign): void

public function translatePrice(?float $price = null): ?float
{
if ($price !== null) {
$price /= $this->getCurrencyTranslation();
return round($price * 100.0) / 100.0;
if (is_null($this->currencyTranslationService)) {
$this->currencyTranslationService = GeneralUtility::makeInstance(CurrencyTranslationServiceInterface::class);
}

return null;
return $this->currencyTranslationService->translatePrice($this->getCurrencyTranslation(), $price);
}
}
24 changes: 24 additions & 0 deletions Classes/Service/CurrencyTranslationService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Extcode\Cart\Service;

/*
* This file is part of the package extcode/cart.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/

class CurrencyTranslationService implements CurrencyTranslationServiceInterface
{
public function translatePrice(float $factor, ?float $price = null): ?float
{
if (is_null($price)) {
return null;
}

return round($price / $factor * 100.0) / 100.0;
}
}
20 changes: 20 additions & 0 deletions Classes/Service/CurrencyTranslationServiceInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Extcode\Cart\Service;

/*
* This file is part of the package extcode/cart.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/

/**
* @internal This class is marked internal and is not considered part of the public API. The interface will change in the next major version (v12.0.0).
*/
interface CurrencyTranslationServiceInterface
{
public function translatePrice(float $factor, ?float $price = null): ?float;
}
4 changes: 4 additions & 0 deletions Configuration/Services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ services:
identifier: 'cart--order--update--log-service-update'
event: Extcode\Cart\Event\Order\UpdateServiceEvent

Extcode\Cart\Service\CurrencyTranslationServiceInterface:
alias: Extcode\Cart\Service\CurrencyTranslationService
public: true

Extcode\Cart\Service\PaymentMethodsFromTypoScriptService:
public: true

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.. include:: ../../Includes.rst.txt

======================================================
Feature: #638 - Move currency translation to a service
======================================================

See `Issue 638 <https://github.com/extcode/cart/issues/638>`__

Description
===========

Currency conversion is currently very strongly linked to the cart model and the
way in which the currency conversion factor is loaded from TypoScript.

In order to be able to obtain the conversion factor from other sources, the
calculation is carried out by the `CurrencyTranslationService`. This service is
instantiated via the `CurrencyTranslationServiceInterface` interface so that a
corresponding exchange via DI is possible.

The implementation should only be a start and does not yet offer a stable API
because the behaviour must remain the same within the published versions. For
this reason, the `CurrencyTranslationServiceInterface` is marked as `@internal`.
Use is at your own risk! Changes to the service interface must be observed
independently in the event of updates!

Impact
======

No direct impact.

.. index:: API
20 changes: 20 additions & 0 deletions Documentation/Changelog/11.1/Index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.. include:: ../../Includes.rst.txt

10.1 Changes
============

**Table of contents**

.. contents::
:local:
:depth: 1

Features
--------

.. toctree::
:maxdepth: 1
:titlesonly:
:glob:

Feature-*
1 change: 1 addition & 0 deletions Documentation/Changelog/Index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ ChangeLog
:maxdepth: 5
:titlesonly:

11.1/Index
11.0/Index
10.0/Index
9.4/Index
Expand Down
4 changes: 2 additions & 2 deletions Documentation/guides.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
interlink-shortcode="extcode/cart"
/>
<project title="Cart"
release="11.0.1"
version="11.0"
release="11.1.0"
version="11.1"
copyright="2018 - 2025"
/>
<inventory id="t3tsref" url="https://docs.typo3.org/typo3cms/TyposcriptReference/"/>
Expand Down
37 changes: 21 additions & 16 deletions Tests/Unit/Domain/Model/Cart/CartCouponFixTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@
use Extcode\Cart\Domain\Model\Cart\Cart;
use Extcode\Cart\Domain\Model\Cart\CartCouponFix;
use Extcode\Cart\Domain\Model\Cart\TaxClass;
use Extcode\Cart\Service\CurrencyTranslationService;
use Extcode\Cart\Service\CurrencyTranslationServiceInterface;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\MockObject\MockObject;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;

#[CoversClass(CartCouponFix::class)]
Expand Down Expand Up @@ -178,10 +182,7 @@ public function getGrossReturnsTranslatedDiscount(): void
{
$currencyTranslation = 2.0;

$cart = $this->getMockBuilder(Cart::class)
->onlyMethods(['getCurrencyTranslation'])
->setConstructorArgs([[$this->taxClass]])
->getMock();
$cart = $this->createCartMock(['getGross', 'getCurrencyTranslation']);
$cart->method('getCurrencyTranslation')->willReturn($currencyTranslation);

$this->coupon->setCart($cart);
Expand Down Expand Up @@ -238,10 +239,7 @@ public function isUsableReturnsTrueIfCartMinPriceIsLessToGivenPrice(): void
$discount = 5.00;
$cartMinPrice = 9.99;

$cart = $this->getMockBuilder(Cart::class)
->onlyMethods(['getGross'])
->setConstructorArgs([[$this->taxClass]])
->getMock();
$cart = $this->createCartMock();
$cart->method('getGross')->willReturn($gross);

$coupon = new CartCouponFix(
Expand All @@ -267,10 +265,7 @@ public function isUsableReturnsTrueIfCartMinPriceIsEqualToGivenPrice(): void
$discount = 5.00;
$cartMinPrice = 10.00;

$cart = $this->getMockBuilder(Cart::class)
->onlyMethods(['getGross'])
->setConstructorArgs([[$this->taxClass]])
->getMock();
$cart = $this->createCartMock();
$cart->method('getGross')->willReturn($gross);

$coupon = new CartCouponFix(
Expand All @@ -296,10 +291,7 @@ public function isUsableReturnsFalseIfCartMinPriceIsGreaterToGivenPrice(): void
$discount = 5.00;
$cartMinPrice = 10.01;

$cart = $this->getMockBuilder(Cart::class)
->onlyMethods(['getGross'])
->setConstructorArgs([[$this->taxClass]])
->getMock();
$cart = $this->createCartMock();
$cart->method('getGross')->willReturn($gross);

$coupon = new CartCouponFix(
Expand All @@ -317,4 +309,17 @@ public function isUsableReturnsFalseIfCartMinPriceIsGreaterToGivenPrice(): void
$coupon->isUseable()
);
}

private function createCartMock(array $methods = ['getGross']): Cart|MockObject
{
GeneralUtility::addInstance(
CurrencyTranslationServiceInterface::class,
new CurrencyTranslationService()
);

return $this->getMockBuilder(Cart::class)
->onlyMethods($methods)
->setConstructorArgs([[$this->taxClass]])
->getMock();
}
}
62 changes: 26 additions & 36 deletions Tests/Unit/Domain/Model/Cart/CartCouponPercentageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@
use Extcode\Cart\Domain\Model\Cart\Cart;
use Extcode\Cart\Domain\Model\Cart\CartCouponPercentage;
use Extcode\Cart\Domain\Model\Cart\TaxClass;
use Extcode\Cart\Service\CurrencyTranslationService;
use Extcode\Cart\Service\CurrencyTranslationServiceInterface;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\MockObject\MockObject;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;

#[CoversClass(CartCouponPercentage::class)]
Expand Down Expand Up @@ -151,10 +155,7 @@ public function getGrossInitiallyReturnsGrossSetDirectlyByConstructor(): void
{
$taxClass = new TaxClass(1, '19 %', 0.19, 'normal');

$cart = $this->getMockBuilder(Cart::class)
->onlyMethods(['getGross'])
->setConstructorArgs([[$taxClass]])
->getMock();
$cart = $this->createCartMock();
$cart->method('getGross')->willReturn(0.0);

$this->coupon->setCart($cart);
Expand All @@ -171,10 +172,7 @@ public function getGrossReturnsTranslatedDiscount(): void
$taxClass = new TaxClass(1, '19 %', 0.19, 'normal');
$currencyTranslation = 1.0;

$cart = $this->getMockBuilder(Cart::class)
->onlyMethods(['getGross', 'getCurrencyTranslation'])
->setConstructorArgs([[$taxClass]])
->getMock();
$cart = $this->createCartMock(['getGross', 'getCurrencyTranslation']);
$cart->method('getGross')->willReturn(100.0);
$cart->method('getCurrencyTranslation')->willReturn($currencyTranslation);

Expand All @@ -191,10 +189,7 @@ public function getNetInitiallyReturnsNetSetIndirectlyByConstructor(): void
{
$taxClass = new TaxClass(1, '19 %', 0.19, 'normal');

$cart = $this->getMockBuilder(Cart::class)
->onlyMethods(['getGross'])
->setConstructorArgs([[$taxClass]])
->getMock();
$cart = $this->createCartMock();
$cart->method('getGross')->willReturn(0.0);

$this->coupon->setCart($cart);
Expand All @@ -204,10 +199,7 @@ public function getNetInitiallyReturnsNetSetIndirectlyByConstructor(): void
$this->coupon->getNet()
);

$cart = $this->getMockBuilder(Cart::class)
->onlyMethods(['getGross', 'getTaxes'])
->setConstructorArgs([[$taxClass]])
->getMock();
$cart = $this->createCartMock(['getGross', 'getTaxes']);
$cart->method('getGross')->willReturn(100.0);
$cart->method('getTaxes')->willReturn([$taxClass->getId() => 19.0]);

Expand Down Expand Up @@ -242,10 +234,7 @@ public function getTaxesInitiallyReturnsTaxesSetIndirectlyByConstructor(): void
{
$taxClass = new TaxClass(1, '19 %', 0.19, 'normal');

$cart = $this->getMockBuilder(Cart::class)
->onlyMethods(['getGross'])
->setConstructorArgs([[$taxClass]])
->getMock();
$cart = $this->createCartMock();
$cart->method('getGross')->willReturn(0.0);

$this->coupon->setCart($cart);
Expand All @@ -255,10 +244,7 @@ public function getTaxesInitiallyReturnsTaxesSetIndirectlyByConstructor(): void
$this->coupon->getTaxes()
);

$cart = $this->getMockBuilder(Cart::class)
->onlyMethods(['getGross', 'getTaxes'])
->setConstructorArgs([[$taxClass]])
->getMock();
$cart = $this->createCartMock(['getGross', 'getTaxes']);
$cart->method('getGross')->willReturn(100.0);
$cart->method('getTaxes')->willReturn([$taxClass->getId() => 19.0]);

Expand Down Expand Up @@ -286,10 +272,7 @@ public function isUsableReturnsTrueIfCartMinPriceIsLessToGivenPrice(): void
$discount = 5.00;
$cartMinPrice = 9.99;

$cart = $this->getMockBuilder(Cart::class)
->onlyMethods(['getGross'])
->setConstructorArgs([[$this->taxClass]])
->getMock();
$cart = $this->createCartMock();
$cart->method('getGross')->willReturn($gross);

$coupon = new CartCouponPercentage(
Expand All @@ -314,10 +297,7 @@ public function isUsableReturnsTrueIfCartMinPriceIsEqualToGivenPrice(): void
$discount = 5.00;
$cartMinPrice = 10.00;

$cart = $this->getMockBuilder(Cart::class)
->onlyMethods(['getGross'])
->setConstructorArgs([[$this->taxClass]])
->getMock();
$cart = $this->createCartMock();
$cart->method('getGross')->willReturn($gross);

$coupon = new CartCouponPercentage(
Expand All @@ -342,10 +322,7 @@ public function isUsableReturnsFalseIfCartMinPriceIsGreaterToGivenPrice(): void
$discount = 5.00;
$cartMinPrice = 10.01;

$cart = $this->getMockBuilder(Cart::class)
->onlyMethods(['getGross'])
->setConstructorArgs([[$this->taxClass]])
->getMock();
$cart = $this->createCartMock();
$cart->method('getGross')->willReturn($gross);

$coupon = new CartCouponPercentage(
Expand All @@ -362,4 +339,17 @@ public function isUsableReturnsFalseIfCartMinPriceIsGreaterToGivenPrice(): void
$coupon->isUseable()
);
}

private function createCartMock(array $methods = ['getGross']): Cart|MockObject
{
GeneralUtility::addInstance(
CurrencyTranslationServiceInterface::class,
new CurrencyTranslationService()
);

return $this->getMockBuilder(Cart::class)
->onlyMethods($methods)
->setConstructorArgs([[$this->taxClass]])
->getMock();
}
}
Loading