mirror of
https://github.com/myronblair/tomsjavajive-app
synced 2026-06-30 17:50:56 -05:00
v1.0.0 - Initial backup
This commit is contained in:
+121
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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'
|
||||
]);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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
|
||||
]);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user