Coupon ↔ Rule Linking
{/* AUTO-GENERATED from ../docs/coupon-rule-linking.md by scripts/sync-dev-docs.mjs — do not edit by hand. */}
status: active last_reviewed: 2026-05-14
Section titled “status: active last_reviewed: 2026-05-14”Coupon ↔ Rule Linking
Section titled “Coupon ↔ Rule Linking”How coupons and discount rules are connected.
Note on field naming: the canonical targeting node is
dino_coupon_applied. Legacydino_campaign_appliedtrees remain readable via a case-fallthrough alias inTargetingEvaluator::evaluate_node(); the v4.18.0 migration rewrites stored trees in place on first load.
Core Principle
Section titled “Core Principle”The targeting tree is the single source of truth for which coupons gate a rule. A rule references coupons via dino_coupon_applied nodes in its targeting_tree. Coupons are independent entities with no rule awareness.
Concepts
Section titled “Concepts”| Term | Description |
|---|---|
| Rule | A discount configuration stored in the dino_discounts_active_rules WP option (JSON array). Each rule has a UUID (id) and a targeting_tree that may contain dino_coupon_applied condition nodes. |
| Coupon | A row (or group of rows for bulk pools) in the wp_dino_codes table. Coupons are standalone entities that own redeemable codes. |
| Link | Derived at runtime: a coupon is “linked” to a rule when the rule’s targeting tree contains a dino_coupon_applied node referencing that coupon’s code. There is no denormalized rule_id column used for this purpose. |
| Draft | A coupon referenced by a rule in the browser’s local draft state but not yet Published. Shown with a draft badge in the Coupons table. |
Multiple rules can reference the same coupon
Section titled “Multiple rules can reference the same coupon”Unlike the old architecture, there is no one-to-one constraint. Multiple rules may include the same coupon code in their targeting trees.
Lifecycle: What Happens When
Section titled “Lifecycle: What Happens When”1. Create Coupon (inline, inside the Rule Wizard or BYO Targeting)
Section titled “1. Create Coupon (inline, inside the Rule Wizard or BYO Targeting)”The admin types a code and clicks Create Coupon (or Generate Random).
Frontend Backend-------- -------POST /coupons CouponCodesController::create_campaign() { campaign, activation_type } -> CouponManager::create_coupon() (inserts row) <- returns { success, coupon, campaign }
React adds a dino_coupon_applied (local targeting tree state only --node to the rule's targeting_tree not saved to server until Publish)The legacy POST /coupon-campaigns alias still resolves (with a Deprecation header) for one release; new integrations should target POST /coupons.
Result: The coupon exists in the DB. The rule’s targeting tree references it in local state.
2. Select Existing Coupon (dropdown)
Section titled “2. Select Existing Coupon (dropdown)”The admin picks an existing coupon from the dropdown.
Frontend Backend-------- -------No API call. Nothing happens.
React adds/updates the (local targeting tree state only)dino_coupon_applied nodeResult: The rule references the coupon locally. No server state changes until Publish.
3. Save Draft
Section titled “3. Save Draft”Updates local React state only. Nothing is posted to the server.
4. Publish Discounts
Section titled “4. Publish Discounts”Frontend Backend-------- -------POST /dino-discounts/v1/rules RulesController::save_rules() { rules: [...] } -> sanitize & save to dino_discounts_active_rules -> do_action('dino_discounts_rules_saved') <- returns { success }Result: The saved rules contain targeting trees with dino_coupon_applied nodes. The engine evaluates these at cart time. No separate linking/syncing step is needed.
5. Revert (discard unsaved changes)
Section titled “5. Revert (discard unsaved changes)”Resets local state to last saved snapshot. If a coupon was created in step 1 but the rule was never Published with it, the coupon still exists in the DB as an unlinked (orphan) row. Orphans can be linked to other rules or archived.
How the Coupons Table Derives Links
Section titled “How the Coupons Table Derives Links”The Coupons table shows which rule(s) reference each coupon by walking targeting trees client-side:
- Draft rules first (
window.__ddLocalRules) — most up-to-date editor state. - Saved rules fallback — from the rules API response (includes
targeting_tree).
The utility getCouponsFromTree(tree) extracts coupon codes from dino_coupon_applied nodes (and the legacy dino_campaign_applied alias). The function findLinkedRule(couponCode) searches both sources.
Engine Evaluation
Section titled “Engine Evaluation”When a shopper applies a coupon code at checkout:
- The engine evaluates each rule’s targeting tree.
TargetingEvaluator::evaluate_dino_coupon_applied()checks if the applied codes match the coupons listed in the node.- The engine resolves codes to coupons via a DB lookup on the
wp_dino_codestable (couponcolumn). - The
rule_idcolumn is not read during evaluation.
Database Schema
Section titled “Database Schema”wp_dino_codes table (renamed from wp_dino_coupon_codes in v4.27.0)
Section titled “wp_dino_codes table (renamed from wp_dino_coupon_codes in v4.27.0)”| Column | Type | Purpose |
|---|---|---|
id | BIGINT AUTO_INCREMENT | Primary key |
uuid | CHAR(36) | Unique identifier |
coupon | VARCHAR(100) | Coupon slug (e.g. SUMMER20). Groups bulk pool rows. |
coupon_status | ENUM('active','archived') | Soft-delete flag |
activation_type | VARCHAR(20) | single_code, bulk_pool, or url_token |
rule_id | VARCHAR(36) | Legacy column — always empty string. Not read or written by current code. Kept for schema compatibility. |
code | VARCHAR(100) | The typeable redemption code |
url_token | VARCHAR(100) | URL activation token |
usage_limit | INT | Max redemptions (0 = unlimited) |
used_count | INT | Current redemption count |
expires_at | DATETIME | Expiry (NULL = never) |
Implementation: Key Files
Section titled “Implementation: Key Files”Backend (PHP)
Section titled “Backend (PHP)”| File | Key Methods |
|---|---|
CouponManager.php | create_coupon(), generate_codes(), list_coupons(), archive_coupon() |
CouponCodesController.php | REST endpoints: POST /coupons (create), GET /coupons (list). Legacy /coupon-campaigns/* alias kept for one release with deprecation headers. |
RulesController.php | save_rules() — saves rules with targeting trees |
TargetingEvaluator.php | evaluate_dino_coupon_applied() — runtime coupon matching |
Frontend (React)
Section titled “Frontend (React)”| File | Role |
|---|---|
useCouponCreation.js | Shared hook for creating/generating coupons (used by both recipe and BYO) |
FieldRenderers.js | BYO targeting field DinoCouponTargetingField (multi-select dropdown with Create/Generate + chips) |
Coupons/index.js | Coupons table — derives linked rule from targeting trees via findLinkedRule() |
targetingTreeHelpers.js | getCouponsFromTree(), addCouponNode(), removeCouponNode() |