Coupon ↔ Rule Linking
{/* AUTO-GENERATED from ../docs/coupon-rule-linking.md by scripts/sync-dev-docs.mjs — do not edit by hand. */}
How coupon campaigns and discount rules are connected.
Note on field naming: the canonical targeting node is
dino_coupon_applied. Legacydino_campaign_appliedtrees remain readable via an alias atTargetingEvaluator::evaluate(); 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 campaigns gate a rule. A rule references campaigns via dino_coupon_applied nodes in its targeting_tree. Campaigns 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. |
| Campaign | A row (or group of rows for bulk pools) in the wp_dino_coupon_codes table. Campaigns are standalone entities that own coupon codes. |
| Link | Derived at runtime: a campaign is “linked” to a rule when the rule’s targeting tree contains a dino_coupon_applied node referencing that campaign’s code. There is no denormalized rule_id column used for this purpose. |
| Draft | A campaign 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 campaign
Section titled “Multiple rules can reference the same campaign”Unlike the old architecture, there is no one-to-one constraint. Multiple rules may include the same campaign 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 /coupon-campaigns CouponCodesController::create_campaign() { campaign, activation_type } -> CouponCodeManager::create_campaign() (inserts row) <- returns { success, 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)Result: The campaign 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 campaign 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 campaign 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 campaign was created in step 1 but the rule was never Published with it, the campaign still exists in the DB as an unlinked (orphan) campaign. 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 campaign 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 getCampaignsFromTree(tree) extracts campaign codes from dino_coupon_applied nodes (and the legacy dino_campaign_applied alias). The function findLinkedRule(campaignCode) 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 coupon codes match the campaigns listed in the node.- The engine resolves coupon codes to campaigns via a DB lookup on the
wp_dino_coupon_codestable (campaigncolumn). - The
rule_idcolumn is not read during evaluation.
Database Schema
Section titled “Database Schema”wp_dino_coupon_codes table
Section titled “wp_dino_coupon_codes table”| Column | Type | Purpose |
|---|---|---|
id | BIGINT AUTO_INCREMENT | Primary key |
uuid | CHAR(36) | Unique identifier |
campaign | VARCHAR(100) | Campaign slug (e.g. SUMMER20). Groups bulk pool rows. |
campaign_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 coupon 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 |
|---|---|
CouponCodeManager.php | create_campaign(), generate_codes(), list_campaigns(), archive_campaign() |
CouponCodesController.php | REST endpoints: POST /coupon-campaigns (create), GET /coupon-campaigns (list) |
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 campaigns (used by both recipe and BYO) |
CampaignSelector.js | Recipe campaign dropdown (single-select with Create/Generate) |
FieldRenderers.js | BYO targeting field DinoCampaignTargetingField (multi-select dropdown with Create/Generate + chips) |
CouponCampaigns/index.js | Coupons table — derives linked rule from targeting trees via findLinkedRule() |
targetingTreeHelpers.js | getCampaignsFromTree(), addCampaignNode(), removeCampaignNode() |