E-commerce 6.1.6

Latest

11 August 2026

This release has upgrade notes. Read them before updating — they describe behaviour changes that need your attention.

ORDERSDISCOUNTSCHECKOUTCARTINVENTORYMULTI-CURRENCYSHIPPINGDOWNLOADSLICENSINGPERMISSIONSADMINRELIABILITYBUG-FIXESPERFORMANCESTOREFRONTCATALOGPRODUCTSREVIEWSRATINGS

E-commerce v6.1.6

Release Date: August 11, 2026 Tags: ORDERS, DISCOUNTS, CHECKOUT, CART, INVENTORY, MULTI-CURRENCY, SHIPPING, DOWNLOADS, LICENSING, PERMISSIONS, ADMIN, RELIABILITY, BUG-FIXES, PERFORMANCE, STOREFRONT, CATALOG, PRODUCTS, REVIEWS, RATINGS

Overview

A correctness release for the money paths around discount codes, alongside repairs to the totals shown to buyers in the cart, at checkout and on the order page, and a reliability pass on the admin order screens.

A discount scheduled to start in the future was spendable from the moment it was saved, and a code's usage limits — in total and per customer — did not hold when two checkouts arrived at the same moment. Separately, wherever shipping was switched on and the basket held a physical product, the cart and checkout joined the shipping cost onto the basket as text instead of adding it, and the totals panel failed rather than rendering at all. The amount charged was always correct; the screen was not.

Read Upgrade Notes before updating. Discounts already redeemed, stock counts on orders that were cancelled twice, and one permission that tightens all need a look.

Requires Core v6.6.3.

Update Instructions

pnpm updator

No setting changes and no migration. There are existing records worth inspecting and one permission to grant — see Upgrade Notes.


Upgrade Notes

Check any promotion you scheduled ahead, and any code with a usage limit

Until this release a discount whose start date was in the future could be redeemed the day it was created, and the limits on how many times a code could be used — in total, and per customer — could be passed by two checkouts arriving at the same moment. Both are refused from now on, but neither can undo an order that has already been placed at the wrong price.

  • Open Admin → E-commerce → Discounts and look at every code with a start date. If any of them shows usage before that date, the orders behind it were discounted early.
  • For each code with a usage limit, compare the recorded usage against the limit you set. A count above the limit, or two uses by the same customer on a once-per-customer code, is from the old behaviour.
  • Nothing is corrected automatically. Refunding, re-invoicing or letting it stand is your decision per order.

Check stock on any order that was cancelled more than once

Cancelling a paid order refunds the buyer and puts physical stock back on the shelf. The refund could only ever happen once, but the restock could happen twice — so an order cancelled by two admins at the same time, or by one admin double-clicking, left the product's inventory permanently higher than the stock that exists. Fixed here, but past cancellations already inflated the figure.

  • Re-count physical products that have had orders cancelled and correct their quantity in Admin → E-commerce → Products. An inventory figure above the real stock oversells.

Attaching a licence key or a download file now needs edit access to orders

Every other action on the order screen — changing status, editing the order, assigning a shipment, saving a shipping address — required permission to edit orders. Attaching a licence key or a download file required only permission to view them, so an admin given read-only access to orders could change what a buyer receives. That action now asks for the same edit permission as the rest of the screen.

  • Grant edit.ecommerce.order in Admin → Roles to any role that handles digital delivery and currently holds only view access on orders. A role that can already edit orders is unaffected, and no new permission key is introduced.

Changed

Attaching a licence key or a download file is now an edit, not a view

  • Changed the action that attaches a licence key or a download file to an order item to require permission to edit orders, matching every other write on that screen. Roles holding only view access lose it — see Upgrade Notes.

Star ratings on the shop and category pages

Both pages show a product's average rating and how many reviews it has. Neither figure needs the reviews themselves, but every review of every product on the page was being fetched to produce them — so a catalogue's busiest, best-reviewed products were the ones that cost the most to list, and the page got slower the more reviews it earned.

  • Changed the shop's product list to have the database total and count the reviews, rather than fetching them and adding them up afterwards. The same reviews are counted — only approved ones carry into a product's public rating, so hiding a review still takes it out of the average.
  • Changed the category listing the same way. It has never excluded unapproved reviews from its ratings and still does not, so no category's rating moves.
  • Changed both responses to be sent as they are built, instead of being copied out a second time before they leave.

Fixed

A promotion scheduled for a future date could be spent the day it was created

