Zend certified PHP/Magento developer

How to Correct Magento’s Paypal Rounding Error

The Problem

Magento prices can be defined as inclusive of tax (which in the UK is 20% at the moment). If you want to calculate the price of a product, without that tax, sometimes the result is not a nice round number.

For example, if you are selling a product at £9.99, then the exact price without tax is £8.325.

Paypal, unfortunately, won’t allow prices set to three decimal places. This, on the face of it, makes sense – you can’t charge people fractions of pennies, after all.

Here’s where the problem is, though – what price do you send to Paypal? If you round down, and send Paypal a price of £8.32, then when tax is added the customer is charged £9.98. If you round up to £8.33, Paypal adds tax and the customer is charged £10.00. Whatever you send, you can’t get Paypal to charge the customer £9.99 including tax.

Whichever price you send, Paypal adds up the line items, sees that the total doesn’t match the order total (which you send through separately), and rejects the order with a message saying “Transaction refused because of an invalid argument”, “Item total is invalid” or “The totals of the cart item amounts do not match order amounts”.

The Solution

There are a few approaches to this problem which revolve around changing how Magento sends its orders to Paypal. Many people choose to send all prices inclusive of VAT, to avoid the error entirely. Sometimes, though, that’s just not possible.

My preferred solution is to modify the Paypal handler for Magento, tracking the missing pennies and adjusting the first order line item price to compensate for the offset.

The first step is to copy the Abstract.php file from app/code/core/Mage/Paypal/Model/Api/ to app/code/local/Mage/Paypal/Model/Api/. You can make these changes directly to the core file if you really have to, but it’s not recommended. Find the _exportLineItems function (line 390 in my version) – this is what we’re going to change.

Before the foreach ($items as $item) { (around line 412) add this:

$running_total = 0;

Before the $request[sprintf($privateFormat, $i)] = $value; (around line 423) add this:

if ($publicKey == 'amount') {
    $running_total += $value;
}

Before the return $result; (around line 427) add this:

// Check ITEMAMT. If different to running total, offset prices
if ((isset($request['ITEMAMT']))  ($request['ITEMAMT']  0)) {
    if ($running_total  (float) $request['ITEMAMT']) {
        $difference = ($running_total - (float) $request['ITEMAMT']);
        // Apply difference to first product.
        $request['L_AMT0'] = (string) ((float) $request['L_AMT0'] - $difference);
    }
}

And that’s it – Magento will now play nicely with rounding of tax when payments go through Paypal.

<!–

Live Comment Preview

#1, Anonymous, United Kingdom, 1 minute ago. Reply to this.

–>