Appearance
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 adiscount_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_customeris enabled, the system checks that customer's prior redemption records for the offer before allowing another redemption. - Usage Limit: When
limit_discount_usageis enabled,limit_discount_usage_amountcaps total redemptions. Each successful use writes a redemption record and increments the offer'sredemptionscounter; 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
| Property | Value |
|---|---|
| Field ID | method |
| Label | Method |
| Type | Tab / Select |
| Options | Automatic, Code |
| Required | Yes |
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
| Property | Value |
|---|---|
| Field ID | discount_code |
| Label | Discount Code |
| Type | Text |
| Required | Yes (only when method = 'Code') |
| Validation | Unique 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
| Property | Value |
|---|---|
| Field ID | name |
| Label | Name |
| Type | Text |
| Required | Yes |
| Validation | Unique 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
| Property | Value |
|---|---|
| Field ID | type |
| Label | Offer Type |
| Type | Select |
| Options | ITEMS (Amount of items), ORDER (Amount of order), BOGO (Buy X Get Y) |
| Required | Yes |
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
| Property | Value |
|---|---|
| Field ID | discount_unit |
| Label | Discount Unit |
| Type | Select |
| Options | percent, amount (for ITEMS/ORDER); percent, amount, free for BOGO |
| Required | Yes |
Description: Whether the discount is a percentage or a fixed amount. The free unit exists only for Buy X Get Y offers.
Discount Value
| Property | Value |
|---|---|
| Field ID | discount_value |
| Label | Discount Value |
| Type | Number |
| Required | Yes (for ITEMS/ORDER, and BOGO percent/amount) |
| Validation | percent: > 0 and <= 100; amount: > 0 |
Description: Amount of discount.
Examples:
- Percent: 20 (20% off)
- Amount: 5 (€5 off)
Offer Applies To
| Property | Value |
|---|---|
| Field ID | offer_applies_to |
| Label | Applies To |
| Type | Select |
| Options | specific-groups, specific-items |
| Required | Yes (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
| Property | Value |
|---|---|
| Field ID | minimum_purchase_type |
| Label | Minimum Purchase Requirements |
| Type | Select |
| Options | no-minimum-requirements, minimum-purchase-amount, minimum-quantity-items |
| Required | Yes |
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
| Property | Value |
|---|---|
| Field ID | limit_discount_usage |
| Label | Limit discount usage |
| Type | Boolean |
| Required | Yes |
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
| Property | Value |
|---|---|
| Field ID | limit_one_usage_per_customer |
| Label | Limit one usage per customer |
| Type | Boolean |
| Required | Yes |
Description: When enabled, each customer may redeem the offer only once.
Can Be Combined
| Property | Value |
|---|---|
| Field ID | can_be_combined |
| Label | Can be combined |
| Type | Boolean |
| Required | Yes |
Description: Whether this offer can stack with other offers.
Availability
| Property | Value |
|---|---|
| Field ID | availability |
| Label | Availability (channels) |
| Type | Multi-select (array) |
| Options | Kiosk, POS, Online Ordering, Table Qr Ordering, Uber Eats, Takeaway, Shopify |
| Required | Yes |
Description: The sales channels on which the offer (code) is valid.
Active Days
| Property | Value |
|---|---|
| Field ID | active_days |
| Label | Active days |
| Type | Multi-select (array) |
| Options | Sunday–Saturday |
| Required | Yes |
Description: Days of the week the offer is active.
Active Dates
| Property | Value |
|---|---|
| Field ID | active_dates |
| Label | Schedule |
| Type | Object |
| Required | Yes |
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
| Property | Value |
|---|---|
| Field ID | status |
| Label | Status |
| Type | Select (computed) |
| Options | Draft, 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 discountDiscount 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
- Customer enters coupon code
- System validates code
- If valid → Discount applied, shown in summary
- If invalid → Error message displayed
Error Messages
| Scenario | Message |
|---|---|
| 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
Related Features
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_valuemust 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
redemptionscounter. Oncelimit_discount_usage_amountis 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_combinedflag; 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_customeris 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:
- Code entered incorrectly
- Offer expired or not yet started (schedule / active days)
- Usage limit reached (
limit_discount_usage_amount) - Minimum purchase not met
- Offer not active, or channel not selected in
availability
Solutions:
- Check exact code spelling
- Verify the start/end schedule and active days
- Check the redemption count vs the usage cap
- Add more items / meet the minimum purchase
- Activate the offer and confirm the channel is enabled
Problem: Discount amount wrong
Causes:
- Only some items/groups are eligible (
offer_applies_to) - Percentage vs amount unit
- Discount applies to the wrong base (items vs order)
Solutions:
- Review the targeted items/display groups
- Verify the discount unit and value
- 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
}