I've looked a little deeper....
Zen cart calculates the base price * quantity before applying the currency rate and rounding.
It appears that there are three functions primarily involved with this:
function display_price();
function format();
function show_total();
Getting the correct per item calculation is easy enough by modifying:
Code:
function display_price($products_price, $products_tax, $quantity = 1) {
return $this->format(zen_add_tax($products_price, $products_tax) * $quantity);
}
}
to:
Code:
function display_price($products_price, $products_tax, $quantity = 1) {
if (empty($currency_type)) $currency_type = $_SESSION['currency'];
$rate = (zen_not_null($currency_value)) ? $currency_value : $this->currencies[$currency_type]['value'];
$products_price = zen_round($products_price * $rate, $this->currencies[$currency_type]['decimal_places']);
$products_price = $products_price * $quantity;
$format_string = $this->currencies[$currency_type]['symbol_left'] . number_format($products_price, $this->currencies[$currency_type]['decimal_places'], $this->currencies[$currency_type]['decimal_point'], $this->currencies[$currency_type]['thousands_point']) . $this->currencies[$currency_type]['symbol_right'];
if ((DOWN_FOR_MAINTENANCE=='true' and DOWN_FOR_MAINTENANCE_PRICES_OFF=='true') and (!strstr(EXCLUDE_ADMIN_IP_FOR_MAINTENANCE, $_SERVER['REMOTE_ADDR']))) {
$format_string= '';
}
return $format_string;
}
}
This uses parts of the format() function hard coded into display_price() rather than calling it. Not too much of a drama (although I don't know what else this might brake), except the cart totals uses a huge function - show_total() which does the quantity calculations before function format() which applies the currency and rounding calculation.
All this requires a fair bit of time to put right - it has taken me an afternoon writing the above alternate function and realising the issue with the other function.
This would appear to be a fundamental floor in how zen cart calculates pricing. I am sure there was a clear design objective that steered the development down this path but I can't think what it might have been.