From 8cadf8aa40dc74441424aaf26f9c8b33dc9a4638 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Wed, 22 Apr 2026 04:37:48 +0000 Subject: [PATCH] thawani payments --- api/place_order.php | 67 ++++-- api/settings.php | 8 +- edit_online_order.php | 8 + includes/app.php | 393 ++++++++++++++++++++++++++++++++++- includes/footer_settings.php | 50 +++++ online_orders.php | 15 +- shop.php | 63 ++++++ thawani_return.php | 96 +++++++++ 8 files changed, 674 insertions(+), 26 deletions(-) create mode 100644 thawani_return.php diff --git a/api/place_order.php b/api/place_order.php index 0e2e05f..60f9e71 100644 --- a/api/place_order.php +++ b/api/place_order.php @@ -17,6 +17,15 @@ $name = trim($input['name'] ?? ''); $phoneInput = trim($input['phone'] ?? ''); $phone = normalize_oman_phone($phoneInput); $address = trim($input['address'] ?? ''); +$paymentMethod = trim((string) ($input['payment_method'] ?? 'pay_later')); +if (!in_array($paymentMethod, ['pay_later', 'pay_online'], true)) { + echo json_encode(['success' => false, 'error' => 'Invalid payment method']); + exit; +} +if ($paymentMethod === 'pay_online' && !thawani_is_configured()) { + echo json_encode(['success' => false, 'error' => 'Thawani payment is not configured']); + exit; +} if ($name === '' || $phoneInput === '' || $address === '') { echo json_encode(['success' => false, 'error' => 'Missing customer details']); @@ -75,7 +84,10 @@ $totalAmount = $subtotal + $totalVat; try { $db->beginTransaction(); - $stmt = $db->prepare("INSERT INTO online_orders (customer_name, customer_phone, customer_address, items_json, subtotal, vat_amount, total_amount) VALUES (?, ?, ?, ?, ?, ?, ?)"); + $paymentGateway = $paymentMethod === 'pay_online' ? 'thawani' : null; + $paymentStatus = $paymentMethod === 'pay_online' ? 'pending' : 'unpaid'; + + $stmt = $db->prepare("INSERT INTO online_orders (customer_name, customer_phone, customer_address, items_json, subtotal, vat_amount, total_amount, payment_method, payment_gateway, payment_status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); $stmt->execute([ $name, $phone, @@ -83,28 +95,37 @@ try { json_encode($processedItems, JSON_UNESCAPED_UNICODE), $subtotal, $totalVat, - $totalAmount + $totalAmount, + $paymentMethod, + $paymentGateway, + $paymentStatus ]); $orderId = (int) $db->lastInsertId(); sync_online_order_stock_reservation([], 'rejected', $processedItems, 'pending'); - $db->commit(); - - // Optional: send telegram and WhatsApp notifications if configured - try { - $orderData = [ - 'id' => $orderId, + + $checkoutUrl = null; + if ($paymentMethod === 'pay_online') { + $thawaniResult = thawani_create_checkout_session($orderId, [ 'customer_name' => $name, 'customer_phone' => $phone, 'customer_address' => $address, 'items' => $processedItems, - 'subtotal' => $subtotal, - 'vat_amount' => $totalVat, - 'total_amount' => $totalAmount, - 'status' => 'pending', - 'created_at' => date('Y-m-d H:i:s'), - ]; + ]); + if (empty($thawaniResult['success'])) { + throw new RuntimeException((string) ($thawaniResult['error'] ?? 'Unable to create Thawani session')); + } + $checkoutUrl = (string) ($thawaniResult['checkout_url'] ?? ''); + $sessionId = (string) ($thawaniResult['session_id'] ?? ''); + $updateStmt = $db->prepare("UPDATE online_orders SET gateway_session_id = ? WHERE id = ?"); + $updateStmt->execute([$sessionId, $orderId]); + } + + $db->commit(); + + // Optional: send Telegram admin notice and customer WhatsApp notification. + try { $msg = "🛒 *New Online Order #{$orderId}* "; @@ -136,15 +157,19 @@ try { $context = stream_context_create($options); @file_get_contents($url, false, $context); } - - if (wablas_is_configured()) { - wablas_notify_online_order($orderData, 'created'); - } - } catch (Exception $e) { - // ignore notification errors + } catch (Throwable $e) { + error_log('Telegram notify failed for online order #' . $orderId . ': ' . $e->getMessage()); } - echo json_encode(['success' => true]); + if ($paymentMethod === 'pay_later' && wablas_is_configured()) { + try { + wablas_notify_online_order_by_id($orderId, 'created'); + } catch (Throwable $e) { + error_log('Customer WhatsApp notify failed for online order #' . $orderId . ': ' . $e->getMessage()); + } + } + + echo json_encode(['success' => true, 'redirect_url' => $checkoutUrl]); } catch (Throwable $e) { if ($db->inTransaction()) { $db->rollBack(); diff --git a/api/settings.php b/api/settings.php index e881ae1..7d63f7f 100644 --- a/api/settings.php +++ b/api/settings.php @@ -19,7 +19,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { 'wablas_daily_auto_send', 'wablas_daily_auto_time', 'wablas_daily_auto_last_date', 'wablas_template_invoice', 'wablas_template_daily_report', 'wablas_template_created', 'wablas_template_pending', 'wablas_template_accepted', 'wablas_template_completed', 'wablas_template_rejected', - 'smtp_host', 'smtp_port', 'smtp_user', 'smtp_pass', 'smtp_secure', 'mail_from', 'mail_from_name' + 'smtp_host', 'smtp_port', 'smtp_user', 'smtp_pass', 'smtp_secure', 'mail_from', 'mail_from_name', + 'thawani_enabled', 'thawani_mode', 'thawani_publishable_key', 'thawani_secret_key', 'thawani_success_url', 'thawani_cancel_url' ]; $stmt = $pdo->prepare("INSERT INTO settings (setting_key, setting_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)"); @@ -51,6 +52,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!isset($_POST['wablas_daily_auto_send'])) { $_POST['wablas_daily_auto_send'] = '0'; } + if (!isset($_POST['thawani_enabled'])) { + $_POST['thawani_enabled'] = '0'; + } + $thawaniMode = strtolower(trim((string) ($_POST['thawani_mode'] ?? 'sandbox'))); + $_POST['thawani_mode'] = in_array($thawaniMode, ['sandbox', 'live'], true) ? $thawaniMode : 'sandbox'; unset($_POST['wablas_daily_auto_last_date']); foreach ($keys as $key) { diff --git a/edit_online_order.php b/edit_online_order.php index 2543036..3ea5dc0 100644 --- a/edit_online_order.php +++ b/edit_online_order.php @@ -385,6 +385,14 @@ require __DIR__ . '/includes/header.php'; +
+
+
+
+ +
+ +
diff --git a/includes/app.php b/includes/app.php index 3858ee8..19735fb 100644 --- a/includes/app.php +++ b/includes/app.php @@ -121,6 +121,41 @@ try { @file_put_contents($flagFileV7, '1'); } + + $flagFileV8 = sys_get_temp_dir() . '/.schema_migrated_v8_' . md5(__DIR__); + if (!file_exists($flagFileV8)) { + $pdo = db(); + $hasOnlineOrdersTable = (bool) $pdo->query("SHOW TABLES LIKE 'online_orders'")->fetchColumn(); + if ($hasOnlineOrdersTable) { + $requiredColumns = [ + 'payment_method' => "ALTER TABLE online_orders ADD COLUMN payment_method varchar(30) NOT NULL DEFAULT 'pay_later' AFTER total_amount", + 'payment_gateway' => "ALTER TABLE online_orders ADD COLUMN payment_gateway varchar(30) DEFAULT NULL AFTER payment_method", + 'payment_status' => "ALTER TABLE online_orders ADD COLUMN payment_status varchar(20) NOT NULL DEFAULT 'unpaid' AFTER payment_gateway", + 'gateway_session_id' => "ALTER TABLE online_orders ADD COLUMN gateway_session_id varchar(120) DEFAULT NULL AFTER payment_status", + 'gateway_transaction_id' => "ALTER TABLE online_orders ADD COLUMN gateway_transaction_id varchar(120) DEFAULT NULL AFTER gateway_session_id", + 'paid_at' => "ALTER TABLE online_orders ADD COLUMN paid_at datetime DEFAULT NULL AFTER gateway_transaction_id", + ]; + foreach ($requiredColumns as $column => $sql) { + $exists = $pdo->query("SHOW COLUMNS FROM online_orders LIKE " . $pdo->quote($column))->fetchColumn(); + if (!$exists) { + $pdo->exec($sql); + } + } + + $pdo->exec("UPDATE online_orders SET payment_method = 'pay_later' WHERE payment_method IS NULL OR payment_method = ''"); + $pdo->exec("UPDATE online_orders SET payment_status = CASE WHEN payment_method = 'pay_online' THEN 'pending' ELSE 'unpaid' END WHERE payment_status IS NULL OR payment_status = ''"); + } + + $pdo->exec("INSERT IGNORE INTO settings (setting_key, setting_value) VALUES + ('thawani_enabled', '0'), + ('thawani_mode', 'sandbox'), + ('thawani_publishable_key', ''), + ('thawani_secret_key', ''), + ('thawani_success_url', ''), + ('thawani_cancel_url', '')"); + + @file_put_contents($flagFileV8, '1'); + } } catch (\Throwable $e) {} @@ -308,6 +343,73 @@ function url_for(string $path, array $params = []): string return $path . ($query ? ('?' . $query) : ''); } +function request_scheme(): string +{ + $https = (string) ($_SERVER['HTTPS'] ?? ''); + if ($https !== '' && strtolower($https) !== 'off') { + return 'https'; + } + + $forwardedProto = trim((string) ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '')); + if ($forwardedProto !== '') { + return strtolower(explode(',', $forwardedProto)[0]); + } + + return 'http'; +} + +function app_base_url(): string +{ + $host = trim((string) ($_SERVER['HTTP_X_FORWARDED_HOST'] ?? $_SERVER['HTTP_HOST'] ?? '')); + if ($host === '') { + $host = '127.0.0.1'; + } + + return request_scheme() . '://' . $host; +} + +function append_query_params(string $url, array $params): string +{ + $parts = parse_url($url); + $queryParams = []; + if (!empty($parts['query'])) { + parse_str($parts['query'], $queryParams); + } + foreach ($params as $key => $value) { + if ($value === null || $value === '') { + continue; + } + $queryParams[$key] = $value; + } + + $rebuilt = ''; + if (!empty($parts['scheme'])) { + $rebuilt .= $parts['scheme'] . '://'; + } + if (!empty($parts['user'])) { + $rebuilt .= $parts['user']; + if (!empty($parts['pass'])) { + $rebuilt .= ':' . $parts['pass']; + } + $rebuilt .= '@'; + } + if (!empty($parts['host'])) { + $rebuilt .= $parts['host']; + } + if (!empty($parts['port'])) { + $rebuilt .= ':' . $parts['port']; + } + $rebuilt .= $parts['path'] ?? ''; + if ($queryParams !== []) { + $rebuilt .= '?' . http_build_query($queryParams); + } + if (!empty($parts['fragment'])) { + $rebuilt .= '#' . $parts['fragment']; + } + + return $rebuilt; +} + function redirect_to(string $path, array $params = []): void { header('Location: ' . url_for($path, $params)); @@ -597,6 +699,236 @@ function payment_status_badge_class(string $status): string }; } +function online_payment_method_label(string $method): string +{ + return match ($method) { + 'pay_online' => tr('ادفع أونلاين', 'Pay Online'), + default => tr('ادفع لاحقاً', 'Pay Later'), + }; +} + +function online_payment_status_label(string $status): string +{ + return match ($status) { + 'paid' => tr('مدفوع', 'Paid'), + 'pending' => tr('بانتظار الدفع', 'Awaiting Payment'), + 'failed' => tr('فشل الدفع', 'Payment Failed'), + 'cancelled' => tr('تم الإلغاء', 'Cancelled'), + default => tr('غير مدفوع', 'Unpaid'), + }; +} + +function online_payment_status_badge_class(string $status): string +{ + return match ($status) { + 'paid' => 'bg-success text-white', + 'pending' => 'bg-warning text-dark', + 'failed' => 'bg-danger text-white', + 'cancelled' => 'bg-secondary text-white', + default => 'bg-danger-subtle text-danger-emphasis', + }; +} + +function thawani_is_enabled(): bool +{ + return (string) get_setting('thawani_enabled', '0') === '1'; +} + +function thawani_mode(): string +{ + $mode = strtolower(trim((string) get_setting('thawani_mode', 'sandbox'))); + return in_array($mode, ['sandbox', 'live'], true) ? $mode : 'sandbox'; +} + +function thawani_checkout_base_url(): string +{ + return thawani_mode() === 'live' ? 'https://checkout.thawani.om' : 'https://uatcheckout.thawani.om'; +} + +function thawani_api_key(): string +{ + return trim((string) get_setting('thawani_secret_key', '')); +} + +function thawani_publishable_key(): string +{ + return trim((string) get_setting('thawani_publishable_key', '')); +} + +function thawani_is_configured(): bool +{ + return thawani_is_enabled() && thawani_api_key() !== '' && thawani_publishable_key() !== ''; +} + +function thawani_default_return_url(string $result): string +{ + return app_base_url() . '/thawani_return.php?result=' . rawurlencode($result); +} + +function thawani_success_url(): string +{ + $custom = trim((string) get_setting('thawani_success_url', '')); + return $custom !== '' ? $custom : thawani_default_return_url('success'); +} + +function thawani_cancel_url(): string +{ + $custom = trim((string) get_setting('thawani_cancel_url', '')); + return $custom !== '' ? $custom : thawani_default_return_url('cancel'); +} + +function thawani_build_products(array $items): array +{ + $products = []; + foreach ($items as $item) { + $qty = max(1, (int) ($item['qty'] ?? 0)); + $name = trim((string) ($item['name'] ?? $item['name_ar'] ?? $item['sku'] ?? 'Item')); + $lineTotal = (float) ($item['line_total'] ?? 0); + $vatAmount = (float) ($item['vat_amount'] ?? 0); + $unitAmount = (int) round((($lineTotal + $vatAmount) / $qty) * 1000); + $products[] = [ + 'name' => $name, + 'quantity' => $qty, + 'unit_amount' => max(1, $unitAmount), + ]; + } + + return $products; +} + +function thawani_call(string $method, string $path): array +{ + $url = rtrim(thawani_checkout_base_url(), '/') . $path; + $headers = [ + 'Content-Type: application/json', + 'Accept: application/json', + 'thawani-api-key: ' . thawani_api_key(), + ]; + + $ch = curl_init(); + curl_setopt_array($ch, [ + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_CUSTOMREQUEST => strtoupper($method), + CURLOPT_HTTPHEADER => $headers, + CURLOPT_TIMEOUT => 30, + ]); + $raw = curl_exec($ch); + $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); + $error = curl_error($ch); + curl_close($ch); + + if ($raw === false) { + return ['success' => false, 'status' => $status, 'error' => $error !== '' ? $error : 'Unable to contact Thawani']; + } + + $decoded = json_decode($raw, true); + return [ + 'success' => $status >= 200 && $status < 300, + 'status' => $status, + 'data' => is_array($decoded) ? $decoded : null, + 'raw' => $raw, + 'error' => $status >= 200 && $status < 300 ? '' : ('HTTP ' . $status), + ]; +} + +function thawani_create_checkout_session(int $orderId, array $order): array +{ + if (!thawani_is_configured()) { + return ['success' => false, 'error' => 'Thawani is not configured']; + } + + $payload = [ + 'client_reference_id' => 'online-order-' . $orderId, + 'mode' => 'payment', + 'products' => thawani_build_products($order['items'] ?? []), + 'success_url' => append_query_params(thawani_success_url(), [ + 'order_id' => $orderId, + ]), + 'cancel_url' => append_query_params(thawani_cancel_url(), [ + 'order_id' => $orderId, + ]), + 'metadata' => [ + 'order_id' => (string) $orderId, + 'customer_name' => (string) ($order['customer_name'] ?? ''), + 'customer_phone' => (string) ($order['customer_phone'] ?? ''), + ], + ]; + + $url = rtrim(thawani_checkout_base_url(), '/') . '/api/v1/checkout/session'; + $headers = [ + 'Content-Type: application/json', + 'Accept: application/json', + 'thawani-api-key: ' . thawani_api_key(), + ]; + + $ch = curl_init(); + curl_setopt_array($ch, [ + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), + CURLOPT_HTTPHEADER => $headers, + CURLOPT_TIMEOUT => 30, + ]); + $raw = curl_exec($ch); + $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); + $error = curl_error($ch); + curl_close($ch); + + if ($raw === false) { + return ['success' => false, 'status' => $status, 'error' => $error !== '' ? $error : 'Unable to contact Thawani']; + } + + $decoded = json_decode($raw, true); + $sessionId = (string) ($decoded['data']['session_id'] ?? $decoded['session_id'] ?? ''); + if ($status < 200 || $status >= 300 || $sessionId === '') { + $message = (string) ($decoded['message'] ?? $decoded['description'] ?? ('HTTP ' . $status)); + return ['success' => false, 'status' => $status, 'error' => $message, 'data' => $decoded, 'raw' => $raw]; + } + + return [ + 'success' => true, + 'status' => $status, + 'session_id' => $sessionId, + 'checkout_url' => rtrim(thawani_checkout_base_url(), '/') . '/pay/' . rawurlencode($sessionId) . '?key=' . rawurlencode(thawani_publishable_key()), + 'data' => $decoded, + 'raw' => $raw, + ]; +} + +function thawani_retrieve_session(string $sessionId): array +{ + if ($sessionId === '') { + return ['success' => false, 'error' => 'Missing session id']; + } + + return thawani_call('GET', '/api/v1/checkout/session/' . rawurlencode($sessionId)); +} + +function thawani_session_paid(array $response): bool +{ + $data = (array) ($response['data']['data'] ?? $response['data'] ?? []); + $candidates = [ + strtolower((string) ($data['payment_status'] ?? '')), + strtolower((string) ($data['status'] ?? '')), + strtolower((string) ($data['paymentStatus'] ?? '')), + strtolower((string) ($data['paymentStatusDescription'] ?? '')), + ]; + foreach ($candidates as $candidate) { + if (in_array($candidate, ['paid', 'successful', 'success'], true)) { + return true; + } + } + return false; +} + +function thawani_session_transaction_id(array $response): string +{ + $data = (array) ($response['data']['data'] ?? $response['data'] ?? []); + return (string) ($data['invoice'] ?? $data['payment_reference'] ?? $data['transaction_id'] ?? $data['reference'] ?? ''); +} + function apply_sale_payment(int $saleId, float $paymentAmount, bool $completeOrderWhenPaid = false): array { $sale = fetch_sale($saleId); @@ -730,19 +1062,33 @@ function wablas_default_order_template(string $event): string return match ($event) { 'created' => "مرحباً {customer_name}، تم استلام طلبك رقم #{order_id}. الحالة: {status_label} +طريقة الدفع: {payment_method_label} +حالة الدفع: {payment_status_label} + +الأصناف: +{items_summary} + الإجمالي: {total_amount} العنوان: {customer_address} شكراً لتسوقك معنا.", 'pending' => "مرحباً {customer_name}، طلبك رقم #{order_id} ما زال {status_label}. +طريقة الدفع: {payment_method_label} +حالة الدفع: {payment_status_label} الإجمالي: {total_amount} سنوافيك بأي تحديث جديد.", 'accepted' => "مرحباً {customer_name}، تم قبول طلبك رقم #{order_id}. +طريقة الدفع: {payment_method_label} +حالة الدفع: {payment_status_label} الإجمالي: {total_amount} سنبدأ التجهيز الآن.", 'completed' => "مرحباً {customer_name}، طلبك رقم #{order_id} أصبح {status_label}. +طريقة الدفع: {payment_method_label} +حالة الدفع: {payment_status_label} الإجمالي: {total_amount} شكراً لك.", 'rejected' => "مرحباً {customer_name}، نعتذر، تم تحديث طلبك رقم #{order_id} إلى {status_label}. +طريقة الدفع: {payment_method_label} +حالة الدفع: {payment_status_label} إذا رغبت بالمساعدة تواصل معنا.", default => "مرحباً {customer_name}، تم تحديث طلبك رقم #{order_id} إلى {status_label}.", }; @@ -788,6 +1134,13 @@ function wablas_order_items_summary(array $order): string function wablas_order_template_vars(array $order): array { $status = (string) ($order['status'] ?? 'pending'); + $paymentMethod = (string) ($order['payment_method'] ?? 'pay_later'); + $paymentStatus = (string) ($order['payment_status'] ?? ($paymentMethod === 'pay_online' ? 'pending' : 'unpaid')); + $itemsSummary = wablas_order_items_summary($order); + if (trim($itemsSummary) === '') { + $itemsSummary = '-'; + } + return [ 'order_id' => (string) ($order['id'] ?? ''), 'customer_name' => (string) ($order['customer_name'] ?? ''), @@ -795,11 +1148,15 @@ function wablas_order_template_vars(array $order): array 'customer_address' => (string) ($order['customer_address'] ?? ''), 'status' => $status, 'status_label' => wablas_order_status_label($status), + 'payment_method' => $paymentMethod, + 'payment_method_label' => wablas_payment_method_label($paymentMethod), + 'payment_status' => $paymentStatus, + 'payment_status_label' => wablas_payment_status_label($paymentStatus), 'subtotal' => currency((float) ($order['subtotal'] ?? 0)), 'vat_amount' => currency((float) ($order['vat_amount'] ?? 0)), 'total_amount' => currency((float) ($order['total_amount'] ?? 0)), 'created_at' => (string) ($order['created_at'] ?? ''), - 'items_summary' => wablas_order_items_summary($order), + 'items_summary' => $itemsSummary, ]; } @@ -842,7 +1199,8 @@ function wablas_payment_method_label(string $method): string 'cash' => tr('كاش', 'Cash'), 'card' => tr('بطاقة', 'Card'), 'transfer', 'bank' => tr('تحويل', 'Transfer'), - 'pay_later' => tr('آجل', 'Pay later'), + 'pay_later' => tr('الدفع لاحقاً', 'Pay later'), + 'pay_online' => tr('الدفع أونلاين', 'Pay online'), default => $method, }; } @@ -853,6 +1211,9 @@ function wablas_payment_status_label(string $status): string 'paid' => tr('مدفوع', 'Paid'), 'partial' => tr('مدفوع جزئياً', 'Partial'), 'unpaid' => tr('غير مدفوع', 'Unpaid'), + 'pending' => tr('بانتظار الدفع', 'Pending payment'), + 'failed' => tr('فشل الدفع', 'Payment failed'), + 'cancelled' => tr('تم إلغاء الدفع', 'Payment cancelled'), default => $status, }; } @@ -1228,6 +1589,34 @@ function wablas_notify_online_order(array $order, string $event): array return $result; } +function fetch_online_order(int $orderId): ?array +{ + if ($orderId <= 0) { + return null; + } + + try { + $stmt = db()->prepare('SELECT * FROM online_orders WHERE id = :id LIMIT 1'); + $stmt->bindValue(':id', $orderId, PDO::PARAM_INT); + $stmt->execute(); + $order = $stmt->fetch(PDO::FETCH_ASSOC); + return $order ?: null; + } catch (Throwable $e) { + error_log('Failed to fetch online order #' . $orderId . ': ' . $e->getMessage()); + return null; + } +} + +function wablas_notify_online_order_by_id(int $orderId, string $event): array +{ + $order = fetch_online_order($orderId); + if (!$order) { + return ['success' => false, 'error' => 'Online order not found']; + } + + return wablas_notify_online_order($order, $event); +} + function ensure_sales_table(): void { $sql = "CREATE TABLE IF NOT EXISTS sales_orders ( diff --git a/includes/footer_settings.php b/includes/footer_settings.php index 47f1851..1b8c136 100644 --- a/includes/footer_settings.php +++ b/includes/footer_settings.php @@ -39,6 +39,11 @@ +
+
+
+
+
+
+ > + +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
diff --git a/online_orders.php b/online_orders.php index 713c860..43932b4 100644 --- a/online_orders.php +++ b/online_orders.php @@ -152,6 +152,7 @@ require __DIR__ . '/includes/header.php'; + @@ -159,7 +160,7 @@ require __DIR__ . '/includes/header.php'; - + + +
+ + @@ -188,6 +193,8 @@ require __DIR__ . '/includes/header.php'; "name" => $o["customer_name"], "phone" => $o["customer_phone"], "address" => $o["customer_address"], + "payment_method" => online_payment_method_label((string) ($o["payment_method"] ?? 'pay_later')), + "payment_status" => online_payment_status_label((string) ($o["payment_status"] ?? 'unpaid')), "subtotal" => $o["subtotal"] ?? 0, "vat" => $o["vat_amount"] ?? 0, "total" => $o["total_amount"], "items" => $items ], JSON_UNESCAPED_UNICODE), ENT_QUOTES, "UTF-8") ?>)'> @@ -218,7 +225,7 @@ require __DIR__ . '/includes/header.php'; - + @@ -241,6 +248,8 @@ require __DIR__ . '/includes/header.php';
+
+
@@ -276,6 +285,8 @@ function viewOrder(order) { document.getElementById('vName').innerText = order.name; document.getElementById('vPhone').innerText = order.phone ? ('968 ' + String(order.phone).replace(/^((00968)|(968))/, '')) : ''; document.getElementById('vAddress').innerText = order.address; + document.getElementById('vPaymentMethod').innerText = order.payment_method || ''; + document.getElementById('vPaymentStatus').innerText = order.payment_status || ''; document.getElementById('vSubtotal').innerText = Number(order.subtotal).toFixed(2); document.getElementById('vVat').innerText = Number(order.vat).toFixed(2); document.getElementById('vTotal').innerText = Number(order.total).toFixed(2); diff --git a/shop.php b/shop.php index 730cf48..0c4b279 100644 --- a/shop.php +++ b/shop.php @@ -3,6 +3,10 @@ require_once __DIR__ . '/includes/app.php'; $forcePublic = true; $pageTitle = tr('الطلب عبر الإنترنت', 'Online Ordering'); +$shopPaymentStatus = trim((string) ($_GET['payment_status'] ?? '')); +$shopPaymentMessage = trim((string) ($_GET['message'] ?? '')); +$shopCanPayOnline = thawani_is_configured(); +$shopShowPayOnline = thawani_is_enabled() || $shopCanPayOnline; require __DIR__ . '/includes/header.php'; @@ -110,6 +114,26 @@ body { background-color: #f8f9fa; }
+ + tr('تم الدفع بنجاح وتم استلام طلبك.', 'Payment completed successfully and your order was received.'), + 'cancelled' => tr('تم إلغاء عملية الدفع. يمكنك المحاولة مرة أخرى أو اختيار الدفع لاحقاً.', 'Payment was cancelled. You can try again or choose Pay Later.'), + 'failed' => tr('تعذر تأكيد الدفع. إذا تم الخصم يرجى مراجعة الإدارة.', 'We could not confirm the payment. If you were charged, please contact the store.'), + default => tr('تم تحديث حالة الطلب.', 'The order status has been updated.'), + }; + ?> +
+ +
+ +
@@ -244,6 +268,30 @@ body { background-color: #f8f9fa; }
+
+ +
+ +
+ +
+ +
+ +
+
+ +
+ +