16235369cb
Highlights include configurable menus, specials, cart/checkout, taxes, customer accounts with email 2FA, newsletters, analytics, refunds, PDF/CSV exports, Square/Mailgun adapters, and optional MySQL migration. See README.md for setup and production configuration. Verified PHP/JavaScript syntax, responsive layouts, registration, 2FA, checkout, dashboard workflows, settings persistence, and PDF generation. Real Square, Mailgun, and MySQL connections require credentials/server access. Square implementation follows the official Payments API and Refunds API.
106 lines
3.8 KiB
PHP
106 lines
3.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace FatBottom\Services;
|
|
|
|
use FatBottom\Core\Database;
|
|
use RuntimeException;
|
|
|
|
final class OrderService
|
|
{
|
|
public function __construct(
|
|
private readonly Database $db,
|
|
private readonly PaymentGateway $payments
|
|
) {
|
|
}
|
|
|
|
public function totals(array $cart): array
|
|
{
|
|
$subtotal = (int) $cart['subtotal_cents'];
|
|
$taxRate = (int) $this->db->fetch(
|
|
'SELECT COALESCE(SUM(rate_basis_points), 0) AS total_rate FROM tax_rates WHERE active = 1'
|
|
)['total_rate'];
|
|
$tax = (int) round($subtotal * $taxRate / 10000);
|
|
return [
|
|
'subtotal_cents' => $subtotal,
|
|
'tax_rate_basis_points' => $taxRate,
|
|
'tax_cents' => $tax,
|
|
'total_cents' => $subtotal + $tax,
|
|
];
|
|
}
|
|
|
|
public function place(array $cart, array $customer, ?int $userId, string $sourceId): array
|
|
{
|
|
if (empty($cart['items'])) {
|
|
throw new RuntimeException('Your cart is empty.');
|
|
}
|
|
|
|
$totals = $this->totals($cart);
|
|
$orderNumber = 'FBG-' . date('ymd') . '-'
|
|
. strtoupper(substr(hash('sha256', (string) $cart['token']), 0, 6));
|
|
$payment = $this->payments->charge($sourceId, $totals['total_cents'], $orderNumber);
|
|
|
|
$pdo = $this->db->pdo();
|
|
$pdo->beginTransaction();
|
|
try {
|
|
$statement = $pdo->prepare(
|
|
'INSERT INTO orders
|
|
(order_number, user_id, cart_id, customer_name, customer_email, customer_phone,
|
|
order_type, status, payment_status, payment_provider, payment_reference,
|
|
subtotal_cents, tax_cents, total_cents, special_instructions, pickup_time)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
|
|
);
|
|
$statement->execute([
|
|
$orderNumber,
|
|
$userId,
|
|
(int) $cart['id'],
|
|
$customer['customer_name'],
|
|
$customer['customer_email'],
|
|
$customer['customer_phone'],
|
|
'pickup',
|
|
'paid',
|
|
'paid',
|
|
$payment['provider'],
|
|
$payment['reference'],
|
|
$totals['subtotal_cents'],
|
|
$totals['tax_cents'],
|
|
$totals['total_cents'],
|
|
$customer['special_instructions'],
|
|
$customer['pickup_time'] ?: null,
|
|
]);
|
|
$orderId = (int) $pdo->lastInsertId();
|
|
|
|
$itemStatement = $pdo->prepare(
|
|
'INSERT INTO order_items
|
|
(order_id, menu_item_id, item_name, quantity, unit_price_cents, line_total_cents, notes)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)'
|
|
);
|
|
foreach ($cart['items'] as $item) {
|
|
$itemStatement->execute([
|
|
$orderId,
|
|
(int) $item['menu_item_id'],
|
|
$item['name'],
|
|
(int) $item['quantity'],
|
|
(int) $item['unit_price_cents'],
|
|
(int) $item['quantity'] * (int) $item['unit_price_cents'],
|
|
$item['notes'],
|
|
]);
|
|
}
|
|
|
|
$pdo->prepare(
|
|
'INSERT INTO order_events (order_id, user_id, event_type, details) VALUES (?, ?, ?, ?)'
|
|
)->execute([$orderId, $userId, 'order_placed', 'Online pickup order paid successfully.']);
|
|
$pdo->prepare(
|
|
"UPDATE carts SET status = 'converted', converted_at = CURRENT_TIMESTAMP WHERE id = ?"
|
|
)->execute([(int) $cart['id']]);
|
|
$pdo->commit();
|
|
} catch (\Throwable $error) {
|
|
$pdo->rollBack();
|
|
throw $error;
|
|
}
|
|
|
|
return $this->db->fetch('SELECT * FROM orders WHERE id = ?', [$orderId]) ?? [];
|
|
}
|
|
}
|