E-commerce 6.1.1
29 July 2026
This release has upgrade notes. Read them before updating — they describe behaviour changes that need your attention.
E-commerce v6.1.1
Release Date: July 29, 2026 Tags: MONEY FLOW, REFUNDS, CHECKOUT, DIGITAL DELIVERY, MODERATION, UI, KYC, DESIGN SYSTEM, SHIPPING, PERMISSIONS, ACCESSIBILITY
Overview
Version 6.1.1 repairs the parts of the store that move money and the parts that hand goods over.
No order could be cancelled or rejected. All three admin actions that reverse an order failed and rolled back, so the order stayed pending, the stock stayed off the shelf and the buyer got nothing back. Digital delivery had never worked either: every download returned a server error, for every buyer, since the feature shipped.
A basket built from a category page or from the wishlist could not be checked out at all, and because the basket is saved in the browser it kept failing on every visit until the buyer cleared it by hand. Alongside those: shipping charged once per product but quoted once for the order, hidden reviews counting towards public ratings, a shipping page that told every customer they were forbidden, and a dashboard showing unit counts where order totals belong.
The storefront and the store admin have also been rebuilt on the platform design system.
Three of the steps below look for records a past failure has already left wrong. Run them.
Requires Core v6.5.8.
Update Instructions
pnpm updatorThen, in this order:
1. Restart the backend
pm2 restart all2. Find orders that could not be cancelled
Any order an admin tried to cancel or reject before this release is still PENDING or COMPLETED, and the buyer's money is still with the platform. There is no marker for the failed attempt — it rolled back — so the orders have to be identified from the support side. Once found, cancel them through the admin screen as normal: the refund, the revenue reversal and the stock restoration now all happen together, and re-cancelling something that was already handled pays nothing twice.
-- Orders still holding a buyer's money.
SELECT o.id, o.userId, o.status, o.total, o.currency, o.createdAt
FROM ecommerce_order o
WHERE o.status IN ('PENDING', 'COMPLETED')
ORDER BY o.createdAt DESC;3. Re-check any multi-item order for over-charged shipping
A cart containing more than one physical product was charged the shipping fee once per product while the checkout screen quoted it once. Affected orders are recognisable by more than one order row sharing a createdAt second with shippingCost set on each:
SELECT userId, DATE_FORMAT(createdAt, '%Y-%m-%d %H:%i:%s') AS placedAt,
COUNT(*) AS orders, SUM(shippingCost) AS shippingCharged
FROM ecommerce_order
WHERE shippingCost > 0
GROUP BY userId, placedAt
HAVING orders > 1;shippingCharged minus one shipping fee is what the buyer was over-charged.
4. Re-check your review moderation
Hiding a review had no effect on the rating or the review count anywhere a customer could see them, so any product you moderated is still showing the score it had. Nothing needs changing — the ratings simply correct themselves on the next page load.
Upgrade Notes
None in setup. Six behaviour changes worth knowing:
- Identity verification is now enforced when the order is placed. Where identity verification (KYC) is switched on for store orders, an unverified buyer is stopped at checkout and told which verification level is required. Previously the only check sat on the product detail page, so a buyer who added items from a category listing or from the wishlist reached the checkout form ungated — and the order was accepted regardless of their verification status. Both ways of ordering, the single-product order and the basket checkout, are now checked when the order is submitted, not just in the browser. Operators who have not enabled verification for store orders see no change.
- A multi-item cart is now charged one shipping fee. Revenue on multi-product baskets will fall by the fees that were being over-collected.
- Deleting a paid, un-refunded order is refused. Cancel it first.
- Shipping and tax now reach the platform wallet. Admin revenue figures will rise by the amounts that were previously unrecorded; they are recorded as pass-through, not as profit.
- New orders carry their own store transaction type instead of the generic payment type. Any report that filtered store orders as generic payments needs updating; historical records are unchanged.
- Public ratings will move. Hidden reviews stop counting, and the category listing stops counting only hidden ones — both in the direction of what the moderation screen has been saying all along.
Changed
The storefront and store admin have been rebuilt on the platform design system
The store looks materially different. Nothing about ordering, pricing or delivery changed with it, but every screen has moved onto the shared design system rather than carrying its own styling.
- Landing page: rebuilt from the shared landing kit used by the rest of the platform.
- Page frame: every store screen — products, categories, cart, checkout, orders, wishlist, shipping — now uses the standard page frame, so headings, spacing and page width match the rest of the site.
- Empty and error states: five one-off empty and error screens written just for the store have been dropped in favour of the shared ones, so an empty cart, an empty wishlist, an empty shipping list and a failed product page all behave the way they do elsewhere.
- Loading: each screen now shows a loading skeleton shaped like the screen it is standing in for, instead of a generic spinner.
- Light and dark: the hardcoded amber, green and grey accents and the per-page background overrides are gone; the store follows the active theme and the colours set in the design settings.
- Overlays: product search and the image lightbox are now built on the platform's standard dialog instead of hand-rolled panels. Both close on Escape and on a click outside — the search overlay previously closed only from its own close button — focus stays inside an open overlay and returns where it came from, and both are announced as dialogs to screen readers.
Fixed
Security
- Two admin catalogue lists were readable by any signed-in account. The admin category picker and the admin product picker required a sign-in but no staff role, so any signed-in customer could read them. What they return is low-value — active product names, prices and currencies, and active category names — all of which the storefront already publishes, so no private data was exposed. Both now require the matching staff permission: view categories and view products respectively.
Cancelling or rejecting an order was impossible
All three ways an admin can reverse an order — the status action, the edit screen and the bulk action — failed. Two of them could not find the buyer's payment at all and reported "Transaction not found"; the third found it and then failed writing the refund. Either way nothing was saved: the order stayed as it was and the buyer was not paid.
- Changed: all three work. The refund is checked before the status is changed, so an order can never end up cancelled with the money still taken.
The bulk cancel and the single cancel did different things
Cancelling one order reversed the platform's revenue and put the stock back. Cancelling the same order as part of a selection did neither — it only paid the buyer. Had it worked, a bulk cancel would have created money: the buyer refunded in full while the platform kept the revenue.
- Changed: however an order is cancelled, the buyer is refunded, the platform's revenue is taken back and the stock returns to the shelf. Confirmation emails no longer sit inside the refund itself — a mail failure used to undo refunds that had already been decided.
Adding to the basket from a category page or the wishlist broke the checkout
A product added from a category listing or from the wishlist arrived without its wallet type — the setting that decides which balance the buyer is charged from — and the checkout page then failed outright: a blank error screen instead of a checkout form. Because the basket is saved in the browser, it kept failing on every visit until the buyer emptied the basket by hand. Only a basket built entirely from product pages could be paid for.
- Changed: category listings and the wishlist now carry the wallet type, so the checkout loads. A basket saved before this release is repaired automatically the next time the buyer opens the store; an item whose product no longer exists is dropped rather than left to break the page again.
- Also: a product that genuinely has no payment details set is now refused at the "add to basket" step with an explanation, instead of being accepted and then failing at checkout.
A multi-item cart was charged shipping once per product
The cart and checkout screens quote one shipping fee for the order, but the fee was added once per product. A three-product basket with a 10 shipping fee was quoted 10 and charged 30.
- Changed: shipping is charged once per checkout, and the wallet moves by exactly the figure the checkout quotes.
Deleting an order destroyed the record but not the charge
The delete buttons removed the order and nothing else. On an order that had been paid for and not refunded that erased the only record of the charge while the money stayed with the platform, the stock stayed off the shelf, and the buyer lost the order from their history.
- Changed: deleting a paid, un-refunded order is refused with an explanation. Cancel it first — that refunds the buyer, reverses the revenue and restores the stock — then delete it.
Digital products could not be downloaded
Three separate faults on one path meant every download failed with a server error, for every buyer, since the feature shipped. Files were also looked for in a folder nothing on the platform uploads to, and the admin screen asks for a download URL — which is how most stores deliver — while the backend insisted on a local file path, so a real link was rejected too.
- Changed: a web link is handed to the buyer as-is; a local file is served from the platform's real uploads folder; and a missing file now says "not found" rather than looking like a crash.
The Download button never downloaded anything
The order page sent the wrong identifier, so the request could never match anything — and the page announced "Download started" every single time regardless.
- Changed: the button downloads the file, and a failure now says so.
A purchase delivered as a licence key said nothing had been attached
A downloadable product delivered purely as a licence key — no file and no link — told the buyer nothing had been attached to their purchase. Only products with a file or a link could be handed over at all.
- Changed: the key is handed to the buyer, together with any delivery instructions the operator recorded. Those instructions are now returned for every kind of delivery — key, link or file — and are also shown on the order.
The shipping page told every customer they were forbidden
The buyer's shipping page required an admin permission that no customer holds, so every customer who opened it was refused — on a page that only ever shows their own shipments.
- Changed: customers can open their shipping page. Sign-in is still required and the page still shows only their own shipments.
- Also: the same page was sending the customer's full account record, including their stored password, to the browser. Only their name, email and avatar are sent now.
The shipping page went blank for orders priced in a crypto ticker
Any shipment containing a product priced in USDT — or any other ticker that is not a national currency code — blanked the shipping page. Not an error message: nothing. A shipment with no order attached blanked it the same way.
- Changed: a price in an unrecognised ticker is shown as the amount followed by its code, and a shipment with no order attached is skipped instead of taking the whole page down.
The wishlist never synced with the server
The wishlist was only ever whatever the browser had saved locally — empty on any other device. Removals were worse: they never reached the server at all, so an item removed on one device stayed forever. And a user who had never saved anything was told "Wishlist not found" instead of being shown an empty list.
- Changed: the wishlist syncs, removals persist, and an empty wishlist is an empty wishlist.
Hidden reviews still counted — and one listing counted only the hidden ones
The product list and product page averaged and counted every review regardless of status, so hiding an abusive or fraudulent review changed nothing a customer could see. The category listing had the opposite fault: it rated each product by the reviews an admin had taken down, ignoring the visible ones.
- Changed: every public rating and count includes approved reviews only.
An unknown category showed a server error
A mistyped category link looked like the site had crashed, and every genuine failure on that page was reported with the same useless message.
- Changed: an unknown category is a proper "not found" page.
The admin dashboard showed unit counts as order totals
The dashboard replaced each order's total — what the buyer was actually charged, after discount and including shipping and tax — with the number of units in the order. Recent Orders showed "2" against an order worth 240. Revenue was separately recalculated from today's prices, so editing a price moved last month's revenue, and discounts, tax and shipping were ignored throughout.
- Changed: totals and revenue come from the order as it was charged. The unit count is still shown, separately.
The order confirmation email contradicted the charge
The email recalculated the totals from scratch — re-reading the current tax rate, re-applying shipping that a free-shipping code had waived, and never subtracting the discount. Anyone who used a coupon was emailed a receipt for the full price.
- Changed: the email quotes the order exactly as charged: subtotal, discount, shipping, tax and total.
The checkout page quoted a discount it would not get, and mis-read the balance
A discount code belongs to one product and only ever discounts that product — but the checkout and cart screens applied the percentage to the whole basket, promising a saving the charge would never match. Separately, the "you have enough" check ignored tax, shipping and the discount, so a buyer could pass the button and then be refused — or be blocked when a discount had actually made the order affordable.
- Changed: the discount is quoted against its own product, and the balance check uses what will actually be charged. The cart page now also applies tax to the discounted subtotal, so the cart and the checkout stop disagreeing about the same basket.
The checkout printed raw wallet balances, and the "not enough funds" message never appeared
The wallet picker printed each balance exactly as stored — 0.962390320000000000 rather than 0.96239032. Worse, when a buyer genuinely lacked the funds, the message meant to explain it failed instead of being shown, so the button simply did nothing and the buyer was left with no explanation at all.
- Changed: the shortfall message appears, quoting what is required and what is available, and both figures are shown at the currency's own precision — eight decimals for crypto, two for national currencies. Forcing two decimals had made a crypto shortfall read as a rounding glitch: "1.00 required, 0.96 available".
Shipping and tax were taken from the buyer and reached no wallet
The buyer was charged subtotal − discount + shipping + tax, but only the discounted subtotal reached the platform's wallet. The shipping and tax portions left the buyer's balance and were recorded nowhere: the books did not balance, and admin revenue reports understated what had actually been collected. The operator still has to pay the carrier and remit the tax, so the money is theirs to hold — it simply was not being recorded.
- Changed: shipping and tax now reach the platform wallet, recorded separately from profit so margin is not overstated on every physical order. Cancelling reverses them too, so a refund cannot create them out of nothing.
A store order was indistinguishable from any other payment
A purchase looked identical to a platform fee in the buyer's transaction history, and anything reporting on payments — including the affiliate program — could mistake unrelated fees for store orders.
- Changed: store purchases now have their own transaction type. Existing records are unchanged.
A digital product file could not be uploaded at all
Uploads accepted images and video and rejected everything else, so there was no way to put the actual deliverable — a PDF, a zipped asset pack, an audio file — on the platform. A downloadable product had to be hosted somewhere else entirely.
- Changed: uploads now accept PDF, ZIP/7z/tar/gz/rar, EPUB, MP3/WAV/OGG/FLAC, TXT and CSV. Formats a browser would run or display inline — HTML, SVG, JavaScript, XML — are deliberately excluded, because allowing them would turn the upload form into a security hole. The existing 10MB platform limit applies.
Merchant-written product descriptions were unreadable in dark mode
Code samples and the shaded heading strips inside a formatted description were painted a fixed near-white whatever the theme, so in dark mode they showed light text on a light background. Descriptions also ignored the corner radius and the monospace font chosen in the design settings, and their styling overrode the platform's own base styling wherever the two met.
- Changed: product descriptions follow the active theme, take their corners and monospace font from the design settings, and no longer override the platform's base styling.
Smaller fixes
- The shipping page's address panel showed the buyer's profile, not the delivery address. It was headed "Customer Information" and read the buyer's saved account profile — a name, a phone number and, at best, whatever location they had stored — never where the parcel was actually going. It is now "Delivery Information" and shows the order's own shipping address: name, email, phone, street, city, state, postal code and country, with a plain message when the order does not have one.
- The order confirmation screen never showed the "your digital products are ready" or shipping panels, because it read the cart it had just emptied.
- The shipment badge showed a raw code, a cancelled shipment said nothing, and free shipping showed no line. The badge on the buyer's shipping page printed the internal status "TRANSIT" instead of "Shipped" in the buyer's own language; a cancelled shipment carried a badge with no explanation, where every other status had one; and a shipment costing exactly zero hid the cost line entirely. Statuses are now translated and colour-coded, a cancelled shipment says what happened, and free shipping is shown as 0.00.
- The admin action that changes what a buyer receives required only view permission; it now requires edit permission.
- The shipping page's sign-in button led to another gated page. A signed-out visitor was asked to sign in and handed a button that sent them to the orders page — which also requires signing in. It now goes to the login page, with "back to home" as the secondary action.
- Every order status was the same colour in the buyer's order list. Pending, Processing, Shipped, Delivered and Cancelled all rendered as the same neutral chip; nothing shown was wrong, but the list gave no signal at a glance. Statuses are now colour-coded the same way as everywhere else on the platform.
- Attaching download details to an order item that does not exist reported a crash. An admin attaching a licence key or a download link against an item that does not exist was told the server had broken; they are now told the order item was not found.
- "Verified Buyer" on landing-page reviews showed a raw placeholder instead of the words, in every language including English.
- The admin product form accepted a file-path field that was silently discarded on every save. It has been removed rather than left advertising something that does not happen.
- Store category links logged a missing-translation warning on every draw. The category names in the store header are the ones you created in the admin, but the header still went looking for a translation for each of them. The names themselves always displayed correctly — this was log noise only. Names you supply are now treated as their own titles and are no longer looked up.