Four places read a discount code, and only one of them consulted the date the promotion was due to start — the preview that checks a code and moves no money. The two doors that actually place an order and debit a wallet, and the door that hands a shopper a code to spend at checkout, checked only that the code was switched on and had not yet expired. A Black Friday code built in August was therefore live in August: anyone who knew the code string could take the discount months early, and the preview answering "not yet active" was decoration.

  • Fixed the three doors that spend or hand out a code to ask one shared question about when a code may be spent. A code before its start date is refused, in the same words the preview already used.

A one-use discount code could be redeemed twice over

Both order doors take a lock on the discount before counting how many times it has been used, but they then did that counting with an ordinary read. A lock does not refresh what a transaction can already see, so the second of two checkouts waited its turn, resumed after the first had committed, and still counted zero prior uses. A code capped at one use was redeemable by every buyer inside that window, and a once-per-customer code twice by the same customer. No unusual timing was needed — two submissions a moment apart were enough.

  • Fixed both the total-usage check and the per-customer check to read with a row lock, so each checkout sees the redemptions the checkout before it committed rather than a view of the data taken earlier in its own work. The cap holds at the number the operator set.

The cart and checkout totals added the shipping cost as text

The default shipping cost is a platform setting, and settings arrive at the browser as text rather than as a number. Adding it to the basket joined the two together instead of adding them — and before any of that reached the screen, the summary threw trying to format the shipping line, replacing the whole totals panel with an error. The server has always read that same setting as a number, so the amount charged was correct throughout; only the screen was wrong. It is also invisible in testing, because the value stays a number in memory for the rest of the session in which an admin saves it and is text after the next restart.

  • Fixed the cart and the checkout to read the shipping cost as a number. The totals panel renders again, and the total shown is the total charged.
  • Applies only where shipping is switched on and the basket holds a physical product; a digital-only basket was never affected.

An order paid in two currencies claimed a total in one of them

The order page lists a subtotal for each currency an order was paid in, and then collapsed all of them into a single figure labelled with whichever currency the first product happened to use. An order of 100 USDT and 0.002 BTC showed the breakdown honestly and then declared a total of 100.00 USDT — a sum of unlike amounts presented as a quantity of one of them. That collapsed figure is printed in three places on the page.

  • Fixed the total on the order details tab to show each currency's own subtotal instead of a sum with no unit. Shipping and tax keep their existing lines above it and are not folded into that total. An order paid entirely in one currency is unchanged.
  • The invoice tab and the order summary panel beside it still print the collapsed figure; only the order details total was corrected in this release.

Cancelling the same order twice put the stock back twice

Every money leg of an order reversal is keyed so that repeating it does nothing; returning physical stock to the shelf was the one leg with no such key, and it adds rather than sets. All three admin routes that cancel an order check its status before opening the write, so two admins cancelling the same pending order — or one admin double-clicking — both passed that check and both restocked. The buyer was refunded once and the inventory rose twice, leaving the product overstated by the order quantity for good and sellable beyond what existed.

  • Fixed the reversal to return stock only on the pass that actually refunds the buyer. A repeat cancellation of the same order leaves its inventory alone.

An admin could attach a download to an item on somebody else's order

The download action is opened from one specific order, but it found the item to change from the id in the request alone and never checked that item belonged to the order in front of it. The order the screen was opened from was decorative. An admin allowed to open a single order could attach a licence key or a download file to any order item on the platform by naming its id.

  • Fixed the item to be found within the order the screen was opened from. An id belonging to a different order is refused as not found.

Switching an item to a licence key alone kept serving the old file

The delivery form sends only the fields the chosen option uses, so the field for the option you turned off arrived absent rather than emptied — and an absent field leaves the stored value untouched. An item changed from license key and file to license key only therefore carried on offering the previously attached file, and the buyer could still download it.

  • Fixed the delivery to be replaced rather than merged, on the form and on the server both. What is chosen is what the buyer gets, and the option you turned off is cleared.

An order change that saved could be reported as failed

Recording a shipment, updating shipping details and issuing a download link each write their change, commit it, and then report success. If anything went wrong after that commit, the error handler tried to undo work the database had already accepted. That attempt fails on its own, and its failure is what would reach the screen: a message naming a database transaction state, on a change that had gone through.

  • Fixed all three to leave a committed change alone when reporting an error, so a change that saved can no longer be reported as failed.

A download refused for an item that does not exist blamed the server

The download screen rewrote every error it raised as an internal server error before sending it. A plain "order item not found" — the ordinary answer to an id that is not there — arrived as a fault in the platform, which reads as something to escalate rather than something to correct.

  • Fixed the download action to report a not-found as a not-found, keeping the reason it was refused for.