123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- <?php
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- declare(strict_types=1);
- namespace Magento\Paypal\Model;
- /**
- * PayPal-specific model for shopping cart items and totals
- * The main idea is to accommodate all possible totals into PayPal-compatible 4 totals and line items
- */
- class Cart extends \Magento\Payment\Model\Cart
- {
- /**
- * @var bool
- */
- protected $_areAmountsValid = false;
- /**
- * Get shipping, tax, subtotal and discount amounts all together
- *
- * @return array
- */
- public function getAmounts()
- {
- $this->_collectItemsAndAmounts();
- if (!$this->_areAmountsValid) {
- $subtotal = $this->getSubtotal() + $this->getTax();
- if (empty($this->_transferFlags[self::AMOUNT_SHIPPING])) {
- $subtotal += $this->getShipping();
- }
- if (empty($this->_transferFlags[self::AMOUNT_DISCOUNT])) {
- $subtotal -= $this->getDiscount();
- }
- return [self::AMOUNT_SUBTOTAL => $subtotal];
- }
- return $this->_amounts;
- }
- /**
- * Calculate subtotal from custom items
- *
- * @return void
- */
- protected function _calculateCustomItemsSubtotal()
- {
- parent::_calculateCustomItemsSubtotal();
- $this->_applyDiscountTaxCompensationWorkaround($this->_salesModel);
- $this->_validate();
- }
- /**
- * Check the line items and totals according to PayPal business logic limitations
- *
- * @return void
- */
- protected function _validate()
- {
- $areItemsValid = false;
- $this->_areAmountsValid = false;
- $referenceAmount = $this->_salesModel->getDataUsingMethod('base_grand_total');
- $itemsSubtotal = 0;
- foreach ($this->getAllItems() as $i) {
- $itemsSubtotal = $itemsSubtotal + $i->getQty() * $i->getAmount();
- }
- $sum = $itemsSubtotal + $this->getTax();
- if (empty($this->_transferFlags[self::AMOUNT_SHIPPING])) {
- $sum += $this->getShipping();
- }
- if (empty($this->_transferFlags[self::AMOUNT_DISCOUNT])) {
- $sum -= $this->getDiscount();
- // PayPal requires to have discount less than items subtotal
- $this->_areAmountsValid = round($this->getDiscount(), 4) < round($itemsSubtotal, 4);
- } else {
- $this->_areAmountsValid = $itemsSubtotal > 0.00001;
- }
- /**
- * numbers are intentionally converted to strings because of possible comparison error
- * see http://php.net/float
- */
- // match sum of all the items and totals to the reference amount
- if (sprintf('%.4F', $sum) == sprintf('%.4F', $referenceAmount)) {
- $areItemsValid = true;
- }
- $areItemsValid = $areItemsValid && $this->_areAmountsValid;
- if (!$areItemsValid) {
- $this->_salesModelItems = [];
- $this->_customItems = [];
- }
- }
- /**
- * Import items from sales model with workarounds for PayPal
- *
- * @return void
- */
- protected function _importItemsFromSalesModel()
- {
- $this->_salesModelItems = [];
- foreach ($this->_salesModel->getAllItems() as $item) {
- if ($item->getParentItem()) {
- continue;
- }
- $amount = $item->getPrice();
- $qty = $item->getQty();
- $subAggregatedLabel = '';
- // workaround in case if item subtotal precision is not compatible with PayPal (.2)
- if ($amount - round($amount, 2)) {
- $amount = $amount * $qty;
- $subAggregatedLabel = ' x' . $qty;
- $qty = 1;
- }
- // aggregate item price if item qty * price does not match row total
- $itemBaseRowTotal = $item->getOriginalItem()->getBaseRowTotal();
- if ($amount * $qty != $itemBaseRowTotal) {
- $amount = (double)$itemBaseRowTotal;
- $subAggregatedLabel = ' x' . $qty;
- $qty = 1;
- }
- $this->_salesModelItems[] = $this->_createItemFromData(
- $item->getName() . $subAggregatedLabel,
- $qty,
- $amount
- );
- }
- $this->addSubtotal($this->_salesModel->getBaseSubtotal());
- $this->addTax($this->_salesModel->getBaseTaxAmount());
- $this->addShipping($this->_salesModel->getBaseShippingAmount());
- $this->addDiscount(abs($this->_salesModel->getBaseDiscountAmount()));
- }
- /**
- * Add "hidden" discount and shipping tax
- *
- * Go ahead, try to understand ]:->
- *
- * Tax settings for getting "discount tax":
- * - Catalog Prices = Including Tax
- * - Apply Customer Tax = After Discount
- * - Apply Discount on Prices = Including Tax
- *
- * Test case for getting "hidden shipping tax":
- * - Make sure shipping is taxable (set shipping tax class)
- * - Catalog Prices = Including Tax
- * - Shipping Prices = Including Tax
- * - Apply Customer Tax = After Discount
- * - Create a cart price rule with % discount applied to the Shipping Amount
- * - run shopping cart and estimate shipping
- * - go to PayPal
- *
- * @param \Magento\Payment\Model\Cart\SalesModel\SalesModelInterface $salesEntity
- * @return void
- */
- protected function _applyDiscountTaxCompensationWorkaround(
- \Magento\Payment\Model\Cart\SalesModel\SalesModelInterface $salesEntity
- ) {
- $dataContainer = $salesEntity->getTaxContainer();
- $this->addTax((double)$dataContainer->getBaseDiscountTaxCompensationAmount());
- $this->addTax((double)$dataContainer->getBaseShippingDiscountTaxCompensationAmount());
- }
- /**
- * Check whether any item has negative amount
- *
- * @return bool
- */
- public function hasNegativeItemAmount()
- {
- foreach ($this->_customItems as $item) {
- if ($item->getAmount() < 0) {
- return true;
- }
- }
- return false;
- }
- }
|