/** * Prepare subtotal and line-item detail content to send to PayPal */ function 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) { $this->zcLog('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) { $this->zcLog('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['AMT'] = 0; $optionsST['ITEMAMT'] = 0; $optionsST['TAXAMT'] = 0; $optionsST['SHIPPINGAMT'] = 0; $optionsST['SHIPDISCAMT'] = 0; $optionsST['HANDLINGAMT'] = 0; $optionsST['INSURANCEAMT'] = 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['SHIPPINGAMT'] = round($order_totals[$i]['value'], 2); } if ($order_totals[$i]['code'] == 'ot_total') { $optionsST['AMT'] = round($order_totals[$i]['value'], 2); } if ($order_totals[$i]['code'] == 'ot_tax') { $optionsST['TAXAMT'] = round($order_totals[$i]['value'], 2); } if ($order_totals[$i]['code'] == 'ot_subtotal') { $optionsST['ITEMAMT'] = round($order_totals[$i]['value'], 2); } if (strstr($order_totals[$i]['code'], 'insurance')) { $optionsST['INSURANCEAMT'] += round($order_totals[$i]['value'], 2); } //$optionsST['SHIPDISCAMT'] = ''; // Not applicable } 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['ITEMAMT'] -= $creditsApplied; } if ($surcharges > 0) { $optionsST['ITEMAMT'] += $surcharges; } // Handle tax-included scenario if (DISPLAY_PRICE_WITH_TAX == 'true') { $optionsST['TAXAMT'] = 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 = substr($_SESSION['shipping']['id'], 0, strpos($_SESSION['shipping']['id'], '_')); if (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['SHIPPINGAMT'] += $taxAdjustmentForShipping; $optionsST['TAXAMT'] -= $taxAdjustmentForShipping; } } $flagSubtotalsUnknownYet = $optionsST['SHIPPINGAMT'] + $optionsST['SHIPDISCAMT'] + $optionsST['AMT'] + $optionsST['TAXAMT'] + $optionsST['ITEMAMT'] + $optionsST['INSURANCEAMT'] == 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; } // loop thru all products to prepare details of quantity and price. for ($i = 0, $n = sizeof($order->products), $k = 0; $i < $n; $i++, $k++) { // PayPal won't accept zero-value line-items, so skip this entry if price is zero if ($order->products[$i]['final_price'] == 0) { continue; } $optionsLI["L_NUMBER{$k}"] = $order->products[$i]['model']; $optionsLI["L_NAME{$k}"] = $order->products[$i]['name']; // Append *** if out-of-stock. $optionsLI["L_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) { $optionsLI["L_DESC{$k}"] = ''; for ($j = 0, $n2 = sizeof($order->products[$i]['attributes']); $j < $n2; $j++) { $optionsLI["L_DESC{$k}"] .= "\n " . $order->products[$i]['attributes'][$j]['option'] . ': ' . $order->products[$i]['attributes'][$j]['value']; } // end loop } // endif attribute-info // check for rounding problems with taxes $m1 = zen_round($order->products[$i]['qty'] * $order->products[$i]['final_price'], $currencies->currencies[$_SESSION['currency']]['decimal_places']); $m2 = $order->products[$i]['qty'] * zen_round($order->products[$i]['final_price'], $currencies->currencies[$_SESSION['currency']]['decimal_places']); $n1 = $order->products[$i]['qty'] * zen_calculate_tax($order->products[$i]['final_price'], $order->products[$i]['tax']); $n2 = zen_calculate_tax($order->products[$i]['qty'] * $order->products[$i]['final_price'], $order->products[$i]['tax']); if ($m1 != $m2 || zen_round($n1, $currencies->currencies[$_SESSION['currency']]['decimal_places']) != zen_round($n2, $currencies->currencies[$_SESSION['currency']]['decimal_places'])) { $flag_treat_as_partial = true; } // PayPal can't handle partial-quantity values, so fudge it here if ($flag_treat_as_partial || $order->products[$i]['qty'] != (int) $order->products[$i]['qty']) { $optionsLI["L_NAME{$k}"] = '(' . $order->products[$i]['qty'] . ' x ) ' . $optionsLI["L_NAME{$k}"]; $optionsLI["L_AMT{$k}"] = zen_round($order->products[$i]['qty'] * $order->products[$i]['final_price'], $currencies->currencies[$_SESSION['currency']]['decimal_places']); $optionsLI["L_TAXAMT{$k}"] = zen_calculate_tax(zen_round($order->products[$i]['qty'] * $order->products[$i]['final_price'], $currencies->currencies[$_SESSION['currency']]['decimal_places']), $order->products[$i]['tax']); $optionsLI["L_QTY{$k}"] = 1; } else { $optionsLI["L_AMT{$k}"] = $order->products[$i]['final_price']; $optionsLI["L_QTY{$k}"] = $order->products[$i]['qty']; $optionsLI["L_TAXAMT{$k}"] = zen_calculate_tax(1 * $order->products[$i]['final_price'], $order->products[$i]['tax']); } // For tax-included pricing, combine tax with price instead of treating separately: if (DISPLAY_PRICE_WITH_TAX == 'true') { $optionsLI["L_AMT{$k}"] += $optionsLI["L_TAXAMT{$k}"]; $optionsLI["L_TAXAMT{$k}"] = 0; } $subTotalLI += $optionsLI["L_QTY{$k}"] * $optionsLI["L_AMT{$k}"]; $subTotalTax += $optionsLI["L_QTY{$k}"] * $optionsLI["L_TAXAMT{$k}"]; // add line-item for one-time charges on this product if ($order->products[$i]['onetime_charges'] != 0) { $k++; $optionsLI["L_NAME{$k}"] = MODULES_PAYMENT_PAYPALWPP_LINEITEM_TEXT_ONETIME_CHARGES_PREFIX . substr(htmlentities($order->products[$i]['name'], ENT_QUOTES, 'UTF-8'), 0, 120); $optionsLI["L_AMT{$k}"] = $order->products[$i]['onetime_charges']; $optionsLI["L_QTY{$k}"] = 1; $optionsLI["L_TAXAMT{$k}"] = zen_calculate_tax($order->products[$i]['onetime_charges'], $order->products[$i]['tax']); $subTotalLI += $order->products[$i]['onetime_charges']; $subTotalTax += $optionsLI["L_TAXAMT{$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["L_NAME{$k}"] = MODULES_PAYMENT_PAYPALWPP_LINEITEM_TEXT_SURCHARGES_SHORT; $optionsLI["L_DESC{$k}"] = MODULES_PAYMENT_PAYPALWPP_LINEITEM_TEXT_SURCHARGES_LONG; $optionsLI["L_AMT{$k}"] = $surcharges; $optionsLI["L_QTY{$k}"] = 1; $subTotalLI += $surcharges; } // add line items for discounts such as gift certificates and coupons if ($creditsApplied > 0) { $numberOfLineItemsProcessed++; $k = $numberOfLineItemsProcessed; $optionsLI["L_NAME{$k}"] = MODULES_PAYMENT_PAYPALWPP_LINEITEM_TEXT_DISCOUNTS_SHORT; $optionsLI["L_DESC{$k}"] = MODULES_PAYMENT_PAYPALWPP_LINEITEM_TEXT_DISCOUNTS_LONG; $optionsLI["L_AMT{$k}"] = -1 * $creditsApplied; $optionsLI["L_QTY{$k}"] = 1; $subTotalLI -= $creditsApplied; } // Reformat properly for ($k = 0, $n = $numberOfLineItemsProcessed + 1; $k < $n; $k++) { // Replace & and = and % with * if found. $optionsLI["L_NAME{$k}"] = str_replace(array('&', '=', '%'), '*', $optionsLI["L_NAME{$k}"]); if (isset($optionsLI["L_DESC{$k}"])) { $optionsLI["L_DESC{$k}"] = str_replace(array('&', '=', '%'), '*', $optionsLI["L_DESC{$k}"]); } if (isset($optionsLI["L_NUMBER{$k}"])) { $optionsLI["L_NUMBER{$k}"] = str_replace(array('&', '=', '%'), '*', $optionsLI["L_NUMBER{$k}"]); } // Remove HTML markup if found $optionsLI["L_NAME{$k}"] = zen_clean_html($optionsLI["L_NAME{$k}"], 'strong'); if (isset($optionsLI["L_DESC{$k}"])) { $optionsLI["L_DESC{$k}"] = zen_clean_html($optionsLI["L_DESC{$k}"], 'strong'); } // reformat properly according to API specs $optionsLI["L_NAME{$k}"] = substr($optionsLI["L_NAME{$k}"], 0, 127); if (isset($optionsLI["L_NUMBER{$k}"])) { $optionsLI["L_NUMBER{$k}"] = substr($optionsLI["L_NUMBER{$k}"], 0, 127); } if (isset($optionsLI["L_DESC{$k}"]) && $optionsLI["L_DESC{$k}"] == '') { unset($optionsLI["L_DESC{$k}"]); } if (isset($optionsLI["L_DESC{$k}"])) { $optionsLI["L_DESC{$k}"] = substr($optionsLI["L_DESC{$k}"], 0, 127); } if (isset($optionsLI["L_TAXAMT{$k}"]) && ($optionsLI["L_TAXAMT{$k}"] != '' || $optionsLI["L_TAXAMT{$k}"] > 0)) { $optionsLI["L_TAXAMT{$k}"] = round($optionsLI["L_TAXAMT{$k}"], 2); } } /** * PayPal says their math works like this: * a) ITEMAMT = L_AMTn * L_QTYn * b) TAXAMT = L_QTYn * L_TAXAMTn * c) AMT = ITEMAMT + SHIPPINGAMT + HANDLINGAMT + TAXAMT */ // Sanity Check of line-item subtotals for ($j = 0; $j < $k; $j++) { $itemAMT = $optionsLI["L_AMT{$j}"]; $itemQTY = $optionsLI["L_QTY{$j}"]; $itemTAX = isset($optionsLI["L_TAXAMT{$j}"]) ? $optionsLI["L_TAXAMT{$j}"] : 0; $sumOfLineItems += $itemQTY * $itemAMT; $sumOfLineTax += $itemQTY * $itemTAX; } $sumOfLineItems = round($sumOfLineItems, 2); $sumOfLineTax = round($sumOfLineTax, 2); if ($sumOfLineItems == 0) { $sumOfLineTax = 0; $optionsLI = array(); $discountProblemsFlag = TRUE; if ($optionsST['SHIPPINGAMT'] == $optionsST['AMT']) { $optionsST['SHIPPINGAMT'] = 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['AMT'], 2)) + 0; if (DISPLAY_PRICE_WITH_TAX == 'true' && $stDiffTaxOnly == 0) { $optionsNB['DESC'] = 'Tax included in prices: ' . $sumOfLineTax . ' (' . $optionsST['TAXAMT'] . ') '; $optionsST['TAXAMT'] = 0; for ($k = 0, $n = $numberOfLineItemsProcessed + 1; $k < $n; $k++) { if (isset($optionsLI["L_TAXAMT{$k}"])) { unset($optionsLI["L_TAXAMT{$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) { $this->zcLog('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 = 0, $n = $numberOfLineItemsProcessed + 1; $k < $n; $k++) { if (isset($optionsLI["L_TAXAMT{$k}"])) { unset($optionsLI["L_TAXAMT{$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['TAXAMT']) != strval($sumOfLineTax)) { $pre = $optionsLI; for ($k = 0, $n = $numberOfLineItemsProcessed + 1; $k < $n; $k++) { if (isset($optionsLI["L_TAXAMT{$k}"])) { unset($optionsLI["L_TAXAMT{$k}"]); } } $this->zcLog('getLineItemDetails 4', 'Coupons/Discounts have affected tax calculations, so tax details are being removed from line-item submission data.' . "\n" . $sumOfLineTax . ' ' . $optionsST['TAXAMT'] . "\n" . print_r(array_merge($optionsST, $pre, $optionsNB), true) . "\nAFTER:" . print_r(array_merge($optionsST, $optionsLI, $optionsNB), TRUE)); $subTotalTax = 0; $sumOfLineTax = 0; } if (TRUE) { // disable line-item tax details, leaving only TAXAMT subtotal as tax indicator for ($k = 0, $n = $numberOfLineItemsProcessed + 1; $k < $n; $k++) { if (isset($optionsLI["L_TAXAMT{$k}"])) { unset($optionsLI["L_TAXAMT{$k}"]); } } } // check subtotals if (strval($optionsST['ITEMAMT']) > 0 && strval($subTotalLI) > 0 && strval($subTotalLI) != strval($optionsST['ITEMAMT']) || strval($subTotalLI) - strval($sumOfLineItems) != 0) { $this->zcLog('getLineItemDetails 5', 'Line-item subtotals do not add up properly. Line-item-details skipped.' . "\n" . (double) $sumOfLineItems . ' ' . (double) $subTotalTax . print_r(array_merge($optionsST, $optionsLI), true)); $optionsLI = array(); } // check whether discounts are causing a problem if (strval($optionsST['ITEMAMT']) < 0) { $pre = array_merge($optionsST, $optionsLI); $optionsLI = array(); $optionsST['ITEMAMT'] = $optionsST['AMT']; if ($optionsST['AMT'] < $optionsST['TAXAMT']) { $optionsST['TAXAMT'] = 0; } if ($optionsST['AMT'] < $optionsST['SHIPPINGAMT']) { $optionsST['SHIPPINGAMT'] = 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 AMT or ITEMAMT values are 0 (ie: certain OT modules disabled) or we've started express checkout without going through normal checkout flow, we have to get subtotals manually if ((!isset($optionsST['AMT']) || $optionsST['AMT'] == 0 || $flagSubtotalsUnknownYet == TRUE || $optionsST['ITEMAMT'] == 0) && $discountProblemsFlag != TRUE) { $optionsST['ITEMAMT'] = $sumOfLineItems; $optionsST['TAXAMT'] = $sumOfLineTax; if ($subTotalShipping > 0) { $optionsST['SHIPPINGAMT'] = $subTotalShipping; } $optionsST['AMT'] = $sumOfLineItems + $optionsST['TAXAMT'] + $optionsST['SHIPPINGAMT']; } $this->zcLog('getLineItemDetails 7 - subtotal comparisons', 'BEFORE line-item calcs: ' . print_r($subtotalPRE, true) . ($flagSubtotalsUnknownYet == TRUE ? 'Subtotals Unknown Yet' : '') . ' - 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['ITEMAMT'] + $optionsST['TAXAMT'] + $optionsST['SHIPPINGAMT'] + $optionsST['SHIPDISCAMT'] + $optionsST['HANDLINGAMT'] + $optionsST['INSURANCEAMT']), 2); $stDiff = strval($optionsST['AMT'] - $stAll); $stDiffRounded = strval($stAll - round($optionsST['AMT'], 2)) + 0; // unset any subtotal values that are zero if (isset($optionsST['ITEMAMT']) && $optionsST['ITEMAMT'] == 0) { unset($optionsST['ITEMAMT']); } if (isset($optionsST['TAXAMT']) && $optionsST['TAXAMT'] == 0) { unset($optionsST['TAXAMT']); } if (isset($optionsST['SHIPPINGAMT']) && $optionsST['SHIPPINGAMT'] == 0) { unset($optionsST['SHIPPINGAMT']); } if (isset($optionsST['SHIPDISCAMT']) && $optionsST['SHIPDISCAMT'] == 0) { unset($optionsST['SHIPDISCAMT']); } if (isset($optionsST['HANDLINGAMT']) && $optionsST['HANDLINGAMT'] == 0) { unset($optionsST['HANDLINGAMT']); } if (isset($optionsST['INSURANCEAMT']) && $optionsST['INSURANCEAMT'] == 0) { unset($optionsST['INSURANCEAMT']); } // 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'))) { if (is_array($optionsST)) { foreach ($optionsST as $key => $value) { $optionsST[$key] = number_format($value, $order->info['currency'] == 'JPY' ? 0 : 2); } } if (is_array($optionsLI)) { foreach ($optionsLI as $key => $value) { if (substr($key, 0, 8) == 'L_TAXAMT' && ($optionsLI[$key] == '' || $optionsLI[$key] == 0)) { unset($optionsLI[$key]); } else { if (strstr($key, 'AMT')) { $optionsLI[$key] = number_format($value, $order->info['currency'] == 'JPY' ? 0 : 2); } } } } } $this->zcLog('getLineItemDetails 8', 'checking subtotals... ' . "\n" . print_r(array_merge(array('calculated total' => number_format($stAll, $order->info['currency'] == 'JPY' ? 0 : 2)), $optionsST), true) . "\n-------------------\ndifference: " . ($stDiff + 0) . ' (abs+rounded: ' . ($stDiffRounded + 0) . ')'); if ($stDiffRounded != 0) { $this->zcLog('getLineItemDetails 9', 'Subtotals Bad. Skipping line-item/subtotal details'); return array(); } $this->zcLog('getLineItemDetails 10', 'subtotals balance - okay'); // Send Subtotal and LineItem results back to be submitted to PayPal return array_merge($optionsST, $optionsLI, $optionsNB); }
/** * Prepare subtotal and line-item detail content to send to PayPal */ function getLineItemDetails() { 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 for ($i = 0, $n = sizeof($order_totals); $i < $n; $i++) { if ($order_totals[$i]['code'] == 'ot_subtotal') { $optionsST['ITEMAMT'] = round($order_totals[$i]['value'], 2); } if ($order_totals[$i]['code'] == 'ot_tax') { $optionsST['TAXAMT'] = round($order_totals[$i]['value'], 2); } if ($order_totals[$i]['code'] == 'ot_shipping') { $optionsST['SHIPPINGAMT'] = round($order_totals[$i]['value'], 2); } if ($order_totals[$i]['code'] == 'ot_total') { $optionsST['AMT'] = round($order_totals[$i]['value'], 2); } $optionsST['HANDLINGAMT'] = 0; 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['HANDLINGAMT'] += $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['SHIPPINGAMT'] += $taxAdjustmentForShipping; $optionsST['TAXAMT'] -= $taxAdjustmentForShipping; } } // loop thru all products to display quantity and price. Appends *** if out-of-stock. for ($i = 0, $n = sizeof($order->products), $k = 0; $i < $n; $i++, $k++) { $optionsLI["L_NUMBER{$k}"] = $order->products[$i]['model']; $optionsLI["L_QTY{$k}"] = (int) $order->products[$i]['qty']; $optionsLI["L_NAME{$k}"] = $order->products[$i]['name']; $optionsLI["L_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["L_NAME{$k}"] .= "\n " . $order->products[$i]['attributes'][$j]['option'] . ': ' . $order->products[$i]['attributes'][$j]['value']; } // end loop } // endif attribute-info $optionsLI["L_AMT{$k}"] = $order->products[$i]['final_price']; $optionsLI["L_TAXAMT{$k}"] = zen_calculate_tax($order->products[$i]['final_price'], $order->products[$i]['tax']); // 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["L_NAME{$k}"] = str_replace(array('&', '='), '*', $optionsLI["L_NAME{$k}"]); $optionsLI["L_NAME{$k}"] = zen_clean_html($optionsLI["L_NAME{$k}"], 'strong'); // reformat properly $optionsLI["L_NUMBER{$k}"] = substr($optionsLI["L_NUMBER{$k}"], 0, 127); $optionsLI["L_NAME{$k}"] = substr($optionsLI["L_NAME{$k}"], 0, 127); $optionsLI["L_AMT{$k}"] = $optionsLI["L_AMT{$k}"]; $optionsLI["L_TAXAMT{$k}"] = round($optionsLI["L_TAXAMT{$k}"], 2); } // end for loopthru all products if ($onetimeSum > 0) { $i++; $k++; $optionsLI["L_NUMBER{$k}"] = $k; $optionsLI["L_NAME{$k}"] = 'One-Time Charges'; $optionsLI["L_AMT{$k}"] = $onetimeSum; $optionsLI["L_TAXAMT{$k}"] = $onetimeTax; $optionsLI["L_QTY{$k}"] = 1; } // handle discounts such as gift certificates and coupons if ($creditsApplied > 0) { $optionsST['HANDLINGAMT'] -= $creditsApplied; } // add all one-time charges $optionsST['ITEMAMT'] += $onetimeSum; //ensure things are not negative $optionsST['HANDLINGAMT'] = abs(strval($optionsST['HANDLINGAMT'])); // ensure all numbers are non-negative if (is_array($optionsST)) { foreach ($optionsST as $key => $value) { $optionsST[$key] = abs(strval($value)); } } if (is_array($optionsLI)) { foreach ($optionsLI as $key => $value) { if (strstr($key, 'AMT')) { $optionsLI[$key] = abs(strval($value)); } } } // subtotals have to add up to AMT // Thus, if there is a discrepancy, make adjustment to HANDLINGAMT: $st = $optionsST['ITEMAMT'] + $optionsST['TAXAMT'] + $optionsST['SHIPPINGAMT'] + $optionsST['HANDLINGAMT']; if ($st != $optionsST['AMT']) { $optionsST['HANDLINGAMT'] += strval($optionsST['AMT'] - $st); } /* //PayPal API spec contradicts itself ... and apparently neither of these "requirements" are enforced. //Thus skipping this section for now: // according to API specs, these cannot be set if they contain zero values, so unset if they are zero: if ($optionsST['TAXAMT'] == 0) unset($optionsST['TAXAMT']); if ($optionsST['SHIPPINGAMT'] == 0) unset($optionsST['SHIPPINGAMT']); if ($optionsST['HANDLINGAMT'] == 0) unset($optionsST['HANDLINGAMT']); // set missing subtotals if they are zero values, since all must be submitted if (!isset($optionsST['TAXAMT'])) $optionsST['TAXAMT'] = 0; if (!isset($optionsST['SHIPPINGAMT'])) $optionsST['SHIPPINGAMT'] = 0; if (!isset($optionsST['HANDLINGAMT'])) $optionsST['HANDLINGAMT'] = 0; */ // 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: // 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 for ($j = 0; $j < $k; $j++) { $itemAMT = $optionsLI["L_AMT{$j}"]; $itemTAX = $optionsLI["L_TAXAMT{$j}"]; $itemQTY = $optionsLI["L_QTY{$j}"]; $sumOfLineItems += $itemQTY * $itemAMT; $sumOfLineTax += round($itemQTY * $itemTAX, 2); } if ((double) $optionsST['ITEMAMT'] != (double) strval($sumOfLineItems)) { $optionsLI = array(); $this->zcLog('getLineItemDetails 1', 'Order Subtotal does not match sum of line-item prices. Line-item-details skipped.' . "\n" . (double) $optionsST['ITEMAMT'] . ' ' . (double) $sumOfLineItems); //die('ITEMAMT != $sumOfLineItems ' . $optionsST['ITEMAMT'] . ' ' . $sumOfLineItems); } if ((double) $optionsST['TAXAMT'] != (double) strval($sumOfLineTax)) { $optionsLI = array(); $this->zcLog('getLineItemDetails 2', 'Tax Subtotal does not match sum of taxes for line-items. Line-item-details skipped.' . "\n" . $optionsST['TAXAMT'] . ' ' . $sumOfLineTax); //die('TAXAMT != $sumofLineTax ' . $optionsST['TAXAMT'] . ' ' . $sumOfLineTax); } $this->zcLog('getLineItemDetails 3', 'LineItemDetails: ' . "\n" . ($creditsApplied ? 'Credits apply to this order, so all line-item details are NOT being submitted. Thus, the following data is REDUNDANT' . "\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']) { $this->zcLog('getLineItemDetails 4', 'Not using default currency. Thus, no line-item details can be submitted.'); return array(); } if ($currencies->currencies[$_SESSION['currency']]['value'] != 1) { $this->zcLog('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['ITEMAMT'] + $optionsST['TAXAMT'] + $optionsST['SHIPPINGAMT'] + $optionsST['HANDLINGAMT']), 2); $stDiff = strval($optionsST['AMT'] - $st); $stDiffRounded = strval(abs($st) - abs(round($optionsST['AMT'], 2))); // 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') { if (is_array($optionsST)) { foreach ($optionsST as $key => $value) { $optionsST[$key] = number_format(abs($value), 2); } } if (is_array($optionsLI)) { foreach ($optionsLI as $key => $value) { if (strstr($key, 'AMT')) { $optionsLI[$key] = number_format(abs($value), 2); } } } } $this->zcLog('getLineItemDetails 7', 'checking subtotals... ' . "\nitemamt: " . $optionsST['ITEMAMT'] . "\ntaxamt: " . $optionsST['TAXAMT'] . "\nshippingamt: " . $optionsST['SHIPPINGAMT'] . "\nhandlingamt: " . $optionsST['HANDLINGAMT'] . "\n-------------------\nsubtotal: " . number_format($st, 2) . "\nAMT: " . $optionsST['AMT'] . "\n-------------------\ndifference: " . $stDiff . ' (abs+rounded: ' . $stDiffRounded . ')'); if ($stDiffRounded != 0) { return array(); } //die('bad subtotals'); //return array(); $this->zcLog('getLineItemDetails 8', 'subtotals balance - okay'); if (abs($optionsST['HANDLINGAMT']) == 0) { unset($optionsST['HANDLINGAMT']); } // Send Subtotal and LineItem results back to be submitted to PayPal return array_merge($optionsST, $optionsLI); }
/** * Check if the required stock is available. * * If insufficent stock is available return an out of stock message * * @param int The product id of the product whos's stock is to be checked * @param int Is this amount of stock available * * @TODO naughty html in a function */ function zen_check_stock($products_id, $products_quantity) { $stock_left = zen_get_products_stock($products_id) - $products_quantity; $out_of_stock = ''; if ($stock_left < 0) { $out_of_stock = '<span class="markProductOutOfStock">' . STOCK_MARK_PRODUCT_OUT_OF_STOCK . '</span>'; } return $out_of_stock; }
/** * Method to handle cart Action - multiple add products * * @param string forward destination * @param url parameters * @todo change while loop to a foreach */ function actionMultipleAddProduct($goto, $parameters) { global $messageStack; if ($this->display_debug_messages) { $messageStack->add_session('header', 'FUNCTION ' . __FUNCTION__, 'caution'); } $addCount = 0; if (is_array($_POST['products_id']) && sizeof($_POST['products_id']) > 0) { //echo '<pre>'; echo var_dump($_POST['products_id']); echo '</pre>'; while (list($key, $val) = each($_POST['products_id'])) { $prodId = preg_replace('/[^0-9a-f:.]/', '', $key); if (is_numeric($val) && $val > 0) { $adjust_max = false; $qty = $val; $add_max = zen_get_products_quantity_order_max($prodId); $cart_qty = $this->in_cart_mixed($prodId); $new_qty = $this->adjust_quantity($qty, $prodId, 'shopping_cart'); // bof: adjust new quantity to be same as current in stock $chk_current_qty = zen_get_products_stock($prodId); if (STOCK_ALLOW_CHECKOUT == 'false' && $new_qty > $chk_current_qty) { $new_qty = $chk_current_qty; $messageStack->add_session('shopping_cart', ($this->display_debug_messages ? 'FUNCTION ' . __FUNCTION__ . ': ' : '') . WARNING_PRODUCT_QUANTITY_ADJUSTED . zen_get_products_name($prodId), 'caution'); } // eof: adjust new quantity to be same as current in stock if ($add_max == 1 and $cart_qty == 1) { // do not add $adjust_max = 'true'; } else { // bof: adjust new quantity to be same as current in stock if (STOCK_ALLOW_CHECKOUT == 'false' && $new_qty + $cart_qty > $chk_current_qty) { $adjust_new_qty = 'true'; $alter_qty = $chk_current_qty - $cart_qty; $new_qty = $alter_qty > 0 ? $alter_qty : 0; $messageStack->add_session('shopping_cart', ($this->display_debug_messages ? 'FUNCTION ' . __FUNCTION__ . ': ' : '') . WARNING_PRODUCT_QUANTITY_ADJUSTED . zen_get_products_name($prodId), 'caution'); } // eof: adjust new quantity to be same as current in stock // adjust quantity if needed if ($new_qty + $cart_qty > $add_max and $add_max != 0) { $adjust_max = 'true'; $new_qty = $add_max - $cart_qty; } $this->add_cart($prodId, $this->get_quantity($prodId) + $new_qty); $addCount++; } if ($adjust_max == 'true') { if ($this->display_debug_messages) { $messageStack->add_session('header', 'FUNCTION ' . __FUNCTION__ . '<br>' . ERROR_MAXIMUM_QTY . zen_get_products_name($prodId), 'caution'); } $messageStack->add_session('shopping_cart', ERROR_MAXIMUM_QTY . zen_get_products_name($prodId), 'caution'); } } if (!is_numeric($val) || $val < 0) { // adjust quantity when not a value $chk_link = '<a href="' . zen_href_link(zen_get_info_page($prodId), 'cPath=' . zen_get_generated_category_path_rev(zen_get_products_category_id($prodId)) . '&products_id=' . $prodId) . '">' . zen_get_products_name($prodId) . '</a>'; $messageStack->add_session('header', ERROR_CORRECTIONS_HEADING . ERROR_PRODUCT_QUANTITY_UNITS_SHOPPING_CART . $chk_link . ' ' . PRODUCTS_ORDER_QTY_TEXT . zen_output_string_protected($val), 'caution'); $val = 0; } } // display message if all is good and not on shopping_cart page if ($addCount && DISPLAY_CART == 'false' && $_GET['main_page'] != FILENAME_SHOPPING_CART && $messageStack->size('shopping_cart') == 0) { $messageStack->add_session('header', ($this->display_debug_messages ? 'FUNCTION ' . __FUNCTION__ . ': ' : '') . SUCCESS_ADDED_TO_CART_PRODUCTS, 'success'); } else { if (DISPLAY_CART == 'false') { zen_redirect(zen_href_link(FILENAME_SHOPPING_CART)); } } zen_redirect(zen_href_link($goto, zen_get_all_get_params($parameters))); } }
/** * Check if the required stock is available. * * If insufficent stock is available return an out of stock message * * @param int The product id of the product whos's stock is to be checked * @param int Is this amount of stock available * * @TODO naughty html in a function */ function zen_check_stock($products_id, $products_quantity, $attributes = null, $from = 'products') { // START "Stock by Attributes" if ($from == 'order' && is_array($attributes)) { $tmp_attrib = array(); foreach ($attributes as $attrib) { $tmp_attrib[$attrib['option_id']] = $attrib['value_id']; } $attributes = $tmp_attrib; } $stock_left = zen_get_products_stock($products_id, $attributes) - $products_quantity; // END "Stock by Attributes" $out_of_stock = ''; if ($stock_left < 0) { $out_of_stock = '<span class="markProductOutOfStock">' . STOCK_MARK_PRODUCT_OUT_OF_STOCK . '</span>'; } return $out_of_stock; }
$_SESSION['valid_to_checkout'] = true; $_SESSION['cart']->get_products(true); if ($_SESSION['valid_to_checkout'] == false) { $messageStack->add('header', ERROR_CART_UPDATE, 'error'); zen_redirect(zen_href_link(FILENAME_SHOPPING_CART)); } // Stock Check if (STOCK_CHECK == 'true' && STOCK_ALLOW_CHECKOUT != 'true') { $products = $_SESSION['cart']->get_products(); for ($i = 0, $n = sizeof($products); $i < $n; $i++) { if (zen_check_stock($products[$i]['id'], $products[$i]['quantity'])) { zen_redirect(zen_href_link(FILENAME_SHOPPING_CART)); break; } else { // extra check on stock for mixed YES if (zen_get_products_stock($products[$i]['id']) - $_SESSION['cart']->in_cart_mixed($products[$i]['id']) < 0) { zen_redirect(zen_href_link(FILENAME_SHOPPING_CART)); break; } } } } // if no shipping destination address was selected, use the customers own address as default if (!$_SESSION['sendto']) { $_SESSION['sendto'] = $_SESSION['customer_default_address_id']; } else { // verify the selected shipping address $check_address_query = "SELECT count(*) AS total\n FROM " . TABLE_ADDRESS_BOOK . "\n WHERE customers_id = :customersID\n AND address_book_id = :addressBookID"; $check_address_query = $db->bindVars($check_address_query, ':customersID', $_SESSION['customer_id'], 'integer'); $check_address_query = $db->bindVars($check_address_query, ':addressBookID', $_SESSION['sendto'], 'integer'); $check_address = $db->Execute($check_address_query);
} //echo $messageStack->size('checkout_payment'); //die('here'); // Stock Check $flagAnyOutOfStock = false; $stock_check = array(); if (STOCK_CHECK == 'true') { // bof numinix products variants - check stock if (NMX_PRODUCT_VARIANTS_STATUS == 'true') { // get option_id/option_value_id array $products_attributes = array(); for ($i = 0, $n = sizeof($order->products); $i < $n; $i++) { foreach ($order->products[$i]['attributes'] as $products_attribute) { $products_attributes[$products_attribute['option_id']] = $products_attribute['value_id']; } $stockUpdate = zen_get_products_stock($order->products[$i]['id'], $products_attributes); $stockAvailable = is_array($stockUpdate) ? $stockUpdate['quantity'] : $stockUpdate; if ($stockAvailable - $order->products[$i]['qty'] < 0) { $flagAnyOutOfStock = true; $flagStockCheck = STOCK_MARK_PRODUCT_OUT_OF_STOCK; } } } // eof numinix products variants - check stock for ($i = 0, $n = sizeof($order->products); $i < $n; $i++) { if ($stock_check[$i] = zen_check_stock($order->products[$i]['id'], $order->products[$i]['qty'])) { $flagAnyOutOfStock = true; } } // Out of Stock if (STOCK_ALLOW_CHECKOUT != 'true' && $flagAnyOutOfStock == true) {
$stock_id = $db->Execute($query, false, false, 0, true); if ($stock_id->RecordCount() > 0) { $query = 'select stock_id from ' . TABLE_PRODUCTS_WITH_ATTRIBUTES_STOCK . ' where products_id = :products_id:'; $query = $db->bindVars($query, ':products_id:', $_POST['products_id'], 'integer'); $stock_id = $db->Execute($query, false, false, 0, true); // $_SESSION['stock_idquery'] = $stock_id->RecordCount(); //Check if item is an SBA tracked item, if so, then perform analysis of whether to add or not. } if ($stock_id->RecordCount() > 0) { //Looks like $_SESSION['cart']->in_cart_mixed($prodId) could be used here to pull the attribute related product information to verify same product is being added to cart... This also may help in the shopping_cart routine added for SBA as all SBA products will have this modifier. // $cart_qty = 0; $new_qty = $_POST['cart_quantity']; //Number of items being added (Known to be SBA tracked already) $new_qty = $_SESSION['cart']->adjust_quantity($new_qty, $_POST['products_id'], 'header'); // bof: adjust new quantity to be same as current in stock $chk_current_qty = zen_get_products_stock($product_id, $attributes); $_SESSION['cart']->flag_duplicate_msgs_set = FALSE; $productAttrAreSBA = zen_get_sba_stock_attribute_id($product_id, $attributes, 'products'); if ($productAttrAreSBA === false) { $the_list .= PWA_COMBO_OUT_OF_STOCK . "<br />"; foreach ($_POST['id'] as $key2 => $value2) { $the_list .= TEXT_ERROR_OPTION_FOR . '<span class="alertBlack">' . zen_options_name($key2) . '</span>' . TEXT_INVALID_SELECTION . '<span class="alertBlack">' . ($value == (int) PRODUCTS_OPTIONS_VALUES_TEXT_ID ? TEXT_INVALID_USER_INPUT : zen_values_name($value2)) . '</span>' . '<br />'; } } if (STOCK_ALLOW_CHECKOUT == 'false' && $cart_qty + $new_qty > $chk_current_qty) { $new_qty = $chk_current_qty; $messageStack->add_session('shopping_cart', ($_SESSION['cart']->display_debug_messages ? 'C: FUNCTION ' . __FUNCTION__ . ': ' : '') . WARNING_PRODUCT_QUANTITY_ADJUSTED . zen_get_products_name($_POST['products_id']), 'caution'); $_SESSION['cart']->flag_duplicate_msgs_set = TRUE; } // eof: adjust new quantity to be same as current in stock if ($add_max == 1 and $cart_qty == 1) {
function _draw_js_stock_array($combinations) { if (!(isset($combinations) && is_array($combinations) && sizeof($combinations) > 0)) { return '{}'; } $out = ''; foreach ($combinations[0]['comb'] as $oid => $ovid) { $out .= '{' . '"_' . zen_output_string_protected($ovid) . '"' . ':'; $ovids[] = $ovid; $opts[] = $oid; } if (STOCK_SHOW_ATTRIB_LEVEL_STOCK == 'true') { //Search for quantity in the SBA table... $numbadd = zen_get_products_stock($_GET['products_id'], $ovids); if ($numbadd == 0) { $numbadd = '0'; } $out .= $numbadd; } else { $out .= '1'; } for ($combindex = 1; $combindex < sizeof($combinations); $combindex++) { $comb = $combinations[$combindex]['comb']; for ($i = 0; $i < sizeof($opts) - 1; $i++) { if ($comb[$opts[$i]] != $combinations[$combindex - 1]['comb'][$opts[$i]]) { break; } } $out .= str_repeat('}', sizeof($opts) - 1 - $i) . ','; if ($i < sizeof($opts) - 1) { for ($j = $i; $j < sizeof($opts) - 1; $j++) { $out .= '"_' . zen_output_string_protected($comb[$opts[$j]]) . '"' . ':{'; } } $out .= '"_' . zen_output_string_protected($comb[$opts[sizeof($opts) - 1]]) . '"' . ':'; if (STOCK_SHOW_ATTRIB_LEVEL_STOCK == 'true') { $idvals = array(); foreach ($comb as $ids => $idvalsadd) { $idvals[] = $idvalsadd; } $numadd = zen_get_products_stock($_GET['products_id'], $idvals); if ($numadd == 0) { $numadd = '0'; } $out .= $numadd; } else { $out .= '1'; } } $out .= str_repeat('}', sizeof($opts)); return $out; }