Skip to content

Settings

Loyalty keeps its settings in OpenCart's own setting table, under the module_loyalty group. You set them at Admin > Extensions > Extensions > Modules > Loyalty.

Each key below carries where it applies. per store means a multi-store install can hold a different answer per storefront, and both are honoured: a read takes what that store holds, then what the default store holds, then the shipped default. install-wide means one thing serves every storefront, and the key's own description names that one thing.

Scope

Key Default What it does
module_loyalty_status
per store
0 Whether the programme runs on this store. Off, nothing is earned or spent through this extension on it, whatever the rates say. Remembered across an update, because an update that quietly switched a points programme off is one a merchant finds out about from their customers.

Earning

Key Default What it does
module_loyalty_earn_signup_points
per store
0 Points a customer earns once, for ever, when their account is created on this store: the register page, the checkout's register step and an account a merchant creates in the admin all count, and an account is awarded once whichever way it came. It is awarded only while the programme is on for the store. 0 awards nothing and writes nothing. A customer waiting for approval, or whose account is disabled, is awarded all the same, because approval decides whether somebody may sign in rather than what they are owed. Nothing is backfilled: raising the figure awards the accounts created from then on. The row reads Welcome bonus in the store's default language.
module_loyalty_earn_since
per store
0 When earning was switched on for this store, and 0 is off, which is how it ships. Written by the Start awarding points button and never typed: only an order placed after this moment earns, so switching earning on commits the store from the next order onwards and never backdates anything. Stop awarding points puts it back to 0, and pressing Start again restarts the clock. Remembered across an update, because an update that quietly stopped the programme paying out is one a merchant finds out about from their customers.
module_loyalty_earn_status_ids
per store
empty The order statuses that award an order its points, the first time it reaches any of them. Empty, which is how it ships, awards nothing. An order that reaches one again awards nothing more, and changing this list never backdates anything. An order placed before earning was switched on earns nothing, even when it reaches one of these statuses afterwards: an order is judged by when it was placed, not when it completes.
module_loyalty_earn_reverse_status_ids
per store
empty The order statuses that take an order's points back, the first time it reaches any of them: one negative row for exactly what the order was awarded, whatever the rate says today. Empty, which is how it ships, takes nothing back, and the screen warns in red under the field while awarding statuses are ticked and none of these is, without refusing the save, because a store may decide points once given stay given. A status ticked in both lists is refused on save. Canceled, Refunded and Chargeback are the ones to tick. Reversal keeps working after earning is stopped, for the orders that already earned.
module_loyalty_earn_rate
per store
0 Points earned, as a percentage of the order's base. 100 is one point per unit of the store's default currency, which is what OpenCart records an order's lines in, and 200 is two, so a figure above 100 is ordinary. Rounded down once over the whole order, never per line. Zero awards nothing.
module_loyalty_earn_base
per store
goods What the percentage is taken of: goods, the order's product lines before tax, or goods_incl_tax, the same lines with their tax. Either way it is net of the discounts on the order, such as a coupon or points spent, and never reduced by store credit or a gift voucher, which the customer already owned.
module_loyalty_earn_rate_group
per store
empty A rate per customer group, replacing the store's own for orders placed in that group. A group left blank earns the store's rate, never nothing, so adding a customer group never silently stops it earning. The group is the one the order was placed in, not the one the customer is in today.
module_loyalty_earn_product_override
per store
1 Whether a product's own Reward Points figure, from the product form's Reward Points tab, replaces the percentage for that line. On, which is how it ships, the line earns exactly that figure and takes no share of the percentage. A figure of zero means not set, so there is no way to say a product earns nothing.
module_loyalty_earn_birthday_points
per store
0 Points a customer earns once a year, on or after their birthday, awarded by the hourly scheduled task. 0 awards nothing. Each customer's year is decided once, the first time the task sees that birthday has arrived: it is awarded only while the programme is on, earning has been switched on, and the birthday fell on or after the day it was; otherwise that year is recorded as passed and nothing is paid, so switching anything on never pays for birthdays already gone. A 29 February birthday counts from 1 March in other years, and a task that was down for a fortnight catches up without paying twice. The row reads Birthday bonus and the year in the store's default language. On OpenCart 4.1.0.4, whose scheduler never reaches any extension, the task runs only from a crontab line calling php extension/loyalty/loyalty.php.
module_loyalty_earn_birthday_field_id
per store
0 Which of the store's own custom fields holds the birthday, because OpenCart has no birthday field. Only a Date field at the Account location can be chosen. 0, which is how it ships, means none, and nothing birthday-related runs. A customer who left it empty, or whose value is not a real date, is simply not celebrated; that is written to the detailed log and never warned about.

Order total

