diff --git a/src/Exception/Code/ExceptionCode.php b/src/Exception/Code/ExceptionCode.php index de4bb355e..48adcc3f9 100644 --- a/src/Exception/Code/ExceptionCode.php +++ b/src/Exception/Code/ExceptionCode.php @@ -7,7 +7,6 @@ * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md * * @see https://github.com/mollie/PrestaShop - * @codingStandardsIgnoreStart */ namespace Mollie\Exception\Code; @@ -36,4 +35,15 @@ class ExceptionCode public const ORDER_FAILED_TO_RETRIEVE_PAYMENT_FEE = 3004; public const ORDER_FAILED_TO_CREATE_ORDER_PAYMENT_FEE = 3005; public const ORDER_FAILED_TO_UPDATE_ORDER_TOTAL_WITH_PAYMENT_FEE = 3006; + + // Service error codes starts from 4000 + public const SERVICE_FAILED_TO_ROUND_AMOUNT = 4001; + const SERVICE_FAILED_TO_FILL_PRODUCT_LINES_WITH_REMAINING_DATA = 4002; + const SERVICE_FAILED_TO_ADD_SHIPPING_LINE = 4003; + const SERVICE_FAILED_TO_ADD_WRAPPING_LINE = 4004; + const SERVICE_FAILED_TO_ADD_PAYMENT_FEE = 4005; + const SERVICE_FAILED_TO_UNGROUP_LINES = 4006; + const SERVICE_FAILED_TO_CONVERT_TO_LINE_ARRAY = 4007; + const SERVICE_FAILED_TO_CREATE_PRODUCT_LINES = 4008; + const SERVICE_FAILED_TO_ADD_DISCOUNTS_TO_PRODUCT_LINES = 4009; } diff --git a/src/Exception/CouldNotProcessCartLinesException.php b/src/Exception/CouldNotProcessCartLinesException.php new file mode 100644 index 000000000..10c394244 --- /dev/null +++ b/src/Exception/CouldNotProcessCartLinesException.php @@ -0,0 +1,103 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + */ + +namespace Mollie\Exception; + +use Exception; +use Mollie\Exception\Code\ExceptionCode; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CouldNotProcessCartLinesException extends \Exception +{ + public static function failedToRoundAmount(Exception $exception): self + { + return new self( + 'Failed to round amount.', + ExceptionCode::SERVICE_FAILED_TO_ROUND_AMOUNT, + $exception + ); + } + + public static function failedToFillProductLinesWithRemainingData(Exception $e) + { + return new self( + 'Failed to fill product lines with remaining data.', + ExceptionCode::SERVICE_FAILED_TO_FILL_PRODUCT_LINES_WITH_REMAINING_DATA, + $e + ); + } + + public static function failedToAddShippingLine(Exception $e) + { + return new self( + 'Failed to add shipping line.', + ExceptionCode::SERVICE_FAILED_TO_ADD_SHIPPING_LINE, + $e + ); + } + + public static function failedToAddWrappingLine(Exception $e) + { + return new self( + 'Failed to add wrapping line.', + ExceptionCode::SERVICE_FAILED_TO_ADD_WRAPPING_LINE, + $e + ); + } + + public static function failedToAddPaymentFee(Exception $e) + { + return new self( + 'Failed to add payment fee.', + ExceptionCode::SERVICE_FAILED_TO_ADD_PAYMENT_FEE, + $e + ); + } + + public static function failedToUngroupLines(Exception $e) + { + return new self( + 'Failed to ungroup lines.', + ExceptionCode::SERVICE_FAILED_TO_UNGROUP_LINES, + $e + ); + } + + public static function failedConvertToLineArray(Exception $e) + { + return new self( + 'Failed to convert to line array.', + ExceptionCode::SERVICE_FAILED_TO_CONVERT_TO_LINE_ARRAY, + $e + ); + } + + public static function failedToCreateProductLines(Exception $e) + { + return new self( + 'Failed to create product lines.', + ExceptionCode::SERVICE_FAILED_TO_CREATE_PRODUCT_LINES, + $e + ); + } + + public static function failedToAddDiscountsToProductLines(Exception $e) + { + return new self( + 'Failed to add discounts to product lines.', + ExceptionCode::SERVICE_FAILED_TO_ADD_DISCOUNTS_TO_PRODUCT_LINES, + $e + ); + } +} diff --git a/src/Service/CartLine/CartItemDiscountService.php b/src/Service/CartLine/CartItemDiscountService.php new file mode 100644 index 000000000..c9b51c40f --- /dev/null +++ b/src/Service/CartLine/CartItemDiscountService.php @@ -0,0 +1,50 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + */ + +namespace Mollie\Service\CartLine; + +use Mollie\Config\Config; +use Mollie\Utility\NumberUtility; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CartItemDiscountService +{ + /** + * @param float $totalDiscounts + * @param array $orderLines + * @param float $remaining + * + * @return array + */ + public function addDiscountsToProductLines(float $totalDiscounts, array $orderLines, float $remaining): array + { + if ($totalDiscounts >= 0.01) { + $orderLines['discount'] = [ + [ + 'name' => 'Discount', + 'type' => 'discount', + 'quantity' => 1, + 'unitPrice' => -round($totalDiscounts, Config::API_ROUNDING_PRECISION), + 'totalAmount' => -round($totalDiscounts, Config::API_ROUNDING_PRECISION), + 'targetVat' => 0, + 'category' => '', + ], + ]; + + $remaining = NumberUtility::plus($remaining, $totalDiscounts); + } + + return [$orderLines, $remaining]; + } +} diff --git a/src/Service/CartLine/CartItemPaymentFeeService.php b/src/Service/CartLine/CartItemPaymentFeeService.php new file mode 100644 index 000000000..b236f36a0 --- /dev/null +++ b/src/Service/CartLine/CartItemPaymentFeeService.php @@ -0,0 +1,59 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + */ + +namespace mollie\src\Service\CartLine; + +use Mollie\Config\Config; +use Mollie\DTO\PaymentFeeData; +use Mollie\Service\LanguageService; +use Mollie\Utility\NumberUtility; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CartItemPaymentFeeService +{ + /* @var LanguageService */ + private $languageService; + + public function __construct(LanguageService $languageService) + { + $this->languageService = $languageService; + } + + /** + * @param PaymentFeeData $paymentFeeData + * @param array $orderLines + * + * @return array + */ + public function addPaymentFeeLine(PaymentFeeData $paymentFeeData, array $orderLines): array + { + if (!$paymentFeeData->isActive()) { + return $orderLines; + } + + $orderLines['surcharge'] = [ + [ + 'name' => $this->languageService->lang('Payment fee'), + 'sku' => Config::PAYMENT_FEE_SKU, + 'quantity' => 1, + 'unitPrice' => round($paymentFeeData->getPaymentFeeTaxIncl(), CONFIG::API_ROUNDING_PRECISION), + 'totalAmount' => round($paymentFeeData->getPaymentFeeTaxIncl(), CONFIG::API_ROUNDING_PRECISION), + 'vatAmount' => NumberUtility::minus($paymentFeeData->getPaymentFeeTaxIncl(), $paymentFeeData->getPaymentFeeTaxExcl()), + 'vatRate' => $paymentFeeData->getTaxRate(), + ], + ]; + + return $orderLines; + } +} diff --git a/src/Service/CartLine/CartItemProductLinesService.php b/src/Service/CartLine/CartItemProductLinesService.php new file mode 100644 index 000000000..b8e1b0318 --- /dev/null +++ b/src/Service/CartLine/CartItemProductLinesService.php @@ -0,0 +1,87 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + */ + +namespace Mollie\Service\CartLine; + +use Mollie\Config\Config; +use Mollie\Utility\CalculationUtility; +use Mollie\Utility\NumberUtility; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CartItemProductLinesService +{ + /** + * @param array $orderLines + * @param int $vatRatePrecision + * + * @return array + * + * @throws \PrestaShop\Decimal\Exception\DivisionByZeroException + */ + public function fillProductLinesWithRemainingData(array $orderLines, int $vatRatePrecision): array + { + $roundingPrecision = CONFIG::API_ROUNDING_PRECISION; + + foreach ($orderLines as $productHash => $aItem) { + $orderLines[$productHash] = array_map(function ($line) use ($roundingPrecision, $vatRatePrecision) { + $quantity = (int) $line['quantity']; + $targetVat = $line['targetVat']; + $unitPrice = $line['unitPrice']; + + $unitPriceNoTax = round( + CalculationUtility::getUnitPriceNoTax($line['unitPrice'], $targetVat), + $roundingPrecision + ); + + // Calculate VAT + $totalAmount = $line['totalAmount']; + $actualVatRate = 0; + + if ($unitPriceNoTax > 0) { + $actualVatRate = round( + CalculationUtility::getActualVatRate($unitPrice, $unitPriceNoTax, $quantity), + $vatRatePrecision + ); + } + + $vatRateWithPercentages = NumberUtility::plus($actualVatRate, 100); + + $vatAmount = NumberUtility::times( + $totalAmount, + NumberUtility::divide($actualVatRate, $vatRateWithPercentages) + ); + + $newItem = [ + 'name' => $line['name'], + 'category' => $line['category'], + 'quantity' => (int) $quantity, + 'unitPrice' => round($unitPrice, $roundingPrecision), + 'totalAmount' => round($totalAmount, $roundingPrecision), + 'vatRate' => round($actualVatRate, $roundingPrecision), + 'vatAmount' => round($vatAmount, $roundingPrecision), + 'product_url' => $line['product_url'] ?? null, + 'image_url' => $line['image_url'] ?? null, + ]; + + if (isset($line['sku'])) { + $newItem['sku'] = $line['sku']; + } + + return $newItem; + }, $aItem); + } + + return $orderLines; + } +} diff --git a/src/Service/CartLine/CartItemShippingLineService.php b/src/Service/CartLine/CartItemShippingLineService.php new file mode 100644 index 000000000..1fafaa677 --- /dev/null +++ b/src/Service/CartLine/CartItemShippingLineService.php @@ -0,0 +1,59 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + */ + +namespace Mollie\Service\CartLine; + +use Mollie\Config\Config; +use Mollie\Service\LanguageService; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CartItemShippingLineService +{ + /* @var LanguageService */ + private $languageService; + + public function __construct(LanguageService $languageService) + { + $this->languageService = $languageService; + } + + /** + * @param float $roundedShippingCost + * @param array $cartSummary + * @param array $orderLines + * + * @return array + */ + public function addShippingLine(float $roundedShippingCost, array $cartSummary, array $orderLines): array + { + if (round($roundedShippingCost, 2) <= 0) { + return $orderLines; + } + + $shippingVatRate = round(($cartSummary['total_shipping'] - $cartSummary['total_shipping_tax_exc']) / $cartSummary['total_shipping_tax_exc'] * 100, Config::API_ROUNDING_PRECISION); + + $orderLines['shipping'] = [ + [ + 'name' => $this->languageService->lang('Shipping'), + 'quantity' => 1, + 'unitPrice' => round($roundedShippingCost, Config::API_ROUNDING_PRECISION), + 'totalAmount' => round($roundedShippingCost, Config::API_ROUNDING_PRECISION), + 'vatAmount' => round($roundedShippingCost * $shippingVatRate / ($shippingVatRate + 100), Config::API_ROUNDING_PRECISION), + 'vatRate' => $shippingVatRate, + ], + ]; + + return $orderLines; + } +} diff --git a/src/Service/CartLine/CartItemWrappingService.php b/src/Service/CartLine/CartItemWrappingService.php new file mode 100644 index 000000000..f1719a513 --- /dev/null +++ b/src/Service/CartLine/CartItemWrappingService.php @@ -0,0 +1,62 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + */ + +namespace Mollie\Service\CartLine; + +use Mollie\Config\Config; +use Mollie\Service\LanguageService; +use Mollie\Utility\CalculationUtility; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CartItemWrappingService +{ + /** @var LanguageService */ + private $languageService; + + public function __construct(LanguageService $languageService) + { + $this->languageService = $languageService; + } + + /** + * @param float $wrappingPrice + * @param array $cartSummary + * @param int $vatRatePrecision + * @param array $orderLines + * + * @return array + */ + public function addWrappingLine(float $wrappingPrice, array $cartSummary, int $vatRatePrecision, array $orderLines): array + { + if (round($wrappingPrice, 2) > 0) { + $wrappingVatRate = round( + CalculationUtility::getActualVatRate($cartSummary['total_wrapping'], $cartSummary['total_wrapping_tax_exc']), + $vatRatePrecision + ); + + $orderLines['wrapping'] = [ + [ + 'name' => $this->languageService->lang('Gift wrapping'), + 'quantity' => 1, + 'unitPrice' => round($wrappingPrice, Config::API_ROUNDING_PRECISION), + 'totalAmount' => round($wrappingPrice, Config::API_ROUNDING_PRECISION), + 'vatAmount' => round($wrappingPrice * $wrappingVatRate / ($wrappingVatRate + 100), Config::API_ROUNDING_PRECISION), + 'vatRate' => $wrappingVatRate, + ], + ]; + } + + return $orderLines; + } +} diff --git a/src/Service/CartLine/CartItemsService.php b/src/Service/CartLine/CartItemsService.php new file mode 100644 index 000000000..c3c5a0c7d --- /dev/null +++ b/src/Service/CartLine/CartItemsService.php @@ -0,0 +1,144 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + */ + +namespace Mollie\Service\CartLine; + +use Mollie\Adapter\Context; +use Mollie\Config\Config; +use Mollie\Service\VoucherService; +use Mollie\Utility\CartPriceUtility; +use Mollie\Utility\NumberUtility; +use Mollie\Utility\TextFormatUtility; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class CartItemsService +{ + /** + * @var Context + */ + private $context; + /** + * @var VoucherService + */ + private $voucherService; + + public function __construct(Context $context, VoucherService $voucherService) + { + $this->context = $context; + $this->voucherService = $voucherService; + } + + /** + * @param array $giftProducts + * @param string $selectedVoucherCategory + * @param float $remaining + * + * @return array + */ + public function createProductLines(array $cartItems, array $giftProducts, array $orderLines, string $selectedVoucherCategory, float $remaining): array + { + foreach ($cartItems as $cartItem) { + // Get the rounded total w/ tax + $roundedTotalWithTax = round($cartItem['total_wt'], Config::API_ROUNDING_PRECISION); + + // Skip if no qty + $quantity = (int) $cartItem['cart_quantity']; + if ($quantity <= 0 || $cartItem['price_wt'] <= 0) { + continue; + } + + // Generate the product hash + $idProduct = TextFormatUtility::formatNumber($cartItem['id_product'], 0); + $idProductAttribute = TextFormatUtility::formatNumber($cartItem['id_product_attribute'], 0); + $idCustomization = TextFormatUtility::formatNumber($cartItem['id_customization'], 0); + + $productHash = "{$idProduct}¤{$idProductAttribute}¤{$idCustomization}"; + + foreach ($giftProducts as $gift_product) { + if ($gift_product['id_product'] === $cartItem['id_product']) { + $quantity = NumberUtility::minus($quantity, $gift_product['cart_quantity']); + + $productHashGift = "{$idProduct}¤{$idProductAttribute}¤{$idCustomization}gift"; + $orderLines[$productHashGift][] = [ + 'name' => $cartItem['name'], + 'sku' => $productHashGift, + 'targetVat' => (float) $cartItem['rate'], + 'quantity' => $gift_product['cart_quantity'], + 'unitPrice' => 0, + 'totalAmount' => 0, + 'category' => '', + 'product_url' => $this->context->getProductLink($cartItem['id_product']), + 'image_url' => $this->context->getImageLink($cartItem['link_rewrite'], $cartItem['id_image']), + ]; + continue; + } + } + + if ((int) $quantity <= 0) { + continue; + } + + // Try to spread this product evenly and account for rounding differences on the order line + $orderLines[$productHash][] = [ + 'name' => $cartItem['name'], + 'sku' => $productHash, + 'targetVat' => (float) $cartItem['rate'], + 'quantity' => $quantity, + 'unitPrice' => round($cartItem['price_wt'], Config::API_ROUNDING_PRECISION), + 'totalAmount' => (float) $roundedTotalWithTax, + 'category' => $this->voucherService->getVoucherCategory($cartItem, $selectedVoucherCategory), + 'product_url' => $this->context->getProductLink($cartItem['id_product']), + 'image_url' => $this->context->getImageLink($cartItem['link_rewrite'], $cartItem['id_image']), + ]; + $remaining -= $roundedTotalWithTax; + } + + return [$orderLines, $remaining]; + } + + /** + * Spread the cart line amount evenly. + * + * Optionally split into multiple lines in case of rounding inaccuracies + * + * @param array[] $cartLineGroup Cart Line Group WITHOUT VAT details (except target VAT rate) + * @param float $newTotal + * + * @return array[] + * + * @since 3.2.2 + * @since 3.3.3 Omits VAT details + */ + public static function spreadCartLineGroup($cartLineGroup, $newTotal) + { + $newTotal = round($newTotal, Config::API_ROUNDING_PRECISION); + $quantity = array_sum(array_column($cartLineGroup, 'quantity')); + $newCartLineGroup = []; + $spread = CartPriceUtility::spreadAmountEvenly($newTotal, $quantity); + + foreach ($spread as $unitPrice => $qty) { + $newCartLineGroup[] = [ + 'name' => $cartLineGroup[0]['name'], + 'quantity' => $qty, + 'unitPrice' => (float) $unitPrice, + 'totalAmount' => (float) $unitPrice * $qty, + 'sku' => $cartLineGroup[0]['sku'] ?? '', + 'targetVat' => $cartLineGroup[0]['targetVat'], + 'category' => $cartLineGroup[0]['category'], + ]; + } + + return $newCartLineGroup; + } +} diff --git a/src/Service/CartLinesService.php b/src/Service/CartLinesService.php index 06edd7c32..e106a6e24 100644 --- a/src/Service/CartLinesService.php +++ b/src/Service/CartLinesService.php @@ -7,22 +7,23 @@ * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md * * @see https://github.com/mollie/PrestaShop - * @codingStandardsIgnoreStart */ namespace Mollie\Service; -use Cart; -use Mollie\Adapter\Context; -use Mollie\Adapter\ToolsAdapter; use Mollie\Config\Config; -use Mollie\DTO\Line; -use Mollie\DTO\Object\Amount; use Mollie\DTO\PaymentFeeData; +use Mollie\Exception\CouldNotProcessCartLinesException; +use Mollie\Service\CartLine\CartItemDiscountService; +use Mollie\Service\CartLine\CartItemProductLinesService; +use Mollie\Service\CartLine\CartItemShippingLineService; +use Mollie\Service\CartLine\CartItemsService; +use Mollie\Service\CartLine\CartItemWrappingService; +use mollie\src\Service\CartLine\CartItemPaymentFeeService; +use mollie\src\Utility\LineUtility; +use mollie\src\Utility\RoundingUtility; +use Mollie\Utility\ArrayUtility; use Mollie\Utility\CalculationUtility; -use Mollie\Utility\CartPriceUtility; -use Mollie\Utility\NumberUtility; -use Mollie\Utility\TextFormatUtility; if (!defined('_PS_VERSION_')) { exit; @@ -30,28 +31,53 @@ class CartLinesService { - /** - * @var VoucherService - */ - private $voucherService; + /* @var CartItemsService */ + private $cartItemsService; - /** - * @var LanguageService - */ - private $languageService; + /* @var CartItemDiscountService */ + private $cartItemDiscountService; - /** - * @var ToolsAdapter - */ - private $tools; - private $context; + /* @var CartItemShippingLineService */ + private $cartItemShippingLineService; + + /* @var CartItemWrappingService */ + private $cartItemWrappingService; + + /* @var CartItemProductLinesService */ + private $cartItemProductLinesService; - public function __construct(LanguageService $languageService, VoucherService $voucherService, ToolsAdapter $tools, Context $context) - { - $this->voucherService = $voucherService; - $this->languageService = $languageService; - $this->tools = $tools; - $this->context = $context; + /* @var CartItemPaymentFeeService */ + private $cartItemPaymentFeeService; + + /* @var LineUtility */ + private $lineUtility; + + /* @var RoundingUtility */ + private $roundingUtility; + + /* @var ArrayUtility */ + private $arrayUtility; + + public function __construct( + CartItemsService $cartItemsService, + CartItemDiscountService $cartItemDiscountService, + CartItemShippingLineService $cartItemShippingLineService, + CartItemWrappingService $cartItemWrappingService, + CartItemProductLinesService $cartItemProductLinesService, + CartItemPaymentFeeService $cartItemPaymentFeeService, + LineUtility $lineUtility, + RoundingUtility $roundingUtility, + ArrayUtility $arrayUtility + ) { + $this->cartItemsService = $cartItemsService; + $this->cartItemDiscountService = $cartItemDiscountService; + $this->cartItemShippingLineService = $cartItemShippingLineService; + $this->cartItemWrappingService = $cartItemWrappingService; + $this->cartItemProductLinesService = $cartItemProductLinesService; + $this->cartItemPaymentFeeService = $cartItemPaymentFeeService; + $this->lineUtility = $lineUtility; + $this->roundingUtility = $roundingUtility; + $this->arrayUtility = $arrayUtility; } /** @@ -66,459 +92,91 @@ public function __construct(LanguageService $languageService, VoucherService $vo * * @return array * - * @throws \PrestaShop\Decimal\Exception\DivisionByZeroException + * @throws CouldNotProcessCartLinesException */ public function getCartLines( - $amount, - $paymentFeeData, - $currencyIsoCode, - $cartSummary, - $shippingCost, - $cartItems, - $psGiftWrapping, - $selectedVoucherCategory - ) { - // TODO refactor whole service, split order line append into separate services and test them individually at least!!! + float $amount, + PaymentFeeData $paymentFeeData, + string $currencyIsoCode, + array $cartSummary, + float $shippingCost, + array $cartItems, + bool $psGiftWrapping, + string $selectedVoucherCategory + ): array { + $totalPrice = round($amount, Config::API_ROUNDING_PRECISION); + $roundedShippingCost = round($shippingCost, Config::API_ROUNDING_PRECISION); - $apiRoundingPrecision = Config::API_ROUNDING_PRECISION; - $vatRatePrecision = Config::VAT_RATE_ROUNDING_PRECISION; - - $totalPrice = round($amount, $apiRoundingPrecision); - $roundedShippingCost = round($shippingCost, $apiRoundingPrecision); foreach ($cartSummary['discounts'] as $discount) { if ($discount['free_shipping']) { $roundedShippingCost = 0; } } - $wrappingPrice = $psGiftWrapping ? round($cartSummary['total_wrapping'], $apiRoundingPrecision) : 0; - $totalDiscounts = isset($cartSummary['total_discounts']) ? $cartSummary['total_discounts'] : 0; - $remaining = round( + $wrappingPrice = $psGiftWrapping ? round($cartSummary['total_wrapping'], Config::API_ROUNDING_PRECISION) : 0; + + $remainingAmount = round( CalculationUtility::getCartRemainingPrice((float) $totalPrice, (float) $roundedShippingCost, (float) $wrappingPrice), - $apiRoundingPrecision + Config::API_ROUNDING_PRECISION ); $orderLines = []; - /* Item */ - list($orderLines, $remaining) = $this->createProductLines($cartItems, $apiRoundingPrecision, $cartSummary['gift_products'], $orderLines, $selectedVoucherCategory, $remaining); - - // Add discount if applicable - list($orderLines, $remaining) = $this->addDiscountsToProductLines($totalDiscounts, $apiRoundingPrecision, $orderLines, $remaining); - - // Compensate for order total rounding inaccuracies - $orderLines = $this->compositeRoundingInaccuracies($remaining, $apiRoundingPrecision, $orderLines); - - // Fill the order lines with the rest of the data (tax, total amount, etc.) - $orderLines = $this->fillProductLinesWithRemainingData($orderLines, $apiRoundingPrecision, $vatRatePrecision); - - // Add shipping - $orderLines = $this->addShippingLine($roundedShippingCost, $cartSummary, $apiRoundingPrecision, $orderLines); - - // Add wrapping - $orderLines = $this->addWrappingLine($wrappingPrice, $cartSummary, $vatRatePrecision, $apiRoundingPrecision, $orderLines); - - // Add fee - $orderLines = $this->addPaymentFeeLine($paymentFeeData, $apiRoundingPrecision, $orderLines); - - // Ungroup all the cart lines, just one level - $newItems = $this->ungroupLines($orderLines); - - // Convert floats to strings for the Mollie API and add additional info - return $this->convertToLineArray($newItems, $currencyIsoCode, $apiRoundingPrecision); - } - - /** - * Spread the cart line amount evenly. - * - * Optionally split into multiple lines in case of rounding inaccuracies - * - * @param array[] $cartLineGroup Cart Line Group WITHOUT VAT details (except target VAT rate) - * @param float $newTotal - * - * @return array[] - * - * @since 3.2.2 - * @since 3.3.3 Omits VAT details - */ - public static function spreadCartLineGroup($cartLineGroup, $newTotal) - { - $apiRoundingPrecision = Config::API_ROUNDING_PRECISION; - $newTotal = round($newTotal, $apiRoundingPrecision); - $quantity = array_sum(array_column($cartLineGroup, 'quantity')); - $newCartLineGroup = []; - $spread = CartPriceUtility::spreadAmountEvenly($newTotal, $quantity); - foreach ($spread as $unitPrice => $qty) { - $newCartLineGroup[] = [ - 'name' => $cartLineGroup[0]['name'], - 'quantity' => $qty, - 'unitPrice' => (float) $unitPrice, - 'totalAmount' => (float) $unitPrice * $qty, - 'sku' => isset($cartLineGroup[0]['sku']) ? $cartLineGroup[0]['sku'] : '', - 'targetVat' => $cartLineGroup[0]['targetVat'], - 'category' => $cartLineGroup[0]['category'], - ]; + try { + list($orderLines, $remainingAmount) = $this->cartItemsService->createProductLines($cartItems, $cartSummary['gift_products'], $orderLines, $selectedVoucherCategory, $remainingAmount); + } catch (\Exception $e) { + throw CouldNotProcessCartLinesException::failedToCreateProductLines($e); } - return $newCartLineGroup; - } - - /** - * @param int $apiRoundingPrecision - * @param array $giftProducts - * @param string $selectedVoucherCategory - * @param float $remaining - * - * @return array - */ - private function createProductLines(array $cartItems, $apiRoundingPrecision, $giftProducts, array $orderLines, $selectedVoucherCategory, $remaining) - { - foreach ($cartItems as $cartItem) { - // Get the rounded total w/ tax - $roundedTotalWithTax = round($cartItem['total_wt'], $apiRoundingPrecision); - - // Skip if no qty - $quantity = (int) $cartItem['cart_quantity']; - if ($quantity <= 0 || $cartItem['price_wt'] <= 0) { - continue; - } - - // Generate the product hash - $idProduct = TextFormatUtility::formatNumber($cartItem['id_product'], 0); - $idProductAttribute = TextFormatUtility::formatNumber($cartItem['id_product_attribute'], 0); - $idCustomization = TextFormatUtility::formatNumber($cartItem['id_customization'], 0); - - $productHash = "{$idProduct}¤{$idProductAttribute}¤{$idCustomization}"; - - foreach ($giftProducts as $gift_product) { - if ($gift_product['id_product'] === $cartItem['id_product']) { - $quantity = NumberUtility::minus($quantity, $gift_product['cart_quantity']); - - $productHashGift = "{$idProduct}¤{$idProductAttribute}¤{$idCustomization}gift"; - $orderLines[$productHashGift][] = [ - 'name' => $cartItem['name'], - 'sku' => $productHashGift, - 'targetVat' => (float) $cartItem['rate'], - 'quantity' => $gift_product['cart_quantity'], - 'unitPrice' => 0, - 'totalAmount' => 0, - 'category' => '', - 'product_url' => $this->context->getProductLink($cartItem['id_product']), - 'image_url' => $this->context->getImageLink($cartItem['link_rewrite'], $cartItem['id_image']), - ]; - continue; - } - } - - if ((int) $quantity <= 0) { - continue; - } + $totalDiscounts = $cartSummary['total_discounts'] ?? 0; - // Try to spread this product evenly and account for rounding differences on the order line - $orderLines[$productHash][] = [ - 'name' => $cartItem['name'], - 'sku' => $productHash, - 'targetVat' => (float) $cartItem['rate'], - 'quantity' => $quantity, - 'unitPrice' => round($cartItem['price_wt'], $apiRoundingPrecision), - 'totalAmount' => (float) $roundedTotalWithTax, - 'category' => $this->voucherService->getVoucherCategory($cartItem, $selectedVoucherCategory), - 'product_url' => $this->context->getProductLink($cartItem['id_product']), - 'image_url' => $this->context->getImageLink($cartItem['link_rewrite'], $cartItem['id_image']), - ]; - $remaining -= $roundedTotalWithTax; + try { + list($orderLines, $remainingAmount) = $this->cartItemDiscountService->addDiscountsToProductLines($totalDiscounts, $orderLines, $remainingAmount); + } catch (\Exception $e) { + throw CouldNotProcessCartLinesException::failedToAddDiscountsToProductLines($e); } - return [$orderLines, $remaining]; - } - - /** - * @param float $totalDiscounts - * @param int $apiRoundingPrecision - * @param array $orderLines - * @param float $remaining - * - * @return array - */ - private function addDiscountsToProductLines($totalDiscounts, $apiRoundingPrecision, $orderLines, $remaining) - { - if ($totalDiscounts >= 0.01) { - $orderLines['discount'] = [ - [ - 'name' => 'Discount', - 'type' => 'discount', - 'quantity' => 1, - 'unitPrice' => -round($totalDiscounts, $apiRoundingPrecision), - 'totalAmount' => -round($totalDiscounts, $apiRoundingPrecision), - 'targetVat' => 0, - 'category' => '', - ], - ]; - $remaining = NumberUtility::plus($remaining, $totalDiscounts); - } - - return [$orderLines, $remaining]; - } - - /** - * @param float $remaining - * @param int $apiRoundingPrecision - * @param array $orderLines - * - * @return array - */ - private function compositeRoundingInaccuracies($remaining, $apiRoundingPrecision, $orderLines) - { - $remaining = round($remaining, $apiRoundingPrecision); - if ($remaining < 0) { - foreach (array_reverse($orderLines) as $hash => $items) { - // Grab the line group's total amount - $totalAmount = array_sum(array_column($items, 'totalAmount')); - - // Remove when total is lower than remaining - if ($totalAmount <= $remaining) { - // The line total is less than remaining, we should remove this line group and continue - $remaining = $remaining - $totalAmount; - unset($items); - continue; - } - - // Otherwise spread the cart line again with the updated total - //TODO: check why remaining comes -100 when testing and new total becomes different - $orderLines[$hash] = static::spreadCartLineGroup($items, $totalAmount + $remaining); - break; - } - } elseif ($remaining > 0) { - foreach (array_reverse($orderLines) as $hash => $items) { - // Grab the line group's total amount - $totalAmount = array_sum(array_column($items, 'totalAmount')); - // Otherwise spread the cart line again with the updated total - $orderLines[$hash] = static::spreadCartLineGroup($items, $totalAmount + $remaining); - break; - } + try { + $orderLines = $this->roundingUtility->compositeRoundingInaccuracies($remainingAmount, $orderLines); + } catch (\Exception $e) { + throw CouldNotProcessCartLinesException::failedToRoundAmount($e); } - return $orderLines; - } - - /** - * @param int $apiRoundingPrecision - * @param int $vatRatePrecision - * - * @return array - * - * @throws \PrestaShop\Decimal\Exception\DivisionByZeroException - */ - private function fillProductLinesWithRemainingData(array $orderLines, $apiRoundingPrecision, $vatRatePrecision) - { - foreach ($orderLines as $productHash => $aItem) { - $orderLines[$productHash] = array_map(function ($line) use ($apiRoundingPrecision, $vatRatePrecision) { - $quantity = (int) $line['quantity']; - $targetVat = $line['targetVat']; - $unitPrice = $line['unitPrice']; - $unitPriceNoTax = round(CalculationUtility::getUnitPriceNoTax( - $line['unitPrice'], - $targetVat - ), - $apiRoundingPrecision - ); - - // Calculate VAT - $totalAmount = $line['totalAmount']; - $actualVatRate = 0; - if ($unitPriceNoTax > 0) { - $actualVatRate = round( - $vatAmount = CalculationUtility::getActualVatRate($unitPrice, $unitPriceNoTax, $quantity), - $vatRatePrecision - ); - } - $vatRateWithPercentages = NumberUtility::plus($actualVatRate, 100); - $vatAmount = NumberUtility::times( - $totalAmount, - NumberUtility::divide($actualVatRate, $vatRateWithPercentages) - ); - - $newItem = [ - 'name' => $line['name'], - 'category' => $line['category'], - 'quantity' => (int) $quantity, - 'unitPrice' => round($unitPrice, $apiRoundingPrecision), - 'totalAmount' => round($totalAmount, $apiRoundingPrecision), - 'vatRate' => round($actualVatRate, $apiRoundingPrecision), - 'vatAmount' => round($vatAmount, $apiRoundingPrecision), - 'product_url' => $line['product_url'] ?? null, - 'image_url' => $line['image_url'] ?? null, - ]; - if (isset($line['sku'])) { - $newItem['sku'] = $line['sku']; - } - - return $newItem; - }, $aItem); + try { + $orderLines = $this->cartItemProductLinesService->fillProductLinesWithRemainingData($orderLines, Config::VAT_RATE_ROUNDING_PRECISION); + } catch (\Exception $e) { + throw CouldNotProcessCartLinesException::failedToFillProductLinesWithRemainingData($e); } - return $orderLines; - } - - /** - * @param float $roundedShippingCost - * @param array $cartSummary - * @param int $apiRoundingPrecision - * - * @return array - */ - private function addShippingLine($roundedShippingCost, $cartSummary, $apiRoundingPrecision, array $orderLines) - { - if (round($roundedShippingCost, 2) > 0) { - $shippingVatRate = round(($cartSummary['total_shipping'] - $cartSummary['total_shipping_tax_exc']) / $cartSummary['total_shipping_tax_exc'] * 100, $apiRoundingPrecision); - - $orderLines['shipping'] = [ - [ - 'name' => $this->languageService->lang('Shipping'), - 'quantity' => 1, - 'unitPrice' => round($roundedShippingCost, $apiRoundingPrecision), - 'totalAmount' => round($roundedShippingCost, $apiRoundingPrecision), - 'vatAmount' => round($roundedShippingCost * $shippingVatRate / ($shippingVatRate + 100), $apiRoundingPrecision), - 'vatRate' => $shippingVatRate, - ], - ]; + try { + $orderLines = $this->cartItemShippingLineService->addShippingLine($roundedShippingCost, $cartSummary, $orderLines); + } catch (\Exception $e) { + throw CouldNotProcessCartLinesException::failedToAddShippingLine($e); } - return $orderLines; - } - - /** - * @param float $wrappingPrice - * @param int $vatRatePrecision - * @param int $apiRoundingPrecision - * - * @return array - */ - private function addWrappingLine($wrappingPrice, array $cartSummary, $vatRatePrecision, $apiRoundingPrecision, array $orderLines) - { - if (round($wrappingPrice, 2) > 0) { - $wrappingVatRate = round( - CalculationUtility::getActualVatRate( - $cartSummary['total_wrapping'], - $cartSummary['total_wrapping_tax_exc'] - ), - $vatRatePrecision - ); - - $orderLines['wrapping'] = [ - [ - 'name' => $this->languageService->lang('Gift wrapping'), - 'quantity' => 1, - 'unitPrice' => round($wrappingPrice, $apiRoundingPrecision), - 'totalAmount' => round($wrappingPrice, $apiRoundingPrecision), - 'vatAmount' => round($wrappingPrice * $wrappingVatRate / ($wrappingVatRate + 100), $apiRoundingPrecision), - 'vatRate' => $wrappingVatRate, - ], - ]; + try { + $orderLines = $this->cartItemWrappingService->addWrappingLine($wrappingPrice, $cartSummary, Config::VAT_RATE_ROUNDING_PRECISION, $orderLines); + } catch (\Exception $e) { + throw CouldNotProcessCartLinesException::failedToAddWrappingLine($e); } - return $orderLines; - } - - /** - * @param PaymentFeeData $paymentFeeData - * @param int $apiRoundingPrecision - * - * @return array - */ - private function addPaymentFeeLine($paymentFeeData, $apiRoundingPrecision, array $orderLines) - { - if (!$paymentFeeData->isActive()) { - return $orderLines; + try { + $orderLines = $this->cartItemPaymentFeeService->addPaymentFeeLine($paymentFeeData, $orderLines); + } catch (\Exception $e) { + throw CouldNotProcessCartLinesException::failedToAddPaymentFee($e); } - $orderLines['surcharge'] = [ - [ - 'name' => $this->languageService->lang('Payment fee'), - 'sku' => Config::PAYMENT_FEE_SKU, - 'quantity' => 1, - 'unitPrice' => round($paymentFeeData->getPaymentFeeTaxIncl(), $apiRoundingPrecision), - 'totalAmount' => round($paymentFeeData->getPaymentFeeTaxIncl(), $apiRoundingPrecision), - 'vatAmount' => NumberUtility::minus($paymentFeeData->getPaymentFeeTaxIncl(), $paymentFeeData->getPaymentFeeTaxExcl()), - 'vatRate' => $paymentFeeData->getTaxRate(), - ], - ]; - - return $orderLines; - } - - /** - * @return array - */ - private function ungroupLines(array $orderLines) - { - $newItems = []; - foreach ($orderLines as &$items) { - foreach ($items as &$item) { - $newItems[] = $item; - } + try { + $newItems = $this->arrayUtility->ungroupLines($orderLines); + } catch (\Exception $e) { + throw CouldNotProcessCartLinesException::failedToUngroupLines($e); } - return $newItems; - } - - /** - * @param string $currencyIsoCode - * @param int $apiRoundingPrecision - * - * @return array - */ - private function convertToLineArray(array $newItems, $currencyIsoCode, $apiRoundingPrecision) - { - foreach ($newItems as $index => $item) { - $line = new Line(); - $line->setName($item['name'] ?: $item['sku']); - $line->setQuantity((int) $item['quantity']); - $line->setSku(isset($item['sku']) ? $item['sku'] : ''); - - $currency = strtoupper(strtolower($currencyIsoCode)); - - if (isset($item['discount'])) { - $line->setDiscountAmount(new Amount( - $currency, - TextFormatUtility::formatNumber($item['discount'], $apiRoundingPrecision, '.', '') - ) - ); - } - - $line->setUnitPrice(new Amount( - $currency, - TextFormatUtility::formatNumber($item['unitPrice'], $apiRoundingPrecision, '.', '') - )); - - $line->setTotalPrice(new Amount( - $currency, - TextFormatUtility::formatNumber($item['totalAmount'], $apiRoundingPrecision, '.', '') - )); - - $line->setVatAmount(new Amount( - $currency, - TextFormatUtility::formatNumber($item['vatAmount'], $apiRoundingPrecision, '.', '') - )); - - if (isset($item['category'])) { - $line->setCategory($item['category']); - } - - $line->setVatRate(TextFormatUtility::formatNumber($item['vatRate'], $apiRoundingPrecision, '.', '')); - - if (isset($item['product_url']) && $item['product_url']) { - $line->setProductUrl( - TextFormatUtility::replaceAccentedChars((string) $item['product_url']) - ); - } - - if (isset($item['image_url']) && $item['image_url']) { - $line->setImageUrl($item['image_url']); - } - - $newItems[$index] = $line; + try { + $lines = $this->lineUtility->convertToLineArray($newItems, $currencyIsoCode); + } catch (\Exception $e) { + throw CouldNotProcessCartLinesException::failedConvertToLineArray($e); } - return $newItems; + return $lines; } } diff --git a/src/Service/PaymentMethodService.php b/src/Service/PaymentMethodService.php index 9a9b2b483..1ffdc22d6 100644 --- a/src/Service/PaymentMethodService.php +++ b/src/Service/PaymentMethodService.php @@ -37,6 +37,7 @@ use Mollie\DTO\PaymentData; use Mollie\Exception\OrderCreationException; use Mollie\Factory\ModuleFactory; +use Mollie\Logger\LoggerInterface; use Mollie\Provider\CreditCardLogoProvider; use Mollie\Provider\OrderTotal\OrderTotalProviderInterface; use Mollie\Provider\PaymentFeeProviderInterface; @@ -49,6 +50,7 @@ use Mollie\Subscription\Validator\SubscriptionOrderValidator; use Mollie\Utility\CustomLogoUtility; use Mollie\Utility\EnvironmentUtility; +use Mollie\Utility\ExceptionUtility; use Mollie\Utility\HashUtility; use Mollie\Utility\LocaleUtility; use Mollie\Utility\SecureKeyUtility; @@ -64,6 +66,11 @@ class PaymentMethodService { + const FILE_NAME = 'PaymentMethodService'; + + /** @var LoggerInterface */ + private $logger; + /** @var Mollie */ private $module; @@ -132,7 +139,8 @@ public function __construct( PaymentFeeProviderInterface $paymentFeeProvider, Context $context, OrderTotalProviderInterface $orderTotalProvider, - PaymentMethodLangRepositoryInterface $paymentMethodLangRepository + PaymentMethodLangRepositoryInterface $paymentMethodLangRepository, + LoggerInterface $logger ) { $this->module = $module->getModule(); $this->methodRepository = $methodRepository; @@ -151,6 +159,7 @@ public function __construct( $this->context = $context; $this->orderTotalProvider = $orderTotalProvider; $this->paymentMethodLangRepository = $paymentMethodLangRepository; + $this->logger = $logger; } public function savePaymentMethod($method) @@ -435,8 +444,9 @@ public function getPaymentData( $currency = new Currency($cart->id_currency); $selectedVoucherCategory = Configuration::get(Config::MOLLIE_VOUCHER_CATEGORY); - $orderData->setLines( - $this->cartLinesService->getCartLines( + + try { + $cartLines = $this->cartLinesService->getCartLines( $amount, $paymentFeeData, $currency->iso_code, @@ -445,7 +455,24 @@ public function getPaymentData( $cart->getProducts(), (bool) Configuration::get('PS_GIFT_WRAPPING'), $selectedVoucherCategory - )); + ); + } catch (\Exception $e) { + $this->logger->error(sprintf('%s - Unable to get cart lines', self::FILE_NAME), [ + 'exceptions' => ExceptionUtility::getExceptions($e), + ]); + + return $orderData; + } + + try { + $orderData->setLines($cartLines); + } catch (\Exception $e) { + $this->logger->error(sprintf('%s - Unable to set order lines', self::FILE_NAME), [ + 'exceptions' => ExceptionUtility::getExceptions($e), + ]); + + return $orderData; + } $payment = new Payment(); diff --git a/src/Utility/ArrayUtility.php b/src/Utility/ArrayUtility.php index e769dd2ba..f96b48308 100644 --- a/src/Utility/ArrayUtility.php +++ b/src/Utility/ArrayUtility.php @@ -22,4 +22,21 @@ public static function getLastElement($array) { return end($array); } + + /** + * @param array $lines + * + * @return array + */ + public function ungroupLines(array $lines): array + { + $newItems = []; + foreach ($lines as &$items) { + foreach ($items as &$item) { + $newItems[] = $item; + } + } + + return $newItems; + } } diff --git a/src/Utility/LineUtility.php b/src/Utility/LineUtility.php new file mode 100644 index 000000000..352a790f6 --- /dev/null +++ b/src/Utility/LineUtility.php @@ -0,0 +1,88 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace mollie\src\Utility; + +use Mollie\Config\Config; +use Mollie\DTO\Line; +use Mollie\DTO\Object\Amount; +use Mollie\Utility\TextFormatUtility; +use PrestaShop\PrestaShop\Core\Util\String\StringModifier; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class LineUtility +{ + /** + * @param string $currencyIsoCode + * @param string $currencyIsoCode + * + * @return array + */ + public function convertToLineArray(array $newItems, $currencyIsoCode): array + { + $roundingPrecision = CONFIG::API_ROUNDING_PRECISION; + foreach ($newItems as $index => $item) { + $line = new Line(); + $line->setName($item['name'] ?: $item['sku']); + $line->setQuantity((int) $item['quantity']); + $line->setSku(isset($item['sku']) ? $item['sku'] : ''); + + $currency = strtoupper(strtolower($currencyIsoCode)); + + if (isset($item['discount'])) { + $line->setDiscountAmount(new Amount( + $currency, + TextFormatUtility::formatNumber($item['discount'], $roundingPrecision, '.', '') + ) + ); + } + + $line->setUnitPrice(new Amount( + $currency, + TextFormatUtility::formatNumber($item['unitPrice'], $roundingPrecision, '.', '') + )); + + $line->setTotalPrice(new Amount( + $currency, + TextFormatUtility::formatNumber($item['totalAmount'], $roundingPrecision, '.', '') + )); + + $line->setVatAmount(new Amount( + $currency, + TextFormatUtility::formatNumber($item['vatAmount'], $roundingPrecision, '.', '') + )); + + if (isset($item['category'])) { + $line->setCategory($item['category']); + } + + $line->setVatRate(TextFormatUtility::formatNumber($item['vatRate'], $roundingPrecision, '.', '')); + + if ($item['product_url']) { + $line->setProductUrl( + TextFormatUtility::replaceAccentedChars($item['product_url']) ?? null + ); + } + + if ($item['image_url']) { + $line->setImageUrl($item['image_url'] ?? null); + } + + $newItems[$index] = $line; + } + + return $newItems; + } +} diff --git a/src/Utility/RoundingUtility.php b/src/Utility/RoundingUtility.php new file mode 100644 index 000000000..0aa8b10ec --- /dev/null +++ b/src/Utility/RoundingUtility.php @@ -0,0 +1,73 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace mollie\src\Utility; + +use Mollie\Config\Config; +use Mollie\Service\CartLine\CartItemsService; + +if (!defined('_PS_VERSION_')) { + exit; +} + +class RoundingUtility +{ + /** + * @var CartItemsService + */ + private $cartItemsService; + + public function __construct(CartItemsService $cartItemsService) + { + $this->cartItemsService = $cartItemsService; + } + + /** + * @param float $remaining + * @param array $orderLines + * + * @return array + */ + public function compositeRoundingInaccuracies($remaining, $orderLines): array + { + $remaining = round($remaining, CONFIG::API_ROUNDING_PRECISION); + if ($remaining < 0) { + foreach (array_reverse($orderLines) as $hash => $items) { + // Grab the line group's total amount + $totalAmount = array_sum(array_column($items, 'totalAmount')); + + // Remove when total is lower than remaining + if ($totalAmount <= $remaining) { + // The line total is less than remaining, we should remove this line group and continue + $remaining = $remaining - $totalAmount; + unset($items); + continue; + } + + // Otherwise spread the cart line again with the updated total + //TODO: check why remaining comes -100 when testing and new total becomes different + $orderLines[$hash] = $this->cartItemsService->spreadCartLineGroup($items, $totalAmount + $remaining); + break; + } + } elseif ($remaining > 0) { + foreach (array_reverse($orderLines) as $hash => $items) { + // Grab the line group's total amount + $totalAmount = array_sum(array_column($items, 'totalAmount')); + // Otherwise spread the cart line again with the updated total + $orderLines[$hash] = $this->cartItemsService->spreadCartLineGroup($items, $totalAmount + $remaining); + break; + } + } + + return $orderLines; + } +} diff --git a/tests/Unit/BaseTestCase.php b/tests/Unit/BaseTestCase.php index 92ed86ba8..bb541c1a6 100644 --- a/tests/Unit/BaseTestCase.php +++ b/tests/Unit/BaseTestCase.php @@ -14,6 +14,7 @@ use Mollie\Adapter\ConfigurationAdapter; use Mollie\Adapter\Context; +use Mollie\Service\LanguageService; use PHPUnit\Framework\TestCase; class BaseTestCase extends TestCase @@ -29,11 +30,15 @@ class BaseTestCase extends TestCase /** @var Context */ public $context; + /** @var LanguageService */ + public $languageService; + protected function setUp(): void { $this->cart = $this->mock(\Cart::class); $this->configuration = $this->mock(ConfigurationAdapter::class); $this->context = $this->mock(Context::class); + $this->languageService = $this->mock(LanguageService::class); parent::setUp(); } diff --git a/tests/Unit/Service/CartLine/CartItemDiscountServiceTest.php b/tests/Unit/Service/CartLine/CartItemDiscountServiceTest.php new file mode 100644 index 000000000..5984b457b --- /dev/null +++ b/tests/Unit/Service/CartLine/CartItemDiscountServiceTest.php @@ -0,0 +1,80 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Unit\Service\CartLine; + +use Mollie\Service\CartLine\CartItemDiscountService; +use Mollie\Tests\Unit\BaseTestCase; + +class CartItemDiscountServiceTest extends BaseTestCase +{ + /** @dataProvider dataProvider */ + public function testItAddsDiscount($totalDiscount, $orderLines, $expected) + { + $cartItemDiscountService = new CartItemDiscountService(); + + $orderLinesResult = $cartItemDiscountService->addDiscountsToProductLines( + 15.00, + $orderLines, + 15 + ); + + $this->assertEquals($orderLinesResult[0]['discount'], $expected); + } + + /** @dataProvider dataProvider */ + public function testItDontAddDiscount($totalDiscount, $orderLines, $expected) + { + $cartItemDiscountService = new CartItemDiscountService(); + + $orderLinesResult = $cartItemDiscountService->addDiscountsToProductLines( + 0.0, + $orderLines, + 0 + ); + + $this->assertEquals($orderLinesResult[0], $orderLines); + } + + public function dataProvider() + { + return [ + 'case1' => [ + 15.0, + 'orderLines' => [ + 'orderLines' => [ + 'name' => 'product name', + 'sku' => '123465789', + 'targetVat' => 21.0, + 'quantity' => 1, + 'unitPrice' => 10.0, + 'totalAmount' => 12.1, + 'category' => 'products', + 'product_url' => 'https://www.example.com/product.png', + 'image_url' => 'https://www.example.com/product.png', + ], + ], + 'expected' => [ + [ + 'name' => 'Discount', + 'type' => 'discount', + 'quantity' => 1, + 'unitPrice' => -15.0, + 'totalAmount' => -15.0, + 'targetVat' => 0, + 'category' => '', + ], + ], + ], + ]; + } +} diff --git a/tests/Unit/Service/CartLine/CartItemPaymentFeeServiceTest.php b/tests/Unit/Service/CartLine/CartItemPaymentFeeServiceTest.php new file mode 100644 index 000000000..3f11a1ab5 --- /dev/null +++ b/tests/Unit/Service/CartLine/CartItemPaymentFeeServiceTest.php @@ -0,0 +1,75 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Unit\Service\CartLine; + +use Mollie\DTO\PaymentFeeData; +use mollie\src\Service\CartLine\CartItemPaymentFeeService; +use Mollie\Tests\Unit\BaseTestCase; + +class CartItemPaymentFeeServiceTest extends BaseTestCase +{ + /** @dataProvider dataProvider */ + public function testItAddsPaymentFee($orderLines) + { + $paymentFeeDTO = new PaymentFeeData( + 10, + 10, + 0, + true + ); + + $cartItemPaymentFeeService = new CartItemPaymentFeeService($this->languageService); + + $result = $cartItemPaymentFeeService->addPaymentFeeLine($paymentFeeDTO, $orderLines); + + $this->assertNotEmpty($result['surcharge']); + } + + /** @dataProvider dataProvider */ + public function testItDontAddPaymentFee($orderLines) + { + $paymentFeeDTO = new PaymentFeeData( + 10, + 10, + 0, + false + ); + + $cartItemPaymentFeeService = new CartItemPaymentFeeService($this->languageService); + + $result = $cartItemPaymentFeeService->addPaymentFeeLine($paymentFeeDTO, $orderLines); + + $this->assertSame($result, $orderLines); + } + + public function dataProvider() + { + return [ + 'case1' => [ + 'orderLines' => [ + 'orderLines' => [ + 'name' => 'product name', + 'sku' => '123465789', + 'targetVat' => 21.0, + 'quantity' => 1, + 'unitPrice' => 10.0, + 'totalAmount' => 12.1, + 'category' => 'products', + 'product_url' => 'https://www.example.com/product.png', + 'image_url' => 'https://www.example.com/product.png', + ], + ], + ], + ]; + } +} diff --git a/tests/Unit/Service/CartLine/CartItemShippingLineServiceTest.php b/tests/Unit/Service/CartLine/CartItemShippingLineServiceTest.php new file mode 100644 index 000000000..c2ecc971b --- /dev/null +++ b/tests/Unit/Service/CartLine/CartItemShippingLineServiceTest.php @@ -0,0 +1,97 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Service\CartLine; + +use Mollie\Tests\Unit\BaseTestCase; + +class CartItemShippingLineServiceTest extends BaseTestCase +{ + /** @dataProvider dataProvider */ + public function testItAddsDiscount($totalDiscount, $orderLines, $expected) + { + $cartItemShippingLineService = new CartItemShippingLineService($this->languageService); + + $cart = $this->getMockBuilder(\Cart::class)->setMethods(['getSummaryDetails'])->getMock(); + + $cart->method('getSummaryDetails')->willReturn([ + 'total_shipping' => 10, + 'total_shipping_tax_exc' => 10, + ]); + + $result = $cartItemShippingLineService->addShippingLine(10, $cart->getSummaryDetails(), $orderLines); + + $this->assertEquals($expected, $result); + } + + /** @dataProvider dataProvider */ + public function testItDontAddDiscount($totalDiscount, $orderLines) + { + $cartItemShippingLineService = new CartItemShippingLineService($this->languageService); + + $cart = $this->getMockBuilder(\Cart::class)->setMethods(['getSummaryDetails'])->getMock(); + + $cart->method('getSummaryDetails')->willReturn([ + 'total_shipping' => 0, + 'total_shipping_tax_exc' => 0, + ]); + + $result = $cartItemShippingLineService->addShippingLine(0, $cart->getSummaryDetails(), $orderLines); + + $this->assertEquals($orderLines, $result); + } + + public function dataProvider() + { + return [ + 'case1' => [ + 'totalDiscount' => 10, + 'orderLines' => [ + 'orderLines' => [ + [ + 'type' => 'product', + 'name' => 'product_1', + 'quantity' => 1, + 'unitPrice' => 10, + 'totalAmount' => 10, + 'vatRate' => 0, + 'vatAmount' => 0, + ], + ], + ], + 'expected' => [ + 'orderLines' => [ + [ + 'type' => 'product', + 'name' => 'product_1', + 'quantity' => 1, + 'unitPrice' => 10, + 'totalAmount' => 10, + 'vatRate' => 0, + 'vatAmount' => 0, + ], + ], + 'shipping' => [ + 0 => [ + 'name' => null, + 'quantity' => 1, + 'unitPrice' => 10.0, + 'totalAmount' => 10.0, + 'vatRate' => 0.0, + 'vatAmount' => 0.0, + ], + ], + ], + ], + ]; + } +} diff --git a/tests/Unit/Service/CartLine/CartItemWrappingServiceTest.php b/tests/Unit/Service/CartLine/CartItemWrappingServiceTest.php new file mode 100644 index 000000000..5d60f51bc --- /dev/null +++ b/tests/Unit/Service/CartLine/CartItemWrappingServiceTest.php @@ -0,0 +1,97 @@ + + * @copyright Mollie B.V. + * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md + * + * @see https://github.com/mollie/PrestaShop + * @codingStandardsIgnoreStart + */ + +namespace Mollie\Service\CartLine; + +use Mollie\Tests\Unit\BaseTestCase; + +class CartItemWrappingServiceTest extends BaseTestCase +{ + /** @dataProvider dataProvider */ + public function testItAddsWrapping($totalDiscount, $orderLines, $expected) + { + $cartItemShippingLineService = new CartItemWrappingService($this->languageService); + + $cart = $this->getMockBuilder(\Cart::class)->setMethods(['getSummaryDetails'])->getMock(); + + $cart->method('getSummaryDetails')->willReturn([ + 'total_wrapping' => 10, + 'total_wrapping_tax_exc' => 10, + ]); + + $result = $cartItemShippingLineService->addWrappingLine(10, $cart->getSummaryDetails(), 2, $orderLines); + + $this->assertEquals($expected, $result); + } + + /** @dataProvider dataProvider */ + public function testItDontAddWrapping($totalDiscount, $orderLines) + { + $cartItemShippingLineService = new CartItemWrappingService($this->languageService); + + $cart = $this->getMockBuilder(\Cart::class)->setMethods(['getSummaryDetails'])->getMock(); + + $cart->method('getSummaryDetails')->willReturn([ + 'total_wrapping' => 0, + 'total_wrapping_tax_exc' => 0, + ]); + + $result = $cartItemShippingLineService->addWrappingLine(0, $cart->getSummaryDetails(), 2, $orderLines); + + $this->assertEquals($orderLines, $result); + } + + public function dataProvider() + { + return [ + 'case1' => [ + 'totalDiscount' => 10, + 'orderLines' => [ + 'orderLines' => [ + [ + 'type' => 'product', + 'name' => 'product_1', + 'quantity' => 1, + 'unitPrice' => 10, + 'totalAmount' => 10, + 'vatRate' => 0, + 'vatAmount' => 0, + ], + ], + ], + 'expected' => [ + 'orderLines' => [ + [ + 'type' => 'product', + 'name' => 'product_1', + 'quantity' => 1, + 'unitPrice' => 10, + 'totalAmount' => 10, + 'vatRate' => 0, + 'vatAmount' => 0, + ], + ], + 'wrapping' => [ + 0 => [ + 'name' => null, + 'quantity' => 1, + 'unitPrice' => 10.0, + 'totalAmount' => 10.0, + 'vatRate' => 0.0, + 'vatAmount' => 0.0, + ], + ], + ], + ], + ]; + } +} diff --git a/tests/Unit/Service/CartLinesServiceTest.php b/tests/Unit/Service/CartLinesServiceTest.php deleted file mode 100644 index 3816e97f7..000000000 --- a/tests/Unit/Service/CartLinesServiceTest.php +++ /dev/null @@ -1,469 +0,0 @@ - - * @copyright Mollie B.V. - * @license https://github.com/mollie/PrestaShop/blob/master/LICENSE.md - * - * @see https://github.com/mollie/PrestaShop - * @codingStandardsIgnoreStart - */ - -namespace Mollie\Tests\Unit\Service; - -use Mollie\Adapter\ConfigurationAdapter; -use Mollie\Adapter\Context; -use Mollie\Adapter\ToolsAdapter; -use Mollie\DTO\Line; -use Mollie\DTO\Object\Amount; -use Mollie\DTO\PaymentFeeData; -use Mollie\Service\CartLinesService; -use Mollie\Service\LanguageService; -use Mollie\Service\VoucherService; -use PHPUnit\Framework\TestCase; - -class CartLinesServiceTest extends TestCase -{ - /** - * @dataProvider cartLinesProvider - * - * @param $amount - * @param $paymentFee - * @param $currencyIsoCode - * @param $cartSummary - * @param $shippingCost - * @param $cartItems - * @param $psGiftWrapping - * @param $selectedVoucherCategory - * @param $translationMocks - * @param $toolsMocks - * @param $mocks - * @param $result - */ - public function testGetCartLines( - $amount, - $paymentFee, - $currencyIsoCode, - $cartSummary, - $shippingCost, - $cartItems, - $psGiftWrapping, - $selectedVoucherCategory, - $translationMocks, - $toolsMocks, - $mocks, - $result - ) { - $configurationAdapter = $this->createMock(ConfigurationAdapter::class); - - foreach ($mocks as $mock) { - $configurationAdapter->expects(self::at($mock['at']))->method($mock['function'])->with($mock['expects'])->willReturn($mock['return']); - } - - $languageService = $this->getMockBuilder(LanguageService::class)->disableOriginalConstructor()->getMock(); - - foreach ($translationMocks as $mock) { - $languageService->expects(self::at($mock['at']))->method($mock['function'])->with($mock['expects'])->willReturn($mock['return']); - } - - $toolsAdapter = $this->getMockBuilder(ToolsAdapter::class)->getMock(); - - foreach ($toolsMocks as $mock) { - $toolsAdapter->method($mock['function'])->with($mock['expects'])->willReturn($mock['return']); - } - - $voucherService = $this->getMockBuilder(VoucherService::class)->disableOriginalConstructor()->getMock(); - $context = $this->getMockBuilder(Context::class)->getMock(); - - $cartLineService = new CartLinesService($languageService, $voucherService, $toolsAdapter, $context); - - $cartLines = $cartLineService->getCartLines( - $amount, - $paymentFee, - $currencyIsoCode, - $cartSummary, - $shippingCost, - $cartItems, - $psGiftWrapping, - $selectedVoucherCategory - ); - - self::assertEquals($result, $cartLines); - } - - public function cartLinesProvider() - { - $productName_1 = 'Hummingbird printed sweater'; - $productName_2 = 'Hummingbird printed t-shirt'; - $productName_3 = 'The best is yet to come\' Framed poster'; - $shipping = 'Shipping'; - $giftWrapping = 'Gift wrapping'; - $currencyIsoCode = 'EUR'; - - return [ - 'two products with a gift which is the same as one product' => [ - 'amount' => 204.84, - 'paymentFee' => new PaymentFeeData(0.00, 0.00, 0.00, false), - 'currencyIsoCode' => $currencyIsoCode, - 'cartSummary' => [ - 'gift_products' => [ - 0 => [ - 'id_product' => '1', - 'cart_quantity' => 1, - 'price_with_reduction' => 100, - ], - ], - 'discounts' => [ - ], - 'total_wrapping' => 0, - 'total_wrapping_tax_exc' => 0, - 'total_shipping' => 4.84, - 'total_shipping_tax_exc' => 4, - 'total_products_wt' => 100, - 'total_products' => 82.64, - 'total_price' => 104.84, - 'free_ship' => false, - ], - 4.84, - 'cartItems' => [ - 0 => [ - 'total_wt' => 100, - 'cart_quantity' => '1', - 'price_wt' => 100, - 'id_product' => '2', - 'name' => $productName_1, - 'rate' => 21, - 'id_product_attribute' => '9', - 'id_customization' => null, - 'features' => [], - 'link_rewrite' => 'test-link', - 'id_image' => 'test-image-id', - ], - 1 => [ - 'total_wt' => 100, - 'cart_quantity' => '2', - 'price_wt' => 100, - 'id_product' => '1', - 'name' => $productName_2, - 'rate' => 21, - 'id_product_attribute' => '9', - 'id_customization' => null, - 'features' => [], - 'link_rewrite' => 'test-link', - 'id_image' => 'test-image-id', - ], - ], - 'psGiftWrapping' => '1', - 'selectedVoucherCategory' => 'null', - 'translationMocks' => [ - 0 => [ - 'function' => 'lang', - 'expects' => $shipping, - 'return' => $shipping, - 'at' => 0, - ], - ], - 'toolsMocks' => [ - ], - 'mocks' => [], - 'result' => [ - 0 => (new Line()) - ->setName($productName_1) - ->setQuantity(1) - ->setSku('2¤9¤0') - ->setDiscountAmount(null) - ->setUnitPrice(new Amount($currencyIsoCode, '100.00')) - ->setTotalPrice(new Amount($currencyIsoCode, '100.00')) - ->setVatAmount(new Amount($currencyIsoCode, '17.36')) - ->setCategory('') - ->setVatRate('21.00'), - 1 => (new Line()) - ->setName($productName_2) - ->setQuantity(1) - ->setSku('1¤9¤0gift') - ->setDiscountAmount(null) - ->setUnitPrice(new Amount($currencyIsoCode, '0.00')) - ->setTotalPrice(new Amount($currencyIsoCode, '0.00')) - ->setVatAmount(new Amount($currencyIsoCode, '0.00')) - ->setCategory('') - ->setVatRate('0.00'), - 2 => (new Line()) - ->setName($productName_2) - ->setQuantity(1) - ->setSku('1¤9¤0') - ->setDiscountAmount(null) - ->setUnitPrice(new Amount($currencyIsoCode, '100.00')) - ->setTotalPrice(new Amount($currencyIsoCode, '100.00')) - ->setVatAmount(new Amount($currencyIsoCode, '17.36')) - ->setCategory('') - ->setVatRate('21.00'), - 3 => (new Line()) - ->setName($shipping) - ->setQuantity(1) - ->setSku('') - ->setDiscountAmount(null) - ->setUnitPrice(new Amount($currencyIsoCode, '4.84')) - ->setTotalPrice(new Amount($currencyIsoCode, '4.84')) - ->setVatAmount(new Amount($currencyIsoCode, '0.84')) - ->setCategory(null) - ->setVatRate('21.00'), - ], - ], - 'one products with a gift' => [ - 'amount' => 104.84, - 'paymentFee' => new PaymentFeeData(0.00, 0.00, 0.00, false), - 'currencyIsoCode' => $currencyIsoCode, - 'cartSummary' => [ - 'gift_products' => [ - 0 => [ - 'id_product' => '2', - 'cart_quantity' => 1, - 'price_with_reduction' => 100, - ], - ], - 'discounts' => [ - ], - 'total_wrapping' => 0, - 'total_wrapping_tax_exc' => 0, - 'total_shipping' => 4.84, - 'total_shipping_tax_exc' => 4, - 'total_products_wt' => 100, - 'total_products' => 82.64, - 'total_price' => 104.84, - 'free_ship' => false, - ], - 4.84, - 'cartItems' => [ - 0 => [ - 'total_wt' => 100, - 'cart_quantity' => '1', - 'price_wt' => 100, - 'id_product' => '1', - 'name' => $productName_1, - 'rate' => 21, - 'id_product_attribute' => '9', - 'id_customization' => null, - 'features' => [], - 'link_rewrite' => 'test-link', - 'id_image' => 'test-image-id', - ], - 1 => [ - 'total_wt' => 100, - 'cart_quantity' => '1', - 'price_wt' => 100, - 'id_product' => '2', - 'name' => $productName_2, - 'rate' => 21, - 'id_product_attribute' => '9', - 'id_customization' => null, - 'features' => [], - 'link_rewrite' => 'test-link', - 'id_image' => 'test-image-id', - ], - ], - 'psGiftWrapping' => '1', - 'selectedVoucherCategory' => 'null', - 'translationMocks' => [ - 0 => [ - 'function' => 'lang', - 'expects' => $shipping, - 'return' => $shipping, - 'at' => 0, - ], - ], - 'toolsMocks' => [ - ], - 'mocks' => [], - 'result' => [ - 0 => (new Line()) - ->setName($productName_1) - ->setQuantity(1) - ->setSku('1¤9¤0') - ->setDiscountAmount(null) - ->setUnitPrice(new Amount($currencyIsoCode, '100.00')) - ->setTotalPrice(new Amount($currencyIsoCode, '100.00')) - ->setVatAmount(new Amount($currencyIsoCode, '17.36')) - ->setCategory('') - ->setVatRate('21.00'), - 1 => (new Line()) - ->setName($productName_2) - ->setQuantity(1) - ->setSku('2¤9¤0gift') - ->setDiscountAmount(null) - ->setUnitPrice(new Amount($currencyIsoCode, '0.00')) - ->setTotalPrice(new Amount($currencyIsoCode, '0.00')) - ->setVatAmount(new Amount($currencyIsoCode, '0.00')) - ->setCategory('') - ->setVatRate('0.00'), - 2 => (new Line()) - ->setName($shipping) - ->setQuantity(1) - ->setSku('') - ->setDiscountAmount(null) - ->setUnitPrice(new Amount($currencyIsoCode, '4.84')) - ->setTotalPrice(new Amount($currencyIsoCode, '4.84')) - ->setVatAmount(new Amount($currencyIsoCode, '0.84')) - ->setCategory(null) - ->setVatRate('21.00'), - ], - ], - 'product without name' => [ - 'amount' => 104.84, - 'paymentFee' => new PaymentFeeData(0.00, 0.00, 0.00, false), - 'currencyIsoCode' => $currencyIsoCode, - 'cartSummary' => [ - 'gift_products' => [ - 0 => [ - 'id_product' => '1', - 'cart_quantity' => 1, - 'price_with_reduction' => 100, - ], - ], - 'discounts' => [ - ], - 'total_wrapping' => 0, - 'total_wrapping_tax_exc' => 0, - 'total_shipping' => 4.84, - 'total_shipping_tax_exc' => 4, - 'total_products_wt' => 100, - 'total_products' => 82.64, - 'total_price' => 104.84, - 'free_ship' => false, - ], - 4.84, - 'cartItems' => [ - 0 => [ - 'total_wt' => 100, - 'cart_quantity' => '1', - 'price_wt' => 100, - 'id_product' => '2', - 'name' => '', - 'rate' => 21, - 'id_product_attribute' => '9', - 'id_customization' => null, - 'features' => [], - 'link_rewrite' => 'test-link', - 'id_image' => 'test-image-id', - ], - ], - 'psGiftWrapping' => '1', - 'selectedVoucherCategory' => 'null', - 'translationMocks' => [ - 0 => [ - 'function' => 'lang', - 'expects' => $shipping, - 'return' => $shipping, - 'at' => 0, - ], - ], - 'toolsMocks' => [ - ], - 'mocks' => [], - 'result' => [ - 0 => (new Line()) - ->setName('2¤9¤0') - ->setQuantity(1) - ->setSku('2¤9¤0') - ->setDiscountAmount(null) - ->setUnitPrice(new Amount($currencyIsoCode, '100.00')) - ->setTotalPrice(new Amount($currencyIsoCode, '100.00')) - ->setVatAmount(new Amount($currencyIsoCode, '17.36')) - ->setCategory('') - ->setVatRate('21.00'), - 1 => (new Line()) - ->setName($shipping) - ->setQuantity(1) - ->setSku('') - ->setDiscountAmount(null) - ->setUnitPrice(new Amount($currencyIsoCode, '4.84')) - ->setTotalPrice(new Amount($currencyIsoCode, '4.84')) - ->setVatAmount(new Amount($currencyIsoCode, '0.84')) - ->setCategory(null) - ->setVatRate('21.00'), - ], - ], - 'Cart with discount' => [ - 'amount' => 98.79, - 'paymentFee' => new PaymentFeeData(0.00, 0.00, 0.00, false), - 'currencyIsoCode' => $currencyIsoCode, - 'cartSummary' => [ - 'gift_products' => [ - ], - 'discounts' => [ - ], - 'total_wrapping' => 0, - 'total_wrapping_tax_exc' => 0, - 'total_shipping' => 4.84, - 'total_shipping_tax_exc' => 4, - 'total_products_wt' => 100, - 'total_products' => 82.64, - 'total_price' => 98.79, - 'free_ship' => false, - 'total_discounts' => 6.05, - ], - 4.84, - 'cartItems' => [ - 0 => [ - 'total_wt' => 100, - 'cart_quantity' => '1', - 'price_wt' => 100, - 'id_product' => '2', - 'name' => $productName_1, - 'rate' => 21, - 'id_product_attribute' => '9', - 'id_customization' => null, - 'features' => [], - 'link_rewrite' => 'test-link', - 'id_image' => 'test-image-id', - ], - ], - 'psGiftWrapping' => '1', - 'selectedVoucherCategory' => 'null', - 'translationMocks' => [ - 0 => [ - 'function' => 'lang', - 'expects' => $shipping, - 'return' => $shipping, - 'at' => 0, - ], - ], - 'toolsMocks' => [ - ], - 'mocks' => [], - 'result' => [ - 0 => (new Line()) - ->setName($productName_1) - ->setQuantity(1) - ->setSku('2¤9¤0') - ->setDiscountAmount(null) - ->setUnitPrice(new Amount($currencyIsoCode, '100.00')) - ->setTotalPrice(new Amount($currencyIsoCode, '100.00')) - ->setVatAmount(new Amount($currencyIsoCode, '17.36')) - ->setCategory(null) - ->setVatRate('21.00'), - 1 => (new Line()) - ->setName('Discount') - ->setQuantity(1) - ->setSku('') - ->setDiscountAmount(null) - ->setUnitPrice(new Amount($currencyIsoCode, '-6.05')) - ->setTotalPrice(new Amount($currencyIsoCode, '-6.05')) - ->setVatAmount(new Amount($currencyIsoCode, '0.00')) - ->setCategory(null) - ->setVatRate('0.00'), - 2 => (new Line()) - ->setName($shipping) - ->setQuantity(1) - ->setSku('') - ->setDiscountAmount(null) - ->setUnitPrice(new Amount($currencyIsoCode, '4.84')) - ->setTotalPrice(new Amount($currencyIsoCode, '4.84')) - ->setVatAmount(new Amount($currencyIsoCode, '0.84')) - ->setCategory(null) - ->setVatRate('21.00'), - ], - ], - ]; - } -}