Zen Cart Logo
Forums / Bug Reports / PayPal Express Checkout: Rounding issue causes "Line-item subtotals do not add up"

PayPal Express Checkout: Rounding issue causes "Line-item subtotals do not add up"

Views: 13,289

Results 1 to 14 of 14
3 Oct 2015, 8:10 PM
#1
lat9 avatar

lat9

Administrator

Join Date:
Sep 2009
Location:
Stuart, FL
Posts:
14,081
Plugin Contributions:
56

PayPal Express Checkout: Rounding issue causes "Line-item subtotals do not add up"

Two products in the cart, one with a final_price of 84.5455 and another with a final_price of 62.7273 (as viewed in the order object). Click the PayPal Express Checkout shortcut button, instead of PayPal listing these two products and their prices, I'm presented with "All the items in your shopping cart" with a quantity of 1.

The PayPal debug log shows "Line-item subtotals do not add up", which is why this customer message is issued by PayPal. The issue is one of rounding.

The total price (present in the order-object's total) is 147.2728, properly reflecting the sum of the two products ... assuming a precision of 4. That value, when rounded for the upload to PayPal, is 147.27.

Unfortunately, the paypalwpp processing in getLineItemDetails determines the order's total from the in-cart products by summing each product's price **after **the price has been converted for sending to PayPal ... using a precision of 2. The sum of 84.55 and 62.72 is 147.28, resulting in the mismatch.

3 Oct 2015, 11:00 PM
#2
drbyte avatar

drbyte

Sensei

Join Date:
Jan 2004
Posts:
63,513
Plugin Contributions:
177

Re: PayPal Express Checkout: Rounding issue causes "Line-item subtotals do not add up"

Thanks for posting the detailed example.

PayPal doesn't understand 4-decimal-precision. Only 2.

Thus in order to submit line-item-details one must round everything to 2 digits when calculating the price from base and taxes and discounts and other fees.

Sometimes, depending on precision (ZC can handle more than 2 digits precision), that results in a discrepancy between the "final amount" calculated by the cart vs the "final amount" PayPal calculates when adding the pre-rounded-to-2-digits-per-line-item.

What you're reporting is that Zen Cart is correctly recovering from the problem by adjusting what it submits to PayPal while still retaining more-than-2-digit-precision and not charging the customer more than what the cart calculated.

Now, you reported this as "a bug", so let's talk about what exactly the bug is.

What exactly do you think it should do? Charge the customer more? Or abort because PayPal's rejecting the transaction?
Or something else? What exactly do you suggest it should do, in both tax-excluded and tax-included pricing models suitable for all countries?

4 Oct 2015, 1:45 PM
#3
lat9 avatar

lat9

Administrator

Join Date:
Sep 2009
Location:
Stuart, FL
Posts:
14,081
Plugin Contributions:
56

Re: PayPal Express Checkout: Rounding issue causes "Line-item subtotals do not add up"

I guess that I'm saying that this is a bug ... in the order-class processing.

To my thinking the shopping-cart class is currency-agnostic and should properly calculate prices and totals based on the full precision capable. The customer can (depending on the shop's configuration) change the currency used for the pricing displays, at which point the calculations should be performed in the precision dictated by that currency.

The order-class, on the other hand, should be currency-specific. The order is being processed/displayed for a ***single ***currency, so the precision of the calculations should be based on the precision dictated by the order's chosen currency.

That said, I believe that the order-class requires an interface through which payment modules (like paypalwpp) that allow currencies to be limited should have the capability to instruct the order-class to calculate the cart-based order in a specified currency. For example, in the shortcut button's processing where the Express Checkout configuration includes "Transaction Currency->Only USD" should have the capability to force a cart-based order to be calculated using the USD currency's settings regardless of the current $_SESSION['currency'] value.

P.S. The above processing would correct the PayPal issue for all currencies, since all the currencies supported by PayPal use 2 or fewer digits after the decimal separator; see https://en.wikipedia.org/wiki/ISO_4217.

15 Oct 2015, 4:30 PM
#4
lat9 avatar

lat9

Administrator

Join Date:
Sep 2009
Location:
Stuart, FL
Posts:
14,081
Plugin Contributions:
56

Re: PayPal Express Checkout: Rounding issue causes "Line-item subtotals do not add up"

There were three aspects to this change:

/includes/classes/currencies.php:

  function display_price($products_price, $products_tax, $quantity = 1) {
//-bof-20140729-lat9-Fix rounding error with tax calculation
//      return $this->format(zen_add_tax($products_price, $products_tax) * $quantity);
    return $this->format (zen_add_tax($products_price * $quantity, $products_tax));
//-eof-20140729-lat9-Fix rounding error ...
  }

/includes/classes/order.php:

//-bof-20151015-lat9-Enable cart->order processing to be cognizant of the order's currency  *** 1 of 3 ***
  function __construct($order_id = '', $override_currency = false) {
    $this->currency = ($override_currency === false) ? $_SESSION['currency'] : $override_currency;
//-eof-20151015-lat9-Enable cart->order processing to be cognizant of the order's currency  *** 1 of 3 ***

in function cart()

    $decimals = $currencies->get_decimal_places(/*$_SESSION['currency']*/ $this->currency);  //-20151015-lat9-Enable cart->order processing to be cognizant of the order's currency  *** 2 of 3 ***
    $this->info = array('order_status' => DEFAULT_ORDERS_STATUS_ID,
//-bof-20151015-lat9-Enable cart->order processing to be cognizant of the order's currency  *** 3 of 3 ***
                        'currency' => /*$_SESSION['currency']*/ $this->currency,  
                        'currency_value' => $currencies->currencies[/*$_SESSION['currency']*/ $this->currency]['value'],
//-bof-20151015-lat9-Enable cart->order processing to be cognizant of the order's currency  *** 3 of 3 ***
                        'payment_method' => $GLOBALS[$class]->title,
                        'payment_module_code' => $GLOBALS[$class]->code,
                        'coupon_code' => $coupon_code->fields['coupon_code'],
    //                          'cc_type' => (isset($GLOBALS['cc_type']) ? $GLOBALS['cc_type'] : ''),
    //                          'cc_owner' => (isset($GLOBALS['cc_owner']) ? $GLOBALS['cc_owner'] : ''),
    //                          'cc_number' => (isset($GLOBALS['cc_number']) ? $GLOBALS['cc_number'] : ''),
    //                          'cc_expires' => (isset($GLOBALS['cc_expires']) ? $GLOBALS['cc_expires'] : ''),
    //                          'cc_cvv' => (isset($GLOBALS['cc_cvv']) ? $GLOBALS['cc_cvv'] : ''),
                        'shipping_method' => (isset($_SESSION['shipping']['title'])) ? $_SESSION['shipping']['title'] : '',
                        'shipping_module_code' => (isset($_SESSION['shipping']['id']) && strpos($_SESSION['shipping']['id'], '_') > 0 ? $_SESSION['shipping']['id'] : $_SESSION['shipping']),
//-bof-20151015-lat9-Correct rounding error, using precision dictated by selected currency  *** 1 of 4 ***
                        'shipping_cost' => $currencies->value (isset($_SESSION['shipping']['cost']) ? $_SESSION['shipping']['cost'] : 0, false, $this->currency),
//-eof-20151015-lat9-Correct rounding error, using precision dictated by selected currency  *** 1 of 4 ***
                        'subtotal' => 0,
                        'shipping_tax' => 0,
                        'tax' => 0,
                        'total' => 0,
                        'tax_groups' => array(),
                        'comments' => (isset($_SESSION['comments']) ? $_SESSION['comments'] : ''),
                        'ip_address' => $_SESSION['customers_ip_address'] . ' - ' . $_SERVER['REMOTE_ADDR']
                        );
      /*********************************************
       * Calculate taxes for this product
       *********************************************/
//-bof-20151015-lat9-Correct rounding error, using precision dictated by selected currency  *** 2 of 4 ***
      $shown_price = $currencies->value (zen_add_tax($this->products[$index]['final_price'] * $this->products[$index]['qty'], $this->products[$index]['tax']), false, $this->currency)
      + $currencies->value (zen_add_tax($this->products[$index]['onetime_charges'], $this->products[$index]['tax']), false, $this->currency);
//-eof-20151015-lat9-Correct rounding error, using precision dictated by selected currency  *** 2 of 4 ***
      $this->info['subtotal'] += $shown_price;
      $this->notify('NOTIFIY_ORDER_CART_SUBTOTAL_CALCULATE', array('shown_price'=>$shown_price));
      // find product's tax rate and description
      $products_tax = $this->products[$index]['tax'];

      $products_tax_description = $this->products[$index]['tax_description'];

      if (DISPLAY_PRICE_WITH_TAX == 'true') {
        // calculate the amount of tax "inc"luded in price (used if tax-in pricing is enabled)
        $tax_add = $shown_price - ($shown_price / (($products_tax < 10) ? "1.0" . str_replace('.', '', $products_tax) : "1." . str_replace('.', '', $products_tax)));
      } else {
        // calculate the amount of tax for this product (assuming tax is NOT included in the price)
//        $tax_add = zen_round(($products_tax / 100) * $shown_price, $currencies->currencies[$this->info['currency']]['decimal_places']);
        $tax_add = ($products_tax/100) * $shown_price;
      }
//-bof-20151015-lat9-Correct rounding error, using precision dictated by selected currency  *** 3 of 4 ***
      $tax_add = $currencies->value ($tax_add, false, $this->currency);
//-eof-20151015-lat9-Correct rounding error, using precision dictated by selected currency  *** 3 of 4 ***
      $this->info['tax'] += $tax_add;
      foreach ($taxRates as $taxDescription=>$taxRate)
      {
//-bof-20151015-lat9-Correct rounding error, using precision dictated by selected currency  *** 4 of 4 ***
        $taxAdd = $currencies->value (zen_calculate_tax($this->products[$index]['final_price']*$this->products[$index]['qty'], $taxRate), false, $this->currency)
                +  $currencies->value (zen_calculate_tax($this->products[$index]['onetime_charges'], $taxRate), false, $this->currency);
//-eof-20151015-lat9-Correct rounding error, using precision dictated by selected currency  *** 4 of 4 ***
        if (isset($this->info['tax_groups'][$taxDescription]))
        {
          $this->info['tax_groups'][$taxDescription] += $taxAdd;
        } else
        {
          $this->info['tax_groups'][$taxDescription] = $taxAdd;
        }
      }
      /*********************************************
       * END: Calculate taxes for this product
       *********************************************/

and, finally,** /includes/modules/payment/paypalwpp.php:**

  function ec_step1() {
    global $order, $order_totals, $db, $doPayPal;

    // if cart is empty due to timeout on login or shopping cart page, go to timeout screen
    if ($_SESSION['cart']->count_contents() == 0) {
      $message = 'Logging out due to empty shopping cart.  Is session started properly? ... ' . "\nSESSION Details:\n" . print_r($_SESSION, TRUE) . 'GET:' . "\n" . print_r($_GET, TRUE);
      include_once(DIR_WS_MODULES . 'payment/paypal/paypal_functions.php');
      ipn_debug_email($message);
      zen_redirect(zen_href_link(FILENAME_TIME_OUT, '', 'SSL'));
    }

    // init new order object
    require(DIR_WS_CLASSES . 'order.php');
//-bof-20151015-lat9-Force order to be calculated in the precision dictated by the selected currency  *** 1 of 1 ***
    $order = new order ('', $this->selectCurrency ());
//-bof-20151015-lat9-Force order to be calculated in the precision dictated by the selected currency  *** 1 of 1 ***
15 Oct 2015, 5:43 PM
#5
drbyte avatar

drbyte

Sensei

Join Date:
Jan 2004
Posts:
63,513
Plugin Contributions:
177

Re: PayPal Express Checkout: Rounding issue causes "Line-item subtotals do not add up"

Hmmm ... that affects some critical core files.

Lots of testing scenarios needed.

Have you tried those changes on stores configured for both US taxation (added after prices) and UK/AU/NZ taxation (included in prices)? And on products priced more granular than 2 decimal places?
I'm curious what you've found, or at least which scenarios have been tested.

(And of course I invite you to test other cases too, as that helps us do the final evaluation and integration.)

Grateful for the work you've done already!

15 Oct 2015, 6:16 PM
#6
lat9 avatar

lat9

Administrator

Join Date:
Sep 2009
Location:
Stuart, FL
Posts:
14,081
Plugin Contributions:
56

Re: PayPal Express Checkout: Rounding issue causes "Line-item subtotals do not add up"

So far, validated only on an AU-based location. This doesn't affect the prices with/without taxes processing, only the rounding issues discovered.

I verified that, for the order that instigated this bug-report, on a PPEC shortcut-button press that the order information displayed to the customer is fully displayed (i.e. no rounding-error-mismatch) on the initial PayPal screen. Prices are displayed without tax on PayPal (even though the site's set to display prices w/ tax).

Going to PayPal via the login->checkout method, using a shipping address (the tax basis) in the AU. The order information is fully displayed: both items fully described, shipping amount shown, tax included.

Returned to the store and chose a shipping address in the US. Returning to PayPal, the order information is still fully displayed with the shipping cost changed and no tax shown.

21 Oct 2015, 11:51 PM
#7
drbyte avatar

drbyte

Sensei

Join Date:
Jan 2004
Posts:
63,513
Plugin Contributions:
177

Re: PayPal Express Checkout: Rounding issue causes "Line-item subtotals do not add up"

In initial regression-testing, this throws up some errors related to shipping costs, and perhaps elsewhere too. Could be related to a different tax config scenario than what you're using so might not be affecting your particular site.
More testing to do yet.

22 Oct 2015, 11:23 AM
#8
lat9 avatar

lat9

Administrator

Join Date:
Sep 2009
Location:
Stuart, FL
Posts:
14,081
Plugin Contributions:
56

Re: PayPal Express Checkout: Rounding issue causes "Line-item subtotals do not add up"

Thanks for the update, DrByte. I'll take a peek, too.

23 Oct 2015, 3:34 PM
#9
drbyte avatar

drbyte

Sensei

Join Date:
Jan 2004
Posts:
63,513
Plugin Contributions:
177

Re: PayPal Express Checkout: Rounding issue causes "Line-item subtotals do not add up"

I was wrong. Your code is good.

In fact it caught and fixed a long-standing bug. Kudos!

Merged into v160: https://github.com/zencart/zencart/pull/631

23 Oct 2015, 5:17 PM
#10
lat9 avatar

lat9

Administrator

Join Date:
Sep 2009
Location:
Stuart, FL
Posts:
14,081
Plugin Contributions:
56

Re: PayPal Express Checkout: Rounding issue causes "Line-item subtotals do not add up"

Woo-hoo! That makes up for my stupid question about the products_options/products_options_values tables. What was the long-standing bug?

23 Oct 2015, 10:22 PM
#11
drbyte avatar

drbyte

Sensei

Join Date:
Jan 2004
Posts:
63,513
Plugin Contributions:
177

Re: PayPal Express Checkout: Rounding issue causes "Line-item subtotals do not add up"

lat9:

What was the long-standing bug?
The leaving-out of the currency factoring when putting together the totals. Fortunately it only occurs when something outside of the normal session-specific currency rules overrides the selected session currency ... such as payment modules which can re-convert transactions to submit in a different currency due to gateway limitations.

2 Jun 2016, 7:02 AM
#12
christopher_clough avatar

christopher_clough

New Zenner

Join Date:
May 2013
Location:
Brisbane, Australia
Posts:
6
Plugin Contributions:
0

Re: PayPal Express Checkout: Rounding issue causes "Line-item subtotals do not add up"

Hi Dr Byte and Lat9
I am setting up V1.5.5a, which I understood had resolved the issues of rounding in calculating prices.
Please find below details of an order based on Australian pricing, where we use tax inc pricing, and the extended price for a line item does not match the sub-total of the invoice due to rounding of the ex tax price rather than using the full ex tax price.

Interestingly the total ex tax price in the Products column is calculated correctly. The invoice sub-total is incorrect as is the ex tax extended price in the line item due to rounding.

TAX INVOICE No. 1
Date Purchased: Wednesday 01 June, 2016
Payment Method: Direct-Bank-Deposit, Cheques, Money Orders - Australian

Products Model Tax Price (excl) Price (incl) Total (excl) Total (incl)
5 x Ceramic Pearl half cut hot fix flat back
stones Dark Grey 012 [In Stock]

  • Shape: Round 9mm (AU $31.82) CERPEARL-DARKGREY-HFFB-ROUND-5 10.00% AU $6.36 AU $7.00 AU $31.80 AU $35.00

                                                                                                                                                                Sub-Total:	AU $34.98
                                                                                                                                                  Per Unit (Best Way):	AU $0.00
                                                                                                                                          Taxable Goods - 10% GST:	AU $3.18
                                                                                                                                    Non-Taxable Goods - GST Free:	AU $0.00
                                                                                                                                                                       Total:	AU $34.98
    

Am I missing something here or do I need to correct the pricing calculations myself.

Would appreciate any advice on this.

2 Jun 2016, 1:44 PM
#13
drbyte avatar

drbyte

Sensei

Join Date:
Jan 2004
Posts:
63,513
Plugin Contributions:
177

Re: PayPal Express Checkout: Rounding issue causes "Line-item subtotals do not add up"

Christopher Clough:

Hi Dr Byte and Lat9
I am setting up V1.5.5a, which I understood had resolved the issues of rounding in calculating prices.
The code changes earlier in this discussion did NOT get pulled down into v155.
You can experiment with using them in v155 by making the changes shown here: https://github.com/zencart/zencart/pull/631/files (red means something removed; green means something added)

4 Jun 2016, 1:14 AM
#14
christopher_clough avatar

christopher_clough

New Zenner

Join Date:
May 2013
Location:
Brisbane, Australia
Posts:
6
Plugin Contributions:
0

Re: PayPal Express Checkout: Rounding issue causes "Line-item subtotals do not add up"

OK thanks for that feedback Dr Byte, I appreciate your help.