mirror of
https://github.com/myronblair/do-server-config
synced 2026-07-28 05:22:53 -05:00
[orbis] Weekly backup 2026-07-07 — 318 files changed, 48597 insertions(+), 7 deletions(-)
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* Tom's Java Jive - Apply Wallet Credit / Gift Card at Checkout
|
||||
*
|
||||
* Validates and quotes an amount to apply toward the current order total.
|
||||
* Does NOT deduct wallet balance here - that only happens once the Square
|
||||
* payment actually succeeds (see markSquarePaymentResult() in
|
||||
* includes/square.php), so an abandoned checkout never loses a customer's
|
||||
* real wallet money. A gift card code, if given, is redeemed into wallet
|
||||
* balance immediately (same as the Wallet page does) since that step is
|
||||
* safe/reversible-in-spirit - only the *spending* of wallet balance at
|
||||
* checkout is deferred to payment success.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
require_once __DIR__ . '/../includes/loyalty.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
jsonResponse(['error' => 'Method not allowed'], 405);
|
||||
}
|
||||
|
||||
if (!CustomerAuth::isLoggedIn()) {
|
||||
jsonResponse(['error' => 'Please log in to use wallet balance or a gift card'], 401);
|
||||
}
|
||||
|
||||
$customer = CustomerAuth::getFullUser();
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
$orderTotal = (float) ($input['order_total'] ?? 0);
|
||||
if ($orderTotal <= 0) {
|
||||
jsonResponse(['error' => 'Invalid order total'], 400);
|
||||
}
|
||||
|
||||
$giftCardCode = trim($input['gift_card_code'] ?? '');
|
||||
|
||||
if (!empty($giftCardCode)) {
|
||||
$redeemResult = loyalty()->redeemGiftCardToWallet($customer['customer_id'], $giftCardCode);
|
||||
if (!$redeemResult['success']) {
|
||||
jsonResponse(['error' => $redeemResult['error']], 400);
|
||||
}
|
||||
$walletBalance = $redeemResult['new_balance'];
|
||||
} else {
|
||||
$fresh = db()->fetch("SELECT wallet_balance FROM customers WHERE customer_id = :id", ['id' => $customer['customer_id']]);
|
||||
$walletBalance = (float) ($fresh['wallet_balance'] ?? 0);
|
||||
}
|
||||
|
||||
$requested = isset($input['amount']) ? (float) $input['amount'] : $walletBalance;
|
||||
$applyAmount = max(0, round(min($requested, $walletBalance, $orderTotal), 2));
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'apply_amount' => $applyAmount,
|
||||
'wallet_balance' => $walletBalance,
|
||||
'new_total' => round($orderTotal - $applyAmount, 2)
|
||||
]);
|
||||
@@ -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,94 @@
|
||||
<?php
|
||||
/**
|
||||
* Tom's Java Jive - Create Square Payment API
|
||||
* Accepts a Web Payments SDK card nonce (source_id) and charges it synchronously.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
require_once __DIR__ . '/../includes/square.php';
|
||||
require_once __DIR__ . '/../includes/loyalty.php';
|
||||
require_once __DIR__ . '/../includes/email.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);
|
||||
$orderId = $input['order_id'] ?? '';
|
||||
$sourceId = $input['source_id'] ?? '';
|
||||
|
||||
if (empty($orderId)) {
|
||||
jsonResponse(['error' => 'Order ID required'], 400);
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
// Demo mode - Square not configured
|
||||
if (!isSquareConfigured()) {
|
||||
db()->update('orders',
|
||||
[
|
||||
'payment_status' => 'paid',
|
||||
'order_status' => 'confirmed',
|
||||
'square_payment_id' => 'demo_' . bin2hex(random_bytes(8)),
|
||||
'payment_method' => 'square',
|
||||
],
|
||||
'order_id = :id',
|
||||
['id' => $orderId]
|
||||
);
|
||||
|
||||
jsonResponse([
|
||||
'demo_mode' => true,
|
||||
'message' => 'Payment simulated (Square not configured)',
|
||||
'redirect' => '/order-confirmation.php?order=' . $orderId
|
||||
]);
|
||||
}
|
||||
|
||||
if (empty($sourceId)) {
|
||||
jsonResponse(['error' => 'Card details required'], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = squareCreatePayment(
|
||||
$sourceId,
|
||||
(float) $order['total'],
|
||||
$orderId,
|
||||
['note' => 'Order #' . $order['order_number']]
|
||||
);
|
||||
$payment = $result['payment'] ?? [];
|
||||
$status = $payment['status'] ?? '';
|
||||
|
||||
if ($status === 'COMPLETED') {
|
||||
markSquarePaymentResult($payment['id'], $orderId, 'COMPLETED');
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'redirect' => '/order-confirmation.php?order=' . $orderId
|
||||
]);
|
||||
}
|
||||
|
||||
// Store the payment id even if not yet completed, so the reconciliation
|
||||
// poll and webhook can find this order by square_payment_id.
|
||||
db()->update('orders',
|
||||
['square_payment_id' => $payment['id'] ?? null],
|
||||
'order_id = :id',
|
||||
['id' => $orderId]
|
||||
);
|
||||
|
||||
jsonResponse(['error' => 'Payment was not completed (status: ' . $status . ')'], 400);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('Square payment error: ' . $e->getMessage());
|
||||
jsonResponse(['error' => 'Payment 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);
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
<?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);
|
||||
}
|
||||
|
||||
// Only the admin or the customer who placed this order may view it by raw id.
|
||||
$customer = CustomerAuth::getUser();
|
||||
$isOwner = $customer && (string)$customer['customer_id'] === (string)$order['customer_id'];
|
||||
if (!AdminAuth::isLoggedIn() && !$isOwner) {
|
||||
jsonResponse(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
$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') {
|
||||
if (!AdminAuth::isLoggedIn()) {
|
||||
jsonResponse(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
$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,93 @@
|
||||
<?php
|
||||
/**
|
||||
* Tom's Java Jive - Check Payment Status API
|
||||
* Reconciliation-only fallback: Square payments complete synchronously in
|
||||
* create-square-payment.php, so this endpoint only matters if the browser
|
||||
* lost that response after Square had already accepted the charge. It shares
|
||||
* markSquarePaymentResult() with the webhook and the synchronous path so
|
||||
* loyalty points/emails are never duplicated no matter which path resolves first.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
require_once __DIR__ . '/../includes/square.php';
|
||||
require_once __DIR__ . '/../includes/loyalty.php';
|
||||
require_once __DIR__ . '/../includes/email.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
|
||||
jsonResponse(['error' => 'Method not allowed'], 405);
|
||||
}
|
||||
|
||||
$orderId = $_GET['order_id'] ?? '';
|
||||
|
||||
if (empty($orderId)) {
|
||||
jsonResponse(['error' => 'Order ID required'], 400);
|
||||
}
|
||||
|
||||
$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([
|
||||
'status' => 'complete',
|
||||
'payment_status' => 'paid',
|
||||
'order_id' => $order['order_id'],
|
||||
'order_number' => $order['order_number'],
|
||||
'redirect' => '/order-confirmation.php?order=' . $order['order_id']
|
||||
]);
|
||||
}
|
||||
|
||||
if (!isSquareConfigured()) {
|
||||
jsonResponse([
|
||||
'status' => 'demo_mode',
|
||||
'payment_status' => $order['payment_status'],
|
||||
'message' => 'Square not configured - running in demo mode'
|
||||
]);
|
||||
}
|
||||
|
||||
if (empty($order['square_payment_id'])) {
|
||||
jsonResponse([
|
||||
'status' => 'pending',
|
||||
'payment_status' => $order['payment_status']
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = squareGetPayment($order['square_payment_id']);
|
||||
$payment = $result['payment'] ?? [];
|
||||
$status = $payment['status'] ?? '';
|
||||
|
||||
markSquarePaymentResult($order['square_payment_id'], $order['order_id'], $status);
|
||||
|
||||
$updated = db()->fetch("SELECT * FROM orders WHERE order_id = :id", ['id' => $order['order_id']]);
|
||||
|
||||
if ($updated['payment_status'] === 'paid') {
|
||||
jsonResponse([
|
||||
'status' => 'complete',
|
||||
'payment_status' => 'paid',
|
||||
'order_id' => $updated['order_id'],
|
||||
'order_number' => $updated['order_number'],
|
||||
'redirect' => '/order-confirmation.php?order=' . $updated['order_id']
|
||||
]);
|
||||
}
|
||||
|
||||
jsonResponse([
|
||||
'status' => $status,
|
||||
'payment_status' => $updated['payment_status']
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('Square 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,34 @@
|
||||
<?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';
|
||||
require_once __DIR__ . '/../includes/loyalty.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);
|
||||
|
||||
$result = loyalty()->redeemGiftCardToWallet($customer['customer_id'], $input['code'] ?? '');
|
||||
|
||||
if (!$result['success']) {
|
||||
jsonResponse(['error' => $result['error']], 400);
|
||||
}
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'amount' => $result['amount'],
|
||||
'new_balance' => $result['new_balance'],
|
||||
'message' => formatCurrency($result['amount']) . ' has been added to your wallet!'
|
||||
]);
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* Tom's Java Jive - Customer Search API
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
|
||||
if (!AdminAuth::isLoggedIn()) {
|
||||
jsonResponse(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
$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 :q1 OR name LIKE :q2 OR phone LIKE :q3) AND is_active = 1
|
||||
ORDER BY name ASC
|
||||
LIMIT 20",
|
||||
['q1' => '%' . $query . '%', 'q2' => '%' . $query . '%', 'q3' => '%' . $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,
|
||||
'comment' => $content,
|
||||
'is_approved' => 0 // 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 = emailService()->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'
|
||||
]);
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* Tom's Java Jive - Square Webhook Handler
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../includes/functions.php';
|
||||
require_once __DIR__ . '/../includes/square.php';
|
||||
require_once __DIR__ . '/../includes/loyalty.php';
|
||||
require_once __DIR__ . '/../includes/email.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$payload = file_get_contents('php://input');
|
||||
$sigHeader = $_SERVER['HTTP_X_SQUARE_HMACSHA256_SIGNATURE'] ?? '';
|
||||
$notificationUrl = SITE_URL . '/api/webhook.php';
|
||||
|
||||
if (empty(SQUARE_WEBHOOK_SIGNATURE_KEY) || SQUARE_WEBHOOK_SIGNATURE_KEY === 'REPLACE_ME') {
|
||||
error_log('Square webhook signature key not configured - rejecting webhook');
|
||||
http_response_code(400);
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!verifySquareWebhookSignature($payload, $sigHeader, $notificationUrl, SQUARE_WEBHOOK_SIGNATURE_KEY)) {
|
||||
error_log('Square webhook signature verification failed');
|
||||
http_response_code(400);
|
||||
exit();
|
||||
}
|
||||
|
||||
$event = json_decode($payload, true);
|
||||
if (!$event) {
|
||||
http_response_code(400);
|
||||
exit();
|
||||
}
|
||||
|
||||
$eventType = $event['type'] ?? '';
|
||||
|
||||
switch ($eventType) {
|
||||
|
||||
case 'payment.updated':
|
||||
case 'payment.created':
|
||||
$payment = $event['data']['object']['payment'] ?? [];
|
||||
$paymentId = $payment['id'] ?? '';
|
||||
$status = $payment['status'] ?? '';
|
||||
$orderId = $payment['reference_id'] ?? null;
|
||||
if ($paymentId && $status) {
|
||||
markSquarePaymentResult($paymentId, $orderId, $status);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'refund.updated':
|
||||
case 'refund.created':
|
||||
$refund = $event['data']['object']['refund'] ?? [];
|
||||
$paymentId = $refund['payment_id'] ?? '';
|
||||
$status = $refund['status'] ?? '';
|
||||
if ($status === 'COMPLETED' && $paymentId) {
|
||||
db()->update('orders',
|
||||
[
|
||||
'payment_status' => 'refunded',
|
||||
'order_status' => 'refunded',
|
||||
],
|
||||
'square_payment_id = :pid',
|
||||
['pid' => $paymentId]
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
http_response_code(200);
|
||||
echo json_encode(['received' => true]);
|
||||
@@ -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