WebOrbiton
v1.0.0.1

StocketBase

156 lines · 7.1 KB
  1. <?php
  2. ​
  3. declare(strict_types=1);
  4. ​
  5. if (count(get_included_files()) === 1) {
  6. http_response_code(403);
  7. exit;
  8. }
  9. ​
  10. require_once __DIR__ . '/../includes/currency.php';
  11. ​
  12. if (!Auth::hasRoleAtLeast(Auth::ROLE_STORE_MANAGER)) {
  13. echo '<p>You don\'t have access to orders.</p>';
  14. return;
  15. }
  16. ​
  17. $usersDb = Database::users();
  18. ​
  19. $orderId = (int) ($_GET['id'] ?? 0);
  20. $statement = $usersDb->prepare('SELECT o.*, u.email AS account_email, u.display_name FROM orders o LEFT JOIN user_accounts u ON u.id = o.user_account_id WHERE o.id = :id LIMIT 1');
  21. $statement->execute(['id' => $orderId]);
  22. $order = $statement->fetch();
  23. ​
  24. if (!$order) {
  25. echo '<p>We couldn\'t find this order.</p>';
  26. return;
  27. }
  28. ​
  29. $itemsStatement = $usersDb->prepare('SELECT * FROM order_items WHERE order_id = :id');
  30. $itemsStatement->execute(['id' => $orderId]);
  31. $items = $itemsStatement->fetchAll();
  32. ​
  33. $historyStatement = $usersDb->prepare('SELECT * FROM order_status_history WHERE order_id = :id ORDER BY created_at DESC');
  34. $historyStatement->execute(['id' => $orderId]);
  35. $history = $historyStatement->fetchAll();
  36. ​
  37. $allowedStatuses = ['pending', 'paid', 'processing', 'fulfilled', 'shipped', 'completed', 'canceled', 'refunded', 'failed'];
  38. ​
  39. ?>
  40. <div class="dash-header-row">
  41. <h1 class="dash-title"><?= Icons::icon('checklist', 'icon icon-lg') ?>Order <?= htmlspecialchars($order['order_number']) ?></h1>
  42. <a href="dashboard.php?view=orders" class="dash-btn"><?= Icons::icon('checklist', 'icon icon-sm') ?>Back to orders</a>
  43. </div>
  44. ​
  45. <div class="article-form-grid">
  46. <div class="article-form-main">
  47. ​
  48. <h2 class="dash-subtitle"><?= Icons::icon('grid', 'icon icon-sm') ?>What they ordered</h2>
  49. <table class="dash-table">
  50. <thead><tr><th>Product</th><th>Option</th><th>Quantity</th><th>Price each</th><th>Total</th></tr></thead>
  51. <tbody>
  52. <?php foreach ($items as $item): ?>
  53. <tr>
  54. <td><?= htmlspecialchars($item['product_title_snapshot']) ?></td>
  55. <td><?= htmlspecialchars((string) ($item['variant_name_snapshot'] ?? 'Standard')) ?></td>
  56. <td><?= (int) $item['quantity'] ?></td>
  57. <td><?= htmlspecialchars(Currency::format((int) $item['unit_price_cents'], $order['currency'])) ?></td>
  58. <td><?= htmlspecialchars(Currency::format((int) $item['total_cents'], $order['currency'])) ?></td>
  59. </tr>
  60. <?php endforeach; ?>
  61. </tbody>
  62. </table>
  63. ​
  64. <div class="sidebar-box" style="max-width:360px;margin-top:16px;">
  65. <p>Subtotal: <?= htmlspecialchars(Currency::format((int) $order['subtotal_cents'], $order['currency'])) ?></p>
  66. <?php if ((int) $order['discount_cents'] > 0): ?>
  67. <p>Discount<?= $order['discount_code'] ? ' (' . htmlspecialchars($order['discount_code']) . ')' : '' ?>: -<?= htmlspecialchars(Currency::format((int) $order['discount_cents'], $order['currency'])) ?></p>
  68. <?php endif; ?>
  69. <?php if ((int) $order['shipping_cents'] > 0): ?>
  70. <p>Shipping: <?= htmlspecialchars(Currency::format((int) $order['shipping_cents'], $order['currency'])) ?></p>
  71. <?php endif; ?>
  72. <?php if ((int) $order['tax_cents'] > 0): ?>
  73. <p>Tax: <?= htmlspecialchars(Currency::format((int) $order['tax_cents'], $order['currency'])) ?></p>
  74. <?php endif; ?>
  75. <p><strong>Total: <?= htmlspecialchars(Currency::format((int) $order['total_cents'], $order['currency'])) ?></strong></p>
  76. </div>
  77. ​
  78. <h2 class="dash-subtitle"><?= Icons::icon('map-pin', 'icon icon-sm') ?>Shipping address</h2>
  79. <p>
  80. <?= htmlspecialchars((string) $order['shipping_name']) ?><br>
  81. <?= htmlspecialchars((string) $order['shipping_address_line1']) ?><br>
  82. <?php if (!empty($order['shipping_address_line2'])): ?><?= htmlspecialchars($order['shipping_address_line2']) ?><br><?php endif; ?>
  83. <?= htmlspecialchars((string) $order['shipping_city']) ?>, <?= htmlspecialchars((string) $order['shipping_region']) ?> <?= htmlspecialchars((string) $order['shipping_postal_code']) ?><br>
  84. <?= htmlspecialchars((string) $order['shipping_country']) ?>
  85. </p>
  86. ​
  87. <?php if (!empty($order['customer_note'])): ?>
  88. <h2 class="dash-subtitle"><?= Icons::icon('message', 'icon icon-sm') ?>Customer note</h2>
  89. <p><?= nl2br(htmlspecialchars($order['customer_note'])) ?></p>
  90. <?php endif; ?>
  91. ​
  92. <h2 class="dash-subtitle"><?= Icons::icon('activity', 'icon icon-sm') ?>History</h2>
  93. <ul style="list-style:none;padding:0;margin:0;display:grid;gap:8px;font-size:13px;">
  94. <?php foreach ($history as $entry): ?>
  95. <li><span class="status-pill status-<?= htmlspecialchars($entry['status']) ?>"><?= htmlspecialchars(StatusLabel::get((string) $entry['status'])) ?></span> <?= htmlspecialchars($entry['created_at']) ?><?= $entry['note'] ? ', ' . htmlspecialchars($entry['note']) : '' ?></li>
  96. <?php endforeach; ?>
  97. <?php if (empty($history)): ?>
  98. <li>Nothing has changed yet.</li>
  99. <?php endif; ?>
  100. </ul>
  101. ​
  102. </div>
  103. ​
  104. <div class="article-form-sidebar">
  105. ​
  106. <div class="sidebar-box">
  107. <label>Customer</label>
  108. <p><?= htmlspecialchars((string) ($order['display_name'] ?: 'Guest')) ?><br><?= htmlspecialchars((string) ($order['account_email'] ?: $order['guest_email'] ?: 'No email')) ?></p>
  109. <?php if ($order['user_account_id']): ?>
  110. <a href="dashboard.php?view=customer-detail&id=<?= (int) $order['user_account_id'] ?>" class="dash-btn-small"><?= Icons::icon('eye', 'icon icon-sm') ?>View customer</a>
  111. <?php endif; ?>
  112. </div>
  113. ​
  114. <div class="sidebar-box">
  115. <label>Payment</label>
  116. <p>Paid with: <?= htmlspecialchars((string) ($order['provider'] ? ucfirst((string) $order['provider']) : 'Not paid yet')) ?><br>
  117. Payment reference: <?= htmlspecialchars((string) ($order['provider_payment_id'] ?: 'None yet')) ?></p>
  118. </div>
  119. ​
  120. <div class="sidebar-box">
  121. <label>Change status</label>
  122. <form method="post">
  123. <?= Csrf::field() ?>
  124. <input type="hidden" name="action" value="update_order_status">
  125. <input type="hidden" name="order_id" value="<?= (int) $order['id'] ?>">
  126. <select name="status">
  127. <?php foreach ($allowedStatuses as $status): ?>
  128. <option value="<?= htmlspecialchars($status) ?>" <?= $order['status'] === $status ? 'selected' : '' ?>><?= htmlspecialchars(StatusLabel::get($status)) ?></option>
  129. <?php endforeach; ?>
  130. </select>
  131. <label>Tracking number (optional)</label>
  132. <input type="text" name="tracking_number" value="<?= htmlspecialchars((string) ($order['tracking_number'] ?? '')) ?>">
  133. <label>Tracking link (optional)</label>
  134. <input type="text" name="tracking_url" value="<?= htmlspecialchars((string) ($order['tracking_url'] ?? '')) ?>">
  135. <label>Note for the history (optional)</label>
  136. <input type="text" name="note">
  137. <label><input type="checkbox" name="notify_customer" checked> Let the customer know by email</label>
  138. <button type="submit" class="dash-btn dash-btn-primary" style="margin-top:10px;"><?= Icons::icon('check', 'icon icon-sm') ?>Save status</button>
  139. </form>
  140. </div>
  141. ​
  142. <?php if (in_array($order['status'], ['paid', 'processing', 'fulfilled', 'shipped', 'completed'], true) && !empty($order['provider_payment_id'])): ?>
  143. <div class="sidebar-box">
  144. <label>Refund</label>
  145. <form method="post" onsubmit="return confirm('Refund this order through ' + <?= json_encode($order['provider']) ?> + '? The money goes back to the customer and this can\'t be undone.');">
  146. <?= Csrf::field() ?>
  147. <input type="hidden" name="action" value="refund_order">
  148. <input type="hidden" name="order_id" value="<?= (int) $order['id'] ?>">
  149. <button type="submit" class="dash-btn dash-btn-danger"><?= Icons::icon('x', 'icon icon-sm') ?>Refund</button>
  150. </form>
  151. </div>
  152. <?php endif; ?>
  153. ​
  154. </div>
  155. </div>
  156. ​