mirror of
https://github.com/myronblair/tomsjavajive
synced 2026-06-30 17:50:32 -05:00
Fix loyalty system: load tiers from DB, award points on payment
- LoyaltyProgram now loads tiers from loyalty_tiers DB table in constructor with fallback to hardcoded defaults if table is empty - awardPoints() accepts order_id param with duplicate-prevention check so points cannot be double-awarded for the same order - Inserts balance_after into loyalty_transactions for accurate history - payment-status.php: award points after Stripe checkout session or PaymentIntent confirmed as paid - create-checkout-session.php: award points in demo mode payment path Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
require_once __DIR__ . '/../includes/stripe.php';
|
||||
require_once __DIR__ . '/../includes/loyalty.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
@@ -53,6 +54,15 @@ if (!isStripeConfigured()) {
|
||||
['id' => $orderId]
|
||||
);
|
||||
|
||||
if (!empty($order['customer_id'])) {
|
||||
loyalty()->awardPoints(
|
||||
$order['customer_id'],
|
||||
(float) $order['total'],
|
||||
'Order #' . $order['order_number'],
|
||||
$orderId
|
||||
);
|
||||
}
|
||||
|
||||
jsonResponse([
|
||||
'demo_mode' => true,
|
||||
'message' => 'Payment simulated (Stripe not configured)',
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
require_once __DIR__ . '/../includes/stripe.php';
|
||||
require_once __DIR__ . '/../includes/loyalty.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
@@ -76,6 +77,16 @@ try {
|
||||
['id' => $order['order_id']]
|
||||
);
|
||||
|
||||
// Award loyalty points
|
||||
if (!empty($order['customer_id'])) {
|
||||
loyalty()->awardPoints(
|
||||
$order['customer_id'],
|
||||
(float) $order['total'],
|
||||
'Order #' . $order['order_number'],
|
||||
$order['order_id']
|
||||
);
|
||||
}
|
||||
|
||||
jsonResponse([
|
||||
'status' => 'complete',
|
||||
'payment_status' => 'paid',
|
||||
@@ -105,6 +116,16 @@ try {
|
||||
['id' => $order['order_id']]
|
||||
);
|
||||
|
||||
// Award loyalty points
|
||||
if (!empty($order['customer_id'])) {
|
||||
loyalty()->awardPoints(
|
||||
$order['customer_id'],
|
||||
(float) $order['total'],
|
||||
'Order #' . $order['order_number'],
|
||||
$order['order_id']
|
||||
);
|
||||
}
|
||||
|
||||
jsonResponse([
|
||||
'status' => 'complete',
|
||||
'payment_status' => 'paid',
|
||||
|
||||
+56
-65
@@ -7,63 +7,36 @@
|
||||
|
||||
class LoyaltyProgram {
|
||||
|
||||
// Loyalty tier definitions
|
||||
private array $tiers = [
|
||||
'bronze' => [
|
||||
'name' => 'Bronze Bean',
|
||||
'min_points' => 0,
|
||||
'multiplier' => 1.0,
|
||||
'benefits' => [
|
||||
'Earn 1 point per $1 spent',
|
||||
'Birthday reward',
|
||||
'Member-only offers'
|
||||
],
|
||||
'color' => '#CD7F32',
|
||||
'icon' => 'fa-coffee'
|
||||
],
|
||||
'silver' => [
|
||||
'name' => 'Silver Roast',
|
||||
'min_points' => 500,
|
||||
'multiplier' => 1.25,
|
||||
'benefits' => [
|
||||
'Earn 1.25 points per $1 spent',
|
||||
'Free shipping on orders $25+',
|
||||
'Early access to new products',
|
||||
'Double points weekends'
|
||||
],
|
||||
'color' => '#C0C0C0',
|
||||
'icon' => 'fa-mug-hot'
|
||||
],
|
||||
'gold' => [
|
||||
'name' => 'Gold Blend',
|
||||
'min_points' => 1500,
|
||||
'multiplier' => 1.5,
|
||||
'benefits' => [
|
||||
'Earn 1.5 points per $1 spent',
|
||||
'Free shipping on all orders',
|
||||
'Exclusive Gold-only products',
|
||||
'Priority customer support',
|
||||
'Quarterly free coffee sample'
|
||||
],
|
||||
'color' => '#FFD700',
|
||||
'icon' => 'fa-crown'
|
||||
],
|
||||
'platinum' => [
|
||||
'name' => 'Platinum Reserve',
|
||||
'min_points' => 5000,
|
||||
'multiplier' => 2.0,
|
||||
'benefits' => [
|
||||
'Earn 2 points per $1 spent',
|
||||
'Free express shipping',
|
||||
'VIP early access to everything',
|
||||
'Annual free bag of premium coffee',
|
||||
'Dedicated account manager',
|
||||
'Exclusive tasting events'
|
||||
],
|
||||
'color' => '#E5E4E2',
|
||||
'icon' => 'fa-gem'
|
||||
]
|
||||
];
|
||||
private array $tiers = [];
|
||||
|
||||
public function __construct() {
|
||||
$this->loadTiers();
|
||||
}
|
||||
|
||||
private function loadTiers(): void {
|
||||
$rows = db()->fetchAll("SELECT * FROM loyalty_tiers ORDER BY min_points ASC");
|
||||
foreach ($rows as $row) {
|
||||
$key = strtolower($row['name']);
|
||||
$benefits = json_decode($row['benefits'] ?? '[]', true) ?? [];
|
||||
$this->tiers[$key] = [
|
||||
'name' => $row['name'],
|
||||
'min_points' => (int) $row['min_points'],
|
||||
'multiplier' => (float) $row['multiplier'],
|
||||
'benefits' => $benefits,
|
||||
'color' => $row['color'] ?? '#888',
|
||||
'icon' => 'fa-coffee',
|
||||
];
|
||||
}
|
||||
// Fallback if DB is empty
|
||||
if (empty($this->tiers)) {
|
||||
$this->tiers = [
|
||||
'bronze' => ['name'=>'Bronze', 'min_points'=>0, 'multiplier'=>1.0, 'benefits'=>[], 'color'=>'#CD7F32', 'icon'=>'fa-coffee'],
|
||||
'silver' => ['name'=>'Silver', 'min_points'=>500, 'multiplier'=>1.5, 'benefits'=>[], 'color'=>'#C0C0C0', 'icon'=>'fa-mug-hot'],
|
||||
'gold' => ['name'=>'Gold', 'min_points'=>1500, 'multiplier'=>2.0, 'benefits'=>[], 'color'=>'#FFD700', 'icon'=>'fa-crown'],
|
||||
'platinum' => ['name'=>'Platinum', 'min_points'=>3000, 'multiplier'=>3.0, 'benefits'=>[], 'color'=>'#E5E4E2', 'icon'=>'fa-gem'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Points redemption rates
|
||||
private float $pointsToValue = 0.01; // 1 point = $0.01 (100 points = $1)
|
||||
@@ -153,13 +126,24 @@ class LoyaltyProgram {
|
||||
/**
|
||||
* Award points for a purchase
|
||||
*/
|
||||
public function awardPoints(string $customerId, float $amount, string $description = 'Purchase'): array {
|
||||
public function awardPoints(string $customerId, float $amount, string $description = 'Purchase', string $orderId = ''): array {
|
||||
// Prevent duplicate awards for the same order
|
||||
if ($orderId) {
|
||||
$already = db()->fetch(
|
||||
"SELECT id FROM loyalty_transactions WHERE order_id = :oid AND type = 'earn' LIMIT 1",
|
||||
['oid' => $orderId]
|
||||
);
|
||||
if ($already) {
|
||||
return ['points_earned' => 0, 'already_awarded' => true];
|
||||
}
|
||||
}
|
||||
|
||||
$customerTier = $this->getCustomerTier($customerId);
|
||||
$multiplier = $customerTier['info']['multiplier'];
|
||||
|
||||
// Calculate points (base: 1 point per dollar)
|
||||
$basePoints = floor($amount);
|
||||
$bonusPoints = floor($basePoints * ($multiplier - 1));
|
||||
$basePoints = (int) floor($amount);
|
||||
$bonusPoints = (int) floor($basePoints * ($multiplier - 1));
|
||||
$totalPoints = $basePoints + $bonusPoints;
|
||||
|
||||
// Update customer points
|
||||
@@ -172,14 +156,21 @@ class LoyaltyProgram {
|
||||
['points' => $totalPoints, 'id' => $customerId]
|
||||
);
|
||||
|
||||
$newBalance = db()->fetch(
|
||||
"SELECT reward_points FROM customers WHERE customer_id = :id",
|
||||
['id' => $customerId]
|
||||
)['reward_points'] ?? $totalPoints;
|
||||
|
||||
// Log the transaction
|
||||
db()->insert('loyalty_transactions', [
|
||||
'transaction_id' => generateId('lt_'),
|
||||
'customer_id' => $customerId,
|
||||
'points' => $totalPoints,
|
||||
'type' => 'earn',
|
||||
'description' => $description . ($bonusPoints > 0 ? " (+{$bonusPoints} bonus)" : ''),
|
||||
'reference_amount' => $amount
|
||||
'customer_id' => $customerId,
|
||||
'points' => $totalPoints,
|
||||
'balance_after' => $newBalance,
|
||||
'type' => 'earn',
|
||||
'description' => $description . ($bonusPoints > 0 ? " (+{$bonusPoints} bonus)" : ''),
|
||||
'reference_amount' => $amount,
|
||||
'order_id' => $orderId ?: null,
|
||||
]);
|
||||
|
||||
// Check for tier upgrade
|
||||
|
||||
Reference in New Issue
Block a user