- Implemented the full order lifecycle, secure customer tracker, automatic acceptance emails, configurable status templates, and guest/account CRM management.
Key areas: [OrderStatusService.php](/Users/tyemeclifford/Documents/GH/fatbottomgrille/app/Services/OrderStatusService.php), [AdminController.php](/Users/tyemeclifford/Documents/GH/fatbottomgrille/app/Controllers/AdminController.php), and [Database.php](/Users/tyemeclifford/Documents/GH/fatbottomgrille/app/Core/Database.php). SQLite migration applied successfully. PHP/JS syntax, integration workflows, rendered pages, database integrity, and tracker authorization all passed. Invalid tracker tokens correctly return 403.
This commit is contained in:
@@ -11,8 +11,8 @@ use FatBottom\Core\Http;
|
||||
use FatBottom\Core\Security;
|
||||
use FatBottom\Core\View;
|
||||
use FatBottom\Services\CartService;
|
||||
use FatBottom\Services\Mailer;
|
||||
use FatBottom\Services\OrderService;
|
||||
use FatBottom\Services\OrderStatusService;
|
||||
|
||||
final class StoreController extends BaseController
|
||||
{
|
||||
@@ -22,7 +22,7 @@ final class StoreController extends BaseController
|
||||
CartService $cart,
|
||||
private readonly Database $db,
|
||||
private readonly OrderService $orders,
|
||||
private readonly Mailer $mailer
|
||||
private readonly OrderStatusService $orderStatuses
|
||||
) {
|
||||
parent::__construct($config, $auth, $cart);
|
||||
}
|
||||
@@ -169,13 +169,11 @@ final class StoreController extends BaseController
|
||||
$user ? (int) $user['id'] : null,
|
||||
(string) ($_POST['square_token'] ?? '')
|
||||
);
|
||||
$this->mailer->send(
|
||||
$input['customer_email'],
|
||||
'We received order ' . $order['order_number'],
|
||||
'<p>Thanks, ' . htmlspecialchars($input['customer_name'], ENT_QUOTES, 'UTF-8') . '.</p>'
|
||||
. '<p>Your order <strong>' . htmlspecialchars((string) $order['order_number'], ENT_QUOTES, 'UTF-8')
|
||||
. '</strong> has been paid and sent to the kitchen.</p>'
|
||||
);
|
||||
try {
|
||||
$this->orderStatuses->sendStatusEmail($order, 'pending');
|
||||
} catch (\Throwable) {
|
||||
Http::flash('warning', 'Your order was placed, but the confirmation email could not be sent.');
|
||||
}
|
||||
setcookie('fatbottom_cart', '', ['expires' => time() - 3600, 'path' => '/']);
|
||||
$_SESSION['last_order_id'] = (int) $order['id'];
|
||||
Http::redirect('/order/complete');
|
||||
@@ -198,6 +196,44 @@ final class StoreController extends BaseController
|
||||
'title' => 'Order Received',
|
||||
'order' => $order,
|
||||
'items' => $items,
|
||||
'trackingUrl' => $this->orderStatuses->trackingUrl($order),
|
||||
]));
|
||||
}
|
||||
|
||||
public function orderTracker(): void
|
||||
{
|
||||
$orderNumber = Security::clean((string) ($_GET['order'] ?? ''));
|
||||
$token = (string) ($_GET['token'] ?? '');
|
||||
$order = $this->db->fetch('SELECT * FROM orders WHERE order_number = ?', [$orderNumber]);
|
||||
if ($order === null) {
|
||||
Http::abort(404, 'Order not found.');
|
||||
}
|
||||
|
||||
$user = $this->auth->user();
|
||||
$ownsOrder = $user !== null && (
|
||||
(int) ($order['user_id'] ?? 0) === (int) $user['id']
|
||||
|| strtolower((string) $order['customer_email']) === strtolower((string) $user['email'])
|
||||
);
|
||||
$validToken = $token !== ''
|
||||
&& (string) $order['tracking_token'] !== ''
|
||||
&& hash_equals((string) $order['tracking_token'], $token);
|
||||
if (!$ownsOrder && !$validToken) {
|
||||
Http::abort(403, 'This tracking link is invalid.');
|
||||
}
|
||||
|
||||
View::render('store/order-tracker', $this->shared([
|
||||
'title' => 'Track ' . $order['order_number'],
|
||||
'order' => $order,
|
||||
'events' => $this->db->fetchAll(
|
||||
"SELECT * FROM order_events
|
||||
WHERE order_id = ? AND (status <> '' OR customer_visible = 1)
|
||||
ORDER BY created_at, id",
|
||||
[(int) $order['id']]
|
||||
),
|
||||
'items' => $this->db->fetchAll(
|
||||
'SELECT * FROM order_items WHERE order_id = ? ORDER BY id',
|
||||
[(int) $order['id']]
|
||||
),
|
||||
]));
|
||||
}
|
||||
|
||||
@@ -228,4 +264,3 @@ final class StoreController extends BaseController
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user