-
-
Notifications
You must be signed in to change notification settings - Fork 6
Description
Manual Orders Line item discount and Whole Order discount aren't applied
Short description of the issue
After clicking “Calculate shipping and taxes” during manual order creation, the total tax and shipping calculations seem to be working, but applied discounts (both for line item and whole order) aren't being applied. Note that they also don't seem to be properly saved on the order's Discount overview.
Expected behavior
After triggering the “Calculate shipping and taxes” action, each line item should correctly display its net price based on their applied discount, same if there are additional whole order discounts. Applied discounts should stay on Save.
Actual behavior
Even though the calculation is triggered and taxes/shipping are correctly computed, the net price fields aren't updated with taking the discounts into account when saving.
Steps to reproduce the issue
- Navigate to Orders → Create Manual Order
- Add a product
- Add Product discount
- Add Order discount
- Click “Calculate shipping and taxes”
- Observe the line item price adjusted
- Observe the Discount overview being adjusted
- Save the order
- Observe the Line item Net price not including discount
- Observe Discount overview showing the applied discount for whole Order but ignoring the Line Item discount
- Observe the Subtotal and Total without discounts
Possible fix
After looking at the logs it seems that the discountAmount aren't being stored on the order and orderLineItem:
Date/Time | User | URL | Text
-- | -- | -- | --
2 seconds ago2025-06-27 12:02:58 | pwcommerce-marie | shop/ajax/ | Order logs:WireData Object ( [data] => Array ( [date] => 1751016141 [modified] => 1751018562 [id] => 1606 [orderID] => 473 [paidDate] => 2016-04-08 05:10:00 [paymentMethod] => [discountType] => percentage [discountValue] => 10 [discountAmount] => 0 [handlingFeeType] => inapplicable [handlingFeeValue] => 0 [handlingFee] => 0 [shippingFee] => 0 [shippingRateName] => [shippingRateDeliveryTimeMinimumDays] => 0 [shippingRateDeliveryTimeMaximumDays] => 0 [isCustomHandlingFee] => 0 [isCustomShippingFee] => 0 [totalPrice] => 80 [orderStatus] => 0 [fulfilmentStatus] => 0 [paymentStatus] => 0 [isChargeTaxesManualExemption] => 1 [isPricesIncludeTaxes] => 0 [orderTaxAmountTotal] => 0 [isTaxExempt] => 0 [orderShippingFeePlusHandlingFeeTotal] => 0 [shippingAddressCountry] => Netherlands [shippingAddressCountryID] => 1379 [email] => email@email.com [orderLineItemsTotalPrice] => 80 [orderLineItemsTotalPriceWithTax] => 80 [orderLineItemsTotalPriceDiscounted] => 80 [orderLineItemsTotalPriceDiscountedWithTax] => 80 [orderLineItemsDiscounts] => WireArray Object ( [count] => 0 [items] => Array ( ) ) [orderLineItemsTotalDiscount] => 0 [freeShippingDiscount] => [isLiveCalculateOnly] => 1 [liveOrderLineItemsProductsIDs] => Array ( [0] => 1548 ) [liveOrderLineItems] => Array ( [0] => Array ( [id] => 1607 [productID] => 1548 [isVariant] => 1 [unitPrice] => 80 [title] => MyFont: notRegular / Print/Desktop [quantity] => 1 [discountType] => percentage [discountValue] => 10 [discountAmount] => 0 [shippingType] => digital [taxName] => VAT [taxPercentage] => 0 [taxAmountTotal] => 0 [isTaxOverride] => 0 [unitPriceWithTax] => 80 [unitPriceDiscounted] => 80 [unitPriceDiscountedWithTax] => 80 [totalPrice] => 80 [totalPriceWithTax] => 80 [totalPriceDiscounted] => 80 [totalPriceDiscountedWithTax] => 80 [totalDisplayPrice] => 80 [deliveredDate] => 1751018578 [data] => 1548 [total_price_discounted] => 80 [total_price_discounted_with_tax] => 80 ) ) [isOrderError] => 1 [isOrderErrorMessage] => Shipping is not applicable to this order! [handlingFeeAmount] => 0 [isCustomerTaxExempt] => 0 [numberOfLineItems] => 1 [noticeTypeText] => shipping_not_applicable [noticeType] => warning [matchedShippingRates] => WireArray Object ( [count] => 0 [items] => Array ( ) ) [maximumShippingFee] => 10 [matchedShippingZoneID] => 1518 [matchedShippingZoneName] => Netherlands [subtotalPrice] => 80 ) )
2 seconds ago2025-06-27 12:02:58 | pwcommerce-marie | shop/ajax/ | OrderLineItem logs:WireData Object ( [data] => Array ( [id] => 1607 [productID] => 1548 [isVariant] => 1 [unitPrice] => 80 [title] => MyFont: notRegular / Print/Desktop [quantity] => 1 [discountType] => percentage [discountValue] => 10 [discountAmount] => 0 [shippingType] => digital [taxName] => VAT [taxPercentage] => 0 [taxAmountTotal] => 0 [isTaxOverride] => 0 [unitPriceWithTax] => 80 [unitPriceDiscounted] => 80 [unitPriceDiscountedWithTax] => 80 [totalPrice] => 80 [totalPriceWithTax] => 80 [totalPriceDiscounted] => 80 [totalPriceDiscountedWithTax] => 80 [totalDisplayPrice] => 80 [deliveredDate] => 1751018578 ) )
This is possibly because the discount wasn't defined on the Product, or that they aren't discount stored in /login/shop/discounts/, so there might be the need to re-apply discounts for Manual Order instead of only relaying on TraitPWCommerceDiscounts.php.
Note: I still need to try and process orders from Frontend to confirm that the issue is only of Manual Orders.
These have been working for me at the moment:
site/modules/ProcessWireCommerce/traits/utilities/TraitPWCommerceUtilitiesOrderLineItem.php
Around line 77
getOrderLineItemCalculatedValues()
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;
// +++++++++
// =================
// NORMALIZE DISCOUNTS
$discountType = $orderLineItem->get('discountType') ?? ($orderLineItem->data['discountType'] ?? null);
$discountValue = $orderLineItem->get('discountValue') ?? ($orderLineItem->data['discountValue'] ?? null);
if (
(!$orderLineItem->discounts instanceof WireArray || !$orderLineItem->discounts->count()) &&
(!empty($discountType) && is_numeric($discountValue))
) {
$discount = new WireData();
$discount->set('discountType', $discountType);
$discount->set('discountValue', $discountValue);
$discount->discountType = $discountType;
$discount->discountValue = $discountValue;
$discounts = new WireArray();
$discounts->add($discount);
$orderLineItem->discounts = $discounts;
}
if (!$orderLineItem->discounts instanceof WireArray) {
$orderLineItem->discounts = new WireArray();
}
if (!$orderLineItem->discounts->count() && $orderLineItem->get('pwcommerce_order_discounts')) {
$orderLineItem->discounts = $orderLineItem->get('pwcommerce_order_discounts');
}
$structuredDiscounts = [];
foreach ($orderLineItem->discounts as $discount) {
if (!$discount instanceof WireData) continue;
$discount->discountType = $discount->get('discountType');
$discount->discountValue = $discount->get('discountValue');
if (!isset($discount->discountType) || !isset($discount->discountValue) || !is_numeric($discount->discountValue)) continue;
$structuredDiscounts[] = [
'discountType' => $discount->discountType,
'discountValue' => $discount->discountValue,
];
}
// ===============================
// DISCOUNT AMOUNT
$basePrice = (float) ($orderLineItem->unitPrice ?? 0);
$quantity = (int) ($orderLineItem->quantity ?? 1);
$subtotal = $basePrice * $quantity;
$totalDiscountsAmount = 0;
foreach ($orderLineItem->discounts as $discount) {
$type = $discount->discountType ?? $discount->get('discountType');
$value = $discount->discountValue ?? $discount->get('discountValue');
if (!in_array($type, ['percentage', 'fixed']) || !is_numeric($value)) continue;
if ($type === 'percentage') {
$totalDiscountsAmount += ($subtotal * $value / 100);
} elseif ($type === 'fixed') {
$totalDiscountsAmount += $value;
}
}
// +++++++++
// =================
// 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 = $orderLineItem->quantity;
$this->unitDisplayPrice = $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'
// NOTE, I COULDN'T HAVE IT WORK WITH THE FUNCITON BELOW
// $this->totalDiscountsAmountMoney = $this->getTotalDiscountsAmount();
// $totalDiscountsAmount = $this->totalDiscountsAmountMoney
// ? $this->getWholeMoneyAmount($this->totalDiscountsAmountMoney)
// : 0;
$totalDiscountsAmount = min($totalDiscountsAmount, $subtotal);
$this->totalDiscountsAmountMoney = $this->money($totalDiscountsAmount);
// ++++++++
// (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);
}
}
// ===============================
// ASSIGN FINAL VALUES
// wire('log')->save('pwcommerce', "[{$orderLineItem->title}] Calculated discountAmount: {$totalDiscountsAmount}");
// ==========
// 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)) {
// $orderLineItem->totalDisplayPrice = $orderLineItem->unitPrice * $orderLineItem->quantity ?: 0;
// }
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
// wire('log')->save('pwcommerce', 'getOrderLineItemCalculatedValues() $orderLineItem:' . print_r($orderLineItem, true));
return $orderLineItem;
}
site/modules/ProcessWireCommerce/traits/utilities/TraitPWCommerceUtilitiesOrder.php
Around line 619
Note: I saw this comment, isn't it conflicting then with the Whole Order discount? If intended, then I guess no need to review the suggested adjustments for
getOrderCalculatedValues().// +++++++++++++ // 2. DISCOUNTS // NOTE ORDER DISCOUNTS ARE PROPORTIONATELLY APPLIED ACROSS ALL LINE ITEMS! // nothing to do here
getOrderCalculatedValues()
public function getOrderCalculatedValues(array $options) {
// -------
/** @var WireData $this->order */
$this->order = $options['order'];
/** @var Page $this->orderPage */
$this->orderPage = $options['order_page'];
if (isset($options['is_for_live_shipping_rate_calculation'])) {
/** @var bool $this->isForLiveShippingRateCalculation */
$this->isForLiveShippingRateCalculation = $options['is_for_live_shipping_rate_calculation'];
}
/** @var array $this->orderLineItems (2D) */
// grab order line items for order once for reusability
// TODO - THIS SHOULD BE GETTING ITEMS THAT ARE CURRENTLY IN ORDER EDIT VIEW! NOT NECESSARILY SAVED ONES! THAT MEANS CHECKING IDS IN INPUT! OR BEING PASSED LINE ITEMS HERE!
$this->orderLineItems = $this->getLineItemsForOrder();
// TODO -> CHECK IF TO APPLY SHIPPING TO ORDER -> NEED AT LEAST ONE SHIPPABLE ITEM PLUS IT SHOULD ONLY INCLUDE THOSE ITEMS!
// TODO @note: need to call after $this->orderLineItems has been set!
$this->isShippingApplicable = $this->isShippingApplicableOnOrder();
// ----------------
// SET ORDER SHIPPING COUNTRY
/** @var Page $this->shippingCountry */
$this->shippingCountry = $options['shipping_country'];
// determine and set this order's shipping zone
// TODO! make sure country is always set before order save then!
// SET ORDER MATCHED SHIPPING ZONE
/** @var Page $this->shippingZone */
// TODO: IF THIS IS NULL, IT WILL THROW ERRORS LATER! FIX THAT CHECK?!
$this->shippingZone = $this->getOrderCustomerCountryShippingZone($this->shippingCountry);
// CHECK FOR ERRORS OR NOT APPLICABLE
$notice = '';
$isOrderError = false;
$noticeTypeText = "";
$noticeType = null;
if (empty($this->isShippingApplicable)) {
// shipping not applicable (technically not an error; just a notice)
$notice = $this->_("Shipping is not applicable to this order!");
$isOrderError = true;
$noticeTypeText = "shipping_not_applicable";
$noticeType = 'warning';
} elseif (empty($this->shippingZone)) {
// country not in any shipping zone
$notice = sprintf(__("The selected order customer shipping country %s has not yet been added to any shipping zone."), $this->shippingCountry->title);
$isOrderError = true;
$noticeTypeText = "country_not_in_shipping_zone";
$noticeType = 'error';
}
$this->order->isOrderError = $isOrderError;
// CATCH ERRORS
if (!empty($this->order->isOrderError)) {
$this->order->isOrderErrorMessage = $notice;
$this->order->noticeTypeText = $noticeTypeText;
$this->order->noticeType = $noticeType;
// ------
// if notice type is 'error' we return early
// @note: this meanings we don't stop on warnings
if ($noticeType === 'error') {
return $this->order;
}
}
// --------
// GOOD TO GO
############
// SET ORDER MATCHED SHIPPING ZONE SHIPPING RATES
/** @var WireArray $this->shippingZoneRates */
$this->shippingZoneRates = $this->getZoneShippingRates();
// TODO THIS SHOULD NOT RETURN IF BASKET CONTAINS ONLY NON-SHIPPABLE ITEMS! EVEN PHYSICAL PRODUCTS NOT REQUIRING SHIPPING!
// if (empty($this->shippingZoneRates)) {
// ERROR: IF SHIPPING IS APPLICABLE BUT NO SHIPPING ZONE RATES for zone
if (!empty($this->isShippingApplicable) && empty($this->shippingZoneRates)) {
$notice = sprintf(__("The matched shipping zone for this order %s has not yet added any shipping rates. This has to be done before you can continue editing this order!"), $this->shippingZone->title);
$this->order->isOrderError = true;
$this->order->isOrderErrorMessage = $notice;
return $this->order;
}
// ---------------------
// RAW SUBTOTAL BEFORE ANY DISCOUNTS
$rawOrderSubtotalPriceMoney = $this->money(0);
foreach ($this->orderLineItems as $item) {
$isObj = is_object($item);
$rawTotal = $isObj
? ($item->totalPrice ?? 0)
: ($item['total_price'] ?? 0);
$rawOrderSubtotalPriceMoney = $rawOrderSubtotalPriceMoney->add($this->money($rawTotal));
}
// ------------
// GOOD TO CONTINUE
// ##################
// ----------------
$order = $this->order;
## ********** SET CALCULABLE VALUES FOR ORDER ********** ##
// +++++++++++++
// 2. DISCOUNTS
if (!$order->discounts instanceof WireArray) {
$order->discounts = new WireArray();
}
$discountType = $order->get('discountType') ?? ($order->data['discountType'] ?? null);
$discountValue = $order->get('discountValue') ?? ($order->data['discountValue'] ?? null);
if (!$order->discounts->count() && !empty($discountType) && is_numeric($discountValue)) {
$discount = new WireData();
$discount->set('discountType', $discountType);
$discount->set('discountValue', $discountValue);
$order->discounts->add($discount);
}
if (!$order->discounts->count() && $order->get('pwcommerce_order_discounts')) {
$order->discounts = $order->get('pwcommerce_order_discounts');
}
// ---------------------
// 2.1 SUBTOTAL & TAX BASED ON LINE ITEMS
$orderSubtotalPriceMoney = $this->money(0);
$orderTaxMoney = $this->money(0);
foreach ($this->orderLineItems as $i => $item) {
$isObj = is_object($item);
$totalPrice = $isObj
? ($item->total_price_discounted ?? $item->totalPrice ?? 0)
: ($item['total_price_discounted'] ?? $item['total_price'] ?? 0);
$taxAmount = $isObj ? ($item->taxAmountTotal ?? 0) : ($item['tax_amount_total'] ?? 0);
$lineTotal = $this->money($totalPrice);
$lineTax = $this->money($taxAmount);
$orderSubtotalPriceMoney = $orderSubtotalPriceMoney->add($lineTotal);
$orderTaxMoney = $orderTaxMoney->add($lineTax);
}
// ---------------------
// 2.2. APPLY ORDER DISCOUNTS
// ---------------------
$discountAmountMoney = $this->money(0);
$totalDiscountMoney = $this->money(0);
foreach ($this->order->discounts as $discount) {
$type = strtolower(trim($discount->get('discountType')));
$value = (float) $discount->get('discountValue');
if ($type === 'percentage' && $value > 0 && $value <= 100) {
// $discountMoney = $orderSubtotalPriceMoney->multiply(strval($value / 100));
$discountMoney = $rawOrderSubtotalPriceMoney->multiply(strval($value / 100));
} elseif (in_array($type, ['fixed', 'fixed_applied_once'])) {
$discountMoney = $this->money($value);
} else {
continue;
}
$totalDiscountMoney = $totalDiscountMoney->add($discountMoney);
$discount->discountAmount = $this->getWholeMoneyAmount($discountMoney);
}
$discountAmountMoney = $totalDiscountMoney;
$order->discountAmount = $this->getWholeMoneyAmount($discountAmountMoney);
// $order->discountAmount = (float) $discountAmountMoney->getAmount() / 100;
$order->set('discountAmount', $order->discountAmount);
// ---------------------
// 2.3. RE-CALCULATE SUBTOTAL AFTER DISCOUNT
$orderSubtotalPriceMoney = $orderSubtotalPriceMoney->subtract($discountAmountMoney);
$order->subtotalPrice = $this->getWholeMoneyAmount($orderSubtotalPriceMoney);
// +++++++++++++
// 3. SHIPPING
// @NOTE
// THESE SHIPPING VALUES HAVE TO TAKE INTO ACCOUNT FREE SHIPPING! WHILST WE MAINTAIN THEIR VALUES BEFORE DISCOUNT FOR REPORTING, WE NEED TO ENSURE THAT IN $order->totalPrice WE HAVE MADE THE VALUES NEGATIVE OR ZERO FOR THEM TO BE SUBTRACTED FROM TOTAL
// --------
// handling
// TODO NEED TO APPLY TO SHIPPABLE GOODS ONLY! NO HANDLING FOR OTHERS!
// @note: if handlingFeeType == 'percentage', WE BASE PERCENTAGE ON THE SHIPPABLE ITEMS TOTAL PRICE ONLY!
// @note: if no shippable goods, handling fee type is 'inapplicable', amount and value here will be 0
$order->handlingFeeType = $this->getOrderHandlingFeeType();
$order->handlingFeeValue = $this->getOrderHandlingFeeValue();
$handlingFeeMoney = $this->getOrderHandlingFeeMoney();
$order->handlingFee = $this->getWholeMoneyAmount($handlingFeeMoney);
// --------
// shipping
// TODO THIS WILL NEED TO COME FROM SELECTED MATCHED SHIPPING RATE! SELECTION WILL NEED TO OCCUR ON CLIENT FORM (BACKEND)
// TODO IN THE METHOD, ONLY CALCULATE IF SHIPPING NEEDS (RE)CALCULATING!
$shippingFeeMoney = $this->getOrderShippingFeeMoney();
$order->shippingFee = $this->getWholeMoneyAmount($shippingFeeMoney);
// @note: overloading! TODO OK????
/** @var WireArray $order->matchedShippingRates */
$order->matchedShippingRates = $this->getOrderComputedMatchedShippingRates();
$order->maximumShippingFee = $this->getZoneMaximumShippingFee();
// ---------------------
// 8. RE-CALCULATE TAX FROM TAX ENGINE (if needed)
// $orderTaxMoney = $this->getOrderTaxMoney();
$order->orderTaxAmountTotal = $this->getWholeMoneyAmount($orderTaxMoney);
$recalculatedTax = $this->getOrderTaxMoney();
// +++++++++++++
// 4. TOTALS
// TODO DELETE WHEN DONE
// @note: this is ORDER SUB-TOTAL PRICE/COST (order and line items discounts applied) + SHIPPING + HANDLING + TAX
// @UPDATE: THIS NOW CHANGES -> IT IS PRICE/COST OF LINE ITEMS WITHOUT TAX AND OTHER FEES (INCLUDING SHIPPING) BUT MINUS DISCOUNTS; ALSO NOTE, WHOLE ORDER DISCOUNTS NOW ALSO INCLUDED IN LINE ITEMS (PROPORTIONATELY DIVIDED between them)
$orderTotalPriceMoney = $orderSubtotalPriceMoney
->add($shippingFeeMoney)
->add($handlingFeeMoney)
->add($orderTaxMoney);
$order->totalPrice = $this->getWholeMoneyAmount($orderTotalPriceMoney);
$order->totalDisplayPrice = $this->getWholeMoneyAmount(
$orderTotalPriceMoney->subtract($discountAmountMoney)
);
//-------------------------
// 5. RUNTIMES
// runtime, e.g. for TraitPWCommerceOrder
$order->matchedShippingZoneID = $this->shippingZone->id;
$order->matchedShippingZoneName = $this->shippingZone->title;
// @note: excludes shipping and handling BUT includes taxes + discounts
// runtime, e.g. for TraitPWCommerceOrder
// $orderSubtotalPriceMoney = $this->getOrderSubTotalMoney();
// $order->subtotalPrice = $this->getWholeMoneyAmount($orderSubtotalPriceMoney);
// ---------------------
// DEBUG LOG
wire('log')->save('pwcommerce', "Final order calculation result:" . print_r($order, true));
return $order;
}
Note2: for Whole order discount, the Subtotal and Total are only updated after saving with the code above...
Hope it helps debugging and/or solving the Manual Order discounts !


