API and data model
Every affiliate endpoint with its permission, the five database tables and what is unique about each, the hourly evaluation job, and the settings keys the engine reads.
Every route in this addon lives under /api/affiliate or
/api/admin/affiliate. Nothing is served from /api/mlm — that path exists in
the licensing route map but has no handlers behind it.
Member endpoints
Three of these answer without a session. The rest need one, and all of them scope their data to the calling user — there is no way for a member to read somebody else's downline or rewards.
Admin endpoints
Referrals
Conditions
There is no delete endpoint for conditions.
Rewards
Database tables
Five tables, all prefixed mlm_.
mlm_referral
Who referred whom. referrerId, referredId, status of PENDING / ACTIVE
/ REJECTED. Soft-deleting.
Two unique indexes, and they matter:
referredIdis unique. A user can be somebody's referral exactly once, ever. Sponsors cannot be re-assigned, and a soft-deleted row still holds the slot.(referrerId, referredId)is unique. A relationship cannot be duplicated.
A row where referrerId equals referredId is a self-referral: it marks the
top of a chain and is created automatically for members who need a tree root.
The upline walk stops when it reaches one.
mlm_referral_condition
The commission rules. Unique on name. Not soft-deleting and carrying no
timestamps — conditions are edited in place and never removed.
Notable columns: type (16-value enum driving the ledger scan), reward and
rewardType, rewardWalletType / rewardCurrency / rewardChain (where the
payout lands), minAmount, status, and period (DAILY by default, and not
settable through any endpoint).
mlm_referral_reward
Earned commissions. referrerId, conditionId, reward (a DOUBLE),
isClaimed, and sourceId. Soft-deleting.
sourceId is unique and is the whole of the duplicate protection. The
hourly evaluator writes condition_referrer_referred_period; the multi-level
event path appends :L1, :L2 and so on. MySQL permits multiple NULLs in a
unique index, so rewards created without one are unaffected — those fall back to
a 60-second, same-amount duplicate check instead.
Deleting a reward keeps its sourceId, so the evaluator will not recreate it.
mlm_binary_node and mlm_unilevel_node
Tree placement. Both are unique on referralId — one node per referral — and
neither carries timestamps.
mlm_binary_node holds parentId, leftChildId and rightChildId.
mlm_unilevel_node holds only parentId and allows any number of children.
Both payout paths walk mlm_referral.referrerId. These two tables exist to draw
the network diagram. Deleting rows from them corrupts the member-facing tree and
changes nobody's earnings.
The scheduled job
One job, registered only while the mlm extension is enabled and deregistered
within a scheduler cycle when you switch it off — no restart needed either way.
| Property | Value |
|---|---|
| Name | processMlmReferralConditions |
| Title | Process MLM Referral Conditions |
| Category | mlm |
| Interval | Hourly |
| Writes | Reward rows, and empty payout wallets for referrers |
| Reads | Active conditions, active referrals, referred users' wallets and completed transactions |
It never moves money. It creates reward rows and, alongside a new reward, the referrer's payout wallet if they do not have one — so a member is never blocked at claim time for want of a wallet row.
Each run evaluates both the current calendar period and the one just completed.
That double pass is why the deterministic sourceId matters: without it, the
re-scan would pay everything twice.
Its log is the addon's best diagnostic. It names each reward it creates, each condition it skipped and why, currencies it could not price, and the case where none of the referred users hold any wallet at all. Configuration warnings are rate-limited to roughly every four hours so one misconfiguration across 37 conditions produces one line rather than seventy.
Settings the engine reads
All stored as text in the platform settings table.
| Key | Read by |
|---|---|
affiliateMlmSystem |
Structure selection — DIRECT, BINARY, UNILEVEL |
affiliateBinaryLevels |
Level count under BINARY (2–7) |
affiliateBinaryLevel1 … affiliateBinaryLevel7 |
Per-level share under BINARY |
affiliateUnilevelLevels |
Level count under UNILEVEL (2–7) |
affiliateUnilevelLevel1 … affiliateUnilevelLevel7 |
Per-level share under UNILEVEL |
affiliateRequireApproval |
Whether new referrals start PENDING |
affiliateMaxCommissionRate |
Ceiling on PERCENTAGE condition rewards |
affiliatePayoutThreshold |
Minimum unclaimed balance before claiming |
Two legacy keys are still read as a fallback when the current ones are absent:
mlmSystem (the structure) and mlmSettings (a JSON blob containing both level
configurations, with the values stored as strings). Saving the settings screen
once writes the current keys, which then take precedence.
Full descriptions and consequences: Programme settings.
Where commissions are triggered from
Event-driven conditions are fired by the code that processed the activity, in core and in other addons. Which means: an addon you do not own cannot pay its commission, and disabling an addon stops its rules firing without disabling the conditions themselves. Those conditions stay active and stay invisible to members, because the members' rates page filters by enabled extension.
The one exception to "conditions belong to addons" is the core set — deposits, spot and binary trading, and investments — which is always available.