منع تكرار العمليات المالية: قواعد خطيرة الآن!

x32x01
  • بواسطة x32x01 ||
خطوط حمراء تمنع كارثة تكرار العمليات المالية ⚠️
هقولك على شوية خطوط حمراء لازم ما تتجاوزهاش لو شغّال على نظام بيستقبل أو يسجّل عمليات مالية - لأن الغلط هنا مش مجرد bug، ده ممكن يبقى كارثة مالية بجد. الأسلوب هنا بسيط وواضح ومع أمثلة وكود صغير يساعدك تطبّق بسرعة 👇

أهم الأخطاء اللي لازم تتفادىها فورًا 🚩

  • جداول الحركات المالية لازم تحتوي على عمود فريد Unique يمنع التكرار نهائيًا (وأقصد هنا مش ID التقليدي). الهدف إن أي عملية مالية ليها بصمة فريدة تمنع تسجيلها مرتين سواء بالغلط أو بسبب webhooks اللي بتتكرر.
  • جدول المحافظ لازم كمان يحتوي على عمود فريد يمنع تكرار المحافظ لنفس المستخدم أو لنفس الحساب.
  • ما تهملش النسخ الاحتياطي: نسخ قواعد البيانات والكود دوريًا وبأكثر من نسخة (local + remote).
  • سجلات التغييرات (Audit Logs) لازم تكون شغالة وتسجل كل حاجة - مين عمل إيه وامتى وليه. ده مهم جدًا للمراجعة واستعادة الحالة بعد أي مشكلة.

ليه عمود Unique مهم أكتر من مجرد ID؟ 🤔

الـ ID العادي غالبًا بياجي من auto-increment في قاعدة البيانات، وده مش بيحل مشكلة التكرار الناتج عن وصول نفس الحدث أكثر من مرة. لو بوابة الدفع رجعت الـ webhook مرتين، هنحصل على صفين مختلفين لأن كل صف له ID مختلف - بس محتوى العملية هو نفسه. عشان كده محتاجين بصمة فريدة للعملية (Unique field) بتعبر عن العملية مش رقم الصف.

مثال عملي حصل في أحد الأنظمة (قصة حقيقية) 😱

في نظام، جدول العمليات كان فيه الحقول دي:
id, user_id, transaction_type, amount, slug
المشكلة: بوابة الدفع بترجع الويبهوك مرتين أحيانًا - النتيجة: العميل دفع 200 ريال، وسجلناها مرتين => رصيد العميل اتزوّد 400 ريال. كارثة.

الحل الجذري اللي طبّقناه ✅

أضفنا حقل اسمه internal_checkout_id - رقم فريد داخلي للعملية، متكوّن بطريقة معقّدة نوعًا ما بحيث يكون فريد جدًا. الفكرة إن الرقم ده بيتولّد اعتمادًا على: بيانات العملية، رقم المستخدم، الوقت (ساعة ودقيقة)، نوع العملية، والمبلغ. الهدف: لو الويبهوك اتكرر، القيمة دي هتكون نفسها للنداءين الأول والتاني - وده يخلّي قاعدة البيانات ترفض السطر التاني لو حطيت عليه قيد فريد (UNIQUE).

إزاي نولّد internal_checkout_id عمليًا؟ 🔧 (كود مثال)​

فكرة سريعة: نستخدم تجميع للحقل + هاش (مثل SHA256) ثم نخد أول N حروف. هنا مثال بلغة JavaScript/Pseudo:
JavaScript:
// مثال توليد internal_checkout_id
const crypto = require('crypto');

function generateInternalCheckoutId({userId, amount, txnType, userType, timestamp}) {
  // هنستخدم الساعة والدقيقة بس
  const dt = new Date(timestamp);
  const prefix = `${dt.getFullYear()}${dt.getMonth()+1}${dt.getDate()}${dt.getHours()}${dt.getMinutes()}`;

  // نجمع الحقول الأساسية
  const base = `${prefix}|${userId}|${txnType}|${userType}|${amount}`;

  // نعمل هاش قوي
  const hash = crypto.createHash('sha256').update(base).digest('hex');

  // نرجع prefix + جزء من الهـاش (مثلاً 16 حرف)
  return `${prefix}-${hash.substring(0,16)}`;
}
الفكرة إن الـ prefix بيضمن تمييز زمني على مستوى الدقيقة، والهـاش بيضمن تشكيلة فريدة حتى لو المفترضات شبه بعضها.

ربط ده بالقيد في DB (مثال SQL) 🛡️

لو بتستخدم MySQL/Postgres، ضيف قيود فريدة:
SQL:
-- مثال PostgreSQL
ALTER TABLE transactions
ADD COLUMN internal_checkout_id VARCHAR(64);

-- ثم قيد فريد
CREATE UNIQUE INDEX ux_transactions_internal_checkout_id
ON transactions (internal_checkout_id);
لو أي محاولة لإدخال عملية بنفس internal_checkout_id هتفشل - ودي سلوك مرغوب علشان يمنع التكرار.

