The award seam¶
Any installed extension can award or deduct points through one model call. It writes the points to OpenCart's own reward table, so they appear on the customer's Reward Points tab and in their balance at once, and it writes a row of Loyalty's own beside them. Loyalty's own earning, redemption, reversal and expiry all go through the same call.
if (class_exists('\Opencart\Catalog\Model\Extension\Loyalty\Loyalty\Award')) {
$this->load->model('extension/loyalty/loyalty/award');
$result = $this->model_extension_loyalty_loyalty_award->award([
'customer_id' => 42,
'points' => 250,
'source' => 'review_requests.review',
'key' => 'review_requests:review:918'
]);
}
class_exists() is an exact test: OpenCart only registers an extension's
classes once the merchant has installed it, so Loyalty's files on disk alone
answer false. From the admin, create a storefront instance first, the way
OpenCart's own order-status change does:
$this->load->model('setting/store');
$store = $this->model_setting_store->createStoreInstance($store_id, $language);
$store->load->model('extension/loyalty/loyalty/award');
$result = $store->model_extension_loyalty_loyalty_award->award([...]);
It is a model call rather than an event because OpenCart's event trigger returns nothing, and you could never learn your points were refused.
Parameters¶
| Parameter | Required | What it is |
|---|---|---|
customer_id |
yes | The account the points belong to. A guest has none, and is refused with customer. |
points |
yes | A whole number of points, signed: positive awards, negative deducts. Never zero. |
source |
yes | What the points are for, at most 32 bytes — yourcode.reason is the convention. Recorded on the movement, and used as core's description when none is given. |
key |
yes | Your idempotency key, at most 191 bytes and unique across every caller — yourcode:reason:id is the convention. A second call with the same key is answered claimed and writes nothing. Compared case-insensitively. |
order_id |
no | The order the movement is about, 0 when none. Written to core's reward row too, where core's own screens show it. |
description |
no | The line core's customer Reward Points tab shows. Defaults to source. Stored as given, in no particular language, because core's table has no language dimension. |
store_id |
no | The store the movement is for, which decides whether the programme is on. Defaults to the customer's own store. |
base |
no | For an award computed from an amount, the amount it was computed from. Recorded on the movement so a merchant can see where it came from; nothing is computed from it. |
rate |
no | For an award computed from an amount, the rate it used. Recorded for the same reason. |
A parameter the seam does not know is ignored, so a call written against a
later release still reaches this one. A missing or over-long key or source
is a bug in the caller and throws InvalidArgumentException: MySQL would cut
it short without a word, and two shortened keys could collide.
What comes back¶
| Key | What it holds |
|---|---|
written |
true only when this call wrote the movement. |
refusal |
'' when written, otherwise one of the codes below. |
points |
The points written, or for claimed the original movement's. 0 for any other refusal. |
date_added |
When it was written, or for claimed when the original was. '' for any other refusal. |
state |
posted when written, the original's state for claimed, '' otherwise. |
loyalty_movement_id |
Loyalty's row, 0 when nothing was written. |
customer_reward_id |
OpenCart's row in oc_customer_reward, 0 when there is none. |
Refusals¶
Checked in this order, and each is distinct from the others and from success.
| Code | What it means |
|---|---|
claimed |
Already done: something already holds this key. Nothing is written, and points, date_added and state are the original movement's. Treat it as success you already had. |
amount |
points is zero, or not a whole number. |
customer |
No such customer: a guest, or an account that was deleted. OpenCart declares no foreign keys, so without this the points would be written against nobody. |
store |
No such store. |
off |
The Loyalty module is switched off for that store. This is the module's status, not its earn rate: a store earning nothing on orders still accepts your points. |
What a movement may be¶
Every movement carries one of these states. Only posted counts toward
anything. You see a state only in a claimed answer.
| State | What it means |
|---|---|
reserved |
A claim against something not owed yet, such as points set aside for an order that has not been confirmed. Nothing is in core's table. |
posting |
About to write core's row. A movement left here is a failed write: visible on the Loyalty screen, repaired by the scheduled task, and never counted twice. |
posted |
Written to core's table. The only state that counts toward anything. |
released |
A claim that was voided. |
orphaned |
Core's row was deleted underneath it, by the merchant's own button. Kept as history. |
erased |
The customer was erased. Kept, with the customer emptied, so the ledger still explains its orders. |
If it fails part-way¶
Loyalty's row is written first, as posting, then OpenCart's, then
Loyalty's row is marked posted. OpenCart's reward table has no unique key of
any kind, so writing it first would leave nothing to stop a retry paying twice. A
failure between the two writes reaches you as an exception, leaves your key
held at posting with the balance unmoved, and answers any retry claimed.
Loyalty's scheduled task finishes the write.
What may change¶
Additive, in any release: a new optional parameter, a new refusal code. Breaking, and only with a new major version: removing or renaming a parameter, renaming a refusal code, or changing what a repeat returns.
There is no check on who is calling beyond the code running in this store.