Behebung des Rundungsfehlers in Magento und PayPal

Rounding Error Fix for Magento and PayPal

Der Rundungsfehler wurde in Magento 1.8 behoben. Bei starker Modifikation durch die vielen Erweiterungen kann ein Update von Magento aufwändig werden, sodass die Rundungsfehlerbehebung mit einer leichtgewichtigen Erweiterung für viele Shop-Betreiber weiterhin interessant bleibt.

Rounding Error Fix for Magento and PayPal auf Magento Connect

Ursprung des Rundungsfehlers

Nach PayPal-Anforderungen müssen alle Preise als kaufmännisch auf zwei Nachkommastellen gerundete Fixpunktzahlen übermittelt werden.

Bei kaufmännischer Rundung wird beispielsweise 1,005 auf 1,01 gerundet und 1,004 auf 1,00. Offensichtlich kann dies zu einem Rundungsfehler führen, je nach dem in welchem Rechenschritt gerundet wird:

  • Rundung der Summe: 1,004 + 1,004 = 2,008 ≈ 2,01
  • Rundung der Summanden: 1,004 ? 1,00; 1,00 + 1,00 = 2,00 ≠ 2,01

Wenn bei PayPal-Zahlungen Warenkorbartikel übermittelt werden, werden ihre Nettopreise und der Mehrwertsteuerbetrag übermittelt, sodass PayPal auf seiner Seite den Brutto-Gesamtbetrag berechnet. Durch die Umrechnung der Bruttopreise kann es zu einem Rundungsfehler kommen:

  • Angenommen der Warenkorb enthält drei verschiedene virtuelle Artikel (d. h. keine Versandkosten).
  • Jeder Artikel kostet brutto 9,99 €, sodass der Brutto-Warenkorbwert 3 * 9,99 € = 29,97 € beträgt.
  • An PayPal werden Nettopreise und der Mehrwertsteuerbetrag übertragen:
    • 29,97 € / 1,19 * 0,19 = 4,7851 € ≈ 4,79
    • 9,99 € / 1,19 = 8,3949 ≈ 8,39 €
    • 3 * 8,39 € + 4,79 € = 29,96 € ≠ 29,97 €
  • Durch den Rundungsfehler ist eine Abweichung um 0,01 € entstanden!

Doch in der Praxis bekommt PayPal die Daten so übermittelt, dass der Brutto-Gesamtbetrag mit dem von Magento bis auf Cent genau übereinstimmt:

Obwohl der Bruttopreis bei allen drei Produkten gleich war, wurde in einem der drei Fälle ein anderer Nettopreis berechnet!

Die Korrektur des Rundungsfehlers findet allerdings nicht im PayPal-Modul statt, sondern bereits im Warenkorb. In der Methode Mage_Tax_Model_Sales_Total_Quote_Subtotal ::_totalBaseCalculation wird der Rundungsfehler auf den Nettopreis des nächsten Produkts in der Liste vor der Rundung aufaddiert wird. Die Rundungsfehler werden im Array _roundingDeltas von _deltaRound verwaltet. Im obigen Beispiel rechnet Magento, reduziert auf das Wesentliche, wie folgt:

  • 1. Artikel: 9,99 / 1,19 = 8,39495798
    • Kaufmännisch gerundet: 8,39
    • Rundungsfehler: 8,39495798 – 8,39 = 0,00495798
  • 2. Artikel: 9,99 / 1,19 + 0,00495798 = 8,39991597
    • Kaufmännisch gerundet: 8,40
    • Rundungsfehler: 8,39991597 – 8,40 = -0,000084034
  • 3. Artikel: 9,99 / 1,19 – 0,000084034 = 8,39487395
    • Kaufmännisch gerundet: 8,39
    • Rundungsfehler: 8,39487395 – 8,39 = 0,00487395

