Skip to content

Loyalty Program

Overview

The Loyalty Program rewards customers for repeat purchases. Customers earn points (or stars, or a custom unit) on orders and unlock rewards once their balance reaches a reward's fixed threshold.

Key Purpose: Encourage repeat business through threshold-based rewards.

Purpose

This page lets you configure a loyalty program that awards points to customers based on their spending or visits, and define rewards they can unlock once they accumulate enough points.

Key Concepts

  • Amount-Based Program: Customers earn points proportional to the order total, calculated as order total multiplied by the configured points_earned_each_dollar rate (there is no subtotal option, and no per-category exclusion).
  • Visit-Based Program: Customers earn a fixed number of points (points_earned_each_visit) per completed order regardless of the amount spent, optionally requiring a minimum spend threshold (using_minimum_spend / minimum_spend_to_earn_points).
  • Reward Redemption (threshold-based): Each reward defines a fixed points_needed threshold (or visits_needed for visit-based programs). A reward unlocks once the customer's balance reaches that threshold, and redeeming it deducts the required points. There is no point-to-currency conversion, no point value, and no maximum-redemption percentage cap. Rewards are either a Discount (percentage or fixed amount) or a Free Item with selected eligible items.
  • Reward Unit: The points are labelled by the configured reward_unit — Points, Stars, or a Custom singular/plural label (e.g. "Bean"/"Beans").
  • Bonus Time Multiplier: During configured bonus windows (specific days, optionally a start/end time), earned points are automatically doubled or tripled based on specific_day_time_bonus_amount (Double Points / Triple Points).
  • Suspicious Activity Flag: When program_alert is on, if a customer completes more than 3 orders at the same location in a single day (location timezone), the customer is flagged as suspicious and an alert email is sent to the merchant.

Actions

Configure Loyalty Program

Set the reward unit (Points/Stars/Custom), program type (Amount Based or Visit Based), earning rate, point expiry period (in months), sign-up bonus, birthday bonus, and day/time bonus settings via the 5-step wizard. Activating the program requires a subscription per location.

Manage Rewards

Create, update, or delete rewards that customers unlock with their points. Each reward is a Discount (percentage or fixed amount) or a Free Item with eligible item selection, and defines a points_needed (amount-based) or visits_needed (visit-based) threshold.

Manage Birthday Bonuses

Configure a birthday bonus reward per location, including discount type, value, and eligible items for free-item bonuses.

Subscribe/Unsubscribe Locations

Enable or disable the loyalty program for individual locations, which creates or cancels a Stripe subscription for the loyalty feature.

Location

  • Backoffice Route: /marketing/loyalty
  • Backend Controller: app/Http/Controllers/Api/BackOffice/LoyaltyController.php
  • Validation: app/Http/Requests/BackOffice/Loyalty/StoreLoyaltyRequest.php
  • Vue Page: src/pages/marketing/loyalty/index.vue (renders src/views/marketing/loyalty/Loyalty.vue)
  • Create/Edit Wizard: src/views/marketing/loyalty/LoyaltyForm.vue (5 steps under src/views/marketing/loyalty/components/loyaltyCreateStep/)

Fields

Fields below come from StoreLoyaltyRequest (validation) and RawModels/Loyalty.php (persisted shape). The wizard is organized into 5 steps; the step each field belongs to is noted.

Reward Unit

PropertyValue
Field IDreward_unit (label, singular, plural)
LabelWhat do you call your points?
TypeObject — label is one of Points, Stars, Custom
Requiredreward_unit.label required. For Custom, singular and plural are also required
Step1 (Customize)

Description: Controls how the loyalty currency is labelled. For Points and Stars the singular/plural are filled automatically; for Custom the merchant supplies their own (e.g. "Bean"/"Beans").


Program Type

PropertyValue
Field IDprogram
LabelProgram Type
TypeSelect (enum LoyaltyPrograms)
OptionsAmount Based, Visit Based
RequiredYes
Step2 (Choose Program)

Description: Whether customers earn by amount spent or by completed visits. One type per program — not both.


Points Earned Each Dollar

