Core 1.3.2 Stable
Released on:
Wednesday, 26 August 2026 08:35
The core the first commercial Solidshop extension needs. Discount & Coupons 1.0.1 pins 1.3.2 as its minimum: somewhere to store per-action settings, a discount that can reduce shipping and have that survive into the order and the invoice, a shopper-facing seam for offers that have not paid out yet, and per-line discount allocation so a free item carries no tax.
That work exposed three tax and totals bugs that were never plugin-specific — tax charged on the pre-discount subtotal, tax-inclusive stores double-counting tax in every derived grand total, and a discount engine that treated an unknown condition as no condition. Those are fixed for every store, discounts installed or not.
Added
- Promotion notices — a seam for telling the shopper about an offer that has not paid out yet.
PromotionNoticesEvent(onSolidshopPromotionNotices) fires when thepromotion.noticeslayout renders on the cart or a product page; listeners contribute plain-textPromotionNoticeobjects and the layout owns the markup. The cart re-renders the fragment on everycart.update, so an offer can appear or lapse on a quantity change without a reload.DiscountEngine::resolveApplicable()is split out ofevaluate()so listeners share checkout's exact definition of "qualifies". First consumer: the Discount plugin's free-item notice. - Shipping discounts. New
total_shipping_discountcolumn on#__sshop_ordersand#__sshop_invoices; checkout writes what the discount engine resolved and every grand total subtracts it (order screens, orders list, customer spend, account order list, reports, invoices, subscription renewals, the Square plugin's order total). A "Shipping discount" line (COM_SOLIDSHOP_ORDER_SHIPPING_DISCOUNT, 23 locales) shows wherever totals are printed — admin, account, checkout finish, every invoice layout, and theorder_completed/invoice_issuedemails — only when greater than zero. The Sales-by-period report's Shipping column is net of it so the row still sums to its total. - Discount engine: a discount whose action only reduces shipping is now recorded in
DiscountResult::$appliedDiscounts(newshipping_amountkey), so it gets a usage row, a coupon badge and a name on the order instead of reading "invalid". On the cart page such a code is accepted and marked "Confirmed at checkout", like a location-restricted code — the shipping cost is unknown until a rate is chosen. Both clamps are unchanged. - Orders: new
discount_totalcolumn on#__sshop_order_products— the promotion amount allocated to that line at checkout, kept separate from the manual admin line-discount rule.SUM(discount_total)equalsorders.total_discountfor checkout-created orders. Admin edits round-trip it (so editing a coupon order no longer erases the promotion from the totals),InvoiceGeneratorfolds it intoinvoice_items.discount_amount, and the Sales-by-product report attributes coupon discounts to the right products. Renewal pricing ignores it — a one-time coupon must not reprice every renewal. - Discounts list: a Published status filter. The model already honoured it; the field was missing from the filter form, so drafts and finished campaigns could only be found by scanning.
Fixed
- Tax is now calculated on the discounted amount. The engine allocates every cart discount to the lines it belongs to (
DiscountResult::$lineDiscounts, opt-inLineAllocatingActionInterface, proportional proration for scalar actions). Checkout, the checkout summary, the cart and the mini-cart feed each line's discounted subtotal to the tax computation, so a free item carries $0 tax and a 10 % discount reduces each affected rate by exactly 10 %, in both tax modes. Previously tax was computed pre-discount and subtracted after — the shopper paid tax on money they never spent, and#__sshop_order_taxesoverstated collected tax. - Tax-inclusive stores no longer double-count tax in derived grand totals. With
tax_price_input_type = inclusivethesubtotalis gross and already containstotal_tax, but every grand-total formula added it again — inflating the admin order screen, orders list, dashboard KPIs, customer spend, the account order list, stored invoice totals and (worst) the amount handed to payment gateways. All formulas now branch on the newTaxHelper::pricesIncludeTax(). Exclusive-mode stores are byte-identical to before; checkout summaries were already correct. - Per-line clamping also corrects over-stacking: two 100 % product-scoped discounts on one line no longer both pay out against unrelated lines' money — each sees only the headroom earlier discounts left.
appliedDiscounts[n]['amount']is now the post-clamp figure. - Discount engine: a condition whose handler is not registered now fails closed. Previously disabling the Discount & Coupons plugin turned every discount gated only by a rule-builder condition into an unconditional one.
- Cart page and mini-cart: the discount row shows the display-basis (tax-adjusted) amount, so subtotal − discount = estimated total holds on screen when prices include tax. The
cart.updateJSON keeps itsdiscount_totalkey; only the basis changed. - Payment method names read as labels, not slugs.
payment_method_idstores a machine id (cod,banktransfer,free) and several surfaces printed it raw — confirmation, payment-received and order-cancelled emails said "Payment method: Cod", and the report and admin order screen showedcash/free/(none). NewPaymentMethodHelper::label()resolves both key namespaces and de-slugs when the string is missing, so an uninstalled plugin can no longer leak a raw language key to a shopper. Every display surface funnels through it (emails via a newpayment_method_labelvariable); the stored id is unchanged and stays what reports group on and CSV exports carry. - The orders-list payment filter now offers every method the orders actually used. Its options came from enabled payment plugins only, so admin-created orders (
cash,bank_transfer,other),freeorders and orders with no method were never offered — and disabling a plugin made every order paid through it unreachable. Options are now read from the orders themselves (PaymentMethodUsage, store-scoped); orders with no method are offered as Not specified. New composite indexidx_#__orders_store_payment_methodkeeps that query index-only. - The cart's "Discount" row no longer renders when nothing was discounted. The row carries
hidden, but that attribute'sdisplay: nonecomes from the UA stylesheet and lost to Bootstrap's.d-flexand Foundra's summary-row rule — so every cart with no qualifying discount showed a green "Discount −$0.00". Both stylesheets now carry a[hidden][hidden] { display: none !important }guard, which fixes everyhiddentoggle at once: under Foundra that also covers the collapsed coupon form, the billing address block, and the mini-cart badge's "0" bubble on an empty cart. - Checkout no longer fatals on a free product. A product saved with no price stores
NULL, and the cart line carried it intoDisplayCurrency::format()— a type error whenever a free item was in the cart, exactly what the Discount plugin's Free item action produces.CartControllercasts on add and quantity change, and both checkout summaries coerce on render, so existing sessions are safe too. - Filters module: the section headers were dead on a clean install — they are Bootstrap collapse toggles and nothing loaded
bootstrap.collapse, so with Collapsed by default on, every filter stayed hidden. The module now declares the dependency itself. - The applied-coupon chip showed a wide gap before its remove control (
.btn-closepadding plusms-1plus a collapsed source newline). The chip is now flex,btn-sm ms-1is gone from both coupon layouts, and under Foundra the cross is masked fromcurrentColorso it works in both themes. - The component stylesheet now has an owner on every view that needs it.
com_solidshop.corewas reaching checkout second-hand from the mini-cart module, which is not rendered undertmpl=component. Checkout, Account and Tracking load it themselves, covering the Foundra override and the plugin-owned account templates too; the redundant layout-level call is removed.
Changed
- Discount edit form: the action row's
configJSON now carries handler-specific settings submitted through anaction_configgroup, andloadItem()exposes the decoded JSON so fields bind back on edit. Only keys whoseshowonmatches the chosen action type are stored, so hidden fields' defaults don't leak between types. This is the seam plugin action types (BOGO, Fixed price, Tiered, Free item) save through — no plugin-side save hook needed. Released 1.3.1 accepted these fields and dropped them on save. - Discount edit form: saving a discount whose stored action type the form cannot offer (a plugin-registered type while that plugin is disabled) leaves the action row untouched instead of rewriting it as a plain percentage discount.
- Filters module: colours resolve through the active template's tokens —
--solidshop-filters-*falling back Foundra → Bootstrap → literals — so the module follows the active Foundra preset and its dark values and still looks right under Cassiopeia. - Admin list filters: the empty option on the discount and invoice status filters reads - All - (
JALL), matching the other Solidshop lists and Joomla's own.
Upgrade notes
- Schema update
1.3.2.sqladdstotal_shipping_discountto#__sshop_ordersand#__sshop_invoices,discount_totalto#__sshop_order_products, and the composite index on#__sshop_orders. Existing rows keep NULL / 0 and their totals are unchanged — historical orders are never restated; new orders on discounted carts simply collect less tax (the fix). - Third-party discount actions registered via
DiscountRegisterEventkeep working unchanged:calculate()is untouched and scalar amounts get prorated allocation automatically;LineAllocatingActionInterfaceis opt-in. Callers evaluating the engine withoutcart_itemskeep the previous cart-level behaviour. - Discount & Coupons 1.0.1 pins 1.3.2 as its minimum core and refuses to install against anything older; update core first.
- Stores that customised the
order_completed(seed 1.0.11),invoice_issued(1.0.1),order_cancelled(1.0.1) orpayment_received(1.0.1) bodies will see a drift banner: those defaults gained the shipping-discount row and now printpayment_method_labelinstead ofpayment_method_id. A customised body keeps the old lines until it is reset to the default; the rawpayment_method_idstays available to templates that branch on it, such as the bank-transfer instructions partial.
Solidshop Core v1.3.2
| File size | 9.10 Mb |
| SHA-256 Signature | 28937cc7e4389d2a239468c0e2af4b2e0c434a21e05e7ba70b44c1ff8788e587 |
| Compatibility |