Magento – Paypal payment failure – #10004 Invalid Transaction ID

magento-1.9paypal

I'm getting an error using PayPal payments Pro – but only at a certain quantity of a product. When ordering 55 items of a product the customer sees this error popup:

PayPal gateway has rejected request. The transaction id is not valid (#10004: Transaction refused because of an invalid argument. See additional error messages for details).

I've checked the PayPal log and have found the response to be:

        [TIMESTAMP] => 2016-06-23T10:59:20Z
        [CORRELATIONID] => 241c536268ca3
        [ACK] => Failure
        [VERSION] => 72.0
        [BUILD] => 22386173
        [L_ERRORCODE0] => 10004
        [L_SHORTMESSAGE0] => Transaction refused because of an invalid argument. See additional error messages for details.
        [L_LONGMESSAGE0] => The transaction id is not valid
        [L_SEVERITYCODE0] => Error
        [AMT] => 107.25
        [CURRENCYCODE] => GBP

The strangest thing is that if the customer clicks "Place Order" a second time it will go through, or if the customer orders a different quantity (30 instead of 55 for example) the order still goes through.

Has anyone seen anything like this before? I don't know how it could just happen based on the quantity, and as far as I know PayPal responds with the transaction ID and it isn't something provided by Magento.

Any help would be great!

Best Answer

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 1.9) - 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);
    }
}