الأساس المالي حسب docs/24: فصل ما نملكه عمّا نحتفظ به لغيرنا. - جدولان منفصلان (`tenant_revenue_ledger` · `tenant_pending_ledger`) لا جدول واحد بعمود نوع: استعلامٌ ينسى الشرط كان يجعل المالك يسحب من مال الركّاب. مضافان فقط، والرصيد مشتقّ لا حقل يُحدَّث. - فهرس فريد (tenant_id, ref) = حارس التسوية المزدوجة. التصادم يُلتقط من القاعدة لا بالفحص المسبق وحده — نداءان متزامنان يمرّان معاً قبل أي كتابة. - رسم العملية من مصدر واحد (35 ل.س · 5 ج.م · 0.20 د.أ) قابل للتجاوز من إعداد المستأجر، مع قصّه عند المبلغ حتى لا يخرج المستخدم بصافٍ سالب. - توجيه الدفع: شحن السائق ← إيراد + رصيده التشغيلي · شحن الراكب ← أمانة · الرسم ← إيراد. وفُتح مسار شحن السائق الذي لم يكن له مدخل إطلاقاً. - ثغرة سُدّت: `purpose` يصل من الجسم، فراكب كان يستطيع إرسال `credit_topup` فيُسجَّل مالُه إيراداً ويُشحن حساب سائق لا يملكه. الدور الآن من التوكن. - `/admin/overview`: الإيراد من الدفتر، وعمولة الرحلات حقل منفصل عنه. - `/admin/wallet/summary` و`/revenue-by-reason` لأدمن المستأجر. 25 اختباراً جديداً (188 إجمالاً، كلها خضراء). أحدها أمسك عطلاً فعلياً: صافٍ صفري كان ينادي الدفتر بصفر فيرمي خطأً بعد قيد الرسم — عملية نصف مطبَّقة. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
8.6 KiB
24 — محفظة المستأجر وإيرادات المنصة (فلسفة المال)
قرارات المالك 2026-07-18. هذا المستند يحكم كل ما يتعلّق بحركة المال، ويسبق أي كود في المجموعة P. ذو صلة: 18 (الرصيد والعمولة) · 22 §P · siro-reference.
1. القاعدة الحاكمة: مالٌ لنا، ومالٌ نحتفظ به لغيرنا
الخطأ الذي يقع فيه أغلب التطبيقات — وسيرو منها — هو خلط النوعين في وعاء واحد، فيبدو الرصيد ضخماً بينما أغلبه دَيْن على المنصة لا ملكاً لها.
| النوع | مثاله | مِلك مَن؟ | في الميزانية |
|---|---|---|---|
| إيراد | شحن السائق لرصيده التشغيلي · عمولة كل عملية دفع | المستأجر (مالك التطبيق) | دخل مُحقَّق |
| أمانة (pending) | شحن الراكب لمحفظته · أجرة رحلة لم تُسوَّ بعد | الراكب/السائق | التزام، لا دخل |
المحصّلة العملية: نقود الأمانة تمرّ عبر حسابنا لكنها ليست لنا — سنسلّمها للسائق أو نردّها للراكب. عدّها إيراداً يعني تقريراً ماليّاً كاذباً، وقراراً بالإنفاق من مالٍ ليس لنا.
2. محفظتا المستأجر
لكل مستأجر (مثال: siro) محفظتان منفصلتان — لا حقل واحد بحالتين:
| المحفظة | يدخلها | يخرج منها |
|---|---|---|
tenant_wallet (محفظة المستأجر — «سيرو والت») |
شحن الرصيد التشغيلي للسائقين · عمولة عمليات الدفع · عمولة الرحلات | سحب المالك لأرباحه |
tenant_pending (أمانات) |
شحن الراكب · أجرة محصَّلة لم تُسلَّم | تسوية للسائق · ردّ للراكب |
لماذا محفظتان لا حقل status: التقارير والسحب يقرآن tenant_wallet وحده. لو كان الفصل بحقلٍ في صفوف واحدة، فاستعلامٌ واحد ينسى شرط الحالة يجعل المالك يسحب من أموال الركّاب — وهذا خطأ يقع مرة واحدة ويكلّف ثقة السوق.
دفتر مضاف فقط (append-only) لكل محفظة: كل حركة صفٌّ جديد بمرجعها وسببها، لا تحديثٌ لرصيد. الرصيد مجموعٌ مشتق. (سيرو خزّن المال في varchar(10) وجمع نصوصاً — لا يُنقل هذا إطلاقاً.)
3. مسارات المال الثلاثة
3.1 شحن السائق رصيده التشغيلي ← إيراد مباشر
هذا هو نموذج العمل الأساسي (docs/18). السائق يدفع مقدَّماً ليعمل؛ المبلغ الحقيقي يصل → tenant_wallet فور نجاح الدفع. لا مرحلة أمانة: المال صار للمستأجر مقابل خدمة تشغيلية.
3.2 شحن الراكب محفظته ← أمانة
المال يدخل حسابنا لكنه يبقى ملك الراكب → tenant_pending + رصيد الراكب. حين يركب، يُخصم من رصيده وتُسوَّى الأجرة للسائق (ناقص العمولة). الجزء الوحيد الذي يتحوّل إلى إيراد هو العمولة.
3.3 عمولة عملية الدفع ← إيراد
رسم ثابت على كل عملية تحويل/دفع، محدَّد بالدولة (countryPack):
| الدولة | الرسم لكل عملية |
|---|---|
| سوريا | 35 ل.س (بالعملة الجديدة) |
| مصر | 5 ج.م |
| الأردن | 20 قرشاً (0.20 د.أ) |
يُقتطع عند العملية ويُقيَّد في tenant_wallet. يُخزَّن في إعداد المستأجر لا في الكود — سيرو كتب الرقم مرتين في مكانين فتناقضا وتسرّب المال (docs/21).
4. جدول لكل وسيلة دفع + تقارير
نمط سيرو الصحيح: جدول مستقل لكل وسيلة (cliq_invoices · invoices_shamcash · mtn_invoices · ecash_transactions …) لأن لكل مزوّد حقولَه ومرجعَه وسلوكَ تأكيده. فوقها محوّل موحّد يعطي الباقي واجهة واحدة.
التقارير المطلوبة للوحة المستأجر: الدخل حسب الوسيلة · حسب اليوم · شحن السائقين مقابل الأمانات · العمولات المحصَّلة · المعلّق غير المسوّى.
5. الأسواق بلا API (كليك · شام كاش) — تسوية بالرسائل
كليك وشام كاش لا توفّران API. الحل المُثبَت في سيرو ويُنقل:
- تطبيق أندرويد صغير على جهاز مخصّص يلتقط رسائل التأكيد الواردة من المزوّد.
- يرفعها خاماً إلى webhook عندنا (
raw_sms_log) — تُحفظ كما وصلت، لا تُحلَّل على الجهاز. - يُستخرج المبلغ والمرجع والمرسل (Gemini كما في
process_with_gemini.php) ثم تُطابَق مع فاتورة معلّقة وتُسوَّى المحفظة.
شروط لا تُتنازل عنها: الرسالة الخام تُحفظ دائماً (أثر للنزاع) · المطابقة مرة واحدة بمفتاح تفرّد على مرجع العملية (وإلا سوّت رسالةٌ مكرّرة الفاتورة مرتين) · وما لا يُطابَق آلياً يذهب لطابور مراجعة بشرية لا يُهمَل بصمت.
6. ما لا يُنقل من سيرو (مُدقَّق — docs/21)
- IDOR في
request_payout.php: هوية السائق من الطلب لا من التوكن → سحبُ رصيد الغير. (Tripz سليم: من التوكن.) - بلا حجز رصيد عند طلب السحب → طلبات متزامنة تمرّ كلها = صرفٌ مزدوج.
- رسم 3500 متناقض: الفحص يشترط
amount + 3500والتسوية تخصمamount − 3500→ تسريب مال. finalizePayoutخمس كتابات بلا معاملة → فشلٌ في المنتصف يترك سحباً نصف مسوّى.- المال في
varchar(10). - جدولان متداخلان لنفس المفهوم (
payout_requestsوdriver_withdrawal_requests).
6.5 حالة التنفيذ (2026-07-18)
منجَز (P0 · P3 · التوجيه · P5):
tenant_revenue_ledgerوtenant_pending_ledger— جدولان مضافان فقط بهجرة1721930000000، وفهرس فريد على(tenant_id, ref)يمنع التسوية المزدوجة.TenantWalletService:creditRevenue·creditPending·releasePending·balance·summary·revenueByReason. التكرار يُلتقط من القاعدة لا بالفحص المسبق وحده (نداءان متزامنان يمرّان معاً قبل أن يكتب أيّهما).transactionFeeFor— مصدر واحد للرقم، وcappedFeeيمنع صافياً سالباً حين يفوق الرسمُ المبلغَ.- توجيه الدفع في
markSuccess:credit_topup→ إيراد + رصيد السائق ·topup→ أمانة + محفظة الراكب · الرسم → إيراد دائماً. credit_topupللسائقين فقط — الدور من التوكن لا من الجسم. بدونه يرسل راكبٌcredit_topupفيُسجَّل مالُه إيراداً محقَّقاً ويُشحن حساب سائق لا يملكه.GET /admin/wallet/summaryو/revenue-by-reasonلأدمن المستأجر (نطاقه من التوكن)./admin/overviewيفصلtrip_commissionعنrevenueالكلي.
الباقي: P1 (بوابات فعلية) · P2 (تسوية بالرسائل) · P4 (تقارير أوسع) · وسحب المالك أرباحه من tenant_wallet.
7. أثره على ما هو مبنيّ الآن
GET /admin/overview يحسب revenue = مجموع عمولة الرحلات فقط. بعد هذا المستند يصير الإيراد الحقيقي = عمولة الرحلات + شحن السائقين + عمولات عمليات الدفع، والأمانات تُستثنى صراحةً. النقطة تحتاج تحديثاً ضمن P.