Die Ergebnisse dieser Berechnung lassen sich bereits beim Befüllen des Warenkorbs beobachten. In der Datenbank sind die gerundeten und korrigierten Nettopreise beispielsweise in der Spalte base_price der Tabelle sales_flat_quote_item sichtbar.

Der Ursprung des Rundungsfehlers ist also nicht das PayPal-Modul. Wir haben beobachtet, dass beim Auftreten des Rudnungsfehlers dieser von dem Mehrwertsteuerbetrag ausgeht. Die beschriebene Rudnungsfehlerkorrektur wirkt sich auch auf die Berechnung des Mehrwertsteuerbetrags aus.

Bisherige Lösungsansätze

Im Web gibt es zahlreiche Berichte und Lösungsansätze für die Behebung des Rundungsfehlers. In allen Fällen wird die Rundungsgenauigkeit in Mage_Core_Model_Store::roundPrice von zwei auf vier Nachkommastellen erhöht.

Diese Anpassung verursacht einen Rundungsfehler an anderen Stellen, dem durch weitere Anpassungen entgegengewirkt werden muss. In einigen Lösungen werden Formulardaten des PayPal-Moduls durch Addieren oder Subtrahieren von 0.01 vor dem Absenden manipuliert.

Keines dieser Ansätze behebt den Rundungsfehler vollständig und für alle Konstellationen von Produktpreisen und Steuereinstellungen. Dennoch danken wir allen Authoren, die ihre Lösungen veröffentlicht haben, denn sie haben uns bei der Entwicklung dieser Erweiterung sehr geholfen!

Behebung des Rundungsfehlers mit Xonu_RoundingErrorFix

Auch unsere Erweiterung überschreibt Mage_Core_Model_Store::roundPrice und manipuliert dort die Rundungsgenauigkeit.

Dabei wird abhängig von der aufrufenden Klasse entschieden, auf wieviele Nachkommastellen gerundet wird: Für Aufrufe, die von der Klasse Mage_Tax_Model_Sales_Total_Quote_Subtotal ausgehen wird auf zwei, für alle restlichen Aufrufe auf vier Nachkommastellen genau gerundet.

Wie oben gezeigt, ist diese Klasse in die Berechnung der kaufmännisch gerundeten Nettopreise mit Rundungsfehlerkorrektur involviert. Erhöhung der Rundungsgenauigkeit auf vier Nachkommastellen stört diese Rechenlogik erheblich!

PayPal verwendet für die Verarbeitung der Zahlung die Methode Mage_Sales_Model_Order_Payment::registerCaptureNotification. U. a. hier wird der der „Betrugsverdacht“ erkannt, wenn _isCaptureFinal einen false zurückgibt:

public function registerCaptureNotification($amount)
{
    ....

    if (!$invoice) {
        if ($this->_isCaptureFinal($amount)) {
            $invoice = $order->prepareInvoice()->register();
            $order->addRelatedObject($invoice);
            $this->setCreatedInvoice($invoice);
        } else {
            $this->setIsFraudDetected(true);
            $this->_updateTotals(array('base_amount_paid_online' => $amount));
        }
    }

    ....
}

In Mage_Sales_Model_Order_Payment::_isCaptureFinal wird der über PayPal gezahlter Betrag, übermittelt in der IPN, mit dem Gesamtbetrag der Bestellung verglichen:

protected function _isCaptureFinal($amountToCapture)
{
    $amountToCapture = $this->_formatAmount($amountToCapture, true);
    $orderGrandTotal = $this->_formatAmount($this->getOrder()->getBaseGrandTotal(), true);
    if ($orderGrandTotal == $this->_formatAmount($this->getBaseAmountPaid(), true) + $amountToCapture) {
        if (false !== $this->getShouldCloseParentTransaction()) {
            $this->setShouldCloseParentTransaction(true);
        }
        return true;
    }
    return false;
}