PropertyValue
Field IDpoints_earned_each_dollar
LabelPoints earned per currency unit
TypeNumber
RequiredRequired when program = Amount Based
Step2

Description: Points awarded per unit of order total for amount-based programs. Points earned = points_earned_each_dollar × order total (× bonus multiplier). Calculated on the order total — there is no subtotal option.


Points Earned Each Visit

PropertyValue
Field IDpoints_earned_each_visit
LabelPoints earned per visit
TypeNumber
RequiredRequired when program = Visit Based
Step2

Description: Fixed points awarded for each completed order, regardless of amount, for visit-based programs.


Using Minimum Spend

PropertyValue
Field IDusing_minimum_spend
LabelRequire a minimum spend
TypeBoolean
RequiredRequired when program = Visit Based (defaults false)
Step2

Description: When enabled (visit-based only), an order must meet the minimum spend before any points are awarded.


Minimum Spend To Earn Points

PropertyValue
Field IDminimum_spend_to_earn_points
LabelMinimum spend
TypeNumeric (money)
RequiredRequired when using_minimum_spend is true
Step2

Description: The order total must be greater than or equal to this amount for visit-based points to be awarded.


Do Points Expire

PropertyValue
Field IDdo_points_expire
LabelDo points expire?
TypeBoolean
RequiredYes
Step2

Description: Master switch for point expiry. When off, points never expire.


Months To Expire Points

PropertyValue
Field IDmonths_to_expire_points
LabelPoints expire after
TypeSelect (enum LoyaltyMonthsToExpirePoints), months
Options3, 6, 12, 18, 24 months — these are the only options
RequiredRequired when do_points_expire is true
Step2

Description: Number of months after earning that points expire (not days). On earn, expires_at = now + months_to_expire_points. Expired point records are marked is_expired and skipped during redemption.


Sign-Up Bonus

PropertyValue
Field IDsign_up_bonus (boolean) + sign_up_bonus_points (number)
LabelSign-up bonus
TypeBoolean + Number
Requiredsign_up_bonus required; sign_up_bonus_points required when it is true
Step4 (Personalize)

Description: One-time fixed points granted when a customer first joins (no prior customer-vendor relationship). Respects the same expiry settings as regular points.


How Customer Sign Up

PropertyValue
Field IDhow_customer_sign_up
LabelSign-up method
TypeSelect (enum LoyaltySignUpOptions)
OptionsEmail, Phone, Both
RequiredYes
Step4

Description: How customers can identify/join the program.


Birthday Bonus

PropertyValue
Field IDbirthday_bonus (boolean) + birthday_bonus_details (object)
LabelBirthday bonus
TypeBoolean + Object
Requiredbirthday_bonus required; when true, birthday_bonus_details.type (enum RewardTypes: Discount / Free Item) is required
Step4

Description: A birthday gift — a Discount (percentage/fixed value) or a Free Item with eligible items. It is a reward-style gift, not a fixed points grant. Stored per location as a boolean map plus a linked BirthdayBonus document.


Specific Day/Time Bonus

PropertyValue
Field IDspecific_day_time_bonus (boolean), specific_day_time_bonus_amount, specific_day_time_bonus_days[], specific_day_time_bonus_time (boolean), specific_day_time_bonus_time_start, specific_day_time_bonus_time_end
LabelBonus days/times
TypeBoolean + select + array + time window
Optionsspecific_day_time_bonus_amount = Double Points or Triple Points (enum LoyaltyDayTimeBonusOptions); days are weekday names; times are H:i
Requiredspecific_day_time_bonus required; when true, amount, days and the time toggle are required (and start/end when the time toggle is on)
Step4

Description: During the selected days (and optional time window), earned points are multiplied ×2 (Double) or ×3 (Triple).


Program Alert

PropertyValue
Field IDprogram_alert
LabelSuspicious activity alert
TypeBoolean
RequiredYes
Step4

Description: When on, if a customer completes more than 3 completed orders at the same location in a single day, they are flagged suspicious and the merchant is emailed.


Name & Description

PropertyValue
Field IDname, description
TypeString
RequiredYes
Step4

Description: Program name and description shown to customers.


Rewards

