This major feature release adds full support for WooCommerce variable products with variation-level pricing configuration. ## Key Features - Each product variation can have independent tier and package pricing - AJAX-based dynamic pricing table loading on variation selection - Admin UI integrated into WooCommerce variation panels - Full backward compatibility with existing simple product functionality - WooCommerce Blocks compatibility maintained ## Implementation Highlights - Effective ID pattern throughout codebase for variation handling - Variation-specific meta boxes with field prefix support - Template system updated to support both simple and variation products - JavaScript enhancements for variation selector integration - Cart logic updated to handle variation pricing correctly ## Files Changed - Core: wc-tier-and-package-prices.php (version 1.2.0), composer.json - Cart: includes/class-wc-tpp-cart.php (effective ID logic) - Frontend: includes/class-wc-tpp-frontend.php (AJAX endpoint, variation detection) - Admin: includes/class-wc-tpp-product-meta.php (variation hooks and methods) - Templates: templates/admin/*.twig (field prefix support, table structure) - JavaScript: assets/js/*.js (variation support) - Documentation: CHANGELOG.md, README.md, CLAUDE.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
24 KiB
WooCommerce Tier and Package Prices - AI Context Document
Last Updated: 2025-12-29 Current Version: 1.2.0 Author: Marco Graetsch Project Status: Production-ready WordPress plugin
Project Overview
This is a WooCommerce plugin that adds flexible pricing capabilities to products through two distinct pricing models:
- Tier Pricing (Volume Discounts): Progressive discounts based on quantity ranges (e.g., 1-9 items @ $12, 10-24 @ $10, 25+ @ $8)
- Package Pricing (Fixed Bundles): Exact quantity packages at fixed prices (e.g., exactly 10 items for $95, exactly 25 for $200)
Key Fact: 100% AI-Generated
This project is proudly "vibe-coded" using Claude.AI - the entire codebase was created through AI assistance.
Technical Stack
- Language: PHP 7.4+
- Framework: WordPress Plugin API
- E-commerce: WooCommerce 8.0+ (tested up to 10.x)
- Template Engine: Twig 3.0 (via Composer)
- Frontend: Vanilla JavaScript + jQuery
- Styling: Custom CSS
- Dependency Management: Composer
- Internationalization: WordPress i18n (.pot/.po/.mo files)
Dependencies
{
"twig/twig": "^3.0",
"symfony/polyfill-ctype": "^1.x",
"symfony/polyfill-mbstring": "^1.x"
}
Architecture
Directory Structure
wc-tier-and-package-prices/
├── wc-tier-and-package-prices.php # Main plugin file (entry point)
├── includes/ # PHP classes
│ ├── class-wc-tpp-admin.php # Admin settings integration
│ ├── class-wc-tpp-settings.php # WooCommerce settings page
│ ├── class-wc-tpp-product-meta.php # Product edit page meta boxes
│ ├── class-wc-tpp-frontend.php # Product page display logic
│ ├── class-wc-tpp-cart.php # Cart price calculations
│ └── class-wc-tpp-template-loader.php # Twig template loader
├── templates/ # Twig templates
│ ├── admin/ # Admin interface templates
│ │ ├── tier-row.twig # Single tier input row
│ │ └── package-row.twig # Single package input row
│ └── frontend/ # Customer-facing templates
│ ├── pricing-table.twig # Main pricing display wrapper
│ ├── tier-pricing-table.twig # Tier pricing display
│ └── package-pricing-display.twig # Package buttons/cards
├── assets/
│ ├── css/
│ │ ├── admin.css # Backend styling
│ │ └── frontend.css # Product page & cart styling
│ └── js/
│ ├── admin.js # Meta box interaction (add/remove rows)
│ └── frontend.js # Dynamic price updates, package selection
├── languages/ # Translation files
│ ├── *.pot # Translation template
│ ├── *.po # Translation sources
│ └── *.mo # Compiled translations
├── vendor/ # Composer dependencies (included in releases)
├── releases/ # Release packages (not in git)
└── *.md # Documentation files
Class Responsibilities
1. WC_Tier_Package_Prices (Main Plugin Class)
- Location:
wc-tier-and-package-prices.php - Pattern: Singleton
- Responsibilities:
- Plugin initialization and bootstrapping
- Loading all component classes via
includes() - HPOS (High-Performance Order Storage) compatibility declaration
- Text domain loading for internationalization
- Activation/deactivation hooks
2. WC_TPP_Admin
- Location:
includes/class-wc-tpp-admin.php - Pattern: Singleton
- Responsibilities:
- Enqueues admin CSS/JS
- Registers WooCommerce settings page via filter
- Manages settings page instance (cached to prevent duplicates)
- Product meta box asset loading
3. WC_TPP_Settings
- Location:
includes/class-wc-tpp-settings.php - Extends:
WC_Settings_Page(WooCommerce core) - Responsibilities:
- Creates "Tier & Package Prices" tab in WooCommerce settings
- Defines global plugin settings (enable/disable features, display position, etc.)
- Setting persistence through WooCommerce options API
Global Settings:
wc_tpp_enable_tier_pricing(yes/no)wc_tpp_enable_package_pricing(yes/no)wc_tpp_display_table(yes/no) - Show pricing tables on product pageswc_tpp_display_position(before_add_to_cart / after_add_to_cart / after_price)wc_tpp_restrict_package_quantities(yes/no) - Global quantity restrictions
4. WC_TPP_Product_Meta
- Location:
includes/class-wc-tpp-product-meta.php - Responsibilities:
- Adds tier/package pricing fields to product edit page
- Renders Twig templates for meta box rows
- Saves tier/package data to post meta
- Nonce verification and capability checks for security
- Prevents autosave from corrupting data
Product Meta Keys:
_wc_tpp_tiers- Array of tier objects[{min_qty, price, label}]_wc_tpp_packages- Array of package objects[{qty, price, label}]_wc_tpp_restrict_to_packages- Per-product quantity restriction (yes/no)
5. WC_TPP_Frontend
- Location:
includes/class-wc-tpp-frontend.php - Responsibilities:
- Enqueues frontend CSS/JS on product pages
- Displays pricing tables via Twig templates
- Localizes currency settings to JavaScript
- Hides quantity inputs for restricted products
- Modifies catalog "Add to Cart" buttons to "View Options" for restricted products
- Static methods for price lookups (
get_tier_price(),get_package_price())
6. WC_TPP_Cart
- Location:
includes/class-wc-tpp-cart.php - Responsibilities:
- MOST CRITICAL CLASS - Handles all cart price calculations
- Applies tier/package pricing during cart totals calculation
- Stores pricing metadata in cart items for display
- Customizes cart item display (price labels, quantity indicators)
- Validates package quantities on add-to-cart
- Hides/disables quantity inputs for restricted products (classic cart + blocks)
- WooCommerce Blocks support via
woocommerce_store_api_product_quantity_editablefilter
Price Calculation Priority (in apply_tier_package_pricing()):
- Check for exact package match → Use package price if found
- Check for tier match → Use tier price if found
- Fall back to regular product price
7. WC_TPP_Template_Loader
- Location:
includes/class-wc-tpp-template-loader.php - Pattern: Singleton
- Responsibilities:
- Initializes Twig environment with proper paths
- Renders Twig templates from both admin and frontend directories
- Handles template caching and error handling
Important Implementation Details
Price Calculation Logic
Package Pricing (exact match):
// In cart: if quantity == 10 and package exists for 10, use package price
if ($quantity == $package['qty']) {
$unit_price = $package['price'] / $quantity; // Total price divided by quantity
$product->set_price($unit_price); // WooCommerce expects unit price
}
Tier Pricing (range-based):
// In cart: if quantity >= 10, use tier price for quantities 10+
foreach ($tiers as $tier) {
if ($quantity >= $tier['min_qty']) {
$applicable_price = $tier['price']; // This is already unit price
}
}
$product->set_price($applicable_price);
Quantity Restriction Feature
Products can be configured to ONLY allow purchase in package quantities:
- Global setting:
wc_tpp_restrict_package_quantities - Per-product setting:
_wc_tpp_restrict_to_packages - When enabled:
- Quantity inputs are hidden on product page, cart, and mini-cart
- Customers must use package selection buttons
- Validation prevents arbitrary quantities from being added
- Catalog buttons change to "View Options" instead of "Add to Cart"
WooCommerce Blocks Compatibility
CRITICAL BUG FIXED in v1.1.20:
- Filter
woocommerce_store_api_product_quantity_editablepassesWC_Productobject, NOT cart item array - Previous code tried to use product object as array → fatal error
- Fixed by accepting product object and using
$product->get_id()
Cart Item Metadata
The plugin stores additional data in cart items for display purposes:
WC()->cart->cart_contents[$cart_item_key]['wc_tpp_pricing_type'] = 'package' | 'tier';
WC()->cart->cart_contents[$cart_item_key]['wc_tpp_total_price'] = 99.99; // For packages
WC()->cart->cart_contents[$cart_item_key]['wc_tpp_unit_price'] = 9.99; // For tiers
This metadata is used by display filters to show "(Package price)" or "(Volume discount)" labels.
Common Patterns & Conventions
Class Instantiation Pattern
All classes auto-instantiate at the end of their file:
if (!class_exists('WC_TPP_Frontend')) {
class WC_TPP_Frontend {
// class code
}
}
new WC_TPP_Frontend(); // Auto-instantiate
Exception: Admin and Settings classes use singleton pattern to prevent duplicates.
Security Best Practices
- All user inputs are sanitized (integers for quantities/prices)
- Nonce verification on form submissions
- Capability checks (
edit_products) before saving - Output escaping in templates (
esc_attr,esc_html,esc_js) - Direct file access prevention via
ABSPATHcheck
Translation Ready
All user-facing strings use:
__('Text to translate', 'wc-tier-package-prices')
_e('Text to translate', 'wc-tier-package-prices')
Text domain: wc-tier-package-prices
Available Translations (as of v1.1.22):
en_US- English (United States)de_DE- German (Germany, formal)de_DE_informal- German (Germany, informal "du")de_CH- German (Switzerland, formal "Sie")de_CH_informal- German (Switzerland, informal "du")fr_CH- French (Switzerland)it_CH- Italian (Switzerland)
Note: Swiss locales use CHF currency formatting in examples (e.g., "CHF 50.-")
Known Issues & Historical Context
Settings Page Duplication Saga (v1.1.15-1.1.19)
Multiple versions attempted to fix settings page appearing twice:
- Root cause: Settings file auto-instantiation + Composer autoloader
- Solution: Removed auto-instantiation from settings file, explicit instantiation in admin class
- Prevention: Singleton pattern + duplicate detection in array
Class Redeclaration Issues (v1.1.8-1.1.14)
Plugin was completely non-functional:
- Cause: Incorrect initialization pattern without
class_exists()guards - Solution: Added guards and restored direct instantiation pattern
- Lesson: Always wrap class declarations in
class_exists()checks
WooCommerce Blocks Fatal Error (v1.1.19 → v1.1.20)
Fatal error: Cannot use object of type WC_Product_Simple as array
Location: includes/class-wc-tpp-cart.php:233
- Cause: Filter signature mismatch - expected array, received product object
- Fix: Changed method signature to accept
WC_Product $productinstead of$cart_itemarray - Status: FIXED in v1.1.20
Release Process
Version Bumping
Update version in 3 places:
wc-tier-and-package-prices.php- Plugin header comment (line 7)wc-tier-and-package-prices.php-WC_TPP_VERSIONconstant (line 26)composer.json- version field (optional, not critical)
Creating Release Package
CRITICAL: The zip command must be run from the parent directory of the plugin folder to create proper archive structure.
# From parent directory (/home/magdev/workspaces/node)
cd /home/magdev/workspaces/node
# Create zip excluding dev files - note the correct path structure
zip -r wc-tier-and-package-prices/releases/wc-tier-and-package-prices-X.X.X.zip wc-tier-and-package-prices/ \
-x 'wc-tier-and-package-prices/.git*' \
'wc-tier-and-package-prices/*.log' \
'wc-tier-and-package-prices/.claude/*' \
'wc-tier-and-package-prices/CLAUDE.md' \
'wc-tier-and-package-prices/releases/*' \
'wc-tier-and-package-prices/node_modules/*' \
'wc-tier-and-package-prices/.DS_Store' \
'wc-tier-and-package-prices/Thumbs.db' \
'wc-tier-and-package-prices/.vscode/*' \
'wc-tier-and-package-prices/.idea/*' \
'wc-tier-and-package-prices/*.sublime-*' \
'wc-tier-and-package-prices/notes.*' \
'wc-tier-and-package-prices/logs/*' \
'wc-tier-and-package-prices/templates/cache/*' \
'wc-tier-and-package-prices/composer.lock'
# Return to project directory
cd wc-tier-and-package-prices
# Generate checksums
cd releases
md5sum wc-tier-and-package-prices-X.X.X.zip > wc-tier-and-package-prices-X.X.X.zip.md5
sha256sum wc-tier-and-package-prices-X.X.X.zip > wc-tier-and-package-prices-X.X.X.zip.sha256
cd ..
IMPORTANT NOTES:
- The
vendor/directory MUST be included in releases (Twig dependency required for runtime) - Running zip from wrong directory creates empty or malformed archives
- Exclusion patterns must match the relative path structure used in zip command
- Always verify the package with
unzip -land test extraction before committing
Verification Steps
After creating the release package, always verify:
# Check package size (should be ~400-450KB, NOT 8MB+ or near 0)
ls -lh releases/wc-tier-and-package-prices-X.X.X.zip
# Verify exclusions worked
unzip -l releases/wc-tier-and-package-prices-X.X.X.zip | grep -E "CLAUDE\.md|\.claude/|\.git" && echo "ERROR: Excluded files found!" || echo "OK: No excluded files"
# Test extraction
cd /tmp && rm -rf test-extract && unzip -q /path/to/releases/wc-tier-and-package-prices-X.X.X.zip -d test-extract && ls -la test-extract/wc-tier-and-package-prices/
# Verify version in extracted package
head -30 /tmp/test-extract/wc-tier-and-package-prices/wc-tier-and-package-prices.php | grep -E "Version:|WC_TPP_VERSION"
# Verify template changes (if applicable)
grep 'class="regular"' /tmp/test-extract/wc-tier-and-package-prices/templates/admin/*.twig
Git Workflow for Releases
Standard workflow: Work on dev branch → merge to main → tag → push
# 1. Ensure you're on dev branch with all changes committed
git checkout dev
git add [files]
git commit -m "Release version X.X.X - [description]
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
# 2. Merge dev to main
git checkout main
git merge dev --no-edit # Should be fast-forward
# 3. Create annotated tag
git tag -a vX.X.X -m "Release version X.X.X - [description]"
# 4. Push everything
git push origin main
git push origin vX.X.X
# 5. Update dev and push
git checkout dev
git rebase main # Should be up-to-date already
git push origin dev
# 6. If you have uncommitted local changes (like .claude/settings.local.json)
git stash push -m "Local development settings"
# ... do git operations ...
git stash pop
Important Git Notes:
- Always commit from
devbranch first - Tags should use format
vX.X.X(e.g.,v1.1.22) - Use annotated tags (
-a) not lightweight tags - Commit messages should follow the established format with Claude Code attribution
.claude/settings.local.jsonchanges are typically local-only (stash before rebasing)
What Gets Released
- All plugin source files
- Compiled vendor dependencies
- Translation files (.mo compiled from .po)
- Assets (CSS, JS)
- Documentation (README, CHANGELOG, etc.)
What's Excluded
- Git metadata (
.git/) - Development files (
.vscode/,.claude/,CLAUDE.md) - Logs and cache files
- Previous releases
composer.lock(butvendor/is included)
Testing Checklist
When making changes, test these critical paths:
Admin
- Settings page appears once under WooCommerce > Tier & Package Prices
- Settings save correctly
- Product edit page shows tier/package meta boxes
- Adding/removing tiers works
- Adding/removing packages works
- Data saves when clicking "Update" on product
Frontend (Product Page)
- Pricing tables display when configured
- Package buttons update quantity selector
- Price updates dynamically when quantity changes
- Restricted products hide quantity input
- "View Options" appears on catalog for restricted products
Cart & Checkout
- Correct prices applied for tier pricing
- Correct prices applied for package pricing
- Cart displays pricing type labels
- Package quantities can't be edited if restricted
- Prices recalculate if quantity changed (non-restricted products)
- Checkout totals are correct
WooCommerce Blocks (Critical!)
- Mini cart block doesn't throw fatal errors
- Cart block works correctly
- Checkout block processes orders
- Quantity editable flag works for blocks
Development Tips for Future AI Assistants
Common Pitfalls and Solutions
Release Package Creation
Problem: Empty or corrupted zip files (0 bytes or wrong structure)
Cause: Running zip command from wrong directory or incorrect path patterns
Solution: Always run from parent directory (/home/magdev/workspaces/node) and use full relative paths in exclusions
Problem: Development files included in release (CLAUDE.md, .claude/, .git)
Cause: Exclusion patterns don't match actual paths used in zip command
Solution: Test with unzip -l | grep to verify exclusions before committing
Problem: Package size is 8MB+ instead of ~400KB Cause: Development files not excluded (especially .git directory) Solution: Follow verification steps and check package size immediately after creation
UI Changes in Admin
WooCommerce CSS Classes:
short- Small input fields (~60px width)regular- Medium input fields (~120px width)long- Large input fields (~200px+ width)
When modifying admin input fields in Twig templates, use WooCommerce's standard classes for consistency.
Location: templates/admin/*.twig for admin UI changes
Git Workflow Issues
Problem: Cannot rebase due to uncommitted changes
Solution: Stash local config files (.claude/settings.local.json) before git operations
Problem: Tag already exists
Solution: Delete with git tag -d vX.X.X locally and git push --delete origin vX.X.X remotely
Problem: Wrong branch for commits
Solution: Always start on dev branch, merge to main, never commit directly to main
Working with Twig Templates
The plugin uses Twig 3.0 for templating. Key files:
templates/admin/tier-row.twig- Single tier input row in product edit pagetemplates/admin/package-row.twig- Single package input row in product edit pagetemplates/frontend/*.twig- Customer-facing pricing displays
Template rendering: Done via WC_TPP_Template_Loader singleton class
When modifying templates:
- Templates are cached - clear cache or test in development mode
- Always escape output: use Twig's built-in filters or
|esc_attr,|esc_html - Translation strings:
{{ 'Text'|__('wc-tier-package-prices') }} - Keep consistent with WooCommerce admin UI patterns
Complete Release Workflow Summary
Based on v1.1.22 release experience, here's the complete workflow:
- Fix bugs/add features on
devbranch - Update version numbers (3 files: main plugin file header, constant, composer.json)
- Update CHANGELOG.md with detailed changes
- Update CLAUDE.md version number and roadmap
- Create release package from parent directory with correct exclusions
- Verify package (size, contents, exclusions, extraction test)
- Commit changes to
devbranch with proper message format - Merge to main (fast-forward merge)
- Create annotated tag (
vX.X.X) - Push all (main, tag, dev)
- Verify remote (check repository web UI)
Time estimate: 15-20 minutes for full release cycle
Files typically changed in a release:
wc-tier-and-package-prices.php- Version bumpscomposer.json- Version bumpCHANGELOG.md- Release notesCLAUDE.md- Version and roadmap updatesreleases/wc-tier-and-package-prices-X.X.X.zip*- Package and checksums- Feature-specific files (templates, PHP classes, etc.)
Future Features and Roadmap
The is a hierarchical list for upcoming features and can be considered as a Roadmap for the upcoming development.
Version 1.1.x (Completed)
Add translations for✅ COMPLETED in v1.1.21de_CH,de_DE_informal,fr_CH,it_CHThe double-install bug is back again. A new version of the plugin is installed as new plugin instead of updating the previous version.✅ DOCUMENTED in v1.1.22 - Added workaround to CHANGELOG. Root cause: No automatic update mechanism (requires WordPress.org repository or custom update server).Make the label fields in the backend for tierprices and package-prices twice as long as it is.✅ COMPLETED in v1.1.22Make the plugin work with variable products✅ COMPLETED in v1.2.0 - Full variation-level pricing support with independent configuration per variation, AJAX-based frontend display, and complete WooCommerce Blocks compatibility.
Version 1.2.x
- New Feature: Create different, selectable templates for tierprices and packages to use in the frontend. Make the new templates selectable globally on the settings-page, not per product.
When Debugging Cart Issues
- Check
includes/class-wc-tpp-cart.phpfirst - The
apply_tier_package_pricing()method runs onwoocommerce_before_calculate_totals - Always validate product objects with
is_a($product, 'WC_Product') - Remember: WooCommerce expects UNIT prices, not total prices (except for internal calculations)
When Working with WooCommerce Hooks
- WooCommerce has both classic and block-based systems
- Classic cart uses different hooks than Store API (blocks)
- Always check filter/action documentation for parameter types
- Don't assume cart item arrays everywhere - sometimes it's product objects!
When Adding Features
- Follow the existing pattern: add setting → add UI → add logic → add template
- Use Twig for all new templates (consistency)
- Add translations for all user-facing strings
- Test with both simple products and variable products (if applicable)
- Consider both classic and block-based cart/checkout
When Fixing Bugs
- Check
CHANGELOG.mdfor historical context - Look for similar issues in past versions
- Always add detailed changelog entry explaining root cause
- Consider edge cases (guest checkout, logged-in users, AJAX add-to-cart, etc.)
File Locations Quick Reference
| Task | File(s) |
|---|---|
| Change version | wc-tier-and-package-prices.php (2 places) |
| Add global setting | includes/class-wc-tpp-settings.php |
| Modify product meta box | includes/class-wc-tpp-product-meta.php + templates/admin/*.twig |
| Change product page display | includes/class-wc-tpp-frontend.php + templates/frontend/*.twig |
| Fix cart pricing | includes/class-wc-tpp-cart.php |
| Update styles | assets/css/frontend.css or assets/css/admin.css |
| Fix JavaScript bugs | assets/js/frontend.js or assets/js/admin.js |
| Add translations | languages/*.po then compile to .mo |
| Document changes | CHANGELOG.md |
Compatibility Notes
WordPress
- Minimum: 6.0
- Tested up to: 6.9.x
- Uses standard plugin API, no deprecated functions
WooCommerce
- Minimum: 8.0
- Tested up to: 10.x
- HPOS compatible (declared via
FeaturesUtil::declare_compatibility) - Blocks compatible (with proper filter handling)
PHP
- Minimum: 7.4
- Uses modern PHP features (type hints acceptable in new code)
- Composer autoloader handles namespacing
Browsers
- Modern browsers (ES6+ JavaScript)
- Responsive CSS (mobile-friendly)
- jQuery dependency (WooCommerce provides)
Support & Resources
- Repository: https://src.bundespruefstelle.ch/magdev/wc-tier-package-prices
- Documentation: See
README.md,QUICKSTART.md,USAGE_EXAMPLES.md,INSTALLATION.md - Changelog:
CHANGELOG.md(detailed version history) - Issue Tracking: Check fatal-errors-*.log files for production errors
Final Notes
This is a production-quality plugin with real-world usage. Any changes should:
- Maintain backward compatibility with existing tier/package configurations
- Not break WooCommerce core functionality
- Work with both classic and block-based themes
- Be thoroughly tested before release
- Include proper error handling and validation
- Update CHANGELOG.md with detailed explanations
The plugin architecture is solid and well-tested. Most bugs arise from:
- WooCommerce API changes (especially blocks)
- Filter/action signature changes
- Edge cases in cart calculations
- Settings persistence issues
Always refer to this document when starting work on this project. Good luck!