Referrals
Give every user a link, record who arrives through it, and credit a reward when they register and when they pay.
The referral program gives each user a link. When someone registers after following it, the two accounts are linked and the referrer is credited a fixed reward; every time the referred account pays a subscription invoice, the referrer is credited a percentage. The rewards are numbers the kit stores and shows. Nothing pays them out: that part is yours to build.
Turn it on
Five environment variables, read by config/features.php under referrals. None of them is in .env.example, so add the ones you need.
| Key | Env variable | Default | Values | What it changes |
|---|---|---|---|---|
enabled |
FEATURE_REFERRALS_ENABLED |
false |
true, false |
Off: the Referrals entry leaves the account sidebar, /account/referrals is a 404, ?referrer= is ignored, no referral and no reward is recorded, and "Referral Subscriptions" leaves the admin dashboard. Data already stored is kept. |
registration_reward |
FEATURE_REFERRALS_REGISTRATION_REWARD |
10 |
A number | Credited to the referrer once when the referred person registers. 0 creates no registration reward. |
subscription_reward_percent |
FEATURE_REFERRALS_SUBSCRIPTION_REWARD_PERCENT |
10 |
A percentage | Share of every paid subscription invoice of the referred account credited to the referrer. 0 creates no subscription reward. |
currency |
FEATURE_REFERRALS_CURRENCY |
mxn |
Lower-case ISO code | Currency of the registration reward. Also the fallback currency when a Stripe invoice carries none. |
cookie_days |
FEATURE_REFERRALS_COOKIE_DAYS |
30 |
Whole days | How long the visitor's browser remembers the referrer after following the link. Values under 1 are read as 1. |
FEATURE_REFERRALS_ENABLED=true
FEATURE_REFERRALS_REGISTRATION_REWARD=10
FEATURE_REFERRALS_SUBSCRIPTION_REWARD_PERCENT=10
FEATURE_REFERRALS_CURRENCY=mxn
FEATURE_REFERRALS_COOKIE_DAYS=30
The subscription reward needs plans and the Stripe webhook, because it is created from invoice.paid. See Turn on plans and billing. The registration reward needs nothing else.
The referral link
https://your-project.test/?referrer=K7QP2XM4
The code is users.referral_code: eight upper-case random characters, unique, created the first time the user opens the referrals page or your code calls $user->getOrCreateReferralCode(). $user->referralUrl() builds the link above.
The referrer parameter is read on any page, not only the home page. The App\Http\Middleware\CaptureReferralCode middleware runs on every web request and, on a normal GET that is not a Livewire or JSON call, hands the request to App\Classes\Referrals:
- The code is trimmed and upper-cased. If no user has that code, nothing happens.
- It is stored in the session under
referrals.referrer_code, and in a cookie namedreferrer_codethat lastscookie_daysdays. - On later visits with no code in the session, the cookie restores it and is queued again, so the window restarts each time the visitor comes back.
A visitor who follows the link today and registers within the cookie window is still attributed. A code that reaches the site while the flag is off is ignored, not saved.
When the referred person registers
App\Observers\UserObserver::created() calls $user->registerReferredUser() for every new user, whichever way it was created. It reads the code from the session, then from the cookie, finds the referrer, and:
- Does nothing if there is no code, the code matches no user, or the referrer is the new user themselves.
- Creates one
referralsrow:referrer_id,referred_id, thecodeused andregistered_at. A user can be referred once;referred_idis unique. - Creates the registration reward when
registration_rewardis above zero: areferral_rewardsrow withtyperegistration, the configuredamount, the configuredcurrency, and the referred user's id assource_id, so the same registration can never be rewarded twice.
Nothing changes for the referred person: no discount, no credit, no mention on screen.
When the referred person pays
Every invoice.paid event Stripe sends for a subscription reaches the package webhook controller. When the invoice's amount_paid is above zero it dispatches WeblaborMx\BillingCore\Events\SubscriptionPaid with the amount in major units, the invoice currency, the invoice id and the subscription id. App\Listeners\RecordSubscriptionPayment listens, ignores accounts that are not user accounts, and calls $user->recordPaidSubscriptionReward($amount, $currency, $invoiceId):
- Nothing is recorded if the flag is off, the amount is zero, the user was not referred, or
subscription_reward_percentis zero. - Otherwise a
referral_rewardsrow withtypesubscription,amount= invoice amount × percent ÷ 100 rounded to two decimals,source_amount= the invoice amount,percentage= the percent,currency= the invoice currency in lower case, andsource_id= the Stripe invoice id.
The pair type + source_id is unique per referral, so a webhook delivered twice records one reward. Every invoice counts: the first payment, each renewal, and the invoices that add-ons on the subscription generate. Free plans and trials pay nothing, so they credit nothing. The reward is credited the moment the webhook arrives; a missing or misconfigured webhook means no subscription rewards at all.
How much, and in which currency
The registration reward is always in FEATURE_REFERRALS_CURRENCY. The subscription reward is in the currency of the invoice, whatever the plan was priced in. A referrer whose referred accounts pay in two currencies therefore has two totals, and the kit never converts one into the other: rewardTotals() groups by currency and every screen lists one figure per currency, formatted as $ followed by the amount and the code, for example $150.00 MXN.
How it is stored
| Table | Columns | Notes |
|---|---|---|
users |
referral_code |
Nullable, unique, 32 characters. |
referrals |
referrer_id, referred_id, code, registered_at |
One row per referred user. |
referral_rewards |
referral_id, type, amount, source_amount, percentage, currency, source_id |
type is registration or subscription. Unique on referral_id, type, source_id. |
There is no balance column, no payout table and no debit. The totals a user sees are SUM(amount) over their rewards, grouped by currency, computed on every page load. The screen calls it "virtual money" for that reason. When you build payouts, add your own table that records what was paid and subtract it from these totals.
The pieces you call from code:
| Where | Method | Returns |
|---|---|---|
User (trait App\Traits\HasReferrals) |
getOrCreateReferralCode() |
The code, creating it if missing. |
referralUrl() |
The full link. | |
referrals() |
The Referral rows where this user is the referrer. |
|
referredByReferral() |
The single Referral where this user is the referred one, or null. |
|
rewardTotals() |
Collection keyed by currency with the summed amounts. | |
registerReferredUser() |
What the observer calls. Returns the Referral or null. |
|
recordPaidSubscriptionReward($amount, $currency, $sourceId) |
What the listener calls. Returns the ReferralReward or null. |
|
App\Models\Referral |
referrer(), referred(), rewards() |
Relations. |
reward_totals |
Attribute: this referral's rewards summed by currency. | |
App\Classes\Referrals |
rememberCode($code) |
Stores a code in session and cookie by hand, for example after your own landing page. |
enabled() |
The flag. |
What the user sees
Referrals, with a gift icon, appears in the account sidebar and opens /account/referrals. The page has three cards:
- "Refer and earn": the registration reward and the subscription percentage as configured, the total of virtual money generated per currency, and how many users registered with the link.
- "Your referral link": the link in a read-only field and a Copy link button, with the note that a visitor who registers later is still associated for several days.
- "Users registered with your link": one row per referred user with name and email, the registration date, and what that user generated, per currency. A deleted referred account shows as "Deleted user".

The referred person sees nothing about the referral anywhere.
What the admin sees
The admin dashboard, /admin, offers Referral Subscriptions in its metric selector while the flag is on. It draws two series over the selected date range: new referral registrations, from referrals.registered_at, and paid referral subscriptions, counting subscription rewards by the date they were created.
That is all. There is no admin resource for referrals or rewards, the user form does not show the referral code, and nothing lets an administrator edit or cancel a reward. Corrections are done in the database or through code you add.