PropertyValue
Field IDrewards[]
TypeArray of reward objects
RequiredYes (at least one)
Step3 (Rewards)

Each reward object (RawModels/Reward.php):

Sub-fieldNotes
typeenum RewardTypes: Discount or Free Item
namerequired string
discount_unitenum DiscountUnits (required for Discount)
discount_valuerequired for Discount; for percentage must be >0 and <=100; for amount must be >0
eligible_items[]required for Free Item (item ids)
points_neededthreshold for Amount Based programs (required, >0)
visits_neededthreshold for Visit Based programs (required, >0); points-needed is derived from visits × points_earned_each_visit

Description: Rewards are unlocked by reaching a fixed points_needed (amount-based) or visits_needed (visit-based) threshold. There is no point-to-currency conversion or maximum-redemption cap.


Business Logic

Points Earning

Customer places a Complete order


Is it bonus time? (specific_day_time_bonus, day/time match)
- Yes → multiplier = 2 (Double) or 3 (Triple)
- No  → multiplier = 1


Amount Based:
  points = points_earned_each_dollar × order TOTAL × multiplier
Visit Based:
  if (!using_minimum_spend OR total >= minimum_spend_to_earn_points):
      points = points_earned_each_visit × multiplier
  (no minimum met → no points)


If points == 0 → skip


Save LoyaltyPoint record (with expires_at = now + months_to_expire_points
                          when do_points_expire is on)


Recalculate customer lifetime points / visits / spend

Reward Redemption (threshold-based)

Customer selects one or more rewards at checkout


