feat: P1 — بوابات دفع حقيقية (PayMob) + إغلاق ثغرة webhook حرجة
الأهم أولاً: `PaymentsService.webhook()` كان يقبل أي جسم `{ payment_id }`
بلا أي تحقّق توقيع — من يعرف معرّف دفعة معلَّقة كان يستطيع تحويلها «ناجحة»
ويشحن رصيداً من عدم (محفظة راكب أو رصيد سائق تشغيلي). الآن كل تغيير حالة
محروس بـ`adapter.verifyWebhook(headers, payload, tenant)`، ولا شيء يُقرأ من
الحمولة قبل ذلك كقرار ثقة — قراءة المرجع لتحديد المستأجر ليست قراراً.
البنية (docs/07 · docs/24 — P1):
- `PaymentAdapter`: charge() يعيد instant (كاش) · redirect (بوابة API حقيقية)
· invoice (بلا API، تسوية عبر P2).
- PayMob (مصر): تسلسل auth→order→payment_key→iframe حقيقي عبر fetch،
وتحقّق HMAC-SHA512 على تسلسل حقول ثابت (بروتوكول PayMob الرسمي بالضبط)
بمقارنة ثابتة الزمن. مفاتيح كل مستأجر مستقلة — حساب تاجر خاص به.
- كليق/شام كاش/MTN/سيرياتيل/زين كاش: محوّل مشترك واحد لأن سلوكها متطابق
فعلياً في سيرو (`create_*_invoice.php` تُنشئ فاتورة فقط، لا نداء بوابة
حيّاً) — مرجع + حساب استلام معروض، والتسوية عبر رسالة SMS لا webhook.
MTN/سيرياتيل الحقيقيَّين (توكن+OTP) موثَّقان كبند مفتوح: لا نبني تكاملاً
لا نملك اعتماداً حيّاً للتحقّق منه.
- `PATCH /payments/settings` لأدمن المستأجر: مفاتيح PayMob · حسابات
الاستلام · سرّ webhook الرسائل — الاستجابة لا تُعيد الأسرار.
تنظيف: إزالة الإشارات المتبقّية لحاوية `martin` من docs/07 (أُزيلت فعلياً
سابقاً)، وتحديث هيكل الكود الموثَّق ليطابق ما هو مبنيّ فعلاً.
25 اختباراً جديداً (226 إجمالاً) — منها توقيع PayMob محسوب فعلياً ومُتحقَّق،
وتلاعبٌ بالحمولة بعد التوقيع يُرفض، وسبع حالات تثبت إغلاق ثغرة الـwebhook.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
da035e46a4
commit
d4ac38ca87
@@ -93,7 +93,16 @@
|
||||
- `GET /payments/sms/review` + `POST /payments/sms/review/:id/match` لطابور المراجعة والربط اليدوي.
|
||||
- 13 اختباراً تغطّي التزوير والتكرار والتخمين والالتباس.
|
||||
|
||||
**الباقي**: P1 (بوابات فعلية) · P4 (تقارير أوسع) · وسحب المالك أرباحه من `tenant_wallet`.
|
||||
**منجَز أيضاً (P1 — البوابات الفعلية):**
|
||||
- **الثغرة الأخطر في P كلها أُغلقت**: `webhook()` كان يقبل أي جسم `{ payment_id }` بلا أي تحقّق — من يعرف معرّف دفعة معلَّقة يحوّلها «ناجحة» ويشحن رصيداً من عدم. الآن كل تغيير حالة محروس بـ`adapter.verifyWebhook(headers, payload, tenant)`، ولا قراءة قبله سوى استخراج المرجع لتحديد أي مستأجر (قراءة بحتة، لا قرار ثقة).
|
||||
- `PaymentAdapter` (docs/07): `charge()` يعيد `instant` (كاش) · `redirect` (بوابة API حقيقية) · `invoice` (بلا API — مرجع + حساب استلام، تسوية عبر P2).
|
||||
- **PayMob** (مصر) — البوابة الوحيدة بـAPI فعلية بين مزوّدي المنطقة: تسلسل auth→order→payment_key→iframe حقيقي، وتحقّق HMAC-SHA512 على تسلسل حقول ثابت (بروتوكول PayMob الرسمي، لا اختراعاً) بمقارنة **ثابتة الزمن**. مفاتيح كل مستأجر في `settings.payments.paymob` — حساب تاجر مستقل لكل مستأجر، فمالُه يصل لحسابه هو.
|
||||
- **كليك · شام كاش · MTN · سيرياتيل · زين كاش** — محوّل واحد مشترك (`InvoiceAdapter`): بلا API فعلية اليوم (مطابق لما هو مطبَّق فعلاً في `payment_server` بسيرو)، يولّد مرجعاً ويعرض حساب الاستلام المضبوط في `settings.payments.transfer_targets`، والتسوية عبر رسالة SMS (P2) لا webhook من المزوّد.
|
||||
- ⚠️ MTN وسيرياتيل يملكان API إنتاجية حقيقية (توكن/OTP/مفتاح خاص) في سيرو، لكنها تتطلّب بيانات اعتماد حيّة لا نملكها لاختبارها فعلياً — **لن نبني تكاملاً لا يمكن التحقّق منه**. بند مفتوح يُستأنف عند توفّر حساب تجريبي حقيقي.
|
||||
- `PATCH /payments/settings` (أدمن المستأجر): يضبط مفاتيح PayMob · حسابات الاستلام · سرّ webhook الرسائل. الاستجابة لا تُعيد الأسرار — "مضبوط/غير مضبوط" فقط.
|
||||
- 25 اختباراً جديداً (منها توقيعٌ حقيقي مُتحقَّق ومُتلاعَبٌ به عمداً بعد التوقيع، وسبع حالات تثبت إغلاق ثغرة الـwebhook تحديداً).
|
||||
|
||||
**الباقي**: P4 (تقارير أوسع) · سحب المالك أرباحه من `tenant_wallet` · MTN/سيرياتيل الحقيقيَّين عند توفّر اعتماد.
|
||||
|
||||
## 7. أثره على ما هو مبنيّ الآن
|
||||
|
||||
|
||||
Reference in New Issue
Block a user