110 lines
4.7 KiB
PHP
110 lines
4.7 KiB
PHP
<?php
|
|
/**
|
|
* driver_assurance/subscribe.php — اشتراك السائق في خطة تأمين
|
|
*
|
|
* الأهلية تُحسب هنا من جديد لا يُوثق بما أرسله التطبيق: الشاشة قد تكون
|
|
* مفتوحة منذ ساعة، وقد يُعدَّل الطلب يدوياً. النقطة التي تمنح تغطية
|
|
* مالية لا تصدّق عميلها.
|
|
*/
|
|
|
|
require_once __DIR__ . '/../connect.php';
|
|
require_once __DIR__ . '/eligibility.php';
|
|
require_once __DIR__ . '/../obligations/functions.php';
|
|
|
|
$driverId = $user_id ?? '';
|
|
if (empty($driverId) || ($role ?? '') !== 'driver') {
|
|
jsonError('Unauthorized', 401);
|
|
}
|
|
|
|
$planId = (int) (filterRequest('plan_id', 'int') ?: 0);
|
|
if ($planId <= 0) {
|
|
jsonError('plan_id is required');
|
|
}
|
|
|
|
try {
|
|
$st = $con->prepare("
|
|
SELECT pl.*, pr.is_active AS provider_active
|
|
FROM insurance_plans pl
|
|
JOIN insurance_providers pr ON pr.id = pl.provider_id
|
|
WHERE pl.id = ? AND pl.is_active = 1 LIMIT 1
|
|
");
|
|
$st->execute([$planId]);
|
|
$plan = $st->fetch(PDO::FETCH_ASSOC);
|
|
} catch (PDOException $e) {
|
|
error_log('[assurance/subscribe] ' . $e->getMessage());
|
|
jsonError('Server error');
|
|
}
|
|
|
|
if (!$plan || (int) $plan['provider_active'] !== 1) {
|
|
jsonError('Plan not available');
|
|
}
|
|
|
|
// وثيقة نشطة قائمة: لا نشترك مرتين ولا نستبدل بصمت. تبديل الخطة
|
|
// قرار مالي — يمرّ بإلغاء صريح ثم اشتراك جديد، ليبقى في السجل أثر.
|
|
if (assuranceActivePolicy($con, $driverId)) {
|
|
jsonError('لديك وثيقة تأمين نشطة بالفعل. ألغِها أولاً لتغيير الخطة.');
|
|
}
|
|
|
|
$check = assuranceCheckEligibility($con, $driverId, $plan);
|
|
if (!$check['eligible']) {
|
|
jsonError('لم تستوفِ شروط هذه الخطة بعد: ' . implode('، ', $check['reasons']));
|
|
}
|
|
|
|
// بوابة الائتمان تُفحص هنا صراحةً رغم أن `obligationOpen` تفحصها أيضاً:
|
|
// الفحص هناك حارسٌ أخير يرمي استثناءً، ورسالته تصل للسائق كخطأ عام.
|
|
// السائق الذي يُرفض طلبه يستحق أن يعرف السبب وما يفعله ليستوفيه.
|
|
$product = obligationProduct($con, $plan['code']);
|
|
if (!$product) {
|
|
error_log("[assurance/subscribe] منتج الالتزام مفقود للخطة {$plan['code']}");
|
|
jsonError('تعذّر تفعيل الوثيقة حالياً');
|
|
}
|
|
|
|
$walletGate = obligationCheckWalletIncome($product, $driverId);
|
|
if (!$walletGate['eligible']) {
|
|
jsonError(
|
|
'التأمين يُخصم قسطه من أرباحك في المحفظة، ولذلك يشترط أن تمرّ أرباحك بها: '
|
|
. implode('، ', $walletGate['reasons'])
|
|
);
|
|
}
|
|
|
|
// كتابتان في معاملة واحدة: الوثيقة (السجل التأميني الذي يقرأه التطبيق)
|
|
// والالتزام (ما يراه محرك القيد والتسوية). وثيقة بلا التزام تعني تغطية
|
|
// تُمنح ولا يُحصَّل قسطها أبداً — وهي بالضبط الثغرة التي جاء هذا المحرك
|
|
// ليغلقها، فلا يجوز أن نعيد فتحها من باب الاشتراكات الجديدة.
|
|
try {
|
|
$con->beginTransaction();
|
|
|
|
$con->prepare("
|
|
INSERT INTO driver_insurance_policies
|
|
(driver_id, plan_id, status, started_at, rides_at_signup, rating_at_signup)
|
|
VALUES (?, ?, 'active', CURDATE(), ?, ?)
|
|
")->execute([$driverId, $planId, $check['rides'], $check['rating']]);
|
|
|
|
$policyId = (int) $con->lastInsertId();
|
|
|
|
obligationOpen($con, $driverId, $plan['code'], [
|
|
'cycle_amount' => (float) $plan['premium'],
|
|
'rides' => $check['rides'],
|
|
'rating' => $check['rating'],
|
|
]);
|
|
|
|
$con->commit();
|
|
} catch (Throwable $e) {
|
|
if ($con->inTransaction()) $con->rollBack();
|
|
error_log('[assurance/subscribe] تعذّر إنشاء الوثيقة: ' . $e->getMessage());
|
|
jsonError('تعذّر تفعيل الوثيقة حالياً');
|
|
}
|
|
|
|
error_log("[assurance] السائق $driverId اشترك في الخطة {$plan['code']} — وثيقة #$policyId");
|
|
|
|
jsonSuccess([
|
|
'policy_id' => $policyId,
|
|
'plan' => $plan['name_ar'],
|
|
'premium' => (float) $plan['premium'],
|
|
'currency' => $plan['currency'],
|
|
'billing_cycle' => $plan['billing_cycle'],
|
|
// أول قسط يُقيَّد في تشغيل الكرون القادم لا الآن: الاشتراك ليس
|
|
// دفعاً، والقيد يجب أن يكون له تاريخ واضح يوافق دورة الفوترة.
|
|
'starts_on' => date('Y-m-d'),
|
|
], 'تم تفعيل وثيقة التأمين');
|