Changelog¶
What changed in each release of Returns Portal, in terms of what it means for you rather than which files moved.
1.5.1 — 3 October 2026¶
Corrections only: nothing new to switch on, and nothing you have configured changes meaning.
A second store keeps the portal on after an update. Each update wrote the full set of shipped defaults for every store, Status off among them, and a store's own row wins over the default store's. So from the next update on, every shopfront but the default one ran with the portal off and OpenCart's own unverified return form open again. An update now writes a second store only what somebody set on that store's own tab, and nothing at all for a store nobody configured separately, which then reads the default store's answers, as which settings are per store says. Updating to this version removes the row an earlier update left, so there is nothing to put right by hand; if you run more than one store, follow the return link on each of them afterwards and you land on the portal.
The settings screen opens with a captcha extension installed. As soon as any captcha extension was installed, including OpenCart's own Basic Captcha, the screen that lists them under Captcha on the lookup stopped with an error instead of opening. It lists them now.
Your own words come back as you typed them. Wording with an &, a quote
or a < in it, and the return address, gained a layer of & each time the
settings were saved, and printed the entity on the page and the slip. They are
now stored as typed. Text already saved with & in it is not changed by the
update: retype it once and save. A customer's name in the greeting of their
emails now reads O'Brien rather than O'Brien.
The storefront's forms are closed to a forged request. For a customer
signed in to an account, submitting a return, uploading or removing a photo
and cancelling a request now check OpenCart's own customer_token, as core's
account pages do. It was not checked before, so a page on another site could
act for a signed-in customer. OpenCart's own return form can no longer be
reached by spelling its address in capitals either: while the portal is on,
every spelling lands on the portal.
A store credit is paid once, or not at all, and you are told which. The credit and the record that it was paid now go in together. A database fault between them used to leave the request offering the button again, so a second click could pay a second credit; and a fault before them was read as somebody else having paid it, so nothing was paid and nothing said so. Both are now reported and written in full to the log. If the store does not answer the click at all, the button stays off and the screen tells you to reload and look before trying again.
A screen that gets no answer says so. The request screen, the customer's lookup, line picker and confirmation, the erasure and personal-data screens and the button that removes what the extension holds used to sit still when the store sent back an error page or nothing. Each now says it could not tell whether the action went through and to reload before trying again. A photo whose file cannot be deleted from disk now keeps its record and says so, instead of vanishing from the request while the file stayed. A receipt email the mail server refuses is counted as not sent.
The API returns names and comments as typed. First and last names,
product names, models, photo file names and history comments came back the way
OpenCart stores them, so an ERP received Bell & Howell; it now receives
Bell & Howell. If your integration decoded these itself, it can stop. A
comment posted through the API is stored the way a typed one is, so it prints
as text on the request screen and in the emails. An OpenCart API user whose key
has been emptied by hand is refused rather than let in with no key. No field
was added, moved or removed; see the API reference.
Detailed logging now records something. The switch saved its window and then wrote nothing extra. It now writes a line per submission, per request refused at the last step for being out of window, and per status change with the emails it sent, in ids and counts only; see what Kyvero extensions log.
Smaller corrections. Updating no longer adds another copy of the queue,
erasure and personal-data permissions to the installing group each time. Putting
stock back now writes each line's stock and its record together. A freshly
uploaded photo whose name has an & in it is listed by its real name.
Nothing to do on update beyond retyping any wording that already shows
&. No setting is added or changed, no table or column changes, and the
API's shape is unchanged.
1.5.0 — 30 September 2026¶
If you are coming from 1.1.0, which is the version the marketplace has offered until now, read 1.4.0, 1.3.0 and 1.2.0 below as well: they arrive in the same download. The short version is Returns Portal's own sentences in your words, per language; a faster API on a busy store; a published list of everything held about a person, with a button that removes it; per-store settings that no longer borrow the default store's; a configurable overdue mark on the queue; and OpenCart 4.1.0.4 as a tested release. What follows is what is new in 1.5.0 itself. Every setting and every row you already have is kept.
Closing a request can put the returned stock back. It is off until you switch it on: Put returned stock back on close, on the default store's settings screen under Deciding, and paying, one setting for the whole installation. With it on, the close control on an approved request lists every accepted line with a Put back in stock box, ticked, beside whether the customer said it was opened; untick what you cannot sell again. Ticked lines go back on the counters checkout took them off: the product, its master where it is a variant, and each option value, wherever that counter subtracts stock. Approving never restocks: before the parcel arrives there is nothing on the shelf. The stock then follows the order's status, so marking the order Refunded afterwards, which makes OpenCart put the whole order back, does not count the returned units twice. A line whose stock is not tracked, a Product Bundles bundle included, says so and is left alone. Requests closed before you update, or while the switch is off, are never restocked later. See Putting stock back for how it keeps in step and the few cases where it cannot, and the guide.
You can record the refund you paid. A Recorded refunds panel on every approved or closed request takes the amount, the day and how you paid it, as many times as there were payments, and shows their sum against the estimated refund. Returns Portal still moves no money, and every label says recorded. The customer is sent nothing. A request still waiting for its parcel with a refund recorded against it is flagged Refunded before the parcel arrived on the queue. See the guide.
The queue exports to a spreadsheet. Export CSV at the top of the queue downloads one row per request line for the tab, store and filters on screen, every page of them. Telephone numbers, comments and photos are left out. See the guide for the columns.
The API says both. A line now carries restock (what was put back, and
whether it is in effect) and a request carries refunds; closing a request
through the API takes an optional not_restocked list and, with the switch on,
changes stock exactly as closing it on screen does. All of it is additive, and
recording a refund through the API is not offered. See the API
reference.
What it costs. No storefront page changes: the performance record is unchanged on all nineteen pages it measures. The one new work is on an order status change, where Returns Portal now runs one indexed query to find restock rows out of step with the new status. Measured by hand against 100,000 restock rows, that query reads one index entry and takes well under a millisecond. On an order nobody returned anything from it finds nothing and writes nothing; it writes stock only for rows whose order has just crossed into or out of OpenCart's processing and complete statuses.
Two new tables, for the stock put back and the refunds recorded, which makes nine. Both are kept on uninstall like the other seven, and both are on the personal-data page: they hold which admin did it and nothing about the customer. Returns Portal's log gains a line per restock, per status change that moves one, and per refund recorded or removed; see what Kyvero extensions log.
1.4.0 — 21 September 2026¶
OpenCart 4.1.0.4 is now a tested release. The full pass (archive, install, use, upgrade over itself, uninstall) has been through a 4.1.0.4 store, so the compatibility list runs 4.0.2.0 through 4.1.0.4 and the settings screen stops calling that release untested.
That release has a fault in OpenCart itself which stops every scheduled task on the store; see requirements. It costs Returns Portal nothing. The throttle prunes on write, photo orphans prune on upload, and every step of a return is driven by the shopper or by you, so there was never anything here waiting on a schedule. Nothing else changes.
1.3.0 — 21 September 2026¶
Everything Returns Portal puts in your database about a person is now published field by field, with what removes each one: table, column, why it is there, how long it is kept, and what an erasure does to it. It is on what this holds about a person, generated from the extension's own declaration rather than written beside it, so it cannot describe a column the code does not have or miss one it does.
A new screen answers what do you hold about me for one named person.
Customers → Personal Data, an email address or a username in, and out comes
every holding from this extension, from any other Kyvero extension you have
installed, and from OpenCart itself. Each extension answers for itself and
nothing there decides anything on another's behalf. It also prints what
OpenCart's own account-deletion flow does and does not do on the release you
are running, so read it before you promise anybody anything. There
is a Remove it button on the same screen, which takes what each declaration
says an erasure takes and keeps what it says is kept, with the reason shown
against every line it kept. Opening the screen needs OpenCart's own Modify on
customer/customer, because reading out everything a store holds about one named
person is at least as privileged as editing their account.
And, on the settings screen, a button that removes everything this extension holds about a person, for everybody rather than one person. It is deliberately not uninstalling. Uninstalling deletes nothing at all, on purpose: an OpenCart update is an uninstall followed by an install, so an extension that dropped its tables on the way out would destroy your returns on a routine upgrade. So removing what is held is a thing you do on a screen, while you can still see what you are about to lose. It prints the plan first (every table, file and key with a real count beside it), reports what went rather than saying success, and cannot be undone. What goes is the contact details on every request, the photographs, and the same columns on the core return rows this extension opened, never on a return it did not open. Your settings, the request lines, the decision history, the credit ledger and the throttle stay: those are your own record of what happened, and the reason each is kept is on the page beside the button.
Nothing about the portal, the queue, the estimate, the slip, the emails or the API changed.
You can now set how long an approved return may wait for its parcel before the queue calls it overdue. It was fourteen days, fixed; it is fourteen days by default, on the settings screen under Deciding, and paying. Nothing about the request itself changes when it passes that mark: no state moves, no email goes out, nothing closes. What changes is the count in the Approved, awaiting parcel tab's header. If you have decided everything and land on an empty Needs a decision, that number tells you something is still waiting. It is one setting for the whole installation, because the queue counts every store at once.
On a multi-store install, a store's settings tab no longer borrows the default store's answer field by field. Returns Portal reads settings the way OpenCart reads its own: a store that has its own row answers with that row, and a store that has none answers with the default store's. Until now an empty field on a store's row fell through to the default store's value instead, which meant a box you had deliberately cleared on one store kept showing another store's answer and there was no way to say nothing on that store at all.
Six fields could behave differently as a result (the captcha choice, the two exclusion lists, the alert address, the return address and the wording), and only on a store whose own settings tab has never been saved. On such a store the portal is switched off, so nothing a shopper can reach reads any of them; what you will see is that opening that store's tab for the first time now shows what Returns Portal ships rather than what the default store was set to. Saving the tab once makes the two identical, as it always did. A single-store install is not affected in any way.
1.2.0 — 10 September 2026¶
Reading your returns out through the API is much faster on a busy store, and updating puts the index there. Both API collections (the return requests and the photographs) are read a page at a time in a fixed order, and until now nothing in the database was arranged to serve that order, so each page was a read of the whole table. On a store with 200,000 rows that was 71.8 milliseconds a page; it is now 0.16, and walking the lot end to end went from about four and a half minutes to under a second. You do not have to do anything: updating adds the index to the tables you already have. Nothing about what the API returns changed, so an integration built against 1.1.0 keeps working untouched.
You can now write Returns Portal's own sentences in your own words, per language. A wording panel on the settings screen holds four of them: what a customer is told beside a line you excluded, what else to do with the parcel, the caveat under the refund estimate, and how the emails open. Each is stored per language, each language is saved on its own, and a box left empty shows the wording this extension ships in grey, so you can tell inherited from empty. Clearing a box puts our wording back rather than leaving the sentence out.
Your existing return instructions and exclusion notice move into it, filed under the language you were reading the admin in. They used to be single boxes with no language at all, and there is no other correct place to put them: filing them under English would serve Dutch prose to English customers, and copying them into every language would put one language's words in all of them. If your admin and your storefront are in different languages, open the wording panel after updating: the sentence is there, under the language you typed it in. See Language.
The emails and the portal now resolve their language the same way every other extension we sell does. Nothing a customer reads changed, and nothing you have configured changed: this release replaces Returns Portal's own hand-rolled handling with the shared one, which behaves identically on a store running one language and does real work on a store running more. What a named native speaker has signed off is on the shared language promise page.
Your own words are escaped everywhere they are printed. A return address or an instruction carrying a character HTML treats as markup used to be able to break the page it was printed on. It cannot now, on the settings screen, on the account screens, on the slip or in the emails.
1.1.0 — 4 September 2026¶
Returns Portal now answers a JSON API, for the system that keeps your own records rather than for anybody's browser. Nothing about the portal, the queue, the RMA slip or the erasure screen changed: this release adds a way to read returns out and settle them from somewhere else, and takes nothing away.
What it can do: read the return requests, filtered by the moment they last
moved, so a nightly synchronisation can ask for what changed rather than for
everything; read a request's lines, its refund estimate, its store credit and
its history; read the metadata of the photos a customer attached, and fetch the
bytes of one. And it can settle a request the way your operator would: decide
it line by line, close it when the parcel arrives, or cancel it because the
customer withdrew it. A decision made through the API is recorded in the same
history the admin screen writes, naming api as who did it.
It is off until you switch it on. The switch is on this extension's own settings screen under Extensions → Extensions → Modules, in a new API section that also shows you the address to hand an integrator, which versions are answered, and a warning if no OpenCart API user is set up to reach it. With the switch off every route answers as though the API were never built.
That switch is separate from the extension's Status. You can switch Returns Portal off (closing the storefront portal and restoring OpenCart's own return form) and the API keeps answering. That is deliberate: when the extension stops doing its job in the shop, the system holding your records still needs to read what is already there.
Before you hand the address to anybody, note three things:
- A caller authenticates as one of OpenCart's own API users, under System → Users → API, over HTTPS. That credential is not scoped to this extension: it opens every API in the installation, including OpenCart's own. There is no per-extension key.
- A
closecan move your money. Where your own Approve automatically and store-credit settings fire on that step, closing a request credits the customer's balance. It is your setting doing what it says, but a call somebody else makes can trigger it. - Erasure cannot reach a copy already pulled. Once a system has synchronised a request into itself, that is a second copy of the customer's personal data, outside anything this extension or this store can erase.
Version 1 of the API is promised, not provisional: see how long an API version lasts for what may change inside it and what may not. The full surface is on the API reference, which is generated from the same declaration the routes are served from, so it cannot describe a route your copy does not answer.
It still runs on OpenCart 4.0.2.0 and 4.1.0.3. See requirements and install.
1.0.0 — 30 August 2026¶
The first public release. There is no previous version, so nothing below is a change. This entry is the baseline every later one is measured against.
What ships: a returns portal a guest can use, entered with an order number and the address the confirmation went to and nothing else; a line picker that shows every line on the order, with the ones that cannot come back greyed and the reason printed on them; netting off what has already gone back, including returns filed through OpenCart's own form; a per-line refund estimate with its breakdown, computed from that order's own recorded prices and frozen at submission; photos on a line, accepted on their bytes rather than their filename and stored outside the web root; a printable RMA slip, overprinted NOT YET APPROVED — DO NOT POST until you agree; a queue at Sales → Return Requests with its three tabs and their counts; partial approval line by line, with the customer emailed the outcome and your reason; store credit in one click; and an erasure screen that shows the plan before it runs it. There is no scheduled task to set up anywhere. What it does not do is the shorter list; read it first.
While the portal is enabled it replaces OpenCart's own return form, which is the most invasive thing it does. Switching the module off restores OpenCart's behaviour exactly. See what it is for.
It runs on OpenCart 4.0.2.0 and 4.1.0.3, and refuses to install below 4.0.2.0 rather than half-working. See requirements and install.
What an update keeps¶
Two things are true of every update, on top of what is generated below:
- No return is lost. Requests, lines, decisions, photos and store credit records all survive, because uninstalling drops no table and deletes no row or file.
- RMA numbers keep meaning what they meant, so a slip a customer is holding still names their return afterwards.
The enabled flag comes back with everything else, and that one is deliberate: while the portal is on, OpenCart's own unverified return form is closed, and an update that reset the flag would reopen it without saying so.
Your settings come back, with the exceptions below. An update is an
uninstall and then an
install, and OpenCart deletes
an extension's settings at the uninstall step. Returns Portal keeps its own copy
of them and puts them back afterwards. Each store keeps its own, apart from
module_returns_portal_api_enabled, module_returns_portal_stale_days,
module_returns_portal_restock and module_returns_portal_diary_verbose_until,
which Returns Portal reads once for the whole installation.
These are asked for again rather than put back:
module_returns_portal_diary_verbose_until— Detailed logging comes back off after an update, the way a fresh install does. An upgrade is an uninstall and a reinstall, and putting a diagnostic window back is how a store ends up verbose months after the support exchange that asked for it ended.
A setting a release adds does not change what your store does. Its default is what Returns Portal did before the setting existed, so an update never asks you to go and set something to get back the behaviour you already had.
What this cannot tell you is whether a release changed how a setting behaves, as against whether it survives. Nothing derives that from the code, so it is written by hand: a release that changes an answer you relied on says so in its own entry above, as its own paragraph and never among what is new.