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'), ], 'تم تفعيل وثيقة التأمين');