364 lines
11 KiB
Markdown
364 lines
11 KiB
Markdown
# Coupon System Documentation
|
|
|
|
## Overview
|
|
|
|
The billing module now includes a comprehensive coupon system that allows administrators to create discount codes that customers can apply to their orders. The system supports:
|
|
|
|
- **Percentage-based discounts** (e.g., 10%, 25%, 50% off)
|
|
- **One-time or permanent discounts** (one-time applies to first invoice only, permanent applies to all renewals)
|
|
- **Game-specific filtering** (apply coupons to all games or specific games only)
|
|
- **Usage limits** (optional maximum number of uses per coupon)
|
|
- **Expiration dates** (optional expiry date for time-limited promotions)
|
|
- **Automatic usage tracking** (system tracks how many times each coupon has been used)
|
|
|
|
## Database Schema
|
|
|
|
### Table: `ogp_billing_coupons`
|
|
|
|
The main coupon table stores all coupon definitions:
|
|
|
|
```sql
|
|
CREATE TABLE `ogp_billing_coupons` (
|
|
`coupon_id` INT(11) NOT NULL AUTO_INCREMENT,
|
|
`code` VARCHAR(50) NOT NULL UNIQUE,
|
|
`name` VARCHAR(255) NOT NULL DEFAULT '',
|
|
`description` TEXT,
|
|
`discount_percent` DECIMAL(5,2) NOT NULL DEFAULT 0.00,
|
|
`usage_type` ENUM('one_time', 'permanent') NOT NULL DEFAULT 'one_time',
|
|
`game_filter_type` ENUM('all_games', 'specific_games') NOT NULL DEFAULT 'all_games',
|
|
`game_filter_list` TEXT COMMENT 'JSON array of game keys',
|
|
`max_uses` INT(11) DEFAULT NULL COMMENT 'NULL for unlimited',
|
|
`current_uses` INT(11) NOT NULL DEFAULT 0,
|
|
`expires` DATETIME DEFAULT NULL,
|
|
`created_date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
`created_by` INT(11) DEFAULT NULL,
|
|
`is_active` TINYINT(1) NOT NULL DEFAULT 1,
|
|
PRIMARY KEY (`coupon_id`),
|
|
UNIQUE KEY `idx_code` (`code`)
|
|
);
|
|
```
|
|
|
|
### Updated Tables
|
|
|
|
#### `ogp_billing_invoices`
|
|
Added columns:
|
|
- `coupon_id` INT(11) - Links to the coupon used
|
|
- `discount_amount` DECIMAL(10,2) - Actual discount amount applied
|
|
|
|
#### `ogp_billing_orders`
|
|
Added columns:
|
|
- `coupon_id` INT(11) - Links to the coupon used (for permanent discounts)
|
|
- `discount_amount` DECIMAL(10,2) - Discount amount for renewals
|
|
|
|
## Installation
|
|
|
|
1. **Run the SQL migration:**
|
|
```bash
|
|
mysql -u [username] -p [database_name] < modules/billing/create_coupons_table.sql
|
|
```
|
|
|
|
2. **Verify installation:**
|
|
- Check that the `ogp_billing_coupons` table exists
|
|
- Verify that `coupon_id` and `discount_amount` columns were added to both `ogp_billing_invoices` and `ogp_billing_orders`
|
|
|
|
## Admin Interface
|
|
|
|
### Accessing Coupon Management
|
|
|
|
1. Log in as an administrator
|
|
2. Navigate to `/modules/billing/admin.php`
|
|
3. Click on "Manage Coupons" button
|
|
4. Or go directly to `/modules/billing/admin_coupons.php`
|
|
|
|
### Creating a New Coupon
|
|
|
|
1. On the Manage Coupons page, scroll to "Add New Coupon" section
|
|
2. Fill in the required fields:
|
|
- **Coupon Code**: Unique alphanumeric code (e.g., "SUMMER2025", "WELCOME10")
|
|
- **Display Name**: User-friendly name shown in admin interface
|
|
- **Description**: Internal notes about the coupon
|
|
- **Discount Percentage**: Number between 0-100 (e.g., 25 for 25% off)
|
|
- **Usage Type**:
|
|
- **One Time**: Discount applies only to the first invoice
|
|
- **Permanent**: Discount applies to initial order AND all future renewals
|
|
- **Apply To**:
|
|
- **All Games**: Works for any game server
|
|
- **Specific Games**: Works only for selected games
|
|
- **Maximum Uses**: Optional limit on total uses (blank = unlimited)
|
|
- **Expiration Date**: Optional expiry date (blank = never expires)
|
|
|
|
3. Click "Add Coupon" to save
|
|
|
|
### Example Coupons
|
|
|
|
#### Welcome Discount (One-Time, All Games)
|
|
```
|
|
Code: WELCOME10
|
|
Name: Welcome 10% Off
|
|
Discount: 10%
|
|
Usage Type: One Time
|
|
Apply To: All Games
|
|
Max Uses: (unlimited)
|
|
Expires: (none)
|
|
```
|
|
|
|
#### Arma Series Promotion (Permanent, Specific Games)
|
|
```
|
|
Code: ARMA25
|
|
Name: Arma Series 25% Off
|
|
Discount: 25%
|
|
Usage Type: Permanent
|
|
Apply To: Specific Games
|
|
- arma2_win32
|
|
- arma2oa_win32
|
|
- arma3_linux32
|
|
- arma3_linux64
|
|
- arma3_win64
|
|
- arma-reforger_linux64
|
|
- arma-reforger_win64
|
|
Max Uses: 100
|
|
Expires: 2025-12-31
|
|
```
|
|
|
|
### Editing Coupons
|
|
|
|
1. On the Manage Coupons page, find the coupon in the list
|
|
2. Click the "Edit" button
|
|
3. Modify any fields (except code uniqueness is enforced)
|
|
4. Click "Save Changes"
|
|
|
|
### Deactivating Coupons
|
|
|
|
1. Click "Edit" on the coupon
|
|
2. Uncheck the "Active" checkbox
|
|
3. Click "Save Changes"
|
|
|
|
Note: Deactivating prevents new uses but doesn't affect existing orders.
|
|
|
|
### Deleting Coupons
|
|
|
|
1. Find the coupon in the list
|
|
2. Click "Delete" button
|
|
3. Confirm the deletion
|
|
|
|
Warning: This permanently removes the coupon. Orders that used it will retain the discount but lose the coupon reference.
|
|
|
|
## Customer Usage
|
|
|
|
### Applying a Coupon
|
|
|
|
1. Customer adds items to cart at `/modules/billing/cart.php`
|
|
2. In the coupon section, enter coupon code in the input field
|
|
3. Click "Apply Coupon"
|
|
4. If valid, a success message appears showing:
|
|
- Coupon code
|
|
- Discount percentage
|
|
- Whether it's one-time or permanent
|
|
5. Cart totals update automatically with discounted prices
|
|
6. Proceed to checkout with PayPal as normal
|
|
|
|
### Coupon Validation
|
|
|
|
The system validates:
|
|
- ✅ Code exists and is active
|
|
- ✅ Coupon hasn't expired
|
|
- ✅ Usage limit hasn't been reached
|
|
- ✅ Game matches filter (if game-specific)
|
|
|
|
Error messages shown if:
|
|
- ❌ Code is invalid or expired
|
|
- ❌ Usage limit reached
|
|
- ❌ Coupon doesn't apply to games in cart
|
|
|
|
### Removing a Coupon
|
|
|
|
1. On cart page, click "Remove" button next to active coupon
|
|
2. Cart prices revert to original amounts
|
|
|
|
## Coupon Behavior
|
|
|
|
### One-Time Coupons
|
|
|
|
- Applied to the initial invoice only
|
|
- When order is renewed, renewal invoice uses original price
|
|
- Coupon is cleared from session after first payment
|
|
- Example: "WELCOME10" gives 10% off first month only
|
|
|
|
### Permanent Coupons
|
|
|
|
- Applied to initial invoice AND stored in order record
|
|
- When order is renewed, the discount is automatically applied to renewal invoices
|
|
- Coupon stays associated with the order forever
|
|
- Example: "VIP50" gives 50% off forever for that specific server
|
|
|
|
### Game Filtering
|
|
|
|
#### All Games
|
|
- Coupon applies to any game server in the cart
|
|
- All cart items receive the discount
|
|
|
|
#### Specific Games
|
|
- Coupon checks each cart item's `home_name` field
|
|
- Only matching games receive the discount
|
|
- Uses partial string matching (e.g., "arma3" matches "arma3_linux64")
|
|
- Non-matching games show original price
|
|
|
|
Example:
|
|
```
|
|
Cart contains:
|
|
1. Arma 3 Server → ARMA25 coupon applies (25% off)
|
|
2. Minecraft Server → ARMA25 doesn't apply (full price)
|
|
3. Arma Reforger → ARMA25 applies (25% off)
|
|
|
|
Total discount = 25% off Arma servers only
|
|
```
|
|
|
|
## Technical Implementation
|
|
|
|
### Session Storage
|
|
|
|
Coupons are stored in `$_SESSION['applied_coupon']` when applied:
|
|
```php
|
|
$_SESSION['applied_coupon'] = [
|
|
'coupon_id' => 1,
|
|
'code' => 'ARMA25',
|
|
'discount_percent' => 25.00,
|
|
'usage_type' => 'permanent',
|
|
'game_filter_type' => 'specific_games',
|
|
'game_filter_list' => '["arma3_linux64","arma2_win32"]',
|
|
// ... other fields
|
|
];
|
|
```
|
|
|
|
### Cart Calculation
|
|
|
|
In `cart.php`, the `couponAppliesTo()` function checks if a coupon applies to a specific game:
|
|
|
|
```php
|
|
function couponAppliesTo($coupon, $game_name) {
|
|
if (!$coupon || $coupon['game_filter_type'] === 'all_games') {
|
|
return true;
|
|
}
|
|
|
|
if ($coupon['game_filter_type'] === 'specific_games') {
|
|
$allowed_games = json_decode($coupon['game_filter_list'], true);
|
|
foreach ($allowed_games as $allowed_game) {
|
|
if (stripos($game_name, $allowed_game) !== false) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
```
|
|
|
|
Discount calculation:
|
|
```php
|
|
$rowtotal = $row['amount'] * $row['qty'] * $row['max_players'];
|
|
|
|
if ($applied_coupon && couponAppliesTo($applied_coupon, $row['home_name'])) {
|
|
$discountPercent = floatval($applied_coupon['discount_percent']);
|
|
$itemDiscount = ($rowtotal * $discountPercent) / 100;
|
|
$rowtotal = $rowtotal - $itemDiscount;
|
|
}
|
|
```
|
|
|
|
### Payment Processing
|
|
|
|
In `api/capture_order.php`, when PayPal payment completes:
|
|
|
|
1. Coupon info is retrieved from session
|
|
2. Invoices are updated with `coupon_id`
|
|
3. Coupon usage count is incremented
|
|
4. For one-time coupons, cleared from session
|
|
5. For permanent coupons, stored in order record
|
|
|
|
```php
|
|
// Update invoice with coupon
|
|
UPDATE ogp_billing_invoices
|
|
SET status='paid', coupon_id=?, discount_amount=?
|
|
WHERE user_id=? AND status='due'
|
|
|
|
// Increment usage count
|
|
UPDATE ogp_billing_coupons
|
|
SET current_uses = current_uses + 1
|
|
WHERE coupon_id = ?
|
|
|
|
// For permanent coupons, store in order
|
|
INSERT INTO ogp_billing_orders (
|
|
..., coupon_id, discount_amount
|
|
) VALUES (
|
|
..., ?, ?
|
|
)
|
|
```
|
|
|
|
## Display
|
|
|
|
### Cart Page
|
|
- Shows applied coupon with code and percentage
|
|
- Displays success/error messages
|
|
- Updates prices in real-time
|
|
|
|
### My Servers Page
|
|
- Shows original price (strikethrough)
|
|
- Shows discounted price (bold)
|
|
- Shows coupon code and percentage (green text)
|
|
|
|
### Admin Invoices Page
|
|
- Same display as My Servers
|
|
- Visible to administrators for all orders
|
|
|
|
## Troubleshooting
|
|
|
|
### Coupon not applying
|
|
- Check if code is typed correctly (case-sensitive)
|
|
- Verify coupon is active in admin panel
|
|
- Check expiration date hasn't passed
|
|
- Verify usage limit hasn't been reached
|
|
- For game-specific coupons, ensure game matches filter
|
|
|
|
### Discount not showing after payment
|
|
- Check `discount_amount` column exists in both tables
|
|
- Verify coupon_id was saved to invoice/order
|
|
- Clear browser cache and refresh page
|
|
|
|
### Permanent coupon not applying to renewals
|
|
- Verify `usage_type` is set to "permanent"
|
|
- Check order record has `coupon_id` populated
|
|
- Ensure renewal invoice creation copies coupon from order
|
|
|
|
## Security Considerations
|
|
|
|
1. **Code uniqueness**: System enforces unique coupon codes
|
|
2. **Usage tracking**: Prevents abuse by tracking total uses
|
|
3. **Expiration**: Automatic validation prevents expired coupon use
|
|
4. **Admin-only creation**: Only admins can create/edit coupons
|
|
5. **SQL injection protection**: All inputs are sanitized with `mysqli_real_escape_string()`
|
|
6. **CSRF protection**: Admin forms include CSRF tokens
|
|
|
|
## Future Enhancements
|
|
|
|
Potential features for future development:
|
|
- Minimum purchase amount requirements
|
|
- First-time customer restrictions
|
|
- User-specific coupons (assign to individual users)
|
|
- Combination rules (allow/prevent stacking)
|
|
- Auto-generated unique codes for campaigns
|
|
- Email notification when coupon is used
|
|
- Analytics dashboard for coupon performance
|
|
- Referral system integration
|
|
|
|
## Support
|
|
|
|
For issues or questions:
|
|
1. Check the troubleshooting section above
|
|
2. Review error logs in `/modules/billing/logs/`
|
|
3. Verify database schema matches documentation
|
|
4. Contact system administrator
|
|
|
|
---
|
|
|
|
**Last Updated**: 2025-10-29
|
|
**Version**: 1.0
|
|
**Module**: Billing/Coupons
|