diff --git a/assets/images/uploads/teacher_1775489347_men.webp b/assets/images/uploads/teacher_1775489347_men.webp new file mode 100644 index 0000000..c1016a7 Binary files /dev/null and b/assets/images/uploads/teacher_1775489347_men.webp differ diff --git a/checkout.php b/checkout.php index 49923dc..24c58e8 100644 --- a/checkout.php +++ b/checkout.php @@ -44,13 +44,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { 'preferred_language' => $form['preferred_language'], 'plan_key' => $plan['key'], 'billing_cycle' => $form['billing_cycle'], - 'payment_status' => 'active', + 'payment_status' => 'active', // MVP: Mark active immediately 'payment_gateway' => 'Thawani', 'thawani_reference' => $reference, 'wablas_opt_in' => $form['wablas_opt_in'], ]); - $_SESSION['subscription_id'] = $id; - $_SESSION['student_email'] = $form['email']; $courseId = (int) ($_GET['course_id'] ?? $_POST['course_id'] ?? 0); if ($courseId > 0) { @@ -59,8 +57,90 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $stmt->execute([$courseId, $id]); } catch (Exception $e) {} } - header('Location: ' . app_url('subscription.php', ['id' => $id, 'created' => 1])); - exit; + + // --- Thawani Integration --- + $stmtThawani = db()->query("SELECT thawani_secret_key, thawani_publishable_key, thawani_mode FROM platform_profile WHERE id = 1"); + $thawaniConfig = $stmtThawani->fetch(PDO::FETCH_ASSOC); + + $thawaniRedirect = null; + if ($thawaniConfig && !empty($thawaniConfig['thawani_secret_key']) && !empty($thawaniConfig['thawani_publishable_key'])) { + $mode = $thawaniConfig['thawani_mode'] ?? 'test'; + $baseUrl = $mode === 'live' ? 'https://checkout.thawani.om' : 'https://uatcheckout.thawani.om'; + + $priceOMR = $course ? (float) $course['price'] : (float) ($cycle === 'yearly' ? $plan['price_yearly'] : $plan['price_monthly']); + $priceBaisa = (int) ($priceOMR * 1000); + + $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://"; + if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) { + $protocol = $_SERVER['HTTP_X_FORWARDED_PROTO'] . "://"; + } + $host = $_SERVER['HTTP_HOST']; + $scriptDir = str_replace('\\', '/', dirname($_SERVER['PHP_SELF'])); + $appBaseUrl = $protocol . $host . $scriptDir; + + $successUrl = $appBaseUrl . '/subscription.php?id=' . $id . '&created=1'; + $cancelUrl = $appBaseUrl . '/checkout.php?plan=' . $plan['key'] . '&cycle=' . $cycle; + if ($courseId > 0) { + $cancelUrl .= '&course_id=' . $courseId; + } + + $productName = $course ? (current_lang() === 'ar' ? $course['name_ar'] : $course['name_en']) : plan_name($plan); + $productName = mb_substr($productName, 0, 40); // Thawani character limit + if (empty($productName)) $productName = 'Course Enrollment'; + + $payload = [ + 'client_reference_id' => (string) $id, + 'mode' => 'payment', + 'products' => [ + [ + 'name' => $productName, + 'quantity' => 1, + 'unit_amount' => $priceBaisa + ] + ], + 'success_url' => $successUrl, + 'cancel_url' => $cancelUrl, + 'metadata' => [ + 'subscription_id' => $id, + 'customer_email' => $form['email'] + ] + ]; + + $ch = curl_init("$baseUrl/api/v1/checkout/session"); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "thawani-api-key: {$thawaniConfig['thawani_secret_key']}", + "Content-Type: application/json" + ]); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); + $response = curl_exec($ch); + curl_close($ch); + + if ($response) { + $resData = json_decode($response, true); + if (isset($resData['success']) && $resData['success'] && isset($resData['data']['session_id'])) { + $sessionId = $resData['data']['session_id']; + db()->query("UPDATE subscriptions SET thawani_reference = " . db()->quote($sessionId) . " WHERE id = " . $id); + $thawaniRedirect = "$baseUrl/pay/$sessionId?key={$thawaniConfig['thawani_publishable_key']}"; + } else { + error_log('Thawani Session Error: ' . $response); + } + } + } + + if ($thawaniRedirect) { + $_SESSION['subscription_id'] = $id; + $_SESSION['student_email'] = $form['email']; + header('Location: ' . $thawaniRedirect); + exit; + } else { + db()->query("DELETE FROM student_subscriptions WHERE id = " . $id); + if ($courseId > 0) { + db()->query("DELETE FROM course_students WHERE course_id = " . $courseId . " AND student_id = " . $id); + } + $errors[] = t('Thawani payment gateway is not fully configured (missing keys) or the API call failed. Please update your API keys in the Teacher Panel > Integrations.', 'بوابة دفع ثواني غير مهيأة بالكامل (المفاتيح مفقودة) أو فشل الاتصال. يرجى تحديث مفاتيحك في لوحة المعلم > التكاملات.'); + } } } diff --git a/includes/app.php b/includes/app.php index f1270ec..f327d26 100644 --- a/includes/app.php +++ b/includes/app.php @@ -260,8 +260,8 @@ function subjects_catalog(): array $rows = $stmt->fetchAll(); $result = []; foreach ($rows as $row) { - $row['modules_en'] = json_decode($row['modules_en'] ?? '[]', true) ?: []; - $row['modules_ar'] = json_decode($row['modules_ar'] ?? '[]', true) ?: []; + $row['modules_en'] = json_decode($row['modules_en'] ? (string)$row['modules_en'] : '[]', true) ?: []; + $row['modules_ar'] = json_decode($row['modules_ar'] ? (string)$row['modules_ar'] : '[]', true) ?: []; $class_id = (int)$row['class_id']; $subject_id = (int)$row['id']; @@ -384,22 +384,22 @@ function subject_summary(array $subject): string function subject_teacher(array $subject): string { - return current_lang() === 'ar' ? $subject['teacher_ar'] : $subject['teacher_en']; + return (string)(current_lang() === 'ar' ? ($subject['teacher_ar'] ?? '') : ($subject['teacher_en'] ?? '')); } function subject_level(array $subject): string { - return current_lang() === 'ar' ? $subject['level_ar'] : $subject['level_en']; + return (string)(current_lang() === 'ar' ? ($subject['level_ar'] ?? '') : ($subject['level_en'] ?? '')); } function subject_duration(array $subject): string { - return current_lang() === 'ar' ? $subject['duration_ar'] : $subject['duration_en']; + return (string)(current_lang() === 'ar' ? ($subject['duration_ar'] ?? '') : ($subject['duration_en'] ?? '')); } function subject_next_live(array $subject): string { - return current_lang() === 'ar' ? $subject['next_live_ar'] : $subject['next_live_en']; + return (string)(current_lang() === 'ar' ? ($subject['next_live_ar'] ?? '') : ($subject['next_live_en'] ?? '')); } function subject_modules(array $subject): array diff --git a/live_lesson.php b/live_lesson.php index 5f35509..5466075 100644 --- a/live_lesson.php +++ b/live_lesson.php @@ -55,6 +55,7 @@ if ($is_teacher && isset($_GET['end'])) { $room_name = $lesson['room_name']; $user_display_name = $is_teacher ? 'Teacher' : 'Student'; +$has_meet = !empty($lesson['meet_url']); render_head( t('Live Lesson: ', 'درس مباشر: ') . t($lesson['title'], $lesson['title']), @@ -83,6 +84,9 @@ render_head( .btn-end { background: #dc3545; color: white; border: none; padding: 5px 15px; border-radius: 4px; text-decoration: none; font-size: 0.9rem; } .btn-end:hover { background: #c82333; color: white; } .btn-leave { background: #6c757d; color: white; border: none; padding: 5px 15px; border-radius: 4px; text-decoration: none; font-size: 0.9rem; } + .meet-btn { padding: 15px 30px; font-size: 1.2rem; border-radius: 50px; text-decoration: none; display: inline-flex; align-items: center; gap: 10px; font-weight: bold; background: #fff; color: #3c4043; transition: all 0.3s; } + .meet-btn:hover { background: #f8f9fa; transform: translateY(-2px); box-shadow: 0 4px 15px rgba(255,255,255,0.2); color: #1a73e8; } + .meet-btn svg { width: 24px; height: 24px; }