Example #1
0
/**
 * Prepare subtotal and line-item detail content to send to PayPal
 */
function ipn_getLineItemDetails()
{
    error_reporting(E_ALL);
    global $order, $currencies, $order_totals, $order_total_modules;
    $optionsST = array();
    $optionsLI = array();
    $onetimeSum = 0;
    $onetimeTax = 0;
    $creditsApplied = 0;
    $creditsTax_applied = 0;
    $sumOfLineItems = 0;
    $sumOfLineTax = 0;
    // prepare subtotals
    $optionsST['handling'] = 0;
    for ($i = 0, $n = sizeof($order_totals); $i < $n; $i++) {
        if ($order_totals[$i]['code'] == 'ot_subtotal') {
            $optionsST['subtotal'] = round($order_totals[$i]['value'], 2);
        }
        if ($order_totals[$i]['code'] == 'ot_tax') {
            $optionsST['tax_cart'] = round($order_totals[$i]['value'], 2);
        }
        if ($order_totals[$i]['code'] == 'ot_shipping') {
            $optionsST['shipping'] = round($order_totals[$i]['value'], 2);
        }
        if ($order_totals[$i]['code'] == 'ot_total') {
            $optionsST['amount'] = round($order_totals[$i]['value'], 2);
        }
        global ${$order_totals[$i]['code']};
        if (isset(${$order_totals[$i]['code']}->credit_class) && ${$order_totals[$i]['code']}->credit_class == true) {
            $creditsApplied += round($order_totals[$i]['value'], 2);
        }
        // treat all other OT's as if they're related to handling fees
        if (!in_array($order_totals[$i]['code'], array('ot_total', 'ot_subtotal', 'ot_tax', 'ot_shipping')) && !(isset(${$order_totals[$i]['code']}->credit_class) && ${$order_totals[$i]['code']}->credit_class == true)) {
            $optionsST['handling'] += $order_totals[$i]['value'];
        }
    }
    // Move shipping tax amount from Tax subtotal into Shipping subtotal for submission to PayPal
    $module = substr($_SESSION['shipping']['id'], 0, strpos($_SESSION['shipping']['id'], '_'));
    if (zen_not_null($order->info['shipping_method'])) {
        if ($GLOBALS[$module]->tax_class > 0) {
            $shipping_tax_basis = !isset($GLOBALS[$module]->tax_basis) ? STORE_SHIPPING_TAX_BASIS : $GLOBALS[$module]->tax_basis;
            $shippingOnBilling = zen_get_tax_rate($GLOBALS[$module]->tax_class, $order->billing['country']['id'], $order->billing['zone_id']);
            $shippingOnDelivery = zen_get_tax_rate($GLOBALS[$module]->tax_class, $order->delivery['country']['id'], $order->delivery['zone_id']);
            if ($shipping_tax_basis == 'Billing') {
                $shipping_tax = $shippingOnBilling;
            } elseif ($shipping_tax_basis == 'Shipping') {
                $shipping_tax = $shippingOnDelivery;
            } else {
                if (STORE_ZONE == $order->billing['zone_id']) {
                    $shipping_tax = $shippingOnBilling;
                } elseif (STORE_ZONE == $order->delivery['zone_id']) {
                    $shipping_tax = $shippingOnDelivery;
                } else {
                    $shipping_tax = 0;
                }
            }
            $taxAdjustmentForShipping = zen_calculate_tax($order->info['shipping_cost'], $shipping_tax);
            $optionsST['shipping'] += $taxAdjustmentForShipping;
            $optionsST['tax_cart'] -= $taxAdjustmentForShipping;
        }
    }
    ipn_logging('DEBUG Round 1', 'Subtotal Info:' . "\n" . print_r($optionsST, true));
    // loop thru all products to display quantity and price. Appends *** if out-of-stock.
    for ($i = 0, $n = sizeof($order->products), $k = 1; $i < $n; $i++, $k++) {
        $optionsLI["item_number_{$k}"] = $order->products[$i]['model'];
        $optionsLI["quantity_{$k}"] = (int) $order->products[$i]['qty'];
        $optionsLI["item_name_{$k}"] = $order->products[$i]['name'];
        $optionsLI["item_name_{$k}"] .= zen_get_products_stock($order->products[$i]['id']) - $order->products[$i]['qty'] < 0 ? STOCK_MARK_PRODUCT_OUT_OF_STOCK : '';
        // if there are attributes, loop thru them and add to description
        if (isset($order->products[$i]['attributes']) && sizeof($order->products[$i]['attributes']) > 0) {
            for ($j = 0, $n2 = sizeof($order->products[$i]['attributes']); $j < $n2; $j++) {
                $optionsLI["item_name_{$k}"] .= "\n " . $order->products[$i]['attributes'][$j]['option'] . ': ' . $order->products[$i]['attributes'][$j]['value'];
            }
            // end loop
        }
        // endif attribute-info
        $optionsLI["amount_{$k}"] = $order->products[$i]['final_price'];
        $optionsLI["tax_{$k}"] = zen_calculate_tax($order->products[$i]['final_price'], $order->products[$i]['tax']);
        $optionsLI["shipping_{$k}"] = 0;
        // track one-time charges
        if ($order->products[$i]['onetime_charges'] != 0) {
            $onetimeSum += $order->products[$i]['onetime_charges'];
            $onetimeTax += zen_calculate_tax($order->products[$i]['onetime_charges'], $order->products[$i]['tax']);
        }
        // Replace & and = with * if found.
        $optionsLI["item_name_{$k}"] = str_replace(array('&', '='), '*', $optionsLI["item_name_{$k}"]);
        $optionsLI["item_name_{$k}"] = zen_clean_html($optionsLI["item_name_{$k}"], 'strong');
        $optionsLI["item_name_{$k}"] = substr($optionsLI["item_name_{$k}"], 0, 127);
        // reformat properly
        $optionsLI["item_number_{$k}"] = substr($optionsLI["item_number_{$k}"], 0, 127);
    }
    // end for loopthru all products
    if ($onetimeSum > 0) {
        $i++;
        $k++;
        $optionsLI["item_number_{$k}"] = $k;
        $optionsLI["item_name_{$k}"] = 'One-Time Charges';
        $optionsLI["amount_{$k}"] = $onetimeSum;
        $optionsLI["tax_{$k}"] = $onetimeTax;
        $optionsLI["quantity_{$k}"] = 1;
    }
    ipn_logging('DEBUG Round 2', 'Line Item Info:' . "\n" . print_r($optionsLI, true));
    // handle discounts such as gift certificates and coupons
    if ($creditsApplied > 0) {
        $optionsST['handling'] -= $creditsApplied;
    }
    // add all one-time charges
    $optionsST['subtotal'] += $onetimeSum;
    //ensure things are not negative and not carrying any bad precision
    $optionsST['handling'] = abs(strval($optionsST['handling']));
    // subtotals have to add up to AMT
    // Thus, if there is a discrepancy, make adjustment to HANDLINGAMT:
    $st = $optionsST['subtotal'] + $optionsST['tax_cart'] + $optionsST['shipping'] + $optionsST['handling'];
    if ($st != $optionsST['amount']) {
        $optionsST['handling'] += $optionsST['amount'] - $st;
    }
    // Since the PayPal spec cannot handle mathematically mismatched values caused by one-time charges,
    // must drop line-item details if any one-time charges apply to this order:
    // And, if there are any discounts in this order, do NOT supply line-item details
    if ($onetimeSum > 0) {
        $optionsLI = array();
    }
    // Do sanity check -- if any of the line-item subtotal math doesn't add up properly, skip line-item details,
    // so that the order can go through even though PayPal isn't being flexible to handle Zen Cart's diversity
    $optionsLI['num_cart_items'] = 0;
    for ($j = 1; $j < $k; $j++) {
        $itemAMT = $optionsLI["amount_{$j}"];
        $itemTAX = $optionsLI["tax_{$j}"];
        $itemQTY = $optionsLI["quantity_{$j}"];
        $sumOfLineItems += $itemQTY * $itemAMT;
        $sumOfLineTax += round($itemQTY * $itemTAX, 2);
        $optionsLI['num_cart_items']++;
    }
    if ((double) strval($optionsST['subtotal']) != (double) strval($sumOfLineItems)) {
        $optionsLI = array();
        ipn_logging('getLineItemDetails 1: Order Subtotal does not match sum of line-item prices. Line-item-details skipped.' . "\n" . (double) $optionsST['subtotal'] . ' ' . (double) $sumOfLineItems);
        //die('ITEMAMT != $sumOfLineItems ' . $optionsST['ITEMAMT'] . ' ' . $sumOfLineItems);
    }
    if ((double) strval($optionsST['tax_cart']) != (double) strval($sumOfLineTax)) {
        for ($i = 0, $n = sizeof($order->products), $k = 1; $i < $n; $i++, $k++) {
            $optionsLI["tax_{$k}"] = 0;
        }
        $optionsLI["tax_1"] = $optionsST["tax_cart"];
        ipn_logging('getLineItemDetails 2: Tax Subtotal did not match sum of taxes for line-items. Line-item taxes skipped.' . "\n" . $optionsST['tax_cart'] . ' ' . $sumOfLineTax);
        //die('TAXAMT != $sumofLineTax ' . $optionsST['TAXAMT'] . ' ' . $sumOfLineTax);
    }
    // ensure all numbers are non-negative
    // tidy up all values so that they comply with proper format (number_format(xxxx,2) for PayPal US use )
    if (is_array($optionsST)) {
        foreach ($optionsST as $key => $value) {
            $optionsST[$key] = number_format(abs(strval($value)), 2);
        }
    }
    if (is_array($optionsLI)) {
        foreach ($optionsLI as $key => $value) {
            if (strstr($key, 'amount')) {
                $optionsLI[$key] = number_format(abs(strval($value)), 2);
            }
        }
    }
    ipn_logging('getLineItemDetails 3: IPN LineItemDetails: ' . "\n" . ($creditsApplied ? 'Credits apply to this order, so all line-item details are NOT being submitted. Thus, the following data is REDUNDANT and probably VERY INACCURATE' . "\n" : '') . 'Details:' . print_r(array_merge($optionsST, $optionsLI), true) . "\n\n" . 'DEFAULT_CURRENCY = ' . DEFAULT_CURRENCY . "\nSESSION['currency'] = " . $_SESSION['currency'] . "\n" . "order->info['currency'] = " . $order->info['currency'] . "\n\$currencies->currencies[\$_SESSION['currency']]['value'] = " . $currencies->currencies[$_SESSION['currency']]['value'] . "\n" . print_r($currencies, true));
    // if not default currency, do not send subtotals or line-item details
    if (DEFAULT_CURRENCY != $order->info['currency']) {
        ipn_logging('getLineItemDetails 4: Not using default currency. Thus, no line-item details can be submitted.');
        return array();
    }
    if ($currencies->currencies[$_SESSION['currency']]['value'] != 1) {
        ipn_logging('getLineItemDetails 5: currency val not equal to 1.0000 - cannot proceed without coping with currency conversions. Aborting line-item details.');
        return array();
    }
    // if there are any discounts in this order, do not supply subtotals or line-item details
    if (strval($creditsApplied) > 0) {
        return array();
    }
    //$this->zcLog('getLineItemDetails 6', 'no credits - okay');
    // if subtotals are not adding up correctly, then skip sending any line-item or subtotal details to PayPal
    $st = round(strval($optionsST['subtotal'] + $optionsST['tax_cart'] + $optionsST['shipping'] + $optionsST['handling']), 2);
    $stDiff = strval($optionsST['amount'] - $st);
    $stDiffRounded = strval(abs($st) - abs(round($optionsST['amount'], 2)));
    ipn_logging('getLineItemDetails 7: checking subtotals... ' . "\nitemamt: " . $optionsST['subtotal'] . "\ntaxamt: " . $optionsST['tax_cart'] . "\nshippingamt: " . $optionsST['shipping'] . "\nhandlingamt: " . $optionsST['handling'] . "\n-------------------\nsubtotal: " . number_format($st, 2) . "\namount: " . $optionsST['amount'] . "\n-------------------\ndifference: " . $stDiff . '  (abs+rounded: ' . $stDiffRounded . ')');
    if ($stDiffRounded != 0) {
        return array();
    }
    //die('bad subtotals'); //return array();
    ipn_logging('getLineItemDetails 8: subtotals balance - okay');
    if (abs($optionsST['handling']) == 0) {
        unset($optionsST['handling']);
    }
    // Send Subtotal and LineItem results back to be submitted to PayPal
    return array_merge($optionsST, $optionsLI);
}
/**
 * Prepare subtotal and line-item detail content to send to PayPal
 */
