News:

You may pay someone to create your store, or you visit our seminar and become a professional yourself with the silver certification

Main Menu

Critical pricing bug — variant/modifier price is added to base price before disc

Started by hazael, November 24, 2025, 16:47:35 PM

Previous topic - Next topic

hazael

I would like to report a serious pricing issue in VirtueMart 4.x that affects all products using price modifiers (variants, CustomFieldsForAll additional charges, or any customfield with a price). The problem lies in how VM calculates prices inside calculationHelper.

Currently, VirtueMart adds the modifier price directly to the product's base price (the regular, non-discounted price) and only then applies discount rules such as DATax or DBTax. This results in the modifier being discounted, which is mathematically incorrect.

A simple example makes the problem obvious:
Regular price: 1000
Discount: -20% (so discounted price should be 800)
Variant price (modifier): +200

VirtueMart currently calculates:
(1000 + 200) – 20% = 960

Correct calculation should be:
800 + 200 = 1000

In the current VM behavior, the additional charge (+200) is also discounted, even though it represents a fixed extra cost that should never be affected by product-level discounts.

This leads to incorrect totals on the product page, in the cart, and during checkout, and also produces incorrect VAT calculations.

The root cause is in administrator/components/com_virtuemart/helpers/calculationh.php, inside the getProductPrices() method. The variant/modifier value is added to basePrice before discount rules are applied, which causes the modifier to be discounted together with the product.

Correct behavior should be:

Calculate the product's discounted price normally.

Keep the modifier price separate.

Calculate VAT on the modifier independently.

Add the modifier (net + VAT) after discounts have been applied.

Do not apply any discount rules to the modifier.

This ensures correct mathematical behavior:

FinalPrice = DiscountedPrice + Modifier
instead of the current incorrect approach:

FinalPrice = (BasePrice + Modifier) – Discount

This issue affects every VirtueMart shop using price modifiers (not only CF4All users). If needed, I can provide a working patch or minimal diff for getProductPrices() that fully fixes the logic.


hazael

Edit the following file: administrator/components/com_virtuemart/helpers/calculationh.php

Step A — REMOVE wrong logic
Find this block (the one adding variant too early):
if (!empty($variant)) {
    $variant = $this->roundInternal($this->_currencyDisplay->convertCurrencyTo((int) $this->productCurrency, doubleval($variant),true));
    $basePriceShopCurrency = $basePriceShopCurrency + $variant;
    $this->productPrices['basePrice'] = $this->productPrices['basePriceVariant'] = $basePriceShopCurrency;
}
Delete it completely.


Step B — ADD the corrected logic
Immediately after this existing block:
if (empty($this->productPrices['basePriceVariant'])) {
    $this->productPrices['basePriceVariant'] = $this->productPrices['basePrice'];
}

Insert the following NEW CODE:

/* ============================================================
   FIX: Correct handling of variant/modifier prices in VirtueMart
   ------------------------------------------------------------
   VM incorrectly adds variant price to the base product price
   BEFORE applying discount rules. This causes the variant to be
   discounted, which is mathematically wrong.

   Here we calculate the variant separately and add it *after*
   discounts and tax calculations – which is the correct logic.
   ============================================================ */

// 1. Convert variant price to shop currency but DO NOT add it to base price
$variantNet = 0.0;

if (!empty($variant)) {
    // Variant net amount converted to shop currency
    $variantNet = $this->roundInternal(
        $this->_currencyDisplay->convertCurrencyTo(
            (int)$this->productCurrency,
            (double)$variant,
            true
        )
    );
}

// 2. After VM finished discount calculations, add the variant
if (!empty($variantNet)) {

    // Calculate VAT on variant separately
    $variantGross = $variantNet;

    // Apply VAT/TAX rules to variant
    if (!empty($this->rules['Tax'])) {
        $variantGross = $this->roundInternal(
            $this->executeCalculation($this->rules['Tax'], $variantGross, true),
            'salesPrice'
        );
    }

    if (!empty($this->rules['VatTax'])) {
        $variantGross = $this->roundInternal(
            $this->executeCalculation($this->rules['VatTax'], $variantGross, true),
            'salesPrice'
        );
    }

    // Update priceWithoutTax (variantNet)
    if (isset($this->productPrices['priceWithoutTax'])) {
        $this->productPrices['priceWithoutTax'] += $variantNet;
    }

    // Update VAT amount (difference between gross and net)
    if (isset($this->productPrices['taxAmount'])) {
        $this->productPrices['taxAmount'] += ($variantGross - $variantNet);
    }

    // Add variant to final salesPrice (correct placement)
    if (isset($this->productPrices['salesPrice'])) {
        $this->productPrices['salesPrice'] += $variantGross;
    }

    // Add variant to unit price if packaging is used
    if (!empty($product->product_packaging) && $product->product_packaging != '0.0000') {
        $this->productPrices['unitPrice'] = $this->productPrices['salesPrice'] / $product->product_packaging;
    }

    // Save modifier for output
    $this->productPrices['variantModification'] = $variantNet;
}
/* ========================= END OF FIX ============================ */