Dabei wird die Methode Mage_Sales_Model_Order_Payment::_formatAmount verwendet, sodass wenn roundPrice auf vier statt zwei Nachkommastellen rundet, die Beträge nicht übereinstimmen könnten, weil der von PayPal übermittelte Betrag immer auf zwei Nachkommastellen gerundet ist:

protected function _formatAmount($amount, $asFloat = false)
{
     $amount = Mage::app()->getStore()->roundPrice($amount);
     return !$asFloat ? (string)$amount : $amount;
}

Die Erweiterung überschreibt auch diese Methode, sodass sie auf zwei Nachkommastellen genau rundet:

protected function _formatAmount($amount, $asFloat = false)
{
    $amount = round($amount,2);
    return !$asFloat ? (string)$amount : $amount;
}

Eine weitere Methode im PayPal-Modul scheitert an der Erhöhung der Nachkommastellen in roundPrice: Mage_Paypal_Model_Api_Abstract::_filterAmount und liefert in bestimmten Fällen ein Ergebnis, welches wiederum einen Rundungsfehler verursacht:

protected function _filterAmount($value)
{
    return sprintf('%.2F', $value);
}

Auch diese Methode überschreiben wir:

protected function _filterAmount($value)
{
    return sprintf('%.2F', round($value, 2));
}

In dem Update der Erweiterung werden zusätzlich noch die PayPal-Formulardaten vor der Übermittlung mit den Bestelldaten verglichen. Bei einer Abweichung wird der Rundungsfehler durch gezielte Anpassung der Formulardaten korrigiert.

Rounding Error Fix for Magento and PayPal

Rounding Error Fix for Magento and PayPal auf Magento Connect

Ein Gedanke zu „Behebung des Rundungsfehlers in Magento und PayPal“

  1. Hallo,

    deine Extension hat mir schon mal sehr weitergeholfen, danke dafür.

    Habe jetzt leider noch ein anderes Problem und bekomm es damit nicht in verbindung gesetzt. Bei bestimmten Mengen eines Artikels in meinem Warenkorb bekomme ich den Error 10413. Angeblich stimmt etwas nicht mit der übermittelten Summe.

    Kleines Beispiel:

    Ich habe 40 Artikel in meinem Warenkorb zu je 1.99€ dazu kommt noch 7.99 € Versand. Ergibt Brutto 87.59€ und Netto 73.61€, auch die Netto werte vom Versand werden richtig übertragen. Trotzdem bekomme ich die Meldung „PayPal gateway hat Ihre Anforderung abgewiesen. The totals of the cart item amounts do not match order amounts (#10413: Transaction refused because of an invalid argument. See additional error messages for details).“
    Erhöhe ich jetzt von 40 auf 42 Artikel im Warenkorb funktioniert es und ich kann mit Paypal die Zahlung abschließen.

    Hast du eine Idee? Die Übermittelten Summen sind ja korrekt und es kann also nicht am Rundungsfehler liegen, oder doch und übersehe ich etwas? Ich danke dir für den ein oder anderen Tip. Im grunde funktioniert es ja, aber manchmal halt auch nicht.

    Hier noch die Error log:

    [PAYMENTACTION] => Sale
    [AMT] => 87.59

    [LOCALECODE] => de_DE
    [ITEMAMT] => 66.89
    [TAXAMT] => 13.98
    [SHIPPINGAMT] => 6.71
    [BUSINESS] =>

    )

    [response] => Array
    (
    [TIMESTAMP] => 2017-07-16T15:30:16Z
    [CORRELATIONID] => 2263db177a985
    [ACK] => Failure
    [VERSION] => 72.0
    [BUILD] => 36361320
    [L_ERRORCODE0] => 10413
    [L_SHORTMESSAGE0] => Transaction refused because of an invalid argument. See additional error messages for details.
    [L_LONGMESSAGE0] => The totals of the cart item amounts do not match order amounts.
    [L_SEVERITYCODE0] => Error
    )

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.