function ipn_getLineItemDetails($restrictedCurrency)
{
    global $order, $currencies, $order_totals, $order_total_modules;
    // if not default currency, do not send subtotals or line-item details
    if (DEFAULT_CURRENCY != $order->info['currency'] || $restrictedCurrency != DEFAULT_CURRENCY) {
        ipn_logging('getLineItemDetails 1', 'Not using default currency. Thus, no line-item details can be submitted.');
        return array();
    }
    if ($currencies->currencies[$_SESSION['currency']]['value'] != 1 || $currencies->currencies[$order->info['currency']]['value'] != 1) {
        ipn_logging('getLineItemDetails 2', 'currency val not equal to 1.0000 - cannot proceed without coping with currency conversions. Aborting line-item details.');
        return array();
    }
    $optionsST = array();
    $optionsLI = array();
    $optionsNB = array();
    $numberOfLineItemsProcessed = 0;
    $creditsApplied = 0;
    $surcharges = 0;
    $sumOfLineItems = 0;
    $sumOfLineTax = 0;
    $optionsST['amount'] = 0;
    $optionsST['subtotal'] = 0;
    $optionsST['tax_cart'] = 0;
    $optionsST['shipping'] = 0;
    $flagSubtotalsUnknownYet = true;
    $subTotalLI = 0;
    $subTotalTax = 0;
    $subTotalShipping = 0;
    $subtotalPRE = array('no data');
    $discountProblemsFlag = FALSE;
    $flag_treat_as_partial = FALSE;
    if (sizeof($order_totals)) {
        // prepare subtotals
        for ($i = 0, $n = sizeof($order_totals); $i < $n; $i++) {
            if ($order_totals[$i]['code'] == '') {
                continue;
            }
            if (in_array($order_totals[$i]['code'], array('ot_total', 'ot_subtotal', 'ot_tax', 'ot_shipping')) || strstr($order_totals[$i]['code'], 'insurance')) {
                if ($order_totals[$i]['code'] == 'ot_shipping') {
                    $optionsST['shipping'] = round($order_totals[$i]['value'], 2);
                }
                if ($order_totals[$i]['code'] == 'ot_total') {
                    $optionsST['amount'] = round($order_totals[$i]['value'], 2);
                }
                if ($order_totals[$i]['code'] == 'ot_tax') {
                    $optionsST['tax_cart'] += round($order_totals[$i]['value'], 2);
                }
                if ($order_totals[$i]['code'] == 'ot_subtotal') {
                    $optionsST['subtotal'] = round($order_totals[$i]['value'], 2);
                }
            } else {
                // handle other order totals:
                global ${$order_totals[$i]['code']};
                if (substr($order_totals[$i]['text'], 0, 1) == '-' || isset(${$order_totals[$i]['code']}->credit_class) && ${$order_totals[$i]['code']}->credit_class == true) {
                    // handle credits
                    $creditsApplied += round($order_totals[$i]['value'], 2);
                } else {
                    // treat all other OT's as if they're related to handling fees or other extra charges to be added/included
                    $surcharges += $order_totals[$i]['value'];
                }
            }
        }
        if ($creditsApplied > 0) {
            $optionsST['subtotal'] -= $creditsApplied;
        }
        if ($surcharges > 0) {
            $optionsST['subtotal'] += $surcharges;
        }
        $optionsNB['creditsExist'] = $creditsApplied > 0 ? TRUE : FALSE;
        // Handle tax-included scenario
        if (DISPLAY_PRICE_WITH_TAX == 'true') {
            $optionsST['tax_cart'] = 0;
        }
        $subtotalPRE = $optionsST;
        // Move shipping tax amount from Tax subtotal into Shipping subtotal for submission to PayPal, since PayPal applies tax to each line-item individually
        $module = strpos($_SESSION['shipping']['id'], '_') > 0 ? substr($_SESSION['shipping']['id'], 0, strpos($_SESSION['shipping']['id'], '_')) : $_SESSION['shipping'];
        if (isset($GLOBALS[$module]) && zen_not_null($order->info['shipping_method']) && DISPLAY_PRICE_WITH_TAX != 'true') {
            if ($GLOBALS[$module]->tax_class > 0) {
                $shipping_tax_basis = !isset($GLOBALS[$module]->tax_basis) ? STORE_SHIPPING_TAX_BASIS : $GLOBALS[$module]->tax_basis;
                $shippingOnBilling = zen_get_tax_rate($GLOBALS[$module]->tax_class, $order->billing['country']['id'], $order->billing['zone_id']);
                $shippingOnDelivery = zen_get_tax_rate($GLOBALS[$module]->tax_class, $order->delivery['country']['id'], $order->delivery['zone_id']);
                if ($shipping_tax_basis == 'Billing') {
                    $shipping_tax = $shippingOnBilling;
                } elseif ($shipping_tax_basis == 'Shipping') {
                    $shipping_tax = $shippingOnDelivery;
                } else {
                    if (STORE_ZONE == $order->billing['zone_id']) {
                        $shipping_tax = $shippingOnBilling;
                    } elseif (STORE_ZONE == $order->delivery['zone_id']) {
                        $shipping_tax = $shippingOnDelivery;
                    } else {
                        $shipping_tax = 0;
                    }
                }
                $taxAdjustmentForShipping = zen_round(zen_calculate_tax($order->info['shipping_cost'], $shipping_tax), $currencies->currencies[$_SESSION['currency']]['decimal_places']);
                $optionsST['shipping'] += $taxAdjustmentForShipping;
                $optionsST['tax_cart'] -= $taxAdjustmentForShipping;
            }
        }
        $flagSubtotalsUnknownYet = $optionsST['shipping'] + $optionsST['amount'] + $optionsST['tax_cart'] + $optionsST['subtotal'] == 0;
    } else {
        // if we get here, we don't have any order-total information yet because the customer has clicked Express before starting normal checkout flow
        // thus, we must make a note to manually calculate subtotals, rather than relying on the more robust order-total infrastructure
        $flagSubtotalsUnknownYet = TRUE;
    }
    $decimals = $currencies->get_decimal_places($_SESSION['currency']);
    // loop thru all products to prepare details of quantity and price.
    for ($i = 0, $n = sizeof($order->products), $k = 0; $i < $n; $i++) {
        // PayPal is inconsistent in how it handles zero-value line-items, so skip this entry if price is zero
        if ($order->products[$i]['final_price'] == 0) {
            continue;
        } else {
            $k++;
        }
        $optionsLI["item_number_{$k}"] = $order->products[$i]['model'];
        $optionsLI["item_name_{$k}"] = $order->products[$i]['name'] . ' [' . (int) $order->products[$i]['id'] . ']';
        // Append *** if out-of-stock.
        $optionsLI["item_name_{$k}"] .= zen_get_products_stock($order->products[$i]['id']) - $order->products[$i]['qty'] < 0 ? STOCK_MARK_PRODUCT_OUT_OF_STOCK : '';
        // if there are attributes, loop thru them and add to description
        if (isset($order->products[$i]['attributes']) && sizeof($order->products[$i]['attributes']) > 0) {
            for ($j = 0, $n2 = sizeof($order->products[$i]['attributes']); $j < $n2; $j++) {
                $optionsLI["item_name_{$k}"] .= "\n " . $order->products[$i]['attributes'][$j]['option'] . ': ' . $order->products[$i]['attributes'][$j]['value'];
            }
            // end loop
        }
        // endif attribute-info
        // PayPal can't handle fractional-quantity values, so convert it to qty 1 here
        if ($order->products[$i]['qty'] > 1 && ($order->products[$i]['qty'] != (int) $order->products[$i]['qty'] || $flag_treat_as_partial)) {
            $optionsLI["item_name_{$k}"] = '(' . $order->products[$i]['qty'] . ' x ) ' . $optionsLI["item_name_{$k}"];
            // zen_add_tax already handles whether DISPLAY_PRICES_WITH_TAX is set
            $optionsLI["amount_{$k}"] = zen_round(zen_round(zen_add_tax($order->products[$i]['final_price'], $order->products[$i]['tax']), $decimals) * $order->products[$i]['qty'], $decimals);
            $optionsLI["quantity_{$k}"] = 1;
            // no line-item tax component
        } else {
            $optionsLI["quantity_{$k}"] = $order->products[$i]['qty'];
            $optionsLI["amount_{$k}"] = zen_round(zen_add_tax($order->products[$i]['final_price'], $order->products[$i]['tax']), $decimals);
        }
        $subTotalLI += $optionsLI["quantity_{$k}"] * $optionsLI["amount_{$k}"];
        // $subTotalTax += ($optionsLI["quantity_$k"] * $optionsLI["tax_$k"]);
        // add line-item for one-time charges on this product
        if ($order->products[$i]['onetime_charges'] != 0) {
            $k++;
            $optionsLI["item_name_{$k}"] = MODULES_PAYMENT_PAYPALSTD_LINEITEM_TEXT_ONETIME_CHARGES_PREFIX . substr(htmlentities($order->products[$i]['name'], ENT_QUOTES, 'UTF-8'), 0, 120);
            $optionsLI["amount_{$k}"] = zen_round(zen_add_tax($order->products[$i]['onetime_charges'], $order->products[$i]['tax']), $decimals);
            $optionsLI["quantity_{$k}"] = 1;
            // $optionsLI["tax_$k"] = zen_round(zen_calculate_tax($order->products[$i]['onetime_charges'], $order->products[$i]['tax']), $decimals);
            $subTotalLI += $optionsLI["amount_{$k}"];
            // $subTotalTax += $optionsLI["tax_$k"];
        }
        $numberOfLineItemsProcessed = $k;
    }
    // end for loopthru all products
    // add line items for any surcharges added by order-total modules
    if ($surcharges > 0) {
        $numberOfLineItemsProcessed++;
        $k = $numberOfLineItemsProcessed;
        $optionsLI["item_name_{$k}"] = MODULES_PAYMENT_PAYPALSTD_LINEITEM_TEXT_SURCHARGES_SHORT;
        $optionsLI["amount_{$k}"] = $surcharges;
        $optionsLI["quantity_{$k}"] = 1;
        $subTotalLI += $surcharges;
    }
    // add line items for discounts such as gift certificates and coupons
    if ($creditsApplied > 0) {
        $numberOfLineItemsProcessed++;
        $k = $numberOfLineItemsProcessed;
        $optionsLI["item_name_{$k}"] = MODULES_PAYMENT_PAYPALSTD_LINEITEM_TEXT_DISCOUNTS_SHORT;
        $optionsLI["amount_{$k}"] = -1 * $creditsApplied;
        $optionsLI["quantity_{$k}"] = 1;
        $subTotalLI -= $creditsApplied;
    }
    // Reformat properly
    // Replace & and = and % with * if found.
    // reformat properly according to API specs
    // Remove HTML markup from name if found
    for ($k = 1, $n = $numberOfLineItemsProcessed + 1; $k < $n; $k++) {
        $optionsLI["item_name_{$k}"] = str_replace(array('&', '=', '%'), '*', $optionsLI["item_name_{$k}"]);
        $optionsLI["item_name_{$k}"] = zen_clean_html($optionsLI["item_name_{$k}"], 'strong');
        $optionsLI["item_name_{$k}"] = substr($optionsLI["item_name_{$k}"], 0, 127);
        $optionsLI["amount_{$k}"] = round($optionsLI["amount_{$k}"], 2);
        if (isset($optionsLI["item_number_{$k}"])) {
            if ($optionsLI["item_number_{$k}"] == '') {
                unset($optionsLI["item_number_{$k}"]);
            } else {
                $optionsLI["item_number_{$k}"] = str_replace(array('&', '=', '%'), '*', $optionsLI["item_number_{$k}"]);
                $optionsLI["item_number_{$k}"] = substr($optionsLI["item_number_{$k}"], 0, 127);
            }
        }
        // if (isset($optionsLI["tax_$k"]) && ($optionsLI["tax_$k"] != '' || $optionsLI["tax_$k"] > 0)) {
        // $optionsLI["tax_$k"] = round($optionsLI["tax_$k"], 2);
        // }
    }
    // Sanity Check of line-item subtotals
    $optionsLI['num_cart_items'] = 0;
    for ($j = 1; $j < $k; $j++) {
        $itemAMT = $optionsLI["amount_{$j}"];
        $itemQTY = $optionsLI["quantity_{$j}"];
        $itemTAX = isset($optionsLI["tax_{$j}"]) ? $optionsLI["tax_{$j}"] : 0;
        $sumOfLineItems += $itemQTY * $itemAMT;
        $sumOfLineTax += $itemQTY * $itemTAX;
        $optionsLI['num_cart_items']++;
    }
    $sumOfLineItems = round($sumOfLineItems, 2);
    $sumOfLineTax = round($sumOfLineTax, 2);
    if ($sumOfLineItems == 0) {
        $sumOfLineTax = 0;
        $optionsLI = array();
        $discountProblemsFlag = TRUE;
        if ($optionsST['shipping'] == $optionsST['amount']) {
            $optionsST['shipping'] = 0;
        }
    }
    // // Sanity check -- if tax-included pricing is causing problems, remove the numbers and put them in a comment instead:
    // $stDiffTaxOnly = (strval($sumOfLineItems - $sumOfLineTax - round($optionsST['amount'], 2)) + 0);
    // if (DISPLAY_PRICE_WITH_TAX == 'true' && $stDiffTaxOnly == 0 && ($optionsST['tax_cart'] != 0 && $sumOfLineTax != 0)) {
    // $optionsNB['DESC'] = 'Tax included in prices: ' . $sumOfLineTax . ' (' . $optionsST['tax_cart'] . ') ';
    // $optionsST['tax_cart'] = 0;
    // for ($k=1, $n=$numberOfLineItemsProcessed+1; $k<$n; $k++) {
    // if (isset($optionsLI["tax_$k"])) unset($optionsLI["tax_$k"]);
    // }
    // }
    // // Do sanity check -- if any of the line-item subtotal math doesn't add up properly, skip line-item details,
    // // so that the order can go through even though PayPal isn't being flexible to handle Zen Cart's diversity
    // if ((strval($subTotalTax) - strval($sumOfLineTax)) > 0.02) {
    // $ipn_logging('getLineItemDetails 3', 'Tax Subtotal does not match sum of taxes for line-items. Tax details are being removed from line-item submission data.' . "\n" . $sumOfLineTax . ' ' . $subTotalTax . print_r(array_merge($optionsST, $optionsLI), true));
    // for ($k=1, $n=$numberOfLineItemsProcessed+1; $k<$n; $k++) {
    // if (isset($optionsLI["tax_$k"])) unset($optionsLI["tax_$k"]);
    // }
    // $subTotalTax = 0;
    // $sumOfLineTax = 0;
    // }
    // // If coupons exist and there's a calculation problem, then it's likely that taxes are incorrect, so reset L_TAXAMTn values
    // if ($creditsApplied > 0 && (strval($optionsST['tax_cart']) != strval($sumOfLineTax))) {
    // $pre = $optionsLI;
    // for ($k=1, $n=$numberOfLineItemsProcessed+1; $k<$n; $k++) {
    // if (isset($optionsLI["tax_$k"])) unset($optionsLI["tax_$k"]);
    // }
    // $ipn_logging('getLineItemDetails 4', 'Coupons/Discounts have affected tax calculations, so tax details are being removed from line-item submission data.' . "\n" . $sumOfLineTax . ' ' . $optionsST['tax_cart'] . "\n" . print_r(array_merge($optionsST, $pre, $optionsNB), true) . "\nAFTER:" . print_r(array_merge($optionsST, $optionsLI, $optionsNB), TRUE));
    // $subTotalTax = 0;
    // $sumOfLineTax = 0;
    // }
    // disable line-item tax details, leaving only TAXAMT subtotal as tax indicator
    for ($k = 1, $n = $numberOfLineItemsProcessed + 1; $k < $n; $k++) {
        if (isset($optionsLI["tax_{$k}"])) {
            unset($optionsLI["tax_{$k}"]);
        }
    }
    // check subtotals
    if (strval($optionsST['subtotal']) > 0 && strval($subTotalLI) > 0 && strval($subTotalLI) != strval($optionsST['subtotal']) || strval($subTotalLI) - strval($sumOfLineItems) != 0) {
        $ipn_logging('getLineItemDetails 5', 'Line-item subtotals do not add up properly. Line-item-details skipped.' . "\n" . strval($sumOfLineItems) . ' ' . strval($subTotalLI) . ' ' . print_r(array_merge($optionsST, $optionsLI), true));
        $optionsLI = array();
        $optionsLI["item_name_0"] = MODULE_PAYMENT_PAYPAL_PURCHASE_DESCRIPTION_TITLE;
        $optionsLI["amount_0"] = $sumOfLineItems = $subTotalLI = $optionsST['subtotal'];
    }
    // check whether discounts are causing a problem
    if (strval($optionsST['subtotal']) < 0) {
        $pre = array_merge($optionsST, $optionsLI);
        $optionsST['subtotal'] = $optionsST['amount'];
        $optionsLI = array();
        $optionsLI["item_name_0"] = MODULE_PAYMENT_PAYPAL_PURCHASE_DESCRIPTION_TITLE;
        $optionsLI["amount_0"] = $sumOfLineItems = $subTotalLI = $optionsST['subtotal'];
        if ($optionsST['amount'] < $optionsST['tax_cart']) {
            $optionsST['tax_cart'] = 0;
        }
        if ($optionsST['amount'] < $optionsST['shipping']) {
            $optionsST['shipping'] = 0;
        }
        $discountProblemsFlag = TRUE;
        $this->zcLog('getLineItemDetails 6', 'Discounts have caused the subtotal to calculate incorrectly. Line-item-details cannot be submitted.' . "\nBefore:" . print_r($pre, TRUE) . "\nAfter:" . print_r(array_merge($optionsST, $optionsLI), true));
    }
    // if amount or subtotal values are 0 (ie: certain OT modules disabled), we have to get subtotals manually
    if ((!isset($optionsST['amount']) || $optionsST['amount'] == 0 || $flagSubtotalsUnknownYet == TRUE || $optionsST['subtotal'] == 0) && $discountProblemsFlag != TRUE) {
        $optionsST['subtotal'] = $sumOfLineItems;
        $optionsST['tax_cart'] = $sumOfLineTax;
        if ($subTotalShipping > 0) {
            $optionsST['shipping'] = $subTotalShipping;
        }
        $optionsST['amount'] = $sumOfLineItems + $optionsST['tax_cart'] + $optionsST['shipping'];
    }
    ipn_logging('getLineItemDetails 7 - subtotal comparisons', 'BEFORE line-item calcs: ' . print_r($subtotalPRE, true) . ' - AFTER doing line-item calcs: ' . print_r(array_merge($optionsST, $optionsLI, $optionsNB), true));
    // if subtotals are not adding up correctly, then skip sending any line-item or subtotal details to PayPal
    $stAll = round(strval($optionsST['subtotal'] + $optionsST['tax_cart'] + $optionsST['shipping']), 2);
    $stDiff = strval($optionsST['amount'] - $stAll);
    $stDiffRounded = strval($stAll - round($optionsST['amount'], 2)) + 0;
    // unset any subtotal values that are zero
    if (isset($optionsST['subtotal']) && $optionsST['subtotal'] == 0) {
        unset($optionsST['subtotal']);
    }
    if (isset($optionsST['tax_cart']) && $optionsST['tax_cart'] == 0) {
        unset($optionsST['tax_cart']);
    }
    if (isset($optionsST['shipping']) && $optionsST['shipping'] == 0) {
        unset($optionsST['shipping']);
    }
    // tidy up all values so that they comply with proper format (number_format(xxxx,2) for PayPal US use )
    if (!defined('PAYPALWPP_SKIP_LINE_ITEM_DETAIL_FORMATTING') || PAYPALWPP_SKIP_LINE_ITEM_DETAIL_FORMATTING != 'true' || in_array($order->info['currency'], array('JPY', 'NOK', 'HUF'))) {
        if (is_array($optionsST)) {
            foreach ($optionsST as $key => $value) {
                $optionsST[$key] = number_format($value, (int) $currencies->get_decimal_places($restrictedCurrency) == 0 ? 0 : 2);
            }
        }
        if (is_array($optionsLI)) {
            foreach ($optionsLI as $key => $value) {
                if (substr($key, 0, 8) == 'tax_' && ($optionsLI[$key] == '' || $optionsLI[$key] == 0)) {
                    unset($optionsLI[$key]);
                } else {
                    if (strstr($key, 'amount')) {
                        $optionsLI[$key] = number_format($value, (int) $currencies->get_decimal_places($restrictedCurrency) == 0 ? 0 : 2);
                    }
                }
            }
        }
    }
    ipn_logging('getLineItemDetails 8', 'checking subtotals... ' . "\n" . print_r(array_merge(array('calculated total' => number_format($stAll, (int) $currencies->get_decimal_places($restrictedCurrency) == 0 ? 0 : 2)), $optionsST), true) . "\n-------------------\ndifference: " . ($stDiff + 0) . '  (abs+rounded: ' . ($stDiffRounded + 0) . ')');
    if ($stDiffRounded != 0) {
        ipn_logging('getLineItemDetails 9', 'Subtotals Bad. Skipping line-item/subtotal details');
        return array();
    }
    ipn_logging('getLineItemDetails 10', 'subtotals balance - okay' . "\nSubmitting:   " . print_r(array_merge($optionsST, $optionsLI, $optionsNB), true));
    // Send Subtotal and LineItem results back to be submitted to PayPal
    return array_merge($optionsST, $optionsLI, $optionsNB);
}