Initial commit

This commit is contained in:
2026-05-22 12:52:44 +00:00
commit 996ca0d621
122 changed files with 22749 additions and 0 deletions
+121
View File
@@ -0,0 +1,121 @@
<?php
/**
* Tom's Java Jive - Cart API
*/
require_once __DIR__ . '/../includes/functions.php';
header('Content-Type: application/json');
$input = json_decode(file_get_contents('php://input'), true);
$action = $input['action'] ?? $_POST['action'] ?? '';
switch ($action) {
case 'add':
$productId = $input['product_id'] ?? '';
$quantity = intval($input['quantity'] ?? 1);
if (!$productId) {
jsonResponse(['error' => 'Product ID required'], 400);
}
// Verify product exists and is active
$product = db()->fetch(
"SELECT product_id, stock FROM products WHERE product_id = :id AND is_active = 1",
['id' => $productId]
);
if (!$product) {
jsonResponse(['error' => 'Product not found'], 404);
}
if ($product['stock'] < $quantity) {
jsonResponse(['error' => 'Not enough stock'], 400);
}
addToCart($productId, $quantity);
jsonResponse([
'success' => true,
'cart_count' => getCartCount(),
'message' => 'Item added to cart'
]);
break;
case 'update':
$productId = $input['product_id'] ?? '';
$quantity = intval($input['quantity'] ?? 0);
if (!$productId) {
jsonResponse(['error' => 'Product ID required'], 400);
}
updateCartItem($productId, $quantity);
jsonResponse([
'success' => true,
'cart_count' => getCartCount(),
'subtotal' => getCartTotal()
]);
break;
case 'remove':
$productId = $input['product_id'] ?? '';
if (!$productId) {
jsonResponse(['error' => 'Product ID required'], 400);
}
removeFromCart($productId);
jsonResponse([
'success' => true,
'cart_count' => getCartCount(),
'subtotal' => getCartTotal()
]);
break;
case 'clear':
clearCart();
jsonResponse(['success' => true, 'cart_count' => 0]);
break;
case 'get':
$cart = getCart();
$items = [];
$subtotal = 0;
foreach ($cart as $productId => $quantity) {
$product = db()->fetch(
"SELECT product_id, name, price, sale_price, stock, images FROM products WHERE product_id = :id",
['id' => $productId]
);
if ($product) {
$images = json_decode($product['images'] ?? '[]', true);
$unitPrice = $product['sale_price'] ?? $product['price'];
$total = $unitPrice * $quantity;
$subtotal += $total;
$items[] = [
'product_id' => $product['product_id'],
'name' => $product['name'],
'price' => $unitPrice,
'quantity' => $quantity,
'total' => $total,
'image' => !empty($images) ? $images[0] : null,
'stock' => $product['stock']
];
}
}
jsonResponse([
'items' => $items,
'count' => getCartCount(),
'subtotal' => $subtotal
]);
break;
default:
jsonResponse(['error' => 'Invalid action'], 400);
}
+119
View File
@@ -0,0 +1,119 @@
<?php
/**
* Tom's Java Jive - Create Stripe Checkout Session API
* Uses hosted checkout page (redirects to Stripe)
*/
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../includes/stripe.php';
header('Content-Type: application/json');
// Only accept POST
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
jsonResponse(['error' => 'Method not allowed'], 405);
}
$input = json_decode(file_get_contents('php://input'), true);
$orderId = $input['order_id'] ?? '';
$originUrl = $input['origin_url'] ?? '';
if (empty($orderId)) {
jsonResponse(['error' => 'Order ID required'], 400);
}
if (empty($originUrl)) {
$originUrl = SITE_URL;
}
// Get order
$order = db()->fetch(
"SELECT * FROM orders WHERE order_id = :id",
['id' => $orderId]
);
if (!$order) {
jsonResponse(['error' => 'Order not found'], 404);
}
if ($order['payment_status'] === 'paid') {
jsonResponse(['error' => 'Order already paid'], 400);
}
// Check if Stripe is configured
if (!isStripeConfigured()) {
// Demo mode - simulate successful payment
db()->update('orders',
[
'payment_status' => 'paid',
'order_status' => 'confirmed',
'stripe_payment_intent' => 'demo_' . bin2hex(random_bytes(8))
],
'order_id = :id',
['id' => $orderId]
);
jsonResponse([
'demo_mode' => true,
'message' => 'Payment simulated (Stripe not configured)',
'redirect' => '/order-confirmation.php?order=' . $orderId
]);
}
// Build line items from order
$items = json_decode($order['items'], true) ?? [];
$lineItems = [];
foreach ($items as $item) {
$lineItems[] = [
'name' => $item['name'],
'price' => floatval($item['price']),
'quantity' => intval($item['quantity']),
'currency' => 'usd'
];
}
// Add shipping if applicable
if ($order['shipping_cost'] > 0) {
$lineItems[] = [
'name' => 'Shipping',
'price' => floatval($order['shipping_cost']),
'quantity' => 1,
'currency' => 'usd'
];
}
// Build success/cancel URLs
$successUrl = rtrim($originUrl, '/') . '/order-confirmation.php?order=' . $orderId . '&session_id={CHECKOUT_SESSION_ID}';
$cancelUrl = rtrim($originUrl, '/') . '/payment.php?order=' . $orderId . '&cancelled=1';
try {
$session = stripe()->createCheckoutSession(
$lineItems,
$successUrl,
$cancelUrl,
[
'customer_email' => $order['customer_email'],
'metadata' => [
'order_id' => $orderId,
'order_number' => $order['order_number']
]
]
);
// Store checkout session ID
db()->update('orders',
['stripe_checkout_session' => $session['id']],
'order_id = :id',
['id' => $orderId]
);
jsonResponse([
'url' => $session['url'],
'session_id' => $session['id']
]);
} catch (Exception $e) {
error_log('Stripe Checkout error: ' . $e->getMessage());
jsonResponse(['error' => 'Failed to create checkout session: ' . $e->getMessage()], 500);
}
+87
View File
@@ -0,0 +1,87 @@
<?php
/**
* Tom's Java Jive - Create Stripe Payment Intent API
* Uses cURL-based Stripe integration (no Composer required)
*/
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../includes/stripe.php';
header('Content-Type: application/json');
// Only accept POST
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
jsonResponse(['error' => 'Method not allowed'], 405);
}
$input = json_decode(file_get_contents('php://input'), true);
$orderId = $input['order_id'] ?? '';
if (empty($orderId)) {
jsonResponse(['error' => 'Order ID required'], 400);
}
// Get order
$order = db()->fetch(
"SELECT * FROM orders WHERE order_id = :id",
['id' => $orderId]
);
if (!$order) {
jsonResponse(['error' => 'Order not found'], 404);
}
if ($order['payment_status'] === 'paid') {
jsonResponse(['error' => 'Order already paid'], 400);
}
// Check if Stripe is configured
if (!isStripeConfigured()) {
// Demo mode - simulate successful payment
db()->update('orders',
[
'payment_status' => 'paid',
'order_status' => 'confirmed',
'stripe_payment_intent' => 'demo_' . bin2hex(random_bytes(8))
],
'order_id = :id',
['id' => $orderId]
);
jsonResponse([
'demo_mode' => true,
'message' => 'Payment simulated (Stripe not configured)',
'redirect' => '/order-confirmation.php?order=' . $orderId
]);
}
// Create Stripe Payment Intent using cURL-based API
try {
$paymentIntent = stripe()->createPaymentIntent(
$order['total'],
'usd',
[
'metadata' => [
'order_id' => $orderId,
'order_number' => $order['order_number']
],
'receipt_email' => $order['customer_email'],
'description' => 'Order #' . $order['order_number']
]
);
// Store payment intent ID
db()->update('orders',
['stripe_payment_intent' => $paymentIntent['id']],
'order_id = :id',
['id' => $orderId]
);
jsonResponse([
'client_secret' => $paymentIntent['client_secret']
]);
} catch (Exception $e) {
error_log('Stripe error: ' . $e->getMessage());
jsonResponse(['error' => 'Payment initialization failed: ' . $e->getMessage()], 500);
}
+53
View File
@@ -0,0 +1,53 @@
<?php
/**
* Tom's Java Jive - Delete Account API
*/
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../includes/auth.php';
if (!CustomerAuth::isLoggedIn()) {
redirect('/login.php');
}
$customer = CustomerAuth::getFullUser();
try {
// Start transaction
db()->query("START TRANSACTION");
// Delete wallet transactions
db()->query("DELETE FROM wallet_transactions WHERE customer_id = :id", ['id' => $customer['customer_id']]);
// Delete reviews
db()->query("DELETE FROM reviews WHERE customer_id = :id", ['id' => $customer['customer_id']]);
// Delete wishlist
db()->query("DELETE FROM wishlist WHERE customer_id = :id", ['id' => $customer['customer_id']]);
// Anonymize orders (keep for records but remove personal info)
db()->query(
"UPDATE orders SET customer_name = 'Deleted User', customer_email = 'deleted@example.com',
shipping_address = NULL, billing_address = NULL WHERE customer_id = :id",
['id' => $customer['customer_id']]
);
// Remove from email subscribers
db()->query("DELETE FROM email_subscribers WHERE email = :email", ['email' => $customer['email']]);
// Delete customer
db()->query("DELETE FROM customers WHERE customer_id = :id", ['id' => $customer['customer_id']]);
db()->query("COMMIT");
// Logout
CustomerAuth::logout();
setFlash('success', 'Your account has been deleted. We\'re sorry to see you go!');
redirect('/');
} catch (Exception $e) {
db()->query("ROLLBACK");
setFlash('error', 'Failed to delete account. Please contact support.');
redirect('/account/profile.php');
}
+94
View File
@@ -0,0 +1,94 @@
<?php
/**
* Tom's Java Jive - Loyalty Points API
*/
header('Content-Type: application/json');
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/loyalty.php';
if (!CustomerAuth::isLoggedIn()) {
jsonResponse(['error' => 'Authentication required'], 401);
}
$customer = CustomerAuth::getUser();
$method = $_SERVER['REQUEST_METHOD'];
$input = json_decode(file_get_contents('php://input'), true);
$action = $input['action'] ?? $_GET['action'] ?? '';
switch ($action) {
case 'status':
// Get customer's loyalty status
$status = loyalty()->getCustomerTier($customer['customer_id']);
$conversion = loyalty()->getConversionInfo();
jsonResponse([
'tier' => $status['tier'],
'tier_name' => $status['info']['name'],
'tier_color' => $status['info']['color'],
'tier_icon' => $status['info']['icon'],
'benefits' => $status['info']['benefits'],
'multiplier' => $status['info']['multiplier'],
'points' => $status['points'],
'lifetime_points' => $status['lifetime_points'],
'points_value' => $status['points'] * $conversion['points_value'],
'next_tier' => $status['next_tier'],
'next_tier_name' => $status['next_tier_info']['name'] ?? null,
'points_to_next' => $status['points_to_next'],
'progress_percent' => $status['progress_percent'],
'conversion' => $conversion
]);
break;
case 'history':
// Get loyalty transaction history
$limit = min(50, intval($_GET['limit'] ?? 20));
$history = loyalty()->getHistory($customer['customer_id'], $limit);
jsonResponse(['transactions' => $history]);
break;
case 'redeem':
// Redeem points for credit
if ($method !== 'POST') {
jsonResponse(['error' => 'POST required'], 405);
}
$points = intval($input['points'] ?? 0);
if ($points < 100) {
jsonResponse(['error' => 'Minimum 100 points required for redemption'], 400);
}
$result = loyalty()->redeemPoints($customer['customer_id'], $points);
if ($result['success']) {
jsonResponse([
'success' => true,
'points_redeemed' => $result['points_redeemed'],
'credit_value' => $result['credit_value'],
'new_points_balance' => $result['new_points_balance'],
'new_wallet_balance' => $result['new_wallet_balance'],
'message' => 'Successfully redeemed ' . $points . ' points for ' . formatCurrency($result['credit_value'])
]);
} else {
jsonResponse(['error' => $result['error']], 400);
}
break;
case 'tiers':
// Get all tier information
$tiers = loyalty()->getTiers();
$conversion = loyalty()->getConversionInfo();
jsonResponse([
'tiers' => $tiers,
'conversion' => $conversion
]);
break;
default:
jsonResponse(['error' => 'Invalid action'], 400);
}
+174
View File
@@ -0,0 +1,174 @@
<?php
/**
* Tom's Java Jive - Orders API
*/
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../includes/auth.php';
header('Content-Type: application/json');
$method = $_SERVER['REQUEST_METHOD'];
$input = json_decode(file_get_contents('php://input'), true);
$action = $input['action'] ?? $_POST['action'] ?? '';
switch ($method) {
case 'GET':
// Get order(s)
$orderId = $_GET['id'] ?? '';
$orderNumber = $_GET['number'] ?? '';
if ($orderId) {
$order = db()->fetch(
"SELECT * FROM orders WHERE order_id = :id",
['id' => $orderId]
);
if (!$order) {
jsonResponse(['error' => 'Order not found'], 404);
}
$order['items'] = json_decode($order['items'], true);
$order['shipping_address'] = json_decode($order['shipping_address'], true);
unset($order['id']);
jsonResponse($order);
} elseif ($orderNumber) {
$email = $_GET['email'] ?? '';
$order = db()->fetch(
"SELECT * FROM orders WHERE order_number = :num AND customer_email = :email",
['num' => $orderNumber, 'email' => strtolower($email)]
);
if (!$order) {
jsonResponse(['error' => 'Order not found'], 404);
}
$order['items'] = json_decode($order['items'], true);
$order['shipping_address'] = json_decode($order['shipping_address'], true);
unset($order['id']);
jsonResponse($order);
} else {
// List orders (admin only or customer's own)
$customer = CustomerAuth::getUser();
if ($customer) {
$orders = db()->fetchAll(
"SELECT order_id, order_number, total, payment_status, order_status, created_at
FROM orders WHERE customer_id = :cid ORDER BY created_at DESC LIMIT 50",
['cid' => $customer['customer_id']]
);
} else {
jsonResponse(['error' => 'Authentication required'], 401);
}
jsonResponse(['orders' => $orders]);
}
break;
case 'POST':
// Update order status (admin)
if ($action === 'update_status') {
// Admin check would go here
$orderId = $input['order_id'] ?? '';
$status = $input['status'] ?? '';
$trackingNumber = $input['tracking_number'] ?? null;
if (empty($orderId) || empty($status)) {
jsonResponse(['error' => 'Order ID and status required'], 400);
}
$validStatuses = ['pending', 'confirmed', 'processing', 'shipped', 'delivered', 'cancelled', 'refunded'];
if (!in_array($status, $validStatuses)) {
jsonResponse(['error' => 'Invalid status'], 400);
}
$updateData = ['order_status' => $status];
if ($trackingNumber) {
$updateData['tracking_number'] = $trackingNumber;
}
db()->update('orders', $updateData, 'order_id = :id', ['id' => $orderId]);
// If status is shipped or delivered, send email
$order = db()->fetch("SELECT * FROM orders WHERE order_id = :id", ['id' => $orderId]);
if ($order && in_array($status, ['shipped', 'delivered'])) {
sendStatusUpdateEmail($order, $status, $trackingNumber);
}
jsonResponse(['success' => true, 'status' => $status]);
}
// Cancel order
if ($action === 'cancel') {
$orderId = $input['order_id'] ?? '';
$customer = CustomerAuth::getUser();
if (!$customer) {
jsonResponse(['error' => 'Authentication required'], 401);
}
$order = db()->fetch(
"SELECT * FROM orders WHERE order_id = :id AND customer_id = :cid",
['id' => $orderId, 'cid' => $customer['customer_id']]
);
if (!$order) {
jsonResponse(['error' => 'Order not found'], 404);
}
if (!in_array($order['order_status'], ['pending', 'confirmed'])) {
jsonResponse(['error' => 'This order cannot be cancelled'], 400);
}
db()->update('orders',
['order_status' => 'cancelled'],
'order_id = :id',
['id' => $orderId]
);
// Restore stock
$items = json_decode($order['items'], true) ?? [];
foreach ($items as $item) {
db()->query(
"UPDATE products SET stock = stock + :qty WHERE product_id = :id",
['qty' => $item['quantity'], 'id' => $item['product_id']]
);
}
jsonResponse(['success' => true]);
}
jsonResponse(['error' => 'Invalid action'], 400);
break;
default:
jsonResponse(['error' => 'Method not allowed'], 405);
}
function sendStatusUpdateEmail($order, $status, $trackingNumber = null) {
$statusMessages = [
'shipped' => 'Your order has been shipped!',
'delivered' => 'Your order has been delivered!'
];
$tracking = $trackingNumber ? "<p><strong>Tracking #:</strong> {$trackingNumber}</p>" : '';
$html = <<<HTML
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<div style="background: #8B4513; color: white; padding: 20px; text-align: center;">
<h1 style="margin: 0;">Tom's Java Jive</h1>
</div>
<div style="padding: 30px; background: #FDFBF7;">
<h2>{$statusMessages[$status]}</h2>
<p>Hi {$order['customer_name']},</p>
<p>Order <strong>#{$order['order_number']}</strong> has been updated to: <strong>{$status}</strong></p>
{$tracking}
</div>
</div>
HTML;
sendEmail($order['customer_email'], "Order Update - #{$order['order_number']}", $html);
}
+136
View File
@@ -0,0 +1,136 @@
<?php
/**
* Tom's Java Jive - Check Payment Status API
* Polls Stripe for payment/checkout session status
*/
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../includes/stripe.php';
header('Content-Type: application/json');
// Only accept GET
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
jsonResponse(['error' => 'Method not allowed'], 405);
}
$orderId = $_GET['order_id'] ?? '';
$sessionId = $_GET['session_id'] ?? '';
if (empty($orderId) && empty($sessionId)) {
jsonResponse(['error' => 'Order ID or Session ID required'], 400);
}
// Get order by ID or session
if (!empty($orderId)) {
$order = db()->fetch(
"SELECT * FROM orders WHERE order_id = :id",
['id' => $orderId]
);
} else {
$order = db()->fetch(
"SELECT * FROM orders WHERE stripe_checkout_session = :session OR stripe_payment_intent = :session",
['session' => $sessionId]
);
}
if (!$order) {
jsonResponse(['error' => 'Order not found'], 404);
}
// If already marked as paid, return success
if ($order['payment_status'] === 'paid') {
jsonResponse([
'status' => 'complete',
'payment_status' => 'paid',
'order_id' => $order['order_id'],
'order_number' => $order['order_number'],
'redirect' => '/order-confirmation.php?order=' . $order['order_id']
]);
}
// Check if Stripe is configured
if (!isStripeConfigured()) {
jsonResponse([
'status' => 'demo_mode',
'payment_status' => $order['payment_status'],
'message' => 'Stripe not configured - running in demo mode'
]);
}
try {
// Check with Stripe
if (!empty($order['stripe_checkout_session'])) {
// Check checkout session status
$session = stripe()->getCheckoutSession($order['stripe_checkout_session']);
if ($session['payment_status'] === 'paid') {
// Update order
db()->update('orders',
[
'payment_status' => 'paid',
'order_status' => 'confirmed',
'stripe_payment_intent' => $session['payment_intent'] ?? null
],
'order_id = :id',
['id' => $order['order_id']]
);
jsonResponse([
'status' => 'complete',
'payment_status' => 'paid',
'order_id' => $order['order_id'],
'order_number' => $order['order_number'],
'redirect' => '/order-confirmation.php?order=' . $order['order_id']
]);
}
jsonResponse([
'status' => $session['status'],
'payment_status' => $session['payment_status']
]);
} elseif (!empty($order['stripe_payment_intent'])) {
// Check payment intent status
$paymentIntent = stripe()->getPaymentIntent($order['stripe_payment_intent']);
if ($paymentIntent['status'] === 'succeeded') {
// Update order
db()->update('orders',
[
'payment_status' => 'paid',
'order_status' => 'confirmed'
],
'order_id = :id',
['id' => $order['order_id']]
);
jsonResponse([
'status' => 'complete',
'payment_status' => 'paid',
'order_id' => $order['order_id'],
'order_number' => $order['order_number'],
'redirect' => '/order-confirmation.php?order=' . $order['order_id']
]);
}
jsonResponse([
'status' => $paymentIntent['status'],
'payment_status' => 'pending'
]);
}
// No Stripe reference found
jsonResponse([
'status' => 'pending',
'payment_status' => $order['payment_status']
]);
} catch (Exception $e) {
error_log('Payment status check error: ' . $e->getMessage());
jsonResponse([
'status' => 'error',
'payment_status' => $order['payment_status'],
'error' => 'Failed to check payment status'
]);
}
+191
View File
@@ -0,0 +1,191 @@
<?php
/**
* Tom's Java Jive - POS Order API
* Creates orders from the POS system
*/
header('Content-Type: application/json');
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../includes/auth.php';
// Only accept POST
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
jsonResponse(['error' => 'Method not allowed'], 405);
}
$input = json_decode(file_get_contents('php://input'), true);
if (empty($input['items']) || !is_array($input['items'])) {
jsonResponse(['error' => 'No items provided'], 400);
}
$items = $input['items'];
$paymentMethod = $input['payment_method'] ?? 'cash';
$notes = $input['notes'] ?? '';
$customerId = $input['customer_id'] ?? null;
$customerEmail = $input['customer_email'] ?? null;
$discountAmount = floatval($input['discount'] ?? 0);
$couponCode = $input['coupon_code'] ?? null;
// Calculate totals
$subtotal = 0;
$orderItems = [];
foreach ($items as $item) {
// Verify product exists and has stock
$product = db()->fetch(
"SELECT product_id, name, price, sale_price, stock FROM products WHERE product_id = :id AND is_active = 1",
['id' => $item['product_id']]
);
if (!$product) {
jsonResponse(['error' => 'Product not found: ' . $item['name']], 400);
}
if ($product['stock'] < $item['quantity']) {
jsonResponse(['error' => 'Insufficient stock for: ' . $product['name']], 400);
}
$price = $product['sale_price'] ?? $product['price'];
$lineTotal = $price * $item['quantity'];
$subtotal += $lineTotal;
$orderItems[] = [
'product_id' => $product['product_id'],
'name' => $product['name'],
'price' => $price,
'quantity' => $item['quantity'],
'total' => $lineTotal
];
}
// Apply coupon if provided
$couponDiscount = 0;
if ($couponCode) {
$coupon = db()->fetch(
"SELECT * FROM coupons WHERE code = :code AND is_active = 1
AND (starts_at IS NULL OR starts_at <= NOW())
AND (expires_at IS NULL OR expires_at > NOW())
AND (max_uses IS NULL OR times_used < max_uses)",
['code' => strtoupper($couponCode)]
);
if ($coupon) {
if ($coupon['min_order_amount'] && $subtotal < $coupon['min_order_amount']) {
// Coupon minimum not met, ignore
} else {
if ($coupon['discount_type'] === 'percentage') {
$couponDiscount = $subtotal * ($coupon['discount_value'] / 100);
} else {
$couponDiscount = min($coupon['discount_value'], $subtotal);
}
// Update coupon usage
db()->query("UPDATE coupons SET times_used = times_used + 1 WHERE coupon_id = :id",
['id' => $coupon['coupon_id']]);
}
}
}
// Calculate final total
$discount = $discountAmount + $couponDiscount;
$taxRate = 0; // Adjust based on settings
$tax = ($subtotal - $discount) * $taxRate;
$total = $subtotal - $discount + $tax;
// Handle wallet payment
$walletUsed = 0;
if ($paymentMethod === 'wallet' && $customerId) {
$customer = db()->fetch(
"SELECT wallet_balance FROM customers WHERE customer_id = :id",
['id' => $customerId]
);
if (!$customer || $customer['wallet_balance'] < $total) {
jsonResponse(['error' => 'Insufficient wallet balance'], 400);
}
$walletUsed = $total;
// Deduct from wallet
db()->query(
"UPDATE customers SET wallet_balance = wallet_balance - :amount WHERE customer_id = :id",
['amount' => $walletUsed, 'id' => $customerId]
);
// Log wallet transaction
$newBalance = $customer['wallet_balance'] - $walletUsed;
db()->insert('wallet_transactions', [
'transaction_id' => generateId('wt_'),
'customer_id' => $customerId,
'amount' => -$walletUsed,
'balance_after' => $newBalance,
'type' => 'purchase',
'description' => 'POS Purchase'
]);
}
// Generate order
$orderId = generateId('ord_');
$orderNumber = generateOrderNumber();
try {
// Create order
db()->insert('orders', [
'order_id' => $orderId,
'order_number' => $orderNumber,
'customer_id' => $customerId,
'customer_email' => $customerEmail ?? 'pos@store.local',
'customer_name' => $input['customer_name'] ?? 'POS Customer',
'items' => json_encode($orderItems),
'subtotal' => $subtotal,
'tax' => $tax,
'discount' => $discount,
'wallet_amount_used' => $walletUsed,
'total' => $total,
'payment_method' => $paymentMethod,
'payment_status' => 'paid',
'order_status' => 'confirmed',
'notes' => $notes,
'is_pos_order' => 1
]);
// Insert order items
foreach ($orderItems as $item) {
db()->insert('order_items', [
'order_id' => $orderId,
'product_id' => $item['product_id'],
'name' => $item['name'],
'price' => $item['price'],
'quantity' => $item['quantity'],
'total' => $item['total']
]);
// Update stock
db()->query(
"UPDATE products SET stock = stock - :qty WHERE product_id = :id",
['qty' => $item['quantity'], 'id' => $item['product_id']]
);
}
// Award reward points if customer
if ($customerId) {
$pointsEarned = floor($total); // 1 point per dollar
db()->query(
"UPDATE customers SET reward_points = reward_points + :points WHERE customer_id = :id",
['points' => $pointsEarned, 'id' => $customerId]
);
}
jsonResponse([
'success' => true,
'order_id' => $orderId,
'order_number' => $orderNumber,
'total' => $total,
'items' => $orderItems
]);
} catch (Exception $e) {
jsonResponse(['error' => 'Failed to create order: ' . $e->getMessage()], 500);
}
+93
View File
@@ -0,0 +1,93 @@
<?php
/**
* Tom's Java Jive - Products API
*/
require_once __DIR__ . '/../includes/functions.php';
header('Content-Type: application/json');
$method = $_SERVER['REQUEST_METHOD'];
if ($method === 'GET') {
$productId = $_GET['id'] ?? null;
if ($productId) {
// Get single product
$product = db()->fetch(
"SELECT * FROM products WHERE product_id = :id AND is_active = 1",
['id' => $productId]
);
if (!$product) {
jsonResponse(['error' => 'Product not found'], 404);
}
$product['images'] = json_decode($product['images'] ?? '[]', true);
$product['tags'] = json_decode($product['tags'] ?? '[]', true);
unset($product['id']);
// Get reviews
$reviews = db()->fetchAll(
"SELECT review_id, customer_name, rating, title, comment, is_verified_purchase, created_at
FROM reviews WHERE product_id = :id AND is_approved = 1 ORDER BY created_at DESC",
['id' => $productId]
);
$product['reviews'] = $reviews;
$product['average_rating'] = !empty($reviews)
? round(array_sum(array_column($reviews, 'rating')) / count($reviews), 1)
: 0;
jsonResponse($product);
} else {
// Get products list
$category = $_GET['category'] ?? '';
$search = $_GET['search'] ?? '';
$featured = $_GET['featured'] ?? '';
$limit = min(100, intval($_GET['limit'] ?? 20));
$offset = intval($_GET['offset'] ?? 0);
$where = ['is_active = 1'];
$params = [];
if ($category) {
$where[] = 'category = :category';
$params['category'] = $category;
}
if ($search) {
$where[] = '(name LIKE :search OR description LIKE :search)';
$params['search'] = '%' . $search . '%';
}
if ($featured === '1') {
$where[] = 'is_featured = 1';
}
$whereClause = implode(' AND ', $where);
$products = db()->fetchAll(
"SELECT product_id, name, description, price, sale_price, category, images, stock, is_featured
FROM products WHERE {$whereClause}
ORDER BY is_featured DESC, created_at DESC
LIMIT :limit OFFSET :offset",
array_merge($params, ['limit' => $limit, 'offset' => $offset])
);
foreach ($products as &$p) {
$p['images'] = json_decode($p['images'] ?? '[]', true);
}
$total = db()->count('products', $whereClause, $params);
jsonResponse([
'products' => $products,
'total' => $total,
'limit' => $limit,
'offset' => $offset
]);
}
}
jsonResponse(['error' => 'Method not allowed'], 405);
+83
View File
@@ -0,0 +1,83 @@
<?php
/**
* Tom's Java Jive - Push Subscription API
*/
header('Content-Type: application/json');
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../includes/auth.php';
$method = $_SERVER['REQUEST_METHOD'];
$input = json_decode(file_get_contents('php://input'), true);
switch ($method) {
case 'POST':
// Subscribe to push notifications
$endpoint = $input['endpoint'] ?? '';
$p256dh = $input['keys']['p256dh'] ?? '';
$auth = $input['keys']['auth'] ?? '';
if (empty($endpoint) || empty($p256dh) || empty($auth)) {
jsonResponse(['error' => 'Invalid subscription data'], 400);
}
$customerId = null;
if (CustomerAuth::isLoggedIn()) {
$customerId = CustomerAuth::getUser()['customer_id'];
}
// Check if subscription already exists
$existing = db()->fetch(
"SELECT id FROM push_subscriptions WHERE endpoint = :endpoint",
['endpoint' => $endpoint]
);
if ($existing) {
// Update existing
db()->query(
"UPDATE push_subscriptions SET
customer_id = :cid, p256dh_key = :p256dh, auth_key = :auth,
is_active = 1, updated_at = NOW()
WHERE endpoint = :endpoint",
['cid' => $customerId, 'p256dh' => $p256dh, 'auth' => $auth, 'endpoint' => $endpoint]
);
} else {
// Create new
db()->insert('push_subscriptions', [
'customer_id' => $customerId,
'endpoint' => $endpoint,
'p256dh_key' => $p256dh,
'auth_key' => $auth,
'is_active' => 1
]);
}
jsonResponse(['success' => true, 'message' => 'Subscribed to notifications']);
break;
case 'DELETE':
// Unsubscribe
$endpoint = $input['endpoint'] ?? '';
if (empty($endpoint)) {
jsonResponse(['error' => 'Endpoint required'], 400);
}
db()->query(
"UPDATE push_subscriptions SET is_active = 0 WHERE endpoint = :endpoint",
['endpoint' => $endpoint]
);
jsonResponse(['success' => true, 'message' => 'Unsubscribed from notifications']);
break;
case 'GET':
// Get VAPID public key
require_once __DIR__ . '/../includes/push.php';
jsonResponse(['publicKey' => pushNotify()->getPublicKey()]);
break;
default:
jsonResponse(['error' => 'Method not allowed'], 405);
}
+97
View File
@@ -0,0 +1,97 @@
<?php
/**
* Tom's Java Jive - Redeem Gift Card API
*/
header('Content-Type: application/json');
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../includes/auth.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
jsonResponse(['error' => 'Method not allowed'], 405);
}
if (!CustomerAuth::isLoggedIn()) {
jsonResponse(['error' => 'Please log in to redeem a gift card'], 401);
}
$customer = CustomerAuth::getFullUser();
$input = json_decode(file_get_contents('php://input'), true);
$code = strtoupper(str_replace(['-', ' '], '', trim($input['code'] ?? '')));
if (empty($code) || strlen($code) < 8) {
jsonResponse(['error' => 'Invalid gift card code'], 400);
}
// Find gift card
$giftCard = db()->fetch(
"SELECT * FROM gift_cards WHERE code = :code AND is_active = 1",
['code' => $code]
);
if (!$giftCard) {
jsonResponse(['error' => 'Gift card not found or already used'], 404);
}
if ($giftCard['balance'] <= 0) {
jsonResponse(['error' => 'This gift card has no remaining balance'], 400);
}
if ($giftCard['expires_at'] && strtotime($giftCard['expires_at']) < time()) {
jsonResponse(['error' => 'This gift card has expired'], 400);
}
$amount = $giftCard['balance'];
try {
// Start transaction
db()->query("START TRANSACTION");
// Update gift card balance to 0
db()->query(
"UPDATE gift_cards SET balance = 0, is_active = 0, updated_at = NOW() WHERE gift_card_id = :id",
['id' => $giftCard['gift_card_id']]
);
// Log gift card transaction
db()->insert('gift_card_transactions', [
'gift_card_id' => $giftCard['gift_card_id'],
'amount' => -$amount,
'balance_after' => 0,
'type' => 'redeem',
'description' => 'Redeemed by customer: ' . $customer['email']
]);
// Add to customer wallet
$newWalletBalance = ($customer['wallet_balance'] ?? 0) + $amount;
db()->query(
"UPDATE customers SET wallet_balance = :balance, updated_at = NOW() WHERE customer_id = :id",
['balance' => $newWalletBalance, 'id' => $customer['customer_id']]
);
// Log wallet transaction
db()->insert('wallet_transactions', [
'transaction_id' => generateId('wt_'),
'customer_id' => $customer['customer_id'],
'amount' => $amount,
'balance_after' => $newWalletBalance,
'type' => 'gift_card',
'description' => 'Gift card redeemed: ' . $code
]);
db()->query("COMMIT");
jsonResponse([
'success' => true,
'amount' => $amount,
'new_balance' => $newWalletBalance,
'message' => formatCurrency($amount) . ' has been added to your wallet!'
]);
} catch (Exception $e) {
db()->query("ROLLBACK");
jsonResponse(['error' => 'Failed to redeem gift card. Please try again.'], 500);
}
+25
View File
@@ -0,0 +1,25 @@
<?php
/**
* Tom's Java Jive - Customer Search API
*/
header('Content-Type: application/json');
require_once __DIR__ . '/../includes/functions.php';
$query = $_GET['q'] ?? '';
if (strlen($query) < 2) {
jsonResponse([]);
}
$customers = db()->fetchAll(
"SELECT customer_id, email, name, phone, wallet_balance, reward_points
FROM customers
WHERE (email LIKE :q OR name LIKE :q OR phone LIKE :q) AND is_active = 1
ORDER BY name ASC
LIMIT 20",
['q' => '%' . $query . '%']
);
jsonResponse($customers);
+66
View File
@@ -0,0 +1,66 @@
<?php
/**
* Tom's Java Jive - Submit Review API
*/
header('Content-Type: application/json');
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../includes/auth.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
jsonResponse(['error' => 'Method not allowed'], 405);
}
if (!CustomerAuth::isLoggedIn()) {
jsonResponse(['error' => 'Please log in to submit a review'], 401);
}
$customer = CustomerAuth::getFullUser();
$input = json_decode(file_get_contents('php://input'), true);
$productId = $input['product_id'] ?? '';
$rating = intval($input['rating'] ?? 0);
$title = trim($input['title'] ?? '');
$content = trim($input['content'] ?? '');
if (empty($productId) || $rating < 1 || $rating > 5 || empty($content)) {
jsonResponse(['error' => 'Invalid input. Rating and review content are required.'], 400);
}
// Check if product exists
$product = db()->fetch("SELECT product_id FROM products WHERE product_id = :id", ['id' => $productId]);
if (!$product) {
jsonResponse(['error' => 'Product not found'], 404);
}
// Check if already reviewed
$existingReview = db()->fetch(
"SELECT review_id FROM reviews WHERE customer_id = :cid AND product_id = :pid",
['cid' => $customer['customer_id'], 'pid' => $productId]
);
if ($existingReview) {
jsonResponse(['error' => 'You have already reviewed this product'], 400);
}
// Create review
$reviewId = generateId('rev_');
db()->insert('reviews', [
'review_id' => $reviewId,
'product_id' => $productId,
'customer_id' => $customer['customer_id'],
'customer_name' => $customer['name'] ?? explode('@', $customer['email'])[0],
'customer_email' => $customer['email'],
'rating' => $rating,
'title' => $title,
'content' => $content,
'status' => 'pending' // Reviews require admin approval
]);
jsonResponse([
'success' => true,
'message' => 'Review submitted successfully. It will be visible after approval.',
'review_id' => $reviewId
]);
+75
View File
@@ -0,0 +1,75 @@
<?php
/**
* Tom's Java Jive - Newsletter Subscribe API
*/
require_once __DIR__ . '/../includes/functions.php';
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
jsonResponse(['error' => 'Method not allowed'], 405);
}
$input = json_decode(file_get_contents('php://input'), true);
$email = trim($input['email'] ?? $_POST['email'] ?? '');
if (empty($email)) {
jsonResponse(['error' => 'Email is required'], 400);
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
jsonResponse(['error' => 'Please enter a valid email address'], 400);
}
// Check if already subscribed
$existing = db()->fetch(
"SELECT id FROM email_subscribers WHERE email = :email",
['email' => strtolower($email)]
);
if ($existing) {
jsonResponse(['error' => 'This email is already subscribed'], 400);
}
// Add subscriber
try {
db()->insert('email_subscribers', [
'email' => strtolower($email),
'source' => 'website',
'is_active' => 1
]);
// Send welcome email
$html = <<<HTML
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<div style="background: #8B4513; color: white; padding: 20px; text-align: center;">
<h1 style="margin: 0;">Tom's Java Jive</h1>
</div>
<div style="padding: 30px; background: #FDFBF7;">
<h2>Welcome to the Java Jive Family!</h2>
<p>Thanks for subscribing to our newsletter. You'll be the first to know about:</p>
<ul>
<li>New coffee releases</li>
<li>Exclusive discounts and promotions</li>
<li>Brewing tips and recipes</li>
<li>Behind-the-scenes at our roastery</li>
</ul>
<p>As a thank you, enjoy <strong>10% off</strong> your first order with code: <strong>WELCOME10</strong></p>
<p style="text-align: center; margin-top: 20px;">
<a href="https://tomsjavajive.com/shop.php" style="background: #E86A33; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px;">Shop Now</a>
</p>
</div>
<div style="padding: 20px; text-align: center; color: #666; font-size: 12px;">
<p>Tom's Java Jive | Premium Coffee</p>
</div>
</div>
HTML;
sendEmail($email, 'Welcome to Tom\'s Java Jive!', $html);
jsonResponse(['success' => true, 'message' => 'Successfully subscribed!']);
} catch (Exception $e) {
jsonResponse(['error' => 'Subscription failed. Please try again.'], 500);
}
+65
View File
@@ -0,0 +1,65 @@
<?php
/**
* Tom's Java Jive - Test Notification API
*/
header('Content-Type: application/json');
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../includes/email.php';
require_once __DIR__ . '/../includes/sms.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
jsonResponse(['error' => 'Method not allowed'], 405);
}
$input = json_decode(file_get_contents('php://input'), true);
$type = $input['type'] ?? '';
$recipient = $input['recipient'] ?? '';
if (empty($recipient)) {
jsonResponse(['error' => 'Recipient is required'], 400);
}
switch ($type) {
case 'email':
if (!filter_var($recipient, FILTER_VALIDATE_EMAIL)) {
jsonResponse(['error' => 'Invalid email address'], 400);
}
$result = sendEmail()->send(
$recipient,
"Test Email from Tom's Java Jive",
"<div style='font-family: Arial, sans-serif; max-width: 500px; margin: 0 auto; padding: 20px;'>
<h2 style='color: #FF5E1A;'>Test Email</h2>
<p>This is a test email from your Tom's Java Jive store.</p>
<p>If you received this, your SendGrid integration is working correctly!</p>
<p style='color: #666; font-size: 0.9em; margin-top: 30px;'>
Sent at: " . date('Y-m-d H:i:s') . "
</p>
</div>"
);
if ($result['success']) {
jsonResponse(['success' => true, 'message' => 'Test email sent to ' . $recipient]);
} else {
jsonResponse(['success' => false, 'error' => $result['error'] ?? 'Failed to send email']);
}
break;
case 'sms':
$result = sendSMS()->send(
$recipient,
"Tom's Java Jive: This is a test message. If you received this, your Twilio integration is working! Sent at " . date('g:i A')
);
if ($result['success']) {
jsonResponse(['success' => true, 'message' => 'Test SMS sent to ' . $recipient]);
} else {
jsonResponse(['success' => false, 'error' => $result['error'] ?? 'Failed to send SMS']);
}
break;
default:
jsonResponse(['error' => 'Invalid notification type'], 400);
}
+59
View File
@@ -0,0 +1,59 @@
<?php
/**
* Tom's Java Jive - Coupon Validation API
*/
header('Content-Type: application/json');
require_once __DIR__ . '/../includes/functions.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
jsonResponse(['error' => 'Method not allowed'], 405);
}
$input = json_decode(file_get_contents('php://input'), true);
$code = strtoupper(trim($input['code'] ?? ''));
$subtotal = floatval($input['subtotal'] ?? 0);
if (empty($code)) {
jsonResponse(['error' => 'Coupon code required'], 400);
}
$coupon = db()->fetch(
"SELECT * FROM coupons WHERE code = :code AND is_active = 1",
['code' => $code]
);
if (!$coupon) {
jsonResponse(['error' => 'Invalid coupon code']);
}
// Check if expired
if ($coupon['expires_at'] && strtotime($coupon['expires_at']) < time()) {
jsonResponse(['error' => 'Coupon has expired']);
}
// Check if not started yet
if ($coupon['starts_at'] && strtotime($coupon['starts_at']) > time()) {
jsonResponse(['error' => 'Coupon is not yet active']);
}
// Check usage limit
if ($coupon['max_uses'] && $coupon['times_used'] >= $coupon['max_uses']) {
jsonResponse(['error' => 'Coupon usage limit reached']);
}
// Check minimum order
if ($coupon['min_order_amount'] && $subtotal < $coupon['min_order_amount']) {
jsonResponse(['error' => 'Minimum order of ' . formatCurrency($coupon['min_order_amount']) . ' required']);
}
jsonResponse([
'valid' => true,
'code' => $coupon['code'],
'type' => $coupon['discount_type'],
'value' => floatval($coupon['discount_value']),
'description' => $coupon['discount_type'] === 'percentage'
? $coupon['discount_value'] . '% off'
: formatCurrency($coupon['discount_value']) . ' off'
]);
+147
View File
@@ -0,0 +1,147 @@
<?php
/**
* Tom's Java Jive - Stripe Webhook Handler
* Uses cURL-based Stripe integration (no Composer required)
*/
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../includes/stripe.php';
header('Content-Type: application/json');
$payload = file_get_contents('php://input');
$sigHeader = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? '';
// Verify webhook signature (if secret is configured)
if (!empty(STRIPE_WEBHOOK_SECRET) && STRIPE_WEBHOOK_SECRET !== 'whsec_your_webhook_secret') {
try {
stripe()->verifyWebhookSignature($payload, $sigHeader, STRIPE_WEBHOOK_SECRET);
$event = json_decode($payload, true);
} catch (Exception $e) {
error_log('Stripe webhook signature verification failed: ' . $e->getMessage());
http_response_code(400);
exit();
}
} else {
$event = json_decode($payload, true);
if (!$event) {
http_response_code(400);
exit();
}
}
$eventType = $event['type'] ?? '';
$data = $event['data']['object'] ?? [];
switch ($eventType) {
case 'payment_intent.succeeded':
$paymentIntentId = $data['id'] ?? '';
$orderId = $data['metadata']['order_id'] ?? '';
if ($orderId) {
db()->update('orders',
[
'payment_status' => 'paid',
'order_status' => 'confirmed'
],
'order_id = :id',
['id' => $orderId]
);
// Send confirmation email
$order = db()->fetch("SELECT * FROM orders WHERE order_id = :id", ['id' => $orderId]);
if ($order) {
sendOrderConfirmationEmail($order);
}
}
break;
case 'payment_intent.payment_failed':
$orderId = $data['metadata']['order_id'] ?? '';
if ($orderId) {
db()->update('orders',
['payment_status' => 'failed'],
'order_id = :id',
['id' => $orderId]
);
}
break;
case 'charge.refunded':
$paymentIntentId = $data['payment_intent'] ?? '';
if ($paymentIntentId) {
db()->update('orders',
[
'payment_status' => 'refunded',
'order_status' => 'refunded'
],
'stripe_payment_intent = :pi',
['pi' => $paymentIntentId]
);
}
break;
}
http_response_code(200);
echo json_encode(['received' => true]);
/**
* Send order confirmation email
*/
function sendOrderConfirmationEmail($order) {
$items = json_decode($order['items'], true) ?? [];
$shippingAddress = json_decode($order['shipping_address'], true) ?? [];
$itemsHtml = '';
foreach ($items as $item) {
$itemsHtml .= sprintf(
'<tr><td>%s x%d</td><td style="text-align:right;">$%.2f</td></tr>',
htmlspecialchars($item['name']),
$item['quantity'],
$item['total']
);
}
$html = <<<HTML
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<div style="background: #8B4513; color: white; padding: 20px; text-align: center;">
<h1 style="margin: 0;">Tom's Java Jive</h1>
</div>
<div style="padding: 30px; background: #FDFBF7;">
<h2>Order Confirmed!</h2>
<p>Thank you for your order, {$order['customer_name']}!</p>
<div style="background: white; padding: 20px; border-radius: 8px; margin: 20px 0;">
<p><strong>Order #:</strong> {$order['order_number']}</p>
<p><strong>Total:</strong> \${$order['total']}</p>
</div>
<h3>Order Details</h3>
<table style="width: 100%; border-collapse: collapse;">
{$itemsHtml}
<tr style="border-top: 2px solid #ccc;">
<td><strong>Total</strong></td>
<td style="text-align:right;"><strong>\${$order['total']}</strong></td>
</tr>
</table>
<h3>Shipping To</h3>
<p>
{$shippingAddress['address']}<br>
{$shippingAddress['city']}, {$shippingAddress['state']} {$shippingAddress['zip']}
</p>
<p style="color: #666; font-size: 14px;">
We'll send you tracking information once your order ships.
</p>
</div>
<div style="padding: 20px; text-align: center; color: #666; font-size: 12px;">
<p>Tom's Java Jive | Premium Coffee</p>
</div>
</div>
HTML;
sendEmail($order['customer_email'], "Order Confirmed - #{$order['order_number']}", $html);
}
+95
View File
@@ -0,0 +1,95 @@
<?php
/**
* Tom's Java Jive - Wishlist API
*/
header('Content-Type: application/json');
require_once __DIR__ . '/../includes/functions.php';
require_once __DIR__ . '/../includes/auth.php';
if (!CustomerAuth::isLoggedIn()) {
jsonResponse(['error' => 'Please log in to manage your wishlist'], 401);
}
$customer = CustomerAuth::getFullUser();
$input = json_decode(file_get_contents('php://input'), true);
$action = $input['action'] ?? $_GET['action'] ?? '';
$productId = $input['product_id'] ?? $_GET['product_id'] ?? '';
switch ($action) {
case 'add':
if (empty($productId)) {
jsonResponse(['error' => 'Product ID required'], 400);
}
// Check if product exists
$product = db()->fetch("SELECT product_id FROM products WHERE product_id = :id", ['id' => $productId]);
if (!$product) {
jsonResponse(['error' => 'Product not found'], 404);
}
// Check if already in wishlist
$existing = db()->fetch(
"SELECT id FROM wishlist WHERE customer_id = :cid AND product_id = :pid",
['cid' => $customer['customer_id'], 'pid' => $productId]
);
if ($existing) {
jsonResponse(['success' => true, 'message' => 'Already in wishlist']);
}
db()->insert('wishlist', [
'customer_id' => $customer['customer_id'],
'product_id' => $productId
]);
jsonResponse(['success' => true, 'message' => 'Added to wishlist']);
break;
case 'remove':
if (empty($productId)) {
jsonResponse(['error' => 'Product ID required'], 400);
}
db()->query(
"DELETE FROM wishlist WHERE customer_id = :cid AND product_id = :pid",
['cid' => $customer['customer_id'], 'pid' => $productId]
);
jsonResponse(['success' => true, 'message' => 'Removed from wishlist']);
break;
case 'check':
if (empty($productId)) {
jsonResponse(['error' => 'Product ID required'], 400);
}
$exists = db()->fetch(
"SELECT id FROM wishlist WHERE customer_id = :cid AND product_id = :pid",
['cid' => $customer['customer_id'], 'pid' => $productId]
);
jsonResponse(['in_wishlist' => (bool)$exists]);
break;
case 'list':
$items = db()->fetchAll(
"SELECT p.product_id, p.name, p.slug, p.price, p.sale_price, p.images, p.stock
FROM wishlist w
JOIN products p ON w.product_id = p.product_id
WHERE w.customer_id = :id
ORDER BY w.created_at DESC",
['id' => $customer['customer_id']]
);
foreach ($items as &$item) {
$item['images'] = json_decode($item['images'] ?? '[]', true);
}
jsonResponse(['items' => $items]);
break;
default:
jsonResponse(['error' => 'Invalid action'], 400);
}