Key Default What it does
total_loyalty_status
install-wide
0 Whether the Loyalty line takes part in checkout. Set at Extensions > Extensions > Order Totals > Loyalty rather than on the module's own screen, because OpenCart gives an order total a setting code of its own. Remembered across an update like everything else here.
total_loyalty_sort_order
install-wide
2 Where the Loyalty line sorts among the order totals. 2 is where core's own Reward Points total sits: before Shipping (3), so points cannot pay for postage, and before Taxes (5), so the discount reduces the tax the way a coupon does.

Spending

Key Default What it does
module_loyalty_redeem_rate
per store
100 How many points buy one unit of the store's default currency: at 100, 500 points take 5.00 off. One number per store, the same for every customer group. No live exchange rate is read; an order in another currency is shown in it the way OpenCart shows any discount. It is also the step the cart's control moves in, which is why there is no separate increment. An order carries the rate it was created at, so changing it never changes what an order already quoted will charge.
module_loyalty_redeem_min_points
per store
0 The balance a customer needs before the cart offers to spend any of it. Below it the control does not appear at all. 0 offers spending from the first point.
module_loyalty_redeem_max_percent
per store
100 The most of an order, as a percentage from 0 to 100, points may pay for. Measured on the order as it stands where the Loyalty line sorts, which at its default of 2 is the goods before shipping and tax. However it is set, an order can reach zero and never pass it.

Expiry

Key Default What it does
module_loyalty_expiry_enabled
per store
0 Whether points expire on this store, and off is how it ships. On, a customer who moves no points for the window below loses the part of their balance this extension awarded, and never before a warning email the lead below ahead of it. Switching it off again stops any further expiry and brings back nothing already expired. It applies to the customers whose own account belongs to this store, since a customer has one balance and one clock.
module_loyalty_expiry_months
per store
24 Months with no points movement after which a balance expires. At least 12 and at most 120; a figure outside that is saved as the nearest one inside it. Only a movement counts as activity: points earned, spent, reversed or awarded by another extension through this one. Signing in, browsing, an order that earned nothing and a change made by hand on the customer's Reward Points tab do not.
module_loyalty_expiry_warning_days
per store
30 Days between the warning email and the expiry. At least 14 and at most 365; a figure outside that is saved as the nearest one inside it. Nothing expires until the warning is at least this old, so the date the email names is never sooner than this many days after it was sent.

Advanced

Key Default What it does
module_loyalty_diary_verbose_until
install-wide
0 When detailed logging stops, as a unix timestamp, and 0 is off. Turning Detailed logging on from this extension's settings form stores the moment two days from now; the writer compares that against the clock every time it is asked for a DEBUG line, so the window closes on its own with no scheduled task and nothing to clean up. While it is open this extension records what it did in far more detail, and the shared diary consequently holds less history.
module_loyalty_api_enabled
install-wide
0 Whether the extension answers API requests at all. Off, every API route answers 404 api_disabled in the API's own JSON envelope, so an extension with its API off is shaped like one that has none. Read once for the whole installation from the default store rather than per store, because the one thing it serves is OpenCart's own API user, which has no store of its own. It is a separate switch from the programme's own Status, and the two are read independently: the API answers whether or not the programme is running, because what the store owes in points is the merchant's own record and a storefront switch should not hide it. The API only ever reads; nothing can award, spend or expire points through it.

What you cannot change, and why

These are fixed on purpose. Each one is a decision with a reason beside it rather than a setting nobody got round to adding.

Earning

Shipping, handling and fees are never in the base, and a tender never reduces it. What separates a discount from a tender is OpenCart's own test: a negative order total reduces the price of goods exactly when its total model adjusts the order's taxes, which a coupon and reward points do and store credit and a gift voucher do not. An order total whose model cannot be read is counted as a discount, the direction that costs the store less, and named in the log.

Rounded down, once, over the whole order. Rounding each line would make the award depend on how the order was split rather than on what it was worth, and rounding up is a standing upward drift on what the store owes.

One award per order, for ever. Nothing is backfilled when a setting changes and an award is never recomputed when the order is edited; the award records the base and the rate it used, so a figure can be traced months later. An order too small to earn a whole point writes nothing and claims nothing, so it can still earn if it is edited upward. Guests earn nothing.

A reversal takes back all of an order's award or none of it. OpenCart has no partial refund at the order level, and prorating would recompute a row already written; a merchant who wants to take back part adjusts the customer's Reward Points tab by hand. Points already spent are taken back in full, and the balance goes below zero.

One reversal per order, for ever, and a reversed order never earns again, whatever statuses it passes through afterwards. An order that never earned reverses nothing and writes nothing.

An award already extinguished by expiry is not reversed. Expiry takes the whole expirable balance, so once it has run after an award, that award is gone; taking it again would charge the customer twice, which nobody but the customer would ever notice.

