Magento – Magento 2 – rounding issues with voucher discount applied

magento-2.1magento2roundingtax-rounding

I am using PayPal Plus in my Magento 2.1.9 shop, which rejected payments of some orders of my clients due to rounding issues – OMG. eCommerce software with rounding issues – how great is that?

Anyway…

There are some solutions I will post here for other peoples having the same issue.

If you are using PayPal Plus you can edit the following file for debugging purposes.
In file /vendor/iways/module-pay-pal-plus/Model/Api.php around line 302, right before the try-catch block in the createPayment-method:

$debug = print_r($payment, true);
file_put_contents('paypal-debug.txt', $debug);

This will create a file "paypal-debug.txt" in the root folder of magento everytime your on the last step within your checkout process.

Preconditions I had rounding errors with:
Admin > Configuration > Sales > Tax > Calulation Settings

  • catalog prices, shipping prices, apply discount on prices: including tax
  • apply customer tax: after discount
  • tax calculation method based on: total

settings with rounding errors

I also had issues when I used net prices (set everything to excluding tax and apply discount "before tax").

Test preconditions:

  • Product 199 € incl. 19% tax
  • Voucher 20 € incl. 19% tax
  • Shipping costs
    3.90€ incl. 19% tax

Reproduce:

  • add product with 199€ to cart
  • apply voucher code
  • proceed checkout till payment options showing up and where the PayPal payment methods are rendered

Then lookup the "paypal-debug.txt" (see example below):

PayPal\Api\Payment Object
(
    [_propMap:PayPal\Common\PayPalModel:private] => Array
        (
            [intent] => sale
            [experience_profile_id] => XP-12345-12345-12345
            [payer] => PayPal\Api\Payer Object
                (
                    [_propMap:PayPal\Common\PayPalModel:private] => Array
                        (
                            [payment_method] => paypal
                        )

                )

            [redirect_urls] => PayPal\Api\RedirectUrls Object
                (
                    [_propMap:PayPal\Common\PayPalModel:private] => Array
                        (
                            [return_url] => https://yourshop.com/paypalplus/order/create/
                            [cancel_url] => https://yourshop.com/paypalplus/checkout/cancel/
                        )

                )

            [transactions] => Array
                (
                    [0] => PayPal\Api\Transaction Object
                        (
                            [_propMap:PayPal\Common\PayPalModel:private] => Array
                                (
                                    [amount] => PayPal\Api\Amount Object
                                        (
                                            [_propMap:PayPal\Common\PayPalModel:private] => Array
                                                (
                                                    [currency] => EUR
                                                    [details] => PayPal\Api\Details Object
                                                        (
                                                            [_propMap:PayPal\Common\PayPalModel:private] => Array
                                                                (
                                                                    [shipping] => 3.27
                                                                    [tax] => 29.20
                                                                    [subtotal] => 167.23
                                                                    [shipping_discount] => 16.81
                                                                )

                                                        )

                                                    [total] => 182.90
                                                )

                                        )

                                    [item_list] => PayPal\Api\ItemList Object
                                        (
                                            [_propMap:PayPal\Common\PayPalModel:private] => Array
                                                (
                                                )

                                        )

                                )

                        )

                )

        )

)

Now calculate the total "by hand" based on _propMap:PayPal\Common\PayPalModel:private-property:

  • 3.27 (shipping) + 29.20 (tax) + 167.23 (subtotal, net price of the 199€ article) = 199.70
  • subtract the discount:
    199.70 – 16.81 = 182.89 total

As you can see, the total differs (182.89 vs. 182.90)!

I will post the fix in my answer.

Best Answer

There are two possible fixes:

1st solution (needs more testing though):

  • Admin > Configuration > Sales > Tax > Calulation Settings
  • set Tax Calculation Method Based On to Row Total (worked with my test environment, described in my question)

2nd solution: Create a custom module with a di.xml which contains (vendor in the example is "Koseduhemak", Module "MagentoBugFixes"). I just post the relevant parts:

<preference for="Magento\Directory\Model\PriceCurrency" type="Koseduhemak\MagentoBugFixes\TaxRounding\Model\PriceCurrency" />
    <type name="Magento\Catalog\Pricing\Price\RegularPrice">
        <arguments>
            <argument name="priceCurrency" xsi:type="object">Koseduhemak\MagentoBugFixes\TaxRounding\Model\PriceCurrency</argument>
        </arguments>
    </type>
    <type name="Magento\Tax\Model\Calculation">
        <arguments>
            <argument name="priceCurrency" xsi:type="object">Koseduhemak\MagentoBugFixes\TaxRounding\Model\PriceCurrency</argument>
        </arguments>
    </type>
    <type name="Magento\Quote\Model\Quote\Item">
        <arguments>
            <argument name="priceCurrency" xsi:type="object">Koseduhemak\MagentoBugFixes\TaxRounding\Model\PriceCurrency</argument>
        </arguments>
    </type>

Create a file: Koseduhemak\MagentoBugFixes\TaxRounding\Model\PriceCurrency:

<?php
namespace Koseduhemak\MagentoBugFixes\TaxRounding\Model;

class PriceCurrency extends \Magento\Directory\Model\PriceCurrency
{
    const DEFAULT_PRECISION = 3;
    public function round($price)
    {
        return round($price, self::DEFAULT_PRECISION);
    }
    public function convertAndRound($amount, $scope = null, $currency = null, $precision = self::DEFAULT_PRECISION)
    {
        return parent::convertAndRound($amount, $scope, $currency, $precision);
    }
}

What it does: Rounding to three decimal places. Some people even suggest to round to four decimal places: https://magento.stackexchange.com/a/82444/67243 (If you want to round four decimal places, just change const DEFAULT_PRECISION = 3; to const DEFAULT_PRECISION = 4;.

EDIT: I spoke with the PayPal support and they told me that the PayPal Plus module / PayPal itself rounds to four decimal places. So you should also use four decimal places in your code (fix magento to do that).

However, this works only for modules / stuff that is using \Magento\Directory\Model\PriceCurrency class for rounding. Custom extension have something similar hard coded to two decimal places maybe...

My complete code can be found at Github (along with some other BugFixes especially for Magento 2.1.9): https://github.com/koseduhemak/MagentoBugFixes

Hope I could help someone.

Related Topic