-
-
Notifications
You must be signed in to change notification settings - Fork 6
Description
Line Item Net Price and Total Show 0,00 Despite Correct Shipping Calculation
Short description of the issue
After clicking “Calculate shipping and taxes” during manual order creation, the total tax and shipping calculations are updated correctly, but line item prices (unitPrice, totalPrice) remain 0,00.
Expected behavior
After triggering the “Calculate shipping and taxes” action, each line item should correctly display its net price and total.
Actual behavior
Even though the calculation is triggered and taxes/shipping are correctly computed, the net price fields for each line item are still 0,00.
After saving the Manual Order:


Steps to reproduce the issue
- Navigate to Orders → Create Manual Order
- Add a product
- Click “Calculate shipping and taxes”
- Observe the line item unitPrice and totalPrice — they are correctly showing
- Save the order
- Observe the line item unitPrice and totalPrice — they now are 0,00 (total price shows the added taxes)
Maybe important note, line item discounts and whole order discounts still have problems on my end, no solution found at the moment.
Possible fix
In TraitPWCommerceUtilitiesOrderLineItem.php, ensure that fallback values are applied properly during calculation. Use improved logic like:
$totalPriceBeforeTax = $this->totalPriceBeforeTaxMoney
? $this->getWholeMoneyAmount($this->totalPriceBeforeTaxMoney)
: $unitPriceBeforeTax * $this->quantity;
This fallback ensures that prices are calculated even if $this->totalPriceBeforeTaxMoney is null.
site/modules/ProcessWireCommerce/traits/utilities/TraitPWCommerceUtilitiesOrderLineItem.php
Around line 77
public function getOrderLineItemCalculatedValues(array $options) {
// #######################
# SETUP
// set as temporary class properties for easier retrieval in various methods
// $this->orderLineItem = $orderLineItem;
// $this->productOrVariantPage = $productOrVariantPage;
// $this->shippingCountry = $shippingCountry;
// $this->isChargeTaxesManualExemption = $isChargeTaxesManualExemption;
// $this->isCustomerTaxExempt = $isCustomerTaxExempt;
// $this->isProcessTruePrice = $isProcessTruePrice;
/** @var WireData $this->orderLineItem */
$this->orderLineItem = $options['order_line_item'];
$this->productOrVariantPage = $options['product_or_variant_page'];
/** @var Page $this->shippingCountry */
$this->shippingCountry = $options['shipping_country'];
$this->isChargeTaxesManualExemption = $options['is_charge_taxes_manual_exemption'];
$this->isCustomerTaxExempt = $options['is_customer_tax_exempt'];
$this->isProcessTruePrice = $options['is_process_order_line_item_product_true_price'];
$orderLineItem = $this->orderLineItem;
// +++++++++
// =================
// SET ORDER LINE ITEM TAXABLE SETTING
$this->isOrderLineItemTaxable = $this->isOrderLineItemTaxable();
// SET TAX RATE IF APPLICABLE
$this->setOrderLineItemTaxRate();
// NOTE: THESE TWO SET ABOVE BY $this->setOrderLineItemTaxRate()
$this->taxPercent = $this->orderLineItemTaxPercent;
$this->taxRate = $this->orderLineItemTaxRate;
$this->isTaxOverride = $this->isCategoryTaxOverridesApplicable() && $this->isOrderLineItemTaxable ? 1 : 0;
$this->quantity = $this->orderLineItem->quantity;
$this->unitDisplayPrice = $this->orderLineItem->unitPrice;
// ensures the display price is calculated based on the actual display unit price and quantity
// ORIGINAL: $this->totalDisplayPrice = $this->orderLineItem->totalPrice;
// #######################
# COMPUTATIONS
## ********** SET CALCULABLE VALUES FOR ORDER LINE ITEM ********** ##
// SET VALUES FROM DISPLAY PRICES
// might or might not include tax
$this->unitDisplayPriceMoney = $this->money($this->unitDisplayPrice);
// NOTE - WE WORK WITH NET PRICE BELOW! this is to cater for price inc tax situations
$this->totalDisplayPriceMoney = $this->unitDisplayPriceMoney->multiply($this->quantity);
// add back $this->totalDisplayPrice
$this->totalDisplayPrice = $this->getWholeMoneyAmount($this->totalDisplayPriceMoney);
// $totalPrice = strval($this->getMoneyTotalAsWholeMoneyAmount($unitDisplayPrice, $quantity));
# NOTE: values with tax have the suffix 'WithTaxXXX'. Those without don't get this suffix and are considered 'net', unless stated otherwise
// SET DEFAULTS
// adding fallback logic
// ++++++++
$this->taxAmountMoney = $this->getTaxAmount();
$this->taxAmount = $this->taxAmountMoney
? $this->getWholeMoneyAmount($this->taxAmountMoney)
: 0; // note: discounts not taxed!
// ++++++++
// (i) schema 'unit_price'
$this->unitPriceBeforeTaxMoney = $this->getUnitPriceBeforeTax();
$unitPriceBeforeTax = $this->unitPriceBeforeTaxMoney
? $this->getWholeMoneyAmount($this->unitPriceBeforeTaxMoney)
: $this->unitDisplayPrice; // improved fallback
// (iii) schema 'unit_price_with_tax'
$this->unitPriceAfterTaxMoney = $this->getUnitPriceAfterTax();
$unitPriceAfterTax = $this->unitPriceAfterTaxMoney
? $this->getWholeMoneyAmount($this->unitPriceAfterTaxMoney)
: $unitPriceBeforeTax; // improved fallback
// ++++++++
// (i) schema 'total_price'
$this->totalPriceBeforeTaxMoney = $this->getTotalPriceBeforeTax();
$totalPriceBeforeTax = $this->totalPriceBeforeTaxMoney
? $this->getWholeMoneyAmount($this->totalPriceBeforeTaxMoney)
: $unitPriceBeforeTax * $this->quantity; // improved fallback
// (iii) schema 'total_price_with_tax'
$this->totalPriceAfterTaxMoney = $this->getTotalPriceAfterTax();
$totalPriceAfterTax = $this->totalPriceAfterTaxMoney
? $this->getWholeMoneyAmount($this->totalPriceAfterTaxMoney)
: $unitPriceAfterTax * $this->quantity; // improved fallback
// ++++++++
// schema 'total_discounts'
$this->totalDiscountsAmountMoney = $this->getTotalDiscountsAmount();
$totalDiscountsAmount = $this->totalDiscountsAmountMoney
? $this->getWholeMoneyAmount($this->totalDiscountsAmountMoney)
: 0;
// ++++++++
// (ii) schema 'total_price_discounted'
$this->totalPriceWithDiscountBeforeTaxMoney = $this->getTotalPriceWithDiscountBeforeTax();
$totalPriceWithDiscountBeforeTax = $this->totalPriceWithDiscountBeforeTaxMoney
? $this->getWholeMoneyAmount($this->totalPriceWithDiscountBeforeTaxMoney)
: $totalPriceBeforeTax; // improved fallback
// (iv) schema 'total_price_discounted_with_tax'
// if no discount, discounted total AFTER tax equates to total AFTER tax
$totalPriceWithDiscountAfterTax = $totalPriceAfterTax; // default fallback if no discount
if ($this->totalPriceWithDiscountBeforeTaxMoney && $this->totalPriceWithDiscountBeforeTaxMoney->lessThan($this->totalPriceBeforeTaxMoney)) {
// DISCOUNT WAS APPLIED (BEFORE TAX)
$this->isDiscountApplied = true;
// get discounted price with tax
$this->totalPriceWithDiscountAfterTaxMoney = $this->getTotalPriceWithDiscountAfterTax();
if ($this->totalPriceWithDiscountAfterTaxMoney) {
$totalPriceWithDiscountAfterTax = $this->getWholeMoneyAmount($this->totalPriceWithDiscountAfterTaxMoney);
}
}
// track final price applied after discount removed
// $this->taxAmountAfterDiscountMoney was set in getTotalPriceWithDiscountAfterTax()
// if line item is taxable
$this->taxAmountAfterDiscount = ($this->isDiscountApplied ?? false) && $this->isOrderLineItemTaxable && $this->taxAmountAfterDiscountMoney
? $this->getWholeMoneyAmount($this->taxAmountAfterDiscountMoney)
: 0; // note: discounts not taxed!
// ++++++++
// (ii) schema 'unit_price_discounted'
// if no discount, discounted unit before tax equates to unit before tax
$this->unitPriceWithDiscountBeforeTaxMoney = $this->getUnitPriceWithDiscountBeforeTax();
$unitPriceWithDiscountBeforeTax = $this->unitPriceWithDiscountBeforeTaxMoney
? $this->getWholeMoneyAmount($this->unitPriceWithDiscountBeforeTaxMoney)
: $unitPriceBeforeTax; // improved fallback
// (iv) schema 'unit_price_discounted_with_tax'
// if no discount, discounted unit AFTER tax equates to unit AFTER tax
$unitPriceWithDiscountAfterTax = $unitPriceAfterTax; // default fallback if no discount
if ($this->unitPriceWithDiscountBeforeTaxMoney && $this->unitPriceWithDiscountBeforeTaxMoney->lessThan($this->unitPriceBeforeTaxMoney)) {
// DISCOUNT WAS APPLIED (BEFORE TAX)
// get discounted price with tax
$this->unitPriceWithDiscountAfterTaxMoney = $this->getUnitPriceWithDiscountAfterTax();
if ($this->unitPriceWithDiscountAfterTaxMoney) {
$unitPriceWithDiscountAfterTax = $this->getWholeMoneyAmount($this->unitPriceWithDiscountAfterTaxMoney);
}
}
// ==========
// 1.PRODUCT
// ++ NONE ++
// +++++++++++++
// 2. DISCOUNTS
// 'discount_amount' => (float) $value->discountAmount, // +++
// ++++++++++++++++++
$orderLineItem->discountAmount = $totalDiscountsAmount;
/** @var WireArray $orderLineItemsDiscounts */
$orderLineItemsDiscounts = $orderLineItem->discounts;
if ($orderLineItemsDiscounts instanceof WireArray && $orderLineItemsDiscounts->count()) {
$orderLineItem->totalDiscounts = $orderLineItemsDiscounts->count();
// ------
if (!empty($orderLineItem->isApplyMultipleDiscounts)) {
// multiple discounts were applied
$orderLineItem->discountType = 'multiple';
} else {
// get the one discount
$discount = $orderLineItemsDiscounts->first();
// get the type of the one discount in the WireArray
$orderLineItem->discountType = $discount->discountType;
// get the value of the one discount in the WireArray
$orderLineItem->discountValue = $discount->discountValue;
}
}
// +++++++++++++
// 3. TAXES
// 'tax_name' => $sanitizer->text($value->taxName), // +++
// TODO: STILL SAVE/SHOW THIS IF CUSTOMER IS TAX EXEMPT? yes, maybe, so as to show what customer is exempted from!
$orderLineItem->taxName = $this->getOrderCountryTaxShortName();
// ------------------
// 'tax_percentage' => (float) $value->taxPercentage, // +++
// @note: takes into account presence of country category tax override
// @note can also use: $this->orderLineItemTaxRate
$orderLineItem->taxPercentage = $this->taxPercent;
// ------------------
// 'tax_amount_total' => (float) $value->taxAmountTotal, // +++
// if discount was applied, we get the tax applied after the discount was removed
// if !empty($this->isDiscountApplied): discount was applied: get the total tax applied on total price after discount; else discount was NOT applied: get the tax applied on total price
$orderLineItem->taxAmountTotal = !empty($this->isDiscountApplied) ? $this->taxAmountAfterDiscount : $this->taxAmount;
// 'is_tax_override' => (int) $value->isTaxOverride, // +++
// $orderLineItem->isTaxOverride = $this->isCategoryTaxOverridesApplicable() && $this->isOrderLineItemTaxable ? 1 : 0;
$orderLineItem->isTaxOverride = $this->isTaxOverride;
// +++++++++++++
// 4. UNITS
// 'unit_price' => (float) $value->unitPrice, // +++
$orderLineItem->unitPrice = $unitPriceBeforeTax;
// 'unit_price_with_tax' => (float) $value->unitPriceWithTax, // +++
$orderLineItem->unitPriceWithTax = $unitPriceAfterTax;
// ------------------
// 'unit_price_discounted' => (float) $value->unitPriceDiscounted, // +++
$orderLineItem->unitPriceDiscounted = $unitPriceWithDiscountBeforeTax;
// ------------------
// 'unit_price_discounted_with_tax' => (float) $value->unitPriceDiscountedWithTax, // +++
$orderLineItem->unitPriceDiscountedWithTax = $unitPriceWithDiscountAfterTax;
// +++++++++++++
// 5. TOTALS
// 'total_price' => (float) $value->totalPrice, // +++
$orderLineItem->totalPrice = $totalPriceBeforeTax;
// ------------------
// 'total_price_with_tax' => (float) $value->totalPriceWithTax, // +++
$orderLineItem->totalPriceWithTax = $totalPriceAfterTax;
// ------------------
// 'total_price_discounted' => (float) $value->totalPriceDiscounted, // +++
$orderLineItem->totalPriceDiscounted = $totalPriceWithDiscountBeforeTax;
// ------------------
// 'total_price_discounted_with_tax' => (float) $value->totalPriceDiscountedWithTax, // +++
$orderLineItem->totalPriceDiscountedWithTax = $totalPriceWithDiscountAfterTax;
// ------------------
// additional checks (logs could be removed)
// SAFETY FALLBACK FOR DISPLAY PRICE
if (!isset($orderLineItem->totalDisplayPrice) || !is_numeric($orderLineItem->totalDisplayPrice)) {
if (!empty($orderLineItem->unitPrice) && !empty($orderLineItem->quantity)) {
$orderLineItem->totalDisplayPrice = $orderLineItem->unitPrice * $orderLineItem->quantity;
} else {
wire('log')->save('debug', 'Missing unitPrice or quantity; fallback totalDisplayPrice = 0');
$orderLineItem->totalDisplayPrice = 0;
}
}
// Check for invalid unit/total prices
if (empty($orderLineItem->unitPrice) || empty($orderLineItem->totalPrice)) {
wire('log')->save('debug', 'Line item unit or total price is 0 — checkout may fail. Title: ' . $orderLineItem->title);
}
// +++++++++++++
// 6. SHIPMENT
// 'delivered_date' => $this->_sanitizeValue($value->deliveredDate), // +++
// TODO! - FOR NOW SET AS CURRENT TIME; HOWEVER, IN FUTURE, CHECK WHOLE ORDER STATUS + IF ORDER LINE ITEM IS DOWNLOAD, ETC
$orderLineItem->deliveredDate = time();
// +++++++++++++
// 7. STATUSES
// 'status' => (int) $value->status, // +++
// 'fulfilment_status' => (int) $value->fulfilmentStatus, // +++
// 'payment_status' => (int) $value->paymentStatus, // +++
// $orderLineItem->status = TODO!;
// $orderLineItem->fulfilmentStatus = TODO!;
// $orderLineItem->paymentStatus = TODO!;
//-------------------------
// return the orderLineItem with calculated values now processed
return $orderLineItem;
}
(small error) Missing .htmx-indicator Causes Console Warning
Short description of the issue
The hx-indicator="htmx-indicator" attribute on the "Calculate shipping and taxes" button causes a warning in the browser console if no matching DOM element exists.
Expected behavior
The indicator should point to an actual element, or no warning should be triggered if one isn’t needed.
Actual behavior
Console logs the following error: htmx.1.9.10.min.js:1 The selector "htmx-indicator" on hx-indicator returned no matches!
Steps to reproduce the issue
- Open the Orders → Create Manual Order interface
- Open browser developer tools (Console)
- Click “Calculate shipping and taxes”
- See console warning related to missing htmx-indicator
Possible fix
In getButtonForOrderCalculateShipping(), change: $indicator = "htmx-indicator"; to: $indicator = ".htmx-indicator";
site/modules/ProcessWireCommerce/FieldtypePWCommerceOrder/InputfieldPWCommerceOrderRenderShipping.php
Around line 179
private function getButtonForOrderCalculateShipping() {
//------------------- calculate shipping (getInputfieldButton)
// TODO -> NEED TO MOVE TO ELSEWHERE?
$label = $this->_('Calculate shipping and taxes');
$options = [
'id' => "pwcommerce_order_calculate_shipping_button",
'name' => "pwcommerce_order_calculate_shipping_button",
'label' => $label,
'collapsed' => Inputfield::collapsedNever,
// 'columnWidth' => $columnWidth,
'small' => true,
// TODO: ok?
'secondary' => true,
'wrapClass' => true,
'wrapper_classes' => 'pwcommerce_no_outline mb-5',
'notes' => $this->getSpinnerForOrderCalculateShipping(),
];
$field = $this->pwcommerce->getInputfieldButton($options);
// allow html on notes and description
$field->entityEncodeText = false;
// add htmx stuff to button
$ajaxPostURL = $this->ajaxPostURL;
$calculateShippingAndTaxesParameterJSON =
json_encode(['pwcommerce_calculate_shipping_and_taxes' => 1]);
// $indicator = "htmx-indicator";
$indicator = ".htmx-indicator";
$field->attr([
'hx-post' => $ajaxPostURL,
'hx-vals' => $calculateShippingAndTaxesParameterJSON,
'hx-indicator' => $indicator,
'hx-trigger' => 'pwcommercecalculateshippingandtaxes',
'hx-swap' => 'none',
// do not swap out this button
// alpine js
'x-on:click' => 'handleCalculateShippingAndTaxes'
]);
return $field;
}
Hope this helps!