Scope

OpenCart's own Remove Reward button on the order screen is never blocked. It deletes the order's positive reward rows; this extension records its own matching rows as orphaned, outside every figure it reports, and the balance is right at once because it is OpenCart's own sum. Refusing the deletion is possible on OpenCart 4.0.2.x and not on 4.1, and a guard that holds on one supported release is worse than none.

Uninstalling removes this extension's event rows and its scheduled task, and nothing else: no table, no row, no setting copy. OpenCart performs an update as an uninstall followed by an install, so anything an uninstall deleted would be deleted by every update, and what is at stake is every customer's points history.

A customer's balance is always OpenCart's own live sum of their reward rows, the same figure core's customer screen and checkout use, so a point a merchant adds or removes by hand counts at once. This extension never stores a second copy. The per-customer summary it keeps holds only the part of a balance that can expire and when the customer last moved points, and it is never shown as a balance.

The liability panel on the Loyalty screen states points and never a monetary value or an estimate of one. What points can buy is arithmetic the merchant set; what they cost the business is accounting. The one place money is printed is the confirmation a save shows when a new exchange rate makes the points outstanding buy less, and that is the exchange rate the merchant typed, not a valuation. Its outstanding figure is one sum over the whole of OpenCart's reward table on every page view, not cached, for the reason the balance is not.

Award seam

Another extension awards or deducts points by calling the model extension/loyalty/loyalty/award, not by firing an event. OpenCart's event trigger returns nothing, so a caller could never learn that its points were refused.

Every call carries an idempotency key the caller mints, and a unique index on it is what stops a movement happening twice, even when two requests race. A repeat is answered as already done, with the original points and date. OpenCart's own reward table has no unique key of any kind, which is why this extension's row is always written first.

Rule Value
Longest key, in bytes 191
Longest source, in bytes 32

Spending

What points take off is spread over every goods line in proportion to its value, never over shipping, and each line's percentage taxes come down with its share, the way OpenCart's own coupon adjusts them. A product's own Reward Points figure is never read, in either direction: a cart of products nobody gave a points figure to can be paid for with points like any other.

The cart's control moves in steps of the exchange rate itself, one unit of currency at a time, so there is no increment to set.

A balance may go below zero, and no setting stops it. Points are deducted when OpenCart confirms the order, not when the customer chooses them, so a customer with two tabs open can spend the same points twice; the second order is deducted in full rather than flagged as fraud, the diary says so, and a negative balance blocks all spending until the customer earns it back. Points an order never paid for are never deducted at all.

While OpenCart's own Reward Points order total is switched on, customers cannot spend points through this extension: its line at checkout and its control on the cart stay away, so a customer is never shown two Reward Points lines that disagree. Earning carries on as normal. A fresh OpenCart installation ships with core's total switched on. Both of this extension's admin screens say so and link to core's screen; neither switches core's total off for you, and uninstalling this extension does not switch it back on.

Award seam

The seam checks nothing about who is calling beyond the code running in this store. Any installed extension may award or deduct points, the way any installed extension may already write to OpenCart's own tables.

Expiry

Nothing expires on a balance its customer was not warned about, and the warning has no switch. An email that fails to send records nothing, so that customer cannot expire and is warned again on the next hourly pass. A store whose mail is not working expires nothing at all: that is this rule working, not a fault. The first pass after expiry is switched on warns and expires nobody, so nothing can expire retroactively.

Any points movement this extension writes counts as activity and nothing else does, and a movement clears a pending warning in the same write, so a customer who acts after being warned is safe at once rather than at the next pass. Telling some movements apart from others would be accounting per batch of points by another name.

Only points this extension awarded can expire. A reward row added by hand on core's customer screen, or before this extension was installed, never expires however old it is, so a balance can be part expirable and part not. A customer whose expirable part is zero or below is neither warned nor expired.

One hourly pass warns at most this many customers per store and expires at most this many. A backlog is worked through over several passes, and the date an email names is always the later of the window running out and the warning period running out, so a delayed warning never shortens anybody's notice.

Rule Value
Customers per pass, per phase 50

The warning email's words are shipped, not configured. The notice is what makes expiry fair to the customer, and a box to edit it is a box that can be emptied. It goes to the customer only, in their own language where this extension ships one and in English otherwise; no copy goes to the store.

The warning goes whether or not the customer subscribed to the newsletter, and carries no unsubscribe link. It is a notice about the recipient's own balance, and an opt-out would quietly mean that customer's points never expire.

The warning states how many points will expire and on which date, never the balance. Where part of the balance does not expire the two differ, and naming the balance would promise something the expiry does not do; the email says in one further line that the rest stays.