التعامل مع webhooks المتأخرة أو العابرة للحد الزمني ⏱️

سؤال مهم: وإيه لو الويبهوك الأول جه في نهاية الدقيقة والتاني جه عند بداية الدقيقة اللي بعدها؟ ممكن يطلعوا internal_checkout_id مختلفين لو استخدمنا prefix بالساعة والدقيقة فقط. حلول عملية:
  1. ابدأ تستخدم نافذة زمنية (dedup window): بدل الاعتماد على دقيقة بس، اعمل مقارنة للعمليات المشابهة في آخر X دقائق (مثلاً 2-5 دقائق). لو لقيت عملية بنفس userId وamount وtxnType في آخر 5 دقايق اعتبرها مكررة.
  2. خزن signature للويبهوك: معظم بوابات الدفع بترسل header فيه signature أو id خاص بالمعاملة - خزّن الـ webhook_id أو signature وحط عليه قيد Unique. ده الحل الأقوى لو البوابة بتقدمه.
  3. استخدام upsert/transaction: اعمل محاولة إدخال مع ON CONFLICT DO NOTHING (Postgres) أو INSERT ... ON DUPLICATE KEY UPDATE في MySQL - وارجع الرسالة إن العملية اتسجلت أصلاً بدل ما تضيف واحدة جديدة.
  4. حالة retried webhooks: خلي عندك لوج لكل webhooks الواردة مع حالة (processed/not processed) بحيث أي webhook تيجي تقدر تتحقق بسرعة لو العملية اتنفذت قبل كده.

مثال تبديل بيانات webhook باستعمال upsert (Postgres) 🔁

SQL:
INSERT INTO transactions (internal_checkout_id, user_id, amount, txn_type, created_at)
VALUES ($1, $2, $3, $4, NOW())
ON CONFLICT (internal_checkout_id) DO NOTHING;
لو conflict حصل، العملية مش هتتكرر.

سجلات التغييرات (Audit Logs) مش رفاهية - دي ضرورة 📜

سجل كل حدث مهم: من عمل create/update/delete، ومنين اتعمل؟ مين المستخدم أو السيرفر؟ إمتى؟ واحتفظ بالـold_value والـnew_value. الكود الجيد بيخلّي الـaudit logs جزء لا يتجزأ من المنطق، ومهم جدًا للاسترجاع والتحقيق لو حصلت مشكلة.

النسخ الاحتياطي واستراتيجية الاسترجاع 🧰

  • اعمل نسخ يومية أو أكثر اعتمادًا على حجم التغييرات.
  • احتفظ بنسخ على مواقع مختلفة (off-site).
  • اختبر عملية الاسترجاع بانتظام - لازم تعرف إن النسخ بتشتغل فعلاً.
  • لو النظام حساس جدًا، فكر في replication مع delay لتجنب نشر خطأ تلقائيًا لكل النسخ.

نصايح عملية سريعة قبل ما تخلص 🔍

  • خلي عندك Unique constraint على internal_checkout_id في جدول العمليات.
  • لو البوابة بتديك checkout_id خارجي استخدمه وخرّج له قيد Unique. لو مش بتديك، أنشئ داخليًا زي المثال اللي فوق.
  • اعمل سياسات retry ذكية للـwebhooks، وسجل كل محاولة.
  • فعّل Audit Logs وراجعها دوريًا.
  • خطط لنسخ احتياطي واختبار الاسترجاع.

خلاصة سريعة (خُلاصة تطبيقية) 🔥

  • الخطأ الشائع: الاعتماد على id فقط كحماية ضد التكرار - ده مش كفاية.
  • الحل: خلي لكل عملية بصمة فريدة internal_checkout_id + قيد Unique في DB + منطق للتحقق من webhooks.
  • ضيف Audit Logs + نسخ احتياطي + مراقبة - دول اللي هينقذك لو حاجة غلطت.
 
المواضيع ذات الصلة
x32x01
الردود
0
المشاهدات
507
x32x01
x32x01
x32x01
الردود
0
المشاهدات
769
x32x01
x32x01
x32x01
الردود
0
المشاهدات
805
x32x01
x32x01
x32x01
الردود
0
المشاهدات
536
x32x01
x32x01
x32x01
الردود
0
المشاهدات
703
x32x01
x32x01
x32x01
الردود
0
المشاهدات
451
x32x01
x32x01
x32x01
الردود
0
المشاهدات
423
x32x01
x32x01
x32x01
الردود
0
المشاهدات
1K
x32x01
x32x01
الدخول أو التسجيل السريع
نسيت كلمة مرورك؟
إحصائيات المنتدى
المواضيع
1,867
المشاركات
2,067
أعضاء أكتب كود
475
أخر عضو
غفعفغب
عودة
أعلى