Page 1 of 2 12 LastLast
Results 1 to 10 of 14
  1. #1
    Join Date
    Sep 2009
    Location
    Stuart, FL
    Posts
    12,366
    Plugin Contributions
    87

    Default 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.

  2. #2
    Join Date
    Jan 2004
    Posts
    66,363
    Blog Entries
    7
    Plugin Contributions
    274

    Default 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?
    .

    Zen Cart - putting the dream of business ownership within reach of anyone!
    Donate to: DrByte directly or to the Zen Cart team as a whole

    Remember: Any code suggestions you see here are merely suggestions. You assume full responsibility for your use of any such suggestions, including any impact ANY alterations you make to your site may have on your PCI compliance.
    Furthermore, any advice you see here about PCI matters is merely an opinion, and should not be relied upon as "official". Official PCI information should be obtained from the PCI Security Council directly or from one of their authorized Assessors.

  3. #3
    Join Date
    Sep 2009
    Location
    Stuart, FL
    Posts
    12,366
    Plugin Contributions
    87

    Default 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.
    Last edited by lat9; 4 Oct 2015 at 02:48 PM. Reason: Added P.S.

  4. #4
    Join Date
    Sep 2009
    Location
    Stuart, FL
    Posts
    12,366
    Plugin Contributions
    87

    Default 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:
    Code:
      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:
    Code:
    //-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()
    Code:
        $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 ***
    Code:
        $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']
                            );
    Code:
          /*********************************************
           * 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:
    Code:
      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 ***

  5. #5
    Join Date
    Jan 2004
    Posts
    66,363
    Blog Entries
    7
    Plugin Contributions
    274

    Default 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!
    .

    Zen Cart - putting the dream of business ownership within reach of anyone!
    Donate to: DrByte directly or to the Zen Cart team as a whole

    Remember: Any code suggestions you see here are merely suggestions. You assume full responsibility for your use of any such suggestions, including any impact ANY alterations you make to your site may have on your PCI compliance.
    Furthermore, any advice you see here about PCI matters is merely an opinion, and should not be relied upon as "official". Official PCI information should be obtained from the PCI Security Council directly or from one of their authorized Assessors.

  6. #6
    Join Date
    Sep 2009
    Location
    Stuart, FL
    Posts
    12,366
    Plugin Contributions
    87

    Default 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.

  7. #7
    Join Date
    Jan 2004
    Posts
    66,363
    Blog Entries
    7
    Plugin Contributions
    274

    Default 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.
    .

    Zen Cart - putting the dream of business ownership within reach of anyone!
    Donate to: DrByte directly or to the Zen Cart team as a whole

    Remember: Any code suggestions you see here are merely suggestions. You assume full responsibility for your use of any such suggestions, including any impact ANY alterations you make to your site may have on your PCI compliance.
    Furthermore, any advice you see here about PCI matters is merely an opinion, and should not be relied upon as "official". Official PCI information should be obtained from the PCI Security Council directly or from one of their authorized Assessors.

  8. #8
    Join Date
    Sep 2009
    Location
    Stuart, FL
    Posts
    12,366
    Plugin Contributions
    87

    Default 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.

  9. #9
    Join Date
    Jan 2004
    Posts
    66,363
    Blog Entries
    7
    Plugin Contributions
    274

    Default 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
    .

    Zen Cart - putting the dream of business ownership within reach of anyone!
    Donate to: DrByte directly or to the Zen Cart team as a whole

    Remember: Any code suggestions you see here are merely suggestions. You assume full responsibility for your use of any such suggestions, including any impact ANY alterations you make to your site may have on your PCI compliance.
    Furthermore, any advice you see here about PCI matters is merely an opinion, and should not be relied upon as "official". Official PCI information should be obtained from the PCI Security Council directly or from one of their authorized Assessors.

  10. #10
    Join Date
    Sep 2009
    Location
    Stuart, FL
    Posts
    12,366
    Plugin Contributions
    87

    Default 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?

 

 
Page 1 of 2 12 LastLast

Similar Threads

  1. Newbie- keep getting "Not Configured Yet" on PayPal Express and "Nothing Has Been..."
    By ashleyr in forum Built-in Shipping and Payment Modules
    Replies: 5
    Last Post: 14 Aug 2014, 04:05 PM
  2. Both "Go to checkout" & "paypal express" shows up
    By stefanl in forum Basic Configuration
    Replies: 1
    Last Post: 8 Dec 2009, 10:28 PM
  3. "confirmed address" issue - PayPal Express Checkout
    By smkeith in forum Addon Payment Modules
    Replies: 7
    Last Post: 9 May 2008, 05:55 PM
  4. PayPal Express Checkout - not "on" the site?
    By RobM in forum PayPal Express Checkout support
    Replies: 6
    Last Post: 26 Nov 2007, 09:59 PM
  5. Replies: 1
    Last Post: 24 Nov 2007, 06:08 PM

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
disjunctive-egg
Zen-Cart, Internet Selling Services, Klamath Falls, OR