checkIfRewardsAreRedeemable:
- For each reward, available points >= reward.points_needed?
- And available points >= SUM(all selected rewards' points_needed)?
├── No → throw "Insufficient points" (redemption blocked)

└── Yes → apply reward(s):
            - Discount: percentage or fixed amount off eligible/all items
            - Free Item: eligible item becomes free


        Deduct points across active (non-expired) LoyaltyPoint records
        in order, spanning multiple records if needed
        (expired records are marked is_expired and skipped)

Note: redemption is threshold-based. There is NO point-to-currency value, NO minimum-redemption-points setting, and NO maximum-redemption-% cap.

Points Balance

Available points = sum of non-expired LoyaltyPoint records,
                   minus points already used (points_used) on each.

(For Mplus-linked customers the online balance is read from the POS
 instead, falling back to the local balance if Mplus is unavailable.)

Customer Impact

Online Ordering

  1. Account: Points balance shown in profile
  2. Ways to earn: Earn rate, bonus days, birthday treat shown
  3. Cart: "You'll earn X points on this order"
  4. Rewards: Each reward shows its points/visits threshold; unlocks when the balance reaches it
  5. Confirmation: "You earned X points"

Kiosk

  • Points features may be limited
  • Requires customer identification (phone/email)
  • Balance shown if identified

Receipt

--- Loyalty ---
Points Earned: 25
Reward Redeemed: Free Coffee (100 points)
New Balance: 425 points

Relations

Depends On

  • Customers: Points tied to customer-merchant records
  • Items: For Free Item rewards / birthday Free Item (eligible items)
  • Subscription: Active Stripe subscription per location

Affects

  • Online Ordering: Points display and reward redemption
  • Transactions: Points earned and rewards redeemed recorded
  • Mplus (MplusKassa) POS: Points/redemptions synced for linked customers

Business Rules

  • Points are only awarded on orders with a status of "Complete"; pending or cancelled orders do not earn points.
  • For visit-based programs with a minimum spend enabled, the order total must meet or exceed the configured minimum spend threshold before any points are awarded.
  • When redeeming rewards, the system iterates through active (non-expired) point records in order and deducts points across multiple records if needed; if total available points are insufficient, the redemption fails with an exception.
  • Points expiry is set in months (not days) from the time of earning; expired point records are marked as expired and skipped during redemption.
  • The loyalty program requires an active Stripe subscription per location; the getLoyaltyButOnlyIfSubscribed method returns null if the location is not subscribed, effectively disabling all loyalty features for that location.

FAQs

  • "Can a customer use both amount-based and visit-based programs?" No, each loyalty program is configured as either amount-based or visit-based, not both simultaneously.

  • "What happens to points when an order is refunded?" Points are awarded only on completed orders; however, the system does not automatically reverse points if an order is later refunded -- manual adjustment may be needed.

  • "How does the sign-up bonus work?" When a new customer-vendor record is created (i.e., the customer has no prior relationship with this vendor), the system automatically awards the configured sign-up bonus points, respecting the same expiry settings as regular points.

  • "Can rewards be combined in a single order?" Yes, multiple reward IDs can be submitted in a single order, but the system validates that the customer has enough total available points to cover all selected rewards combined.

  • "What triggers the suspicious activity alert?" If a customer has more than 3 completed orders at the same location within a single calendar day (in the location's timezone), the customer is flagged and the merchant receives an alert email.

  • "How do I set up a loyalty program — what are the steps?" Go to Marketing → Loyalty and follow the 5-step wizard: Customize (choose your reward unit), Choose Program (Amount Based or Visit Based and earn rate), Rewards, Personalize (sign-up/birthday/bonus-time options), then Review & Create.

  • "What's the difference between an Amount Based and a Visit Based program?" Amount Based awards points based on how much is spent (points per €1), while Visit Based awards a fixed number of points for every completed visit regardless of spend. You pick one type per program — not both.

  • "Can I require customers to spend a minimum before they earn points?" Yes, but only on Visit Based programs. Enable "minimum spend" and set the amount; the order total must meet or exceed it for any points to be awarded.

  • "What can I name the points — do they have to be called 'points'?" No. In step 1 you choose a reward unit: Points, Stars, or a Custom unit. If you pick Custom, you enter your own singular and plural label (e.g. "Bean"/"Beans").

  • "How long until points expire, and what options do I have?" Expiry is optional. If you turn on "Do points expire," you choose a fixed period of 3, 6, 12, 18, or 24 months — those are the only options. If expiry is off, points never expire.

  • "How do customers redeem points for a reward?" You define rewards, each with a points (or visits) threshold. A reward unlocks once the customer's balance reaches that threshold, and redeeming it deducts the required points. Rewards can be a Discount (percentage or fixed amount) or a Free Item with selected eligible items.

  • "Can a customer redeem more than one reward in the same order?" Yes. Multiple rewards can be applied to one order, but the system checks the customer has enough total available points to cover them all combined.

  • "How do I offer double or triple points on certain days?" In Personalize, turn on the day/time bonus and choose Double or Triple Points, then pick the days (and optionally a start/end time). Points earned in those windows are automatically multiplied.

  • "What do the sign-up and birthday bonuses do, and how do customers join?" You can grant a one-time sign-up bonus (fixed points when a customer first joins) and a birthday bonus (a Discount or Free Item gift). The sign-up method is set to Email, Phone, or Both.

  • "Why isn't a customer earning points?" Points are only awarded on orders that reach "Complete" status, and only when the location has an active loyalty subscription. For Visit Based programs with minimum spend, the order must also meet the minimum.

  • "Do I have to pay to turn on loyalty, and is it per location?" Yes. Activating the program creates a Stripe subscription per location. If a location isn't subscribed, loyalty features are disabled there — points won't be earned or shown.

  • "I use Mplus (MplusKassa) POS — will loyalty points stay in sync?" Yes. For Mplus-linked customers, earned points and redemptions are pushed to the Mplus relation, and the online balance is read from the POS. The sync is non-fatal — if Mplus is unavailable it falls back to the local balance rather than blocking the order.

Troubleshooting

Problem: Customer not earning points

Causes:

  1. Location has no active loyalty subscription
  2. Customer not identified / not logged in
  3. Visit-based program with minimum spend not met
  4. Order not in "Complete" status

Solutions:

  1. Confirm the location is subscribed to loyalty
  2. Ensure the customer is identified (email/phone per sign-up method)
  3. Check the minimum spend setting and the order total
  4. Verify order status is Complete

Problem: Can't redeem a reward

Causes:

  1. Balance below the reward's points_needed / visits_needed threshold
  2. Multiple rewards selected exceed total available points
  3. Points expired (records marked is_expired)
  4. Mplus balance stale / POS unavailable

Solutions:

  1. Check the reward's threshold versus the customer's balance
  2. Reduce the number of rewards applied to one order
  3. Check expiry settings and earned dates
  4. For Mplus-linked customers, the balance is refreshed before redemption; verify the POS integration

Problem: Points balance incorrect

Causes:

  1. Points expired
  2. Redemption not recorded
  3. Order cancelled/refunded
  4. Mplus sync issue (POS balance overrides local)

Solutions:

  1. Check expiry settings and dates
  2. Review transaction history
  3. Check for refunded orders (points are not auto-reversed)
  4. Verify the Mplus integration / cached balance

Problem: Points expiring too fast

Causes:

  1. Short expiry period configured (3 or 6 months)
  2. Customer inactive

Solutions:

  1. Increase the expiry period (up to 24 months) or turn expiry off
  2. Send reminder communications before expiry

Examples

JSON below reflects the real StoreLoyaltyRequest payload shape.

Simple Amount-Based Program

json
{
  "reward_unit": { "label": "Points", "singular": "Point", "plural": "Points" },
  "program": "Amount Based",
  "points_earned_each_dollar": 1,
  "do_points_expire": false,
  "rewards": [
    {
      "type": "Discount",
      "name": "€1 off",
      "discount_unit": "percent",
      "discount_value": 10,
      "points_needed": 100
    }
  ],
  "name": "Rewards Club",
  "description": "Earn points on every order",
  "how_customer_sign_up": "Email",
  "sign_up_bonus": true,
  "sign_up_bonus_points": 50,
  "birthday_bonus": false,
  "specific_day_time_bonus": false,
  "program_alert": true
}

How it works:

  • Earn 1 point per currency unit of the order total
  • A reward unlocks at 100 points (a 10% discount)
  • Points never expire
  • 50-point sign-up bonus

Visit-Based Program with Minimum Spend

json
{
  "reward_unit": { "label": "Stars", "singular": "Star", "plural": "Stars" },
  "program": "Visit Based",
  "points_earned_each_visit": 1,
  "using_minimum_spend": true,
  "minimum_spend_to_earn_points": 10,
  "do_points_expire": true,
  "months_to_expire_points": 12,
  "rewards": [
    {
      "type": "Free Item",
      "name": "Free Coffee",
      "eligible_items": [{ "id": "<item_id>" }],
      "visits_needed": 10
    }
  ],
  "name": "Coffee Card",
  "description": "Collect a star each visit",
  "how_customer_sign_up": "Both",
  "sign_up_bonus": false,
  "birthday_bonus": true,
  "birthday_bonus_details": {
    "type": "Free Item",
    "eligible_items": [{ "id": "<item_id>" }]
  },
  "specific_day_time_bonus": false,
  "program_alert": true
}

How it works:

  • Earn 1 star per visit, but only when the order total is at least 10
  • 10 stars unlocks a free coffee
  • Stars expire after 12 months
  • Birthday treat is a free item

Amount-Based with Double-Points Days

json
{
  "reward_unit": { "label": "Custom", "singular": "Bean", "plural": "Beans" },
  "program": "Amount Based",
  "points_earned_each_dollar": 2,
  "do_points_expire": true,
  "months_to_expire_points": 6,
  "rewards": [
    {
      "type": "Discount",
      "name": "€5 off",
      "discount_unit": "amount",
      "discount_value": 5,
      "points_needed": 200
    }
  ],
  "name": "Bean Club",
  "description": "Earn beans, double on weekends",
  "how_customer_sign_up": "Phone",
  "sign_up_bonus": true,
  "sign_up_bonus_points": 100,
  "birthday_bonus": false,
  "specific_day_time_bonus": true,
  "specific_day_time_bonus_amount": "Double Points",
  "specific_day_time_bonus_days": ["Saturday", "Sunday"],
  "specific_day_time_bonus_time": false,
  "program_alert": true
}

How it works:

  • Earn 2 beans per currency unit; doubled to 4 on Saturdays and Sundays
  • 200 beans unlocks a €5-off discount
  • Beans expire after 6 months
  • Custom "Bean"/"Beans" labelling, 100-bean sign-up bonus