Coupons — Reference
The Coupons surface is the discount engine. It owns the coupon definitions (the code customers type at checkout, the discount math behind the code, the eligibility rules that decide when the code applies), the per-coupon usage ledger, the promo campaign grouping layer, and the redemption reporting surface. Anything that reduces a cart total during checkout traces back to a record managed here.
This page is a reference for platform engineers and integrators who need to understand the surface before extending it, scripting against it, or mapping a legacy WooCommerce coupon set into SGEN. Customer-facing walkthroughs for issuing a coupon and launching a campaign live in the customer docs set; this page describes the shape of the surface, not the editorial flow.
Overview
Coupons live under the Commerce module in SG-Admin, in the Coupons sub-surface. The module renders four primary views — the coupon list, the coupon create / edit form, the per-coupon usage view, and the campaign grouping view — and exposes a small set of write operations for create, update, duplicate, soft delete, restore, permanent delete, bulk-generate, and bulk-export.
The module is paired by convention: one half renders the views and prepares the data (coupon list, coupon detail, usage ledger, campaign rollup), the other half handles writes and returns AJAX responses (create, update, generate batch, toggle active, delete).
Where it lives in SG-Admin:
- Sidebar: SG-Admin → Commerce → Coupons
- URL prefix:
/sg-admin/commerce/coupons/ - View templates:
application/views/Admin/Commerce/Coupons/
Coupons are paired with the Orders module at checkout: when a customer enters a code, the Orders module calls into the Coupons surface to validate the code against the cart and the customer, and the Coupons surface returns either a resolved discount or a structured rejection (expired, ineligible, cap reached, segment mismatch).
┌──────────────────────────────────────────────────────────────────────┐│ SG-Admin → Commerce → Coupons [+ New coupon]│├──────────────────────────────────────────────────────────────────────┤│ Code Type Value Used Cap Expires ││ ──────────────── ─────────────── ───────── ────── ──── ───────││ WELCOME10 percent 10 % 847 ∞ — ││ SPRING2026 percent 25 % 2,103 5000 2026-06 ││ SHIP-FREE free shipping — 518 ∞ — ││ BOGO-TEE buy-one-get-one — 92 500 2026-05 ││ VIP-50 fixed-amount $ 50.00 11 50 2026-12 ││ LAUNCH-2024 percent 20 % 619 — expired ││ ││ [⋯ Edit] [⋯ Usage] [⋯ Duplicate] [⋯ Toggle active] [⋯ Delete] │└──────────────────────────────────────────────────────────────────────┘Actions
The Coupons surface exposes the following operations. Each is described by what it does to the data and the cart, not by its internal method name.
List and search coupons
Returns the coupon records on the site, paginated, with code, type, value, redemption count, redemption cap, expiration date, and active flag. Supports column sort, free-text filter by code or campaign tag, and status filter (active / expired / paused / trashed). Bulk selection on the list view enables export, toggle, and soft-delete operations.
Create coupon
Opens an empty coupon form. Required fields at minimum: code, type (percentage / fixed-amount / buy-one-get-one / free-shipping), value, active flag. Optional fields cover the eligibility rule set: product allowlist (specific products or product collections), customer-segment scope (all / logged-in / specific segment), minimum cart total, redemption cap (global and per-customer), expiration window (start date and end date), stack-with-other-coupons flag, and a free-text description shown only in SG-Admin.
On submit, the surface validates that the code is unique across active coupons, persists the record, and returns the new identifier. The code becomes redeemable immediately if the active flag is true and the expiration window is open.
Edit coupon
Loads an existing coupon into the same form shape used for create, pre-populated. Submit replaces the stored definition. Edits to the discount math or eligibility rules apply only to redemptions made after the save — historical redemptions retain the rule set that was active at redemption time.
Code edits are reversible inside a 30-day window. The prior code is held in a redirect-style alias table so an old code typed at checkout resolves to the new coupon record. After 30 days the prior code is released and can be re-issued by another coupon.
Duplicate coupon
Creates a new coupon with the same rule set, a copy-suffix on the code (or a generated suffix if the copy-suffix collides), and a fresh ledger. Used to spin up seasonal variants of an evergreen rule set.
Bulk generate
Generates a batch of coupons from a single rule template — useful for one-time-use codes mailed individually to a customer list. Inputs: count, code-prefix or code-pattern, the shared rule template. Output: the generated codes as a downloadable CSV plus the persisted records. Bulk-generated coupons inherit a campaign tag so the batch can be managed as a group.
Toggle active
Flips the active flag without opening the full edit form. Inactive coupons return a structured rejection at checkout — the code is recognized but rejected as paused. Used to pull a coupon mid-flight without destroying the record or the ledger.
Soft delete
Marks the coupon as trashed. Trashed coupons stop accepting redemptions at checkout. The historical ledger remains queryable and the discount values on past orders are unaffected.
Restore
Returns a trashed coupon to active. Redemptions resume if the active flag and expiration window allow.
Permanent delete
Hard-removes the coupon record. Available only after a soft delete. The historical ledger remains — past orders that used the code retain their discount line item with the code printed as a static string, even though the originating record is gone.
View usage ledger
Returns the per-redemption history for a coupon, paginated, with order ID, customer, redemption timestamp, applied discount, and post-redemption cart total. Supports CSV export. The ledger is the source of truth for campaign reporting and for redemption-cap enforcement.
Resolve coupon at checkout (programmatic)
The Orders module calls this entry point during cart resolution. Input: the typed code, the cart contents, the customer record. Output: either a resolved discount (with the rule-set explanation broken out) or a structured rejection naming the rule that rejected the redemption (expired, paused, cap reached, segment mismatch, product not in allowlist, minimum-cart-total not met, stack-conflict with an already-applied coupon).
Export campaign rollup
Produces a CSV of coupon-level metrics for a date range: redemption count, gross discount applied, attributable revenue, redemption rate (used / cap), and per-coupon breakouts. Used for marketing-campaign reporting and for finance reconciliation.
Data model
A coupon record carries the following fields. Field names below are the conceptual shape — the on-disk column names match closely but are not contractually stable across releases.
| Field | Type | Notes |
|---|---|---|
id | integer | Primary key. Stable across edits. |
code | string | Unique across active coupons. Case-insensitive at checkout. |
campaign_tag | string | Optional. Groups coupons under a campaign banner. |
type | enum | percent, fixed, bogo, free_shipping. |
value | decimal | The percentage, the amount, or unused (for bogo / free_shipping). |
currency | string | Required when type is fixed. ISO 4217 code. |
product_allowlist | array | Optional. Product IDs or collection IDs. Empty array = all products. |
segment_scope | enum | all, logged_in, or a specific segment slug. |
minimum_cart_total | decimal | Optional. The cart must reach this total before the coupon applies. |
redemption_cap_global | integer | Optional. Maximum redemptions across all customers. |
redemption_cap_per_customer | integer | Optional. Maximum redemptions per customer. |
starts_at | timestamp | Optional. Coupon inactive before this time. |
ends_at | timestamp | Optional. Coupon inactive after this time. |
stack_with_others | boolean | When false, this coupon cannot combine with any other coupon on the cart. |
active | boolean | Toggle. Inactive coupons reject at checkout regardless of date. |
description | string | SG-Admin only. Never shown to the customer. |
status | enum | active, trash. |
created_at | timestamp | Immutable. |
updated_at | timestamp | Touched on save. |
| Field | Type | Notes |
|---|---|---|
id | integer | Primary key. |
coupon_id | integer | Foreign key to the coupon. |
order_id | integer | Foreign key to the order that used the code. |
customer_id | integer | Foreign key to the customer (null for guest checkouts). |
applied_discount | decimal | The discount value at redemption time. |
cart_total_before | decimal | Pre-discount cart total. |
cart_total_after | decimal | Post-discount cart total. |
rule_snapshot | JSON | The coupon's rule set at redemption time. Carried so historical redemptions render accurately even after the coupon is edited. |
redeemed_at | timestamp | Redemption time. |
status = trash is the soft-deleted state. Trashed coupons retain their rule set and their ledger. Permanent delete removes the coupon row but preserves the ledger and the order-line attribution.Rule snapshot: critical for historical accuracy — when a coupon is edited or deleted, the rule set that was in effect at redemption time stays attached to the ledger row. Refunds, disputes, and audit reads use the snapshot, not the live rule set.
Code uniqueness: unique across active coupons only. Trashed and expired coupons release their code back to the pool, with the 30-day alias window described above.
CUSTOMER ENTERS CODE AT CHECKOUT│▼┌──────────────────────────┐│ Code lookup │ no match → reject ("not recognized")└──────────────┬───────────┘│ found▼┌──────────────────────────┐│ Active flag check │ paused → reject└──────────────┬───────────┘│ active▼┌──────────────────────────┐│ Date window check │ outside starts_at..ends_at → reject└──────────────┬───────────┘│ in-window▼┌──────────────────────────┐│ Eligibility checks │ fail → reject (named rule)│ ├─ product allowlist ││ ├─ segment scope ││ ├─ minimum cart total ││ └─ redemption caps │└──────────────┬───────────┘│ pass▼┌──────────────────────────┐│ Stack check │ conflict → reject ("not stackable")└──────────────┬───────────┘│ ok▼┌──────────────────────────┐│ Compute applied discount ││ Write ledger row ││ Return resolved discount │└──────────────────────────┘Permissions
Access to the Coupons surface is gated at two layers.
Layer 1 — admin gate. Every action under SG-Admin passes through the standard admin access check at request entry. Unauthenticated requests never reach the Coupons surface. The checkout entry point bypasses this gate but enforces its own customer-session check inside the Orders module.
Layer 2 — per-action capability. Within SG-Admin, each Coupons action checks a capability on the calling operator's role. The default role configuration ships with three roles — Administrator, Editor, Viewer — and a finance role is a common custom addition on commerce-heavy sites. The capability map is:
| Capability | Administrator | Editor | Finance | Viewer |
|---|---|---|---|---|
| List coupons | ✔ | ✔ | ✔ | ✔ |
| Create coupon | ✔ | ✔ | — | — |
| Edit coupon | ✔ | ✔ | — | — |
| Bulk generate | ✔ | ✔ | — | — |
| Duplicate | ✔ | ✔ | — | — |
| Toggle active | ✔ | ✔ | — | — |
| Soft delete | ✔ | ✔ | — | — |
| Restore | ✔ | ✔ | — | — |
| Permanent delete | ✔ | — | — | — |
| View usage ledger | ✔ | ✔ | ✔ | — |
| Export campaign rollup | ✔ | ✔ | ✔ | — |
Self-protection rules. A coupon currently attached to an in-flight order (cart resolved but not yet paid) cannot be permanently deleted — the surface returns a structured rejection naming the order. The bulk-generate ceiling is configurable under Settings → Commerce; the default cap is 10,000 codes per batch to prevent runaway generation.
Audit. Every write — create, edit, duplicate, bulk-generate, toggle active, delete, restore, permanent delete — emits an Activity Log entry. The log records the acting operator, the coupon, and the change shape (field-level diff for edits, batch summary for bulk-generate). Redemption ledger writes are not in the Activity Log — the ledger itself is the audit surface for redemptions.
REQUEST → /sg-admin/commerce/coupons/...│▼┌────────────────────────┐│ Admin gate │ unauth → reject└──────────┬─────────────┘│ authed▼┌────────────────────────┐│ Capability check │ role lacks cap → reject│ (per-action) │ (custom roles override defaults)└──────────┬─────────────┘│ allowed▼┌────────────────────────┐│ Self-protection rules │ attached-to-in-flight-order →│ │ reject permanent delete└──────────┬─────────────┘│ passes▼write executes│▼Activity Log entry│▼Redemption ledger writesbypass Activity Log(ledger is the audit surface)Related references
- Orders — Reference. The checkout flow calls the coupon-resolution entry point at cart resolution. Redemption ledger rows reference order IDs.
- Products — Reference. Coupon eligibility rules reference product IDs and collection IDs.
- Customers — Reference. Per-customer redemption caps reference customer IDs. Segment-scoped coupons reference customer segments.
- Settings — Reference. Default redemption-cap policy, bulk-generate ceiling, per-currency rounding rules.
- Users — Reference. Per-action capability checks resolve against Users.
- Email — Reference. Coupon delivery campaigns (one-time-use codes mailed individually) go through the Email module's templates.
- Reports — Reference. Campaign-rollup exports feed the broader commerce reporting surface.
/docs/coupons.