# WooCommerce Tier and Package Prices - AI Context Document

**Last Updated:** 2025-12-23
**Current Version:** 1.1.22
**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:

1. **Tier Pricing (Volume Discounts)**: Progressive discounts based on quantity ranges (e.g., 1-9 items @ $12, 10-24 @ $10, 25+ @ $8)
2. **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
```json
{
  "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 pages
- `wc_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_editable` filter

**Price Calculation Priority (in `apply_tier_package_pricing()`):**

1. Check for exact package match → Use package price if found
2. Check for tier match → Use tier price if found
3. 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):
```php
// 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):
```php
// 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_editable` passes `WC_Product` object, 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:

```php
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:
```php
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 `ABSPATH` check

### Translation Ready
All user-facing strings use:
```php
__('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 $product` instead of `$cart_item` array
- **Status:** FIXED in v1.1.20

## Release Process

### Version Bumping
Update version in 3 places:

1. `wc-tier-and-package-prices.php` - Plugin header comment (line 7)
2. `wc-tier-and-package-prices.php` - `WC_TPP_VERSION` constant (line 26)
3. `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.

```bash
# 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 -l` and test extraction before committing

### Verification Steps

After creating the release package, always verify:

```bash
# 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

```bash
# 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 `dev` branch 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.json` changes 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` (but `vendor/` 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 page
- `templates/admin/package-row.twig` - Single package input row in product edit page
- `templates/frontend/*.twig` - Customer-facing pricing displays

**Template rendering:** Done via `WC_TPP_Template_Loader` singleton class

**When modifying templates:**

1. Templates are cached - clear cache or test in development mode
2. Always escape output: use Twig's built-in filters or `|esc_attr`, `|esc_html`
3. Translation strings: `{{ 'Text'|__('wc-tier-package-prices') }}`
4. Keep consistent with WooCommerce admin UI patterns

### Complete Release Workflow Summary

Based on v1.1.22 release experience, here's the complete workflow:

1. **Fix bugs/add features** on `dev` branch
2. **Update version numbers** (3 files: main plugin file header, constant, composer.json)
3. **Update CHANGELOG.md** with detailed changes
4. **Update CLAUDE.md** version number and roadmap
5. **Create release package** from parent directory with correct exclusions
6. **Verify package** (size, contents, exclusions, extraction test)
7. **Commit changes** to `dev` branch with proper message format
8. **Merge to main** (fast-forward merge)
9. **Create annotated tag** (`vX.X.X`)
10. **Push all** (main, tag, dev)
11. **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 bumps
- `composer.json` - Version bump
- `CHANGELOG.md` - Release notes
- `CLAUDE.md` - Version and roadmap updates
- `releases/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

1. ~~Add translations for `de_CH`, `de_DE_informal`, `fr_CH`, `it_CH`~~ ✅ **COMPLETED in v1.1.21**
2. ~~The 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).
3. ~~Make the label fields in the backend for tierprices and package-prices twice as long as it is.~~ ✅ **COMPLETED in v1.1.22**

#### Version 1.2.x

1. 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

1. Check `includes/class-wc-tpp-cart.php` first
2. The `apply_tier_package_pricing()` method runs on `woocommerce_before_calculate_totals`
3. Always validate product objects with `is_a($product, 'WC_Product')`
4. 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

1. Check `CHANGELOG.md` for historical context
2. Look for similar issues in past versions
3. Always add detailed changelog entry explaining root cause
4. 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:

1. Maintain backward compatibility with existing tier/package configurations
2. Not break WooCommerce core functionality
3. Work with both classic and block-based themes
4. Be thoroughly tested before release
5. Include proper error handling and validation
6. 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!
