288 lines
16 KiB
PHP
288 lines
16 KiB
PHP
<?php
|
|
require_once __DIR__ . '/includes/app.php';
|
|
$plans = plans_catalog();
|
|
$planKey = (string) ($_GET['plan'] ?? $_POST['plan_key'] ?? 'plus');
|
|
$plan = get_plan($planKey) ?? get_plan('plus');
|
|
$cycle = (string) ($_GET['cycle'] ?? $_POST['billing_cycle'] ?? 'monthly');
|
|
$cycle = in_array($cycle, ['monthly', 'yearly'], true) ? $cycle : 'monthly';
|
|
$courseIdForView = (int) ($_GET['course_id'] ?? $_POST['course_id'] ?? 0);
|
|
$course = null;
|
|
if ($courseIdForView > 0) {
|
|
$course = db()->query("SELECT * FROM courses WHERE id = $courseIdForView")->fetch();
|
|
if ($course) {
|
|
if (!$course['registration_open']) {
|
|
$msg = current_lang() === 'ar' ? 'التسجيل في هذه الدورة/الدفعة مغلق حالياً.' : 'Registration for this course/batch is currently closed.';
|
|
die("<div style='padding:2rem;text-align:center;font-family:sans-serif'><h1>" . $msg . "</h1><a href='index.php'>" . (current_lang() === 'ar' ? 'العودة' : 'Back') . "</a></div>");
|
|
}
|
|
if ($course['max_students'] > 0) {
|
|
$enrolled = db()->query("SELECT COUNT(*) FROM course_students WHERE course_id = $courseIdForView")->fetchColumn();
|
|
if ($enrolled >= $course['max_students']) {
|
|
$msg = current_lang() === 'ar' ? 'لقد وصلت هذه الدورة إلى الحد الأقصى لعدد الطلاب.' : 'This course has reached its maximum capacity.';
|
|
die("<div style='padding:2rem;text-align:center;font-family:sans-serif'><h1>" . $msg . "</h1><a href='index.php'>" . (current_lang() === 'ar' ? 'العودة' : 'Back') . "</a></div>");
|
|
}
|
|
}
|
|
} else {
|
|
die('Course not found');
|
|
}
|
|
}
|
|
$errors = [];
|
|
$form = [
|
|
'full_name' => trim((string) ($_POST['full_name'] ?? '')),
|
|
'email' => trim((string) ($_POST['email'] ?? '')),
|
|
'password' => (string) ($_POST['password'] ?? ''),
|
|
'whatsapp' => trim((string) ($_POST['whatsapp'] ?? '')),
|
|
'preferred_language' => (string) ($_POST['preferred_language'] ?? current_lang()),
|
|
'billing_cycle' => $cycle,
|
|
'plan_key' => $plan['key'],
|
|
'wablas_opt_in' => isset($_POST['wablas_opt_in']) ? 1 : 0,
|
|
];
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
if ($form['full_name'] === '' || strlen($form['full_name']) < 2) {
|
|
$errors[] = t('Please enter a valid full name.', 'يرجى إدخال اسم كامل صحيح.');
|
|
}
|
|
if (empty($form['password']) || strlen($form['password']) < 6) {
|
|
$errors[] = t('Password must be at least 6 characters.', 'يجب أن تتكون كلمة المرور من 6 أحرف على الأقل.');
|
|
}
|
|
if (!filter_var($form['email'], FILTER_VALIDATE_EMAIL)) {
|
|
$errors[] = t('Please enter a valid email address.', 'يرجى إدخال بريد إلكتروني صحيح.');
|
|
}
|
|
if (!preg_match('/^[0-9+\-\s]{8,20}$/', $form['whatsapp'])) {
|
|
$errors[] = t('Please enter a valid WhatsApp number.', 'يرجى إدخال رقم واتساب صحيح.');
|
|
}
|
|
if (!in_array($form['preferred_language'], ['en', 'ar'], true)) {
|
|
$errors[] = t('Please select a supported language.', 'يرجى اختيار لغة مدعومة.');
|
|
}
|
|
|
|
if (!$errors) {
|
|
$reference = 'THW-' . date('YmdHis') . '-' . random_int(100, 999);
|
|
$id = create_subscription([
|
|
'full_name' => $form['full_name'],
|
|
'email' => $form['email'],
|
|
'password' => password_hash($form['password'], PASSWORD_DEFAULT),
|
|
'whatsapp' => $form['whatsapp'],
|
|
'preferred_language' => $form['preferred_language'],
|
|
'plan_key' => $plan['key'],
|
|
'billing_cycle' => $form['billing_cycle'],
|
|
'payment_status' => 'active', // MVP: Mark active immediately
|
|
'payment_gateway' => 'Thawani',
|
|
'thawani_reference' => $reference,
|
|
'wablas_opt_in' => $form['wablas_opt_in'],
|
|
]);
|
|
|
|
$courseId = (int) ($_GET['course_id'] ?? $_POST['course_id'] ?? 0);
|
|
if ($courseId > 0) {
|
|
try {
|
|
$stmt = db()->prepare('INSERT IGNORE INTO course_students (course_id, student_id) VALUES (?, ?)');
|
|
$stmt->execute([$courseId, $id]);
|
|
} catch (Exception $e) {}
|
|
}
|
|
|
|
// --- 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'],
|
|
'name' => $form['full_name'],
|
|
'contact_number' => $form['whatsapp'],
|
|
'email_id' => $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 student_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.', 'بوابة دفع ثواني غير مهيأة بالكامل (المفاتيح مفقودة) أو فشل الاتصال. يرجى تحديث مفاتيحك في لوحة المعلم > التكاملات.');
|
|
}
|
|
}
|
|
}
|
|
|
|
render_head(
|
|
t('Checkout', 'الدفع'),
|
|
t('Capture a student subscription with bilingual preferences, WhatsApp opt-in, and a Thawani-ready payment reference.', 'التقط اشتراك طالب مع تفضيلات ثنائية اللغة وخيار واتساب ومرجع دفع جاهز لثواني.')
|
|
);
|
|
render_nav('pricing.php');
|
|
?>
|
|
<main class="py-5">
|
|
<div class="container">
|
|
<div class="row g-4">
|
|
<div class="col-lg-7">
|
|
<div class="panel-card">
|
|
<span class="eyebrow"><?= h(t('Checkout flow', 'مسار الدفع')) ?></span>
|
|
<h1 class="section-title mb-2"><?= h(t('Create a subscription and unlock the student dashboard', 'أنشئ اشتراكاً وافتح لوحة الطالب')) ?></h1>
|
|
<p class="text-secondary mb-4"><?= h(t('This first slice records a real subscription row locally, reserves a Thawani reference, and prepares WhatsApp reminders.', 'تسجل هذه الشريحة الأولى صف اشتراك حقيقياً محلياً وتحجز مرجع Thawani وتجهز تذكيرات واتساب.')) ?></p>
|
|
<?php if ($errors): ?>
|
|
<div class="alert alert-danger border">
|
|
<ul class="mb-0 ps-3">
|
|
<?php foreach ($errors as $error): ?>
|
|
<li><?= h($error) ?></li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</div>
|
|
<?php endif; ?>
|
|
<form method="post" class="row g-3">
|
|
<input type="hidden" name="plan_key" value="<?= h($plan['key']) ?>">
|
|
<?php if (isset($_GET['course_id']) || isset($_POST['course_id'])): ?>
|
|
<input type="hidden" name="course_id" value="<?= h($_GET['course_id'] ?? $_POST['course_id'] ?? 0) ?>">
|
|
<?php endif; ?>
|
|
<div class="col-md-6">
|
|
<label class="form-label" for="full_name"><?= h(t('Full name', 'الاسم الكامل')) ?></label>
|
|
<input class="form-control" id="full_name" name="full_name" required value="<?= h($form['full_name']) ?>">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label" for="email"><?= h(t('Email', 'البريد الإلكتروني')) ?></label>
|
|
<input class="form-control" id="email" type="email" name="email" required value="<?= h($form['email']) ?>">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label" for="password"><?= h(t('Password', 'كلمة المرور')) ?></label>
|
|
<input class="form-control" id="password" type="password" name="password" required>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label" for="whatsapp"><?= h(t('WhatsApp', 'واتساب')) ?></label>
|
|
<input class="form-control" id="whatsapp" name="whatsapp" placeholder="+968 9000 0000" required value="<?= h($form['whatsapp']) ?>">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label" for="preferred_language"><?= h(t('Language', 'اللغة')) ?></label>
|
|
<select class="form-select" id="preferred_language" name="preferred_language">
|
|
<option value="en" <?= $form['preferred_language'] === 'en' ? 'selected' : '' ?>>English</option>
|
|
<option value="ar" <?= $form['preferred_language'] === 'ar' ? 'selected' : '' ?>>العربية</option>
|
|
</select>
|
|
</div>
|
|
<?php if ($course): ?>
|
|
<input type="hidden" name="billing_cycle" value="monthly">
|
|
<?php else: ?>
|
|
<div class="col-md-3">
|
|
<label class="form-label" for="billing_cycle"><?= h(t('Billing', 'الدفع الدوري')) ?></label>
|
|
<select class="form-select" id="billing_cycle" name="billing_cycle">
|
|
<option value="monthly" <?= $form['billing_cycle'] === 'monthly' ? 'selected' : '' ?>><?= h(t('Monthly', 'شهري')) ?></option>
|
|
<option value="yearly" <?= $form['billing_cycle'] === 'yearly' ? 'selected' : '' ?>><?= h(t('Yearly', 'سنوي')) ?></option>
|
|
</select>
|
|
</div>
|
|
<?php endif; ?>
|
|
<div class="col-12">
|
|
<div class="form-check border rounded-3 p-3">
|
|
<input class="form-check-input" type="checkbox" id="wablas_opt_in" name="wablas_opt_in" value="1" <?= $form['wablas_opt_in'] ? 'checked' : '' ?>>
|
|
<label class="form-check-label" for="wablas_opt_in">
|
|
<?= h(t('Send payment success and class reminders through WhatsApp.', 'أرسل نجاح الدفع وتذكيرات الحصص عبر واتساب.')) ?>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 d-flex flex-wrap gap-2 pt-2">
|
|
<button class="btn btn-dark btn-lg" type="submit"><?= h(t('Confirm subscription', 'تأكيد الاشتراك')) ?></button>
|
|
<a class="btn btn-outline-dark btn-lg" href="<?= h(app_url('pricing.php')) ?>"><?= h(t('Back to plans', 'العودة إلى الخطط')) ?></a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-5">
|
|
<aside class="panel-card sticky-card">
|
|
<div class="d-flex justify-content-between align-items-start mb-3">
|
|
<div>
|
|
<span class="eyebrow"><?= h(t('Order summary', 'ملخص الطلب')) ?></span>
|
|
<?php if ($course): ?>
|
|
<h2 class="h5 mb-1"><?= h(current_lang() === 'ar' ? $course['name_ar'] : $course['name_en']) ?></h2>
|
|
<p class="small text-secondary mb-0"><?= h(t('Short course enrollment', 'تسجيل في دورة قصيرة')) ?></p>
|
|
<?php else: ?>
|
|
<h2 class="h5 mb-1"><?= h(plan_name($plan)) ?></h2>
|
|
<p class="small text-secondary mb-0"><?= h(t('Single-platform access', 'وصول إلى منصة موحدة')) ?></p>
|
|
<?php endif; ?>
|
|
</div>
|
|
<span class="badge bg-dark-subtle text-dark-emphasis border"><?= h(t('Step 1 MVP', 'النسخة الأولى')) ?></span>
|
|
</div>
|
|
<div class="summary-row"><span><?= h(t('Billing cycle', 'دورة الدفع')) ?></span><strong>
|
|
<?php if ($course): ?>
|
|
<?= h(t('One-time', 'لمرة واحدة')) ?>
|
|
<?php else: ?>
|
|
<?= h($cycle === 'yearly' ? t('Yearly', 'سنوي') : t('Monthly', 'شهري')) ?>
|
|
<?php endif; ?>
|
|
</strong></div>
|
|
<div class="summary-row"><span><?= h(t('Price', 'السعر')) ?></span><strong data-cycle-price>
|
|
<?php if ($course): ?>
|
|
<?= h(format_price((float)$course['price'])) ?>
|
|
<?php else: ?>
|
|
<?= h(price_label($plan, $cycle)) ?>
|
|
<?php endif; ?>
|
|
</strong></div>
|
|
<hr>
|
|
<ul class="list-unstyled compact-list compact-list-tight mb-0">
|
|
<?php foreach (current_lang() === 'ar' ? $plan['features_ar'] : $plan['features_en'] as $feature): ?>
|
|
<li><?= h($feature) ?></li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</aside>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
<?php render_footer(); ?>
|