Skip to content

Coupons

Overview

A coupon is an Offer with method "Code". Customers must know and enter the code at checkout to receive the discount. Unlike an Automatic offer (which applies on its own when its conditions are met), a Code offer only applies when the customer enters its discount code. Coupons are not a separate feature — they are created and managed under Marketing → Offers.

Key Purpose: Create shareable discount codes for marketing campaigns.

Purpose

This page (the Offers page) lets you create and manage offers, including code-based offers (coupons) that customers enter at checkout. A coupon provides a percentage or fixed-amount discount on qualifying items or on the order total, subject to minimum-purchase, usage, scheduling, and channel rules.

Key Concepts

  • Coupon as Offer: A coupon is an Offer (method = 'Code'). It shares the same model, validation, redemption tracking, and discount calculation as Automatic offers; the only difference is that it requires the customer to enter a discount_code.
  • Discount Code: A unique string tied to a Code offer (discount_code); on publish the backend validates uniqueness across offers at the location to prevent duplicate codes.
  • Limit One Usage Per Customer: When limit_one_usage_per_customer is enabled, the system checks that customer's prior redemption records for the offer before allowing another redemption.
  • Usage Limit: When limit_discount_usage is enabled, limit_discount_usage_amount caps total redemptions. Each successful use writes a redemption record and increments the offer's redemptions counter; once the cap is reached the offer becomes Expired and the code stops working.

Actions

Create a Coupon

Create a new offer, set its method to Code, and define the discount code, offer type (Amount of items, Amount of order, or Buy X Get Y), discount unit and value, minimum-purchase requirements, usage limits, availability channels, and start/end schedule.

Edit a Coupon

Update an existing offer's discount value, minimum-purchase, usage limits, applicable items/groups, availability, or schedule. Draft offers can be freely edited; once an offer leaves Draft it can be updated but cannot revert to Draft.

Deactivate a Coupon

Deactivate or archive an offer so it can no longer be redeemed. An offer past its usage cap or end date becomes Expired automatically.

Location

  • Backoffice Route: /marketing/offers
  • Backend Request: app/Http/Requests/BackOffice/Offer/StoreOfferRequest.php
  • Backend Controller: app/Http/Controllers/Api/OfferController.php
  • Backend Service: app/Services/Offer/OfferService.php
  • Vue Components: src/views/marketing/offers/Offers.vue, src/views/marketing/offers/CreateOfferForm.vue, src/views/marketing/offers/components/create-offers/OfferInformation.vue

Fields

Method

PropertyValue
Field IDmethod
LabelMethod
TypeTab / Select
OptionsAutomatic, Code
RequiredYes

Description: How the offer is triggered. Code makes the offer a coupon (the customer must enter the discount code); Automatic applies on its own when conditions are met. Switching to a different method clears the discount code.


Discount Code

PropertyValue
Field IDdiscount_code
LabelDiscount Code
TypeText
RequiredYes (only when method = 'Code')
ValidationUnique across offers at the location (on publish); max 10 characters (front-end)

Description: The code customers enter at checkout. The "Generate" button produces a random 8-character code using A–Z and 0–9.

Best Practices:

  • Keep codes short and memorable
  • Avoid confusing characters (0/O, 1/I)
  • Make codes relevant to campaign

Examples:

  • "SUMMER20"
  • "WELCOME10"
  • "PIZZA50"

Name

PropertyValue
Field IDname
LabelName
TypeText
RequiredYes
ValidationUnique across offers at the location; max 25 characters (front-end)

Description: Internal name for the offer.

Examples:

  • "Summer Campaign"
  • "Influencer Code"
  • "Newsletter Discount"

Offer Type

PropertyValue
Field IDtype
LabelOffer Type
TypeSelect
OptionsITEMS (Amount of items), ORDER (Amount of order), BOGO (Buy X Get Y)
RequiredYes

Description: What the offer discounts. "Amount of items" discounts specific items/groups, "Amount of order" discounts the order total, and "Buy X Get Y" applies a benefit when a requirement is met.


Discount Unit

PropertyValue
Field IDdiscount_unit
LabelDiscount Unit
TypeSelect
Optionspercent, amount (for ITEMS/ORDER); percent, amount, free for BOGO
RequiredYes

Description: Whether the discount is a percentage or a fixed amount. The free unit exists only for Buy X Get Y offers.


Discount Value

PropertyValue
Field IDdiscount_value
LabelDiscount Value
TypeNumber
RequiredYes (for ITEMS/ORDER, and BOGO percent/amount)
Validationpercent: > 0 and <= 100; amount: > 0

Description: Amount of discount.

Examples:

  • Percent: 20 (20% off)
  • Amount: 5 (€5 off)

Offer Applies To

PropertyValue
Field IDoffer_applies_to
LabelApplies To
TypeSelect
Optionsspecific-groups, specific-items
RequiredYes (for ITEMS type)

Description: Whether an "Amount of items" offer applies to selected display groups (display_groups) or selected items (items). For BOGO, the equivalents are requirement_applies_to / benefit_applies_to.


Minimum Purchase Type

PropertyValue
Field IDminimum_purchase_type
LabelMinimum Purchase Requirements
TypeSelect
Optionsno-minimum-requirements, minimum-purchase-amount, minimum-quantity-items
RequiredYes

Description: The threshold a cart must meet. minimum-purchase-amount requires minimum_purchase_amount (> 0); minimum-quantity-items requires minimum_quantity_items (> 0). BOGO offers do not allow "no minimum requirements".


Limit Discount Usage

PropertyValue
Field IDlimit_discount_usage
LabelLimit discount usage
TypeBoolean
RequiredYes

Description: When enabled, limit_discount_usage_amount (> 0) caps total redemptions across all customers. Once reached, the offer becomes Expired.


Limit One Usage Per Customer

PropertyValue
Field IDlimit_one_usage_per_customer
LabelLimit one usage per customer
TypeBoolean
RequiredYes

Description: When enabled, each customer may redeem the offer only once.


Can Be Combined

PropertyValue
Field IDcan_be_combined
LabelCan be combined
TypeBoolean
RequiredYes

Description: Whether this offer can stack with other offers.


Availability

PropertyValue
Field IDavailability
LabelAvailability (channels)
TypeMulti-select (array)
OptionsKiosk, POS, Online Ordering, Table Qr Ordering, Uber Eats, Takeaway, Shopify
RequiredYes

Description: The sales channels on which the offer (code) is valid.


Active Days

PropertyValue
Field IDactive_days
LabelActive days
TypeMulti-select (array)
OptionsSunday–Saturday
RequiredYes

Description: Days of the week the offer is active.


Active Dates

PropertyValue
Field IDactive_dates
LabelSchedule
TypeObject
RequiredYes

Description: Scheduling fields: active_dates.start_date (Y-m-d), active_dates.start_time (H:i), active_dates.set_end_date (boolean) and, when set, active_dates.end_date / active_dates.end_time. Before the start the status is Scheduled; after the end it becomes Expired.


Status

PropertyValue
Field IDstatus
LabelStatus
TypeSelect (computed)
OptionsDraft, Active, Scheduled, Expired, Archived

Description: The offer's lifecycle status. Drafts are set via is_draft; status is otherwise computed from the schedule and redemption cap (Scheduled before start, Expired past end or once the usage cap is reached, otherwise Active). Once an offer leaves Draft it cannot return to Draft.


Business Logic

Coupon Validation

Customer enters discount code


Find offer by discount_code
├── Not found → "Invalid coupon code"

└── Found → Validate:
    ├── Active status? No → "Coupon not active"
    ├── Within schedule (start/end, active days)? No → "Coupon expired"
    ├── limit_discount_usage reached? Yes → "Coupon limit reached"
    ├── limit_one_usage_per_customer already used? Yes → "Already used"
    ├── Minimum purchase met? No → "Minimum €X required"
    ├── can_be_combined respected? No → reject combination
    └── All valid → Apply discount

Discount Calculation

Percent unit:
  Discount = base × (discount_value / 100)   (discount_value ≤ 100)

Amount unit:
  Discount = discount_value
  Cannot exceed the base it applies to

Where "base" is the eligible items/groups (ITEMS),
the order total (ORDER), or the benefit items (BOGO).

Customer Impact

Checkout Flow

  1. Customer enters coupon code
  2. System validates code
  3. If valid → Discount applied, shown in summary
  4. If invalid → Error message displayed

Error Messages

ScenarioMessage
Invalid code"Invalid coupon code"
Expired"This coupon has expired"
Not yet valid"This coupon is not yet active"
Usage limit"This coupon is no longer available"
Already used"You've already used this coupon"
Below minimum"Minimum order €X required"

Relations

Depends On

  • Customers: For per-customer usage tracking
  • Display Groups/Items: For "Amount of items" and Buy X Get Y targeting

Affects

  • Transactions: Discount applied
  • Reports: Coupon usage analytics

Business Rules

  • Discount codes must be unique across offers at the location; on publish, attempting to use an already-used code returns a "The discount code is already used" error. Draft offers skip this check until published.
  • The system validates offer status, schedule (start/end and active days), total usage limit (limit_discount_usage_amount), per-customer usage (limit_one_usage_per_customer), and minimum-purchase requirements before applying any discount.
  • For a percentage discount, discount_value must be greater than 0 and at most 100; for an amount discount it must be greater than 0.
  • Redemptions are tracked per customer; each successful use creates an offer redemption record and increments the offer's redemptions counter. Once limit_discount_usage_amount is reached the offer becomes Expired.
  • An offer in any non-Draft status cannot be reverted to Draft; once published, it can only be deactivated, expired, or archived.

FAQs

  • "Can a coupon be used together with another offer?" Only if the offers allow it via the can_be_combined flag; otherwise the system rejects the combination.

  • "What happens if a coupon code is entered incorrectly?" The system searches for the offer by discount_code; if no match is found, it returns an "Invalid coupon code" error to the customer.

  • "How is the per-customer limit tracked?" When limit_one_usage_per_customer is enabled, the system checks that customer's prior redemption records for the offer; if one exists, further redemptions are blocked.

  • "Can I reuse a coupon code after removing the old coupon?" Yes — once the old offer is removed, its discount code is no longer matched and can be assigned to a new offer.

  • "Where do I create a coupon code in the back office?" Coupons live inside Offers, not a separate page. Go to Marketing → Offers, create a new offer, and on the Information step set the method to Code instead of Automatic; that turns the offer into a code customers must enter.

  • "What's the difference between a coupon and an offer?" They're the same underlying object. An offer with method "Automatic" applies on its own when conditions are met; an offer with method "Code" requires the customer to enter the code (a coupon). All other settings and redemption tracking are identical.

  • "How long can my coupon code be?" The discount code field accepts up to 10 characters. The Generate button produces a random 8-character code using A-Z and 0-9.

  • "What discount types can a coupon give?" You set a discount unit of percentage or fixed amount. For percentage, the value must be greater than 0 and at most 100; for fixed amount it must be greater than 0. (A "free" unit exists only for Buy X Get Y offers, not as a general coupon discount.)

  • "What kinds of discounts can a coupon apply to?" Choose the offer type: Amount of items (specific items/groups), Amount of order (the order total), or Buy X Get Y. The type determines which items the code discounts.

  • "Can I limit how many times a coupon is used overall?" Yes. Enable "limit discount usage" and set the amount. Once total redemptions reach that number, the offer auto-expires and the code stops working.

  • "Can I stop the same customer from using a coupon more than once?" Yes. Turn on "limit one usage per customer." The system checks that customer's redemption records and blocks a repeat use.

  • "Can I set when a coupon starts and stops working?" Yes. Each offer has a start date/time and an optional end date/time, plus the days of the week it's active. Before the start it shows Scheduled; after the end it becomes Expired.

  • "Which sales channels can a coupon apply to?" You pick channels under availability: Kiosk, POS, Online Ordering, Table QR Ordering, Uber Eats, Takeaway, and Shopify. The code only works on the channels you select.

  • "Can a coupon be combined with other offers?" Each offer has a single "can be combined" setting. Enable it to let the code stack with other offers; otherwise it applies on its own.

  • "How do I require a minimum spend before a coupon works?" Set the minimum purchase type to "minimum purchase amount" and enter the amount, or "minimum quantity items" and enter the count. There's also a "no minimum requirements" option.

  • "Why does saving my coupon say 'The discount code is already used'?" Discount codes must be unique. On publish, the system checks all offers at the location for the same code; if another active offer uses it, pick a different code. Draft offers skip this check until published.

  • "Can I reuse a code after I'm done with an old coupon?" Yes. Once the old offer is removed, its code is no longer matched, so the same code can be assigned to a new offer.

  • "How do I turn off a coupon immediately?" Set its status so it's no longer active (Expired/Archived). An expired coupon can no longer be redeemed. Note that once an offer leaves Draft it can't be moved back to Draft.

Troubleshooting

Problem: Coupon code not working

Causes:

  1. Code entered incorrectly
  2. Offer expired or not yet started (schedule / active days)
  3. Usage limit reached (limit_discount_usage_amount)
  4. Minimum purchase not met
  5. Offer not active, or channel not selected in availability

Solutions:

  1. Check exact code spelling
  2. Verify the start/end schedule and active days
  3. Check the redemption count vs the usage cap
  4. Add more items / meet the minimum purchase
  5. Activate the offer and confirm the channel is enabled

Problem: Discount amount wrong

Causes:

  1. Only some items/groups are eligible (offer_applies_to)
  2. Percentage vs amount unit
  3. Discount applies to the wrong base (items vs order)

Solutions:

  1. Review the targeted items/display groups
  2. Verify the discount unit and value
  3. Confirm the offer type matches the intended base

Examples

Welcome Discount (one per customer, on the order total)

json
{
  "method": "Code",
  "discount_code": "WELCOME15",
  "name": "Welcome",
  "type": "ORDER",
  "discount_unit": "percent",
  "discount_value": 15,
  "minimum_purchase_type": "minimum-purchase-amount",
  "minimum_purchase_amount": 20,
  "limit_discount_usage": false,
  "limit_one_usage_per_customer": true,
  "can_be_combined": false
}

Flash Sale (capped total usage, scheduled window)

json
{
  "method": "Code",
  "discount_code": "FLASH30",
  "name": "Flash Sale",
  "type": "ORDER",
  "discount_unit": "percent",
  "discount_value": 30,
  "minimum_purchase_type": "minimum-purchase-amount",
  "minimum_purchase_amount": 30,
  "limit_discount_usage": true,
  "limit_discount_usage_amount": 100,
  "limit_one_usage_per_customer": true,
  "can_be_combined": false,
  "active_dates": {
    "start_date": "2024-01-15",
    "start_time": "12:00",
    "set_end_date": true,
    "end_date": "2024-01-15",
    "end_time": "14:00"
  }
}

Item-Group Specific (half off pizzas)

json
{
  "method": "Code",
  "discount_code": "PIZZA50",
  "name": "Half Off Pizzas",
  "type": "ITEMS",
  "discount_unit": "percent",
  "discount_value": 50,
  "offer_applies_to": "specific-groups",
  "display_groups": [{ "id": "<pizzas-group-id>" }],
  "minimum_purchase_type": "no-minimum-requirements",
  "limit_discount_usage": false,
  "limit_one_usage_per_customer": false,
  "can_be_combined": false
}

Fixed Amount Off Order

json
{
  "method": "Code",
  "discount_code": "SAVE5",
  "name": "€5 Off",
  "type": "ORDER",
  "discount_unit": "amount",
  "discount_value": 5,
  "minimum_purchase_type": "minimum-purchase-amount",
  "minimum_purchase_amount": 15,
  "limit_discount_usage": false,
  "limit_one_usage_per_customer": false,
  "can_be_combined": false
}