Matrix Pricing System
Deep dive into the core matrix pricing architecture, workflow, and integration with Magento 2 Pricesystem.
Overview
Matrix Pricing enables sophisticated product-customer price matching through configurable matrices that act as logical pricing containers. Unlike traditional pricelists where you assign customers to lists, matrices use bidirectional matching: products can be matched to customer attributes, or customer attributes can select products.
Key Concepts
Matrix Container: Logical unit that groups:
- Product selection rules
- Customer matching rules
- Priority for conflict resolution
- Date range validity
- Quantity-based pricing tiers
Bidirectional Matching:
- Traditional: Customer → Pricelist → Products
- Matrix: Customer ←→ Attributes ←→ Products
Attribute-Based Logic: Customers matched via attributes (group, company, location) rather than manual assignment.
Matrix Architecture
Component Diagram
┌─────────────────────────────────────┐ │ Matrix Container │ │ - name, priority, dates │ │ - attributes_relation (AND/OR) │ └─────────┬───────────────────────────┘ │ ├──► Customer Matching │ ├─ Attributes (group, company, location) │ └─ Manual Assignments (with date overrides) │ └──► Product Selection ├─ Direct product assignment ├─ Quantity tiers (1, 10, 50, 100) └─ Product-level date rangesData Flow
Customer Login ↓ Evaluate Active Matrices ↓ Check Attributes Match (AND/OR logic) ↓ Filter by Date Validity (matrix + customer dates) ↓ Assign Matching Matrices ↓ Cache Assignment (session) ↓ Product View/Cart ↓ Resolve Price (priority + merge logic) ↓ Display Final PriceMatrix Lifecycle
1. Creation Phase
Admin Creates Matrix:
- Set basic info (name, priority, dates)
- Configure attributes (group, company, location)
- Choose attributes relation (AND/OR)
- Save matrix container
Status: Inactive (default) or Active
2. Product Assignment
Add Products:
- Select products from catalog
- Set prices for each product
- Add quantity tiers (optional)
- Set product-level date ranges (optional)
Example:
Product: Widget Pro (SKU-123) Tiers: - qty=1, price=$100, dates=NULL - qty=10, price=$95, dates=NULL - qty=50, price=$90, dates=2025-06-01 to 2025-08-31 (summer special)3. Customer Assignment
Automatic (Attribute-Based):
- System evaluates customer attributes on login
- Matches against matrix attribute rules
- Auto-assigns if all conditions met
Manual:
- Admin explicitly assigns customer to matrix
- Optional: Set customer-specific date overrides
- Bypasses attribute matching
4. Activation
Set Matrix Active:
is_active = Yes- Matrix immediately available for matching
Date-Based Activation:
from_date = 2025-06-01- Automatically activates June 1st
- No manual intervention needed
5. Price Resolution
Customer Views Product:
- System finds all assigned matrices
- Filters by date validity
- Checks product exists in matrix
- Sorts by priority (highest first)
- Applies merge logic (if enabled)
- Returns final price for current quantity
6. Deactivation/Expiration
Manual Deactivation:
- Set
is_active = No - Immediately stops price evaluation
Automatic Expiration:
to_date = 2025-12-31- Expires January 1, 2026
- No cleanup needed
Price Resolution Algorithm
Step-by-Step Resolution
Input:
- Customer ID: 123
- Product ID: 456
- Quantity: 25
Process:
Step 1: Find Matching Matrices
SELECT m.* FROM pricesystem_product_customer_matrix m WHERE m.is_active = 1 AND m.website_id = 1 AND (m.from_date IS NULL OR m.from_date <= CURDATE()) AND (m.to_date IS NULL OR m.to_date >= CURDATE()) AND m.id IN ( -- Matrices customer is assigned to SELECT matrix_id FROM pricesystem_product_customer_matrix_customer WHERE customer_id = 123 ) ORDER BY m.priority DESC;Result: 3 matrices (A=Priority 15, B=Priority 20, C=Priority 30)
Step 2: Check Product Availability
SELECT p.* FROM pricesystem_pricelist_product p WHERE p.pricelist_id IN (matrixA, matrixB, matrixC) AND p.product_id = 456 AND (p.from_date IS NULL OR p.from_date <= CURDATE()) AND (p.to_date IS NULL OR p.to_date >= CURDATE());Result: Product exists in all 3 matrices
Step 3: Find Quantity Tiers
SELECT p.qty, p.price, p.pricelist_id FROM pricesystem_pricelist_product p WHERE p.pricelist_id IN (matrixA, matrixB, matrixC) AND p.product_id = 456 AND p.qty <= 25 -- Customer quantity ORDER BY p.pricelist_id, p.qty DESC;Result:
Matrix A (Priority 15): - qty=1: $100 - qty=10: $95 - qty=25: $92 ← Best tier for qty=25 Matrix B (Priority 20): - qty=1: $98 - qty=10: $93 (no qty=25 tier, use qty=10) Matrix C (Priority 30): - qty=1: $96 - qty=50: $88 (no qty=25 tier, use qty=1)Step 4: Apply Merge Logic
If merge_matrix_qtys = No (Highest Priority Only):
Use Matrix C only (Priority 30) Product qty=25 → Use Matrix C qty=1 tier: $96 Result: $96/unitIf merge_matrix_qtys = Yes (Best Price Wins):
Compare best price at qty=25 across all matrices: - Matrix A qty=25: $92 ← WINNER - Matrix B qty=10: $93 - Matrix C qty=1: $96 Result: $92/unit (from Matrix A)Step 5: Return Final Price
Output:
- merge=No: $96/unit × 25 = $2,400 total
- merge=Yes: $92/unit × 25 = $2,300 total (customer saves $100!)
Integration with Pricesystem Core
Price Resolution Order (Full System)
Product-Customer Matrix integrates into Magento 2 Pricesystem price resolution:
1. Shopping Cart Price Rules (if enabled) ↓ 2. Customer-Specific Prices (direct assignments) ↓ 3. Product-Customer Matrices ← THIS EXTENSION ↓ 4. Named Pricelists (pricesystem-pricelist) ↓ 5. Category Prices (pricesystem-categoryprice) ↓ 6. Catalog Price (fallback)Example Scenario:
Product: Widget Pro Catalog Price: $150 Customer has: - Matrix pricing: $100 (Priority 20) - Pricelist pricing: $110 (Priority 15) - Category pricing: $120 Resolution: Step 1: No cart rules Step 2: No customer-specific price Step 3: Matrix price found: $100 ← WINNER (Pricelists and category prices never evaluated) Final Price: $100Cross-Module Conflicts
Matrix vs Pricelist (both active):
- Matrices evaluated BEFORE pricelists
- Matrix price found → Pricelist ignored
- No matrix price → Fallback to pricelist
Matrix vs Category Price:
- Matrices evaluated BEFORE category prices
- Same logic as pricelists
Priority Within Matrices:
- Multiple matrices resolved via priority field
- NOT via module loading order
Advanced Scenarios
Scenario 1: Overlapping Date Ranges
Setup:
Matrix A: "Standard Wholesale" (Priority 15) - Dates: 2025-01-01 to 2025-12-31 (permanent) - Product X: $100 Matrix B: "Black Friday Special" (Priority 25) - Dates: 2025-11-29 to 2025-12-02 (4 days) - Product X: $75Timeline:
- Nov 28: Only Matrix A active → $100
- Nov 29-Dec 2: Both active, Matrix B wins (Priority 25) → $75
- Dec 3+: Only Matrix A active → $100
Key Point: Temporary high-priority matrices override standard pricing.
Scenario 2: Customer-Specific Date Override
Setup:
Matrix: "Annual Contract - ACME Corp" (Priority 35) - Dates: 2025-01-01 to 2025-12-31 - Company: "ACME" - Product X: $90 Customer #123 (ACME employee): - Manual assignment to matrix - Override dates: 2025-01-01 to 2025-06-30 (6-month trial) Customer #456 (ACME employee): - Automatic assignment via company match - No override dates (uses matrix dates)Result:
- Customer #123: Sees $90 from Jan 1 to Jun 30, then loses access
- Customer #456: Sees $90 from Jan 1 to Dec 31 (full year)
Use Case: Trial periods, temporary access, phased rollouts.
Scenario 3: Product-Level Date Override
Setup:
Matrix: "Seasonal Pricing 2025" (Priority 20) - Dates: 2025-01-01 to 2025-12-31 (permanent) - Customer Group: Wholesale Product X: - qty=1: $100, dates=NULL (year-round) - qty=10: $95, dates=NULL (year-round) - qty=50: $85, dates=2025-06-01 to 2025-08-31 (summer special)Result:
- Jan-May: qty tiers: 1=$100, 10=$95 (qty=50 tier not available)
- Jun-Aug: qty tiers: 1=$100, 10=$95, 50=$85 (summer tier active)
- Sep-Dec: qty tiers: 1=$100, 10=$95 (qty=50 tier expired)
Use Case: Seasonal quantity discounts, limited-time bulk pricing.
Scenario 4: Multi-Product Matrix with Partial Overlap
Setup:
Matrix A (Priority 15): Products X, Y, Z Matrix B (Priority 20): Products X, Y (Z missing) Customer matches both matrices. merge_matrix_qtys = No (highest priority only)Result:
- Product X: Uses Matrix B (Priority 20)
- Product Y: Uses Matrix B (Priority 20)
- Product Z: NO MATRIX PRICE (Matrix B doesn't have Z, Matrix A ignored)
- Fallback to next pricesystem module (pricelist, category, catalog)
Lesson: High-priority matrices with incomplete product sets can cause fallback.
Solution: Use merge_matrix_qtys = Yes to include Matrix A prices for Product Z.
Scenario 5: Three-Way Priority Resolution
Setup:
Customer: John Doe Matches three matrices: Matrix A: "Wholesale 2025" (Priority 15) - Product X: qty=1 ($100), qty=10 ($95), qty=50 ($90) Matrix B: "California Regional" (Priority 20) - Product X: qty=1 ($98), qty=25 ($92) Matrix C: "ACME Contract" (Priority 30) - Product X: qty=1 ($96), qty=100 ($88)Customer orders 30 units of Product X.
merge_matrix_qtys = No:
Use Matrix C only (Priority 30) Best tier for qty=30: qty=1 ($96) Result: $96/unit × 30 = $2,880merge_matrix_qtys = Yes:
Merge all tiers: - qty=1: Best of ($100, $98, $96) = $96 (Matrix C) - qty=10: Best of ($95, $98, $96) = $95 (Matrix A) - qty=25: Best of ($90, $92, $96) = $90 (Matrix A) - qty=50: Best of ($90, $92, $96) = $90 (Matrix A) - qty=100: $88 (Matrix C) Customer orders 30 units → Uses qty=25 tier: $90 Result: $90/unit × 30 = $2,700 Savings: $180 by using merge!Database Operations
Creating a Matrix
INSERT INTO pricesystem_product_customer_matrix (name, is_active, priority, from_date, to_date, website_id, attributes_relation, created_at, updated_at) VALUES ('Wholesale US 2025', 1, 15, '2025-01-01', '2025-12-31', 1, 'AND', NOW(), NOW()); SET @matrix_id = LAST_INSERT_ID();Adding Attributes
INSERT INTO pricesystem_product_customer_matrix_attribute (matrix_id, attribute_code, attribute_value) VALUES (@matrix_id, 'group', '2'), -- Wholesale group (@matrix_id, 'country', 'US'); -- United StatesAdding Products with Qty Tiers
INSERT INTO pricesystem_pricelist_product (pricelist_id, product_id, qty, price, from_date, to_date) VALUES (@matrix_id, 123, 1, 100.00, NULL, NULL), (@matrix_id, 123, 10, 95.00, NULL, NULL), (@matrix_id, 123, 50, 90.00, NULL, NULL), (@matrix_id, 123, 100, 85.00, NULL, NULL);Manual Customer Assignment
INSERT INTO pricesystem_product_customer_matrix_customer (matrix_id, customer_id, from_date, to_date) VALUES (@matrix_id, 456, '2025-01-01', '2025-06-30'); -- 6-month trialFinding Customer's Matrices
SELECT m.id, m.name, m.priority, m.from_date, m.to_date, CASE WHEN mc.from_date IS NOT NULL THEN mc.from_date ELSE m.from_date END AS effective_from_date, CASE WHEN mc.to_date IS NOT NULL THEN mc.to_date ELSE m.to_date END AS effective_to_date FROM pricesystem_product_customer_matrix m JOIN pricesystem_product_customer_matrix_customer mc ON mc.matrix_id = m.id WHERE mc.customer_id = 123 AND m.is_active = 1 ORDER BY m.priority DESC;Performance Optimization
Caching Strategy
Matrix Assignment Cache:
- Cached per customer session
- Cache key:
matrix_assignment_customer_{id} - TTL: Session lifetime
- Invalidated on: Profile update, group change, logout
Price Cache:
- Cached per customer + product + qty
- Cache key:
matrix_price_{customer_id}_{product_id}_{qty} - TTL: Configurable (default: 1 hour)
- Invalidated on: Matrix update, product update, price change
Indexing
Critical Indexes:
-- Matrix priority + active status CREATE INDEX idx_matrix_active_priority ON pricesystem_product_customer_matrix(is_active, priority); -- Matrix dates CREATE INDEX idx_matrix_dates ON pricesystem_product_customer_matrix(from_date, to_date); -- Customer assignments CREATE INDEX idx_matrix_customer ON pricesystem_product_customer_matrix_customer(customer_id); -- Product prices CREATE INDEX idx_product_matrix_qty ON pricesystem_pricelist_product(pricelist_id, product_id, qty);Query Optimization
Avoid:
-- BAD: Scans all matrices for each customer SELECT * FROM pricesystem_product_customer_matrix WHERE is_active = 1;Prefer:
-- GOOD: Uses customer assignment index SELECT m.* FROM pricesystem_product_customer_matrix m JOIN pricesystem_product_customer_matrix_customer mc ON mc.matrix_id = m.id WHERE mc.customer_id = 123 AND m.is_active = 1;Best Practices
Matrix Design
- Use specific attributes: Group + Country faster than Company matching
- Leave gaps in priority: 10, 20, 30 (not 10, 11, 12) allows insertions
- Document hierarchy: Team reference for priority tiers
- Test edge cases: Overlapping dates, missing products, same priority
Quantity Tiers
- Consistent tiers: Use same breakpoints across products (1, 10, 50, 100)
- Logical progression: Each tier should offer meaningful discount
- Avoid too many tiers: 4-6 tiers maximum (performance + UX)
- Document strategy: Why these breakpoints?
Date Management
- Include grace periods: Contract renewals need overlap
- Test transitions: Verify date changes in staging
- Use future dates: Schedule campaigns in advance
- Monitor expirations: Alert before critical matrices expire
Assignment Strategy
- Prefer automatic: Attribute-based assignment scales better
- Manual for exceptions: Override automatic rules sparingly
- Document overrides: Why this customer is special?
- Regular audits: Review manual assignments quarterly
Troubleshooting
Debug Checklist
Matrix not applying:
- ✓ Matrix active? (
is_active = 1) - ✓ Dates valid? (matrix + customer override)
- ✓ Customer assigned? (check both automatic + manual)
- ✓ Product exists in matrix?
- ✓ Quantity tier exists for cart quantity?
- ✓ Priority conflicts? (other matrix winning)
- ✓ Cache cleared?
Wrong price displaying:
- ✓ Check merge config (
merge_matrix_qtys) - ✓ Verify priority values (highest should win)
- ✓ Check quantity tier (correct tier for cart quantity?)
- ✓ Review other pricesystem modules (higher precedence?)
- ✓ Inspect cache (stale price cached?)
Related Documentation
- Creating Matrices - Step-by-step creation guide
- Multi-Attribute Matching - Attribute logic with AND/OR
- Priority Resolution - Conflict resolution strategies
