> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# كيف يعمل الصراف الآلي ATM من الداخل؟
- URL: https://arabroot.io/articles/كيف-يعمل-الصراف-الالي-atm-من-الداخل/
- Published: 2026-10-27T16:28:55.000Z
- Updated: 2026-10-27T19:06:39.000Z
- Description: نفتح الصراف: الموزع والأدراج ولوحة الرقم السري ومعيار XFS ورسائل المضيف، ونحاكي سحباً يتعطل فيه الموزع وكيف يصحح العكس الخصم.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, التقنية المالية والمدفوعات

لنفرض أن فريق القنوات الإلكترونية في بنك متوسط الحجم وصلته صباح الأحد شكوى من عميل يقول إنه طلب من الصراف 600 ريال، فخرجت له ورقة واحدة من فئة 500، وظهرت على الشاشة رسالة «نعتذر، لا يمكن إتمام العملية»، ثم وصلته رسالة نصية تقول إن 600 ريال خصمت من حسابه. والعميل غاضب، وموظف الفرع لا يعرف ماذا حدث، ومدير الفريق يريد جواباً قبل الظهر: هل أخذ البنك 100 ريال من العميل دون حق، أم أن النظام صحح نفسه؟ وما الذي يضمن ألا يتكرر هذا مع آلاف العمليات التي تمر على صرافات البنك كل يوم؟

والإجابة عن هذا السؤال تحتاج أن نفتح الصراف من الداخل: ما الأجهزة التي فيه، وكيف يكلمها التطبيق الذي يرسم الشاشات، وكيف يكلم الصراف نظام البنك عبر الشبكة، وماذا يحدث في الثواني التي تفصل بين ضغطة «تأكيد» وخروج الأوراق. وقد شرحنا في [مقال عملية الدفع بالبطاقة](https://arabroot.io/articles/%D9%83%D9%8A%D9%81-%D8%AA%D8%B9%D9%85%D9%84-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%A7%D9%84%D8%AF%D9%81%D8%B9-%D8%A8%D8%A7%D9%84%D8%A8%D8%B7%D8%A7%D9%82%D8%A9-%D8%A7%D9%84%D8%AA%D9%81%D9%88%D9%8A%D8%B6-%D9%88%D8%A7%D9%84%D8%AA%D8%B3%D9%88%D9%8A%D8%A9-%D9%88%D8%A7%D9%84%D8%A7%D8%B9%D8%AA%D8%B1%D8%A7%D8%B6/) أطراف عملية البطاقة والتفويض والتسوية، وفي [مقال رسائل البنوك](https://arabroot.io/articles/%D8%B1%D8%B3%D8%A7%D8%A6%D9%84-%D8%A7%D9%84%D8%A8%D9%86%D9%88%D9%83-iso-8583-%D9%84%D9%84%D8%A8%D8%B7%D8%A7%D9%82%D8%A7%D8%AA-%D9%88-iso-20022-%D9%84%D9%84%D8%AA%D8%AD%D9%88%D9%8A%D9%84%D8%A7%D8%AA/) كيف تبنى رسالة ISO 8583 وتقرأ خريطتها Bitmap، فلن نعيد ذلك هنا، وإنما نبني عليه: الصراف طرف من نوع خاص، لأنه لا يكتفي بأخذ موافقة البنك، بل يخرج مالاً حقيقياً من خزنة، وكل خطأ بين الموافقة وخروج المال له أثر مباشر على حساب عميل.

وسوف نتابع شكوى العميل خطوة خطوة، ونبني في الطريق محاكاة كاملة بلغة Python لصراف يتبادل رسائل ISO 8583 مع مضيف Host تجريبي، ويكلم موزع نقد Cash Dispenser برسائل على شكل معيار XFS4IoT، ثم نعيد تمثيل العطل نفسه ونرى كيف يصححه النظام. وسوف نناقش في هذا المقال ما يلي:

- ماذا يوجد داخل الصراف: الموزع والأدراج ولوحة الرقم السري المشفرة وقارئ البطاقة والحاسب الداخلي والسجل.
- كيف يكلم التطبيق الأجهزة عبر معيار XFS، ولماذا انتقل المعيار من واجهة Windows إلى رسائل JSON في XFS4IoT.
- كيف يكلم الصراف المضيف: رسالة السحب 0200 ورمز المعالجة 01، ورسائل إدارة الشبكة 0800.
- تجربة عملية: صراف ومضيف في Containers منفصلة، وسبعة سيناريوهات منها عطل الموزع وانتهاء المهلة وضياع الرد.
- العكس الجزئي Partial Reversal والعكس المكرر، وإدارة النقد في الأدراج ومطابقتها.
- الحماية من جهة المدافع: رمز التحقق MAC على الرسائل، وتوكن الصرف من طرف إلى طرف، وحالتان موثقتان.
- كيف يبدو هذا في بنك حقيقي، والأخطاء الشائعة، وقائمة تحقق.

## ماذا يوجد داخل الصراف الآلي ATM؟

من الخارج يبدو الصراف شاشة ولوحة أرقام وفتحتين، لكنه في الحقيقة مجموعة أجهزة مستقلة يربطها حاسب داخلي، ولكل جهاز منها برنامج تشغيل وحالة وأعطال خاصة به. وأول ما فعله الفريق في التحقيق أنه رسم هذه الأجهزة على السبورة، لأن الشكوى تمس ثلاثة منها على الأقل: لوحة الرقم السري التي أدخل عليها العميل رقمه، والحاسب الذي أرسل الطلب إلى البنك، والموزع الذي أخرج ورقة واحدة بدل اثنتين. والصورة التالية تبين الوحدات الرئيسية:

![رسم لصراف آلي من الداخل: الشاشة والأزرار الجانبية FDK في الأعلى، ثم قارئ البطاقة ولوحة الرقم السري EPP وطابعة الإيصال وفتحة النقد، وفي الأسفل خزنة فيها موزع وثلاثة أدراج بفئات 500 و100 و50 ودرج للأوراق المرفوضة Reject، والحاسب الداخلي يتصل بالمضيف عبر شبكة البنك برسائل ISO 8583](https://arabroot.io/content/images/2026/10/atm-inside-01-inside.webp)

الصراف مجموعة أجهزة يربطها حاسب داخلي، والخزنة في الأسفل منفصلة عن الجزء العلوي

والوحدات التي تهمنا في القصة كما يلي:

- **الموزع Cash Dispenser والأدراج Cassettes**: الأدراج صناديق مقفلة يملؤها فريق نقل النقد، ولكل درج فئة وعداد يعرف كم ورقة فيه، والموزع يسحب الأوراق من الأدراج واحدة واحدة، ويعدها ويفحص سمكها، ويجمعها في مكدس داخلي Stacker، ثم يقدمها للعميل من فتحة النقد Shutter. والورقة التي يشك فيها، لأنها ملتصقة بأخرى أو مائلة، يرسلها إلى درج المرفوضات Reject بدل أن يقدمها.
- **لوحة الرقم السري المشفرة Encrypting PIN Pad**، واختصاراً EPP: ليست لوحة مفاتيح عادية، بل جهاز مقاوم للعبث فيه مفاتيح تشفير، يحول الرقم السري إلى كتلة مشفرة PIN Block داخله لحظة إدخاله، فلا يرى التطبيق ولا الحاسب الداخلي الرقم نفسه أبداً. وسوف نشرح كتل الرقم السري ومفاتيحها بالتفصيل في مقال وحدات أمن العتاد HSM من هذه المرحلة.
- **قارئ البطاقة Card Reader**: يقرأ الشريحة EMV أو الشريط الممغنط أو البطاقة دون تلامس، ويستطيع في بعض الأجهزة أن يحتجز البطاقة إذا طلب البنك ذلك. وما يحدث بين القارئ والشريحة موضوع مقال الشريحة والدفع اللاتلامسي.
- **الحاسب الداخلي**: حاسب شخصي في الغالب يعمل عليه تطبيق الصراف، وهو الذي يرسم الشاشات ويقرر تسلسل العملية ويكلم الأجهزة من جهة والمضيف من جهة أخرى.
- **الطابعة والسجل Journal**: طابعة الإيصال للعميل، وسجل العمليات الذي يحفظ كل خطوة وكل عطل، وكان قديماً شريطاً ورقياً، وهو اليوم ملف إلكتروني في الغالب. وهذا السجل هو أول ما يطلبه فريق التحقيق في أي شكوى.

ولاحظ في الصورة أن الخزنة Safe في الأسفل منفصلة عن الجزء العلوي، فالأجهزة التي يصل إليها فني الصيانة لإصلاح الطابعة أو الشاشة ليست في المكان نفسه مع النقد، والوصول إلى الخزنة له إجراءات وأقفال مختلفة. وهذا الفصل بين «منطقة الخدمة» و«منطقة النقد» سنعود إليه في فقرة الحماية، لأن بعض الهجمات الموثقة استغلت أن الحاسب الداخلي في المنطقة الأقل حماية، وهو الذي يأمر الموزع.

![صورة لصراف آلي حقيقي باب خزنته السفلية مفتوح، وفي الأعلى الشاشة والأزرار الجانبية ولوحة الأرقام، وفي الخزنة رفوف الموزع والأدراج، والباب الحديدي السميك بأقفاله إلى جانبها](https://arabroot.io/content/images/2026/10/atm-inside-07-photo-atm-opened.webp)

صراف حقيقي وباب خزنته مفتوح: الجزء العلوي فيه الشاشة ولوحة الأرقام، والموزع والأدراج في الخزنة خلف باب حديدي سميك، وهو الفصل نفسه بين منطقة الخدمة ومنطقة النقد في الرسم أعلاه. الصورة: Ctac، بترخيص [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/?ref=arabroot.io)، المصدر: [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:ATM%5Fopened.jpg?ref=arabroot.io) (تم تصغيرها وتحويلها إلى WebP، والنسخة متاحة بالرخصة نفسها).

## كيف يكلم التطبيق الأجهزة؟ معيار XFS

لنفرض أن البنك يملك صرافات من شركتين مصنعتين، ولكل شركة موزع بأوامر مختلفة. الطريقة البسيطة أن يكتب البنك تطبيقاً لكل شركة، فإذا أراد شاشة جديدة أو خدمة جديدة كتبها مرتين واختبرها مرتين، وإذا أراد شراء صرافات من شركة ثالثة بدأ من الصفر. ولهذا ظهرت في التسعينات طبقة وسيطة تجعل التطبيق يقول «اصرف 600» بلغة واحدة، وتترجمها كل شركة إلى أوامر جهازها. وهذه الطبقة اسمها XFS، اختصاراً لـ Extensions for Financial Services.

وبحسب [ملخص تاريخ المعيار](https://en.wikipedia.org/wiki/CEN/XFS?ref=arabroot.io)، بدأت الفكرة في Microsoft باسم WOSA/XFS، وصدر الإصدار 1.11 في 1995، ثم تبنته اللجنة الأوروبية للتوحيد القياسي CEN في 1998، فصار يعرف في الصناعة باسم CEN/XFS، وتوالت إصداراته من 3.0 في 2000 إلى 3.50 في 2022\. وفكرته معمارية عميل وسيرفر على Windows: التطبيق عميل يستدعي واجهة برمجية موحدة، وتحتها مزود خدمة Service Provider تكتبه الشركة المصنعة لكل جهاز. ويذكر الملخص نفسه ملاحظة يعرفها كل من عمل في هذا المجال: الشركات تفسر المعيار تفسيرات مختلفة، فتستخدم البنوك غالباً برنامجاً وسيطاً Middleware فوق XFS ليخفي هذه الفروق.

### من واجهة Windows إلى رسائل JSON: XFS4IoT

مع الوقت ظهرت مشكلات الإصدار 3.x نفسه، وقد عددتها مقدمة [مواصفة XFS4IoT المنشورة لدى CEN](https://www.cencenelec.eu/media/CEN-CENELEC/AreasOfWork/CEN%20sectors/Digital%20Society/CWA%20Download%20Area/XFS/CWA17852/cwa-17852-xfs4iot-specification-release-v2-0.pdf?ref=arabroot.io) (إصدار 2023-02 المنشور للمراجعة): هياكل البيانات الثنائية تجعل إضافة وظيفة جديدة صعبة دون أن تتعارض مع ما قبلها، فظهرت نسخ متعددة من الأمر نفسه، والأجهزة المركبة مثل أجهزة إعادة تدوير النقد كانت صعبة التطبيق، والمعيار مرتبط بنظام Windows. ولذلك وضعت المواصفة لخليفة المعيار متطلبات منها أن يعمل على أي نظام تشغيل وبأي لغة، وأن يدعم الحماية من طرف إلى طرف على مستوى التطبيق، وأن يعمل في بيئة حوسبة محدودة الموارد.

والنتيجة أن XFS4IoT، كما يقول القسم 2.3 من المواصفة، يتيح خدماته عبر WebSocket برسائل JSON معرفة بمخططات JSON Schema، ولكل رسالة جزءان: ترويسة Header فيها نوع الرسالة واسمها وإصدارها ورقم الطلب، وحمولة Payload فيها البيانات. وأنواع الرسائل خمسة: أمر Command من العميل، ثم إقرار Acknowledge بأن الخدمة استلمته، ثم أحداث Events أثناء التنفيذ، ثم رسالة إتمام Completion، وأحداث غير مطلوبة Unsolicited تبلغ عن تغير الحالة في أي وقت. وتقسم المواصفة الأجهزة إلى واجهات، منها قارئ البطاقة Card Reader وموزع النقد Cash Dispenser وإدارة النقد Cash Management ولوحة الرقم PinPad وإدارة المفاتيح Key Management والطابعة، والصورة التالية تضع هذه الطبقات في مكانها:

![طبقات البرمجيات في الصراف: تطبيق الصراف في الأعلى، ثم طبقة XFS بنسختيها XFS 3.x كواجهة برمجية على Windows وXFS4IoT كرسائل JSON عبر WebSocket، ثم مزود الخدمة من الشركة المصنعة، ثم الأجهزة CardReader وPinPad وCashDispenser وPrinter، وعلى اليمين المضيف الذي يكلمه التطبيق برسائل 0200 و0420 و0800، وملاحظة أن الرقم السري يبقى مشفراً من EPP إلى المضيف](https://arabroot.io/content/images/2026/10/atm-inside-02-layers.webp)

التطبيق يكلم الأجهزة بلغة XFS الموحدة، ويكلم المضيف بلغة ISO 8583

وفي المختبر كتبنا موزعاً في الذاكرة يستقبل رسائل بهذا الشكل نفسه، ترويسة وحمولة، بأسماء الأوامر كما في المواصفة. وهذه رسالة حقيقية من المختبر كما طبعها السكربت:

```json
{"type": "command", "name": "CashDispenser.Dispense", "version": "2.0", "requestId": 2}
```

ولاحظ أن هذا لا يجعل مختبرنا تطبيقاً للمعيار، فالمعيار يعرف مئات الخصائص والأحداث، وإنما أخذنا منه شكل الرسائل وأسماء أربعة أوامر نحتاجها لنفهم القصة: `CashDispenser.Denominate` الذي يسأل الموزع هل يستطيع أن يكون المبلغ من الأوراق المتاحة، و`CashDispenser.Dispense` الذي يسحب الأوراق إلى المكدس، و`CashDispenser.Present` الذي يقدمها للعميل، و`Common.GetCommandNonce` الذي سنشرحه في فقرة الحماية. وهناك حدث خامس مهم في قصتنا هو `CashDispenser.IncompleteDispenseEvent`، وتصفه المواصفة بأنه الحدث الذي يصدر أثناء الصرف حين لا يمكن صرف المبلغ كاملاً لكن جزءاً منه وصل إلى المكدس أو إلى يد العميل، ويحمل قيمة هذا الجزء وعدد الأوراق من كل درج.

### لماذا يسأل الصراف الموزع قبل أن يسأل البنك؟

قد يتساءل البعض: لماذا لا يرسل الصراف الطلب إلى البنك مباشرة، ثم يحاول الصرف بعد الموافقة؟ والإجابة أن الموافقة تعني خصماً من حساب العميل، فإذا خصم البنك 30 ريالاً ثم اكتشف الصراف أن أصغر فئة عنده 50، فقد خصم مالاً لا يستطيع صرفه، واحتاج رسالة عكس لتصحيح ذلك. والأسلم أن يسأل الصراف الموزع أولاً: هل أستطيع أن أكون هذا المبلغ من الأوراق الموجودة في الأدراج الآن؟ وهذا بالضبط ما يقوله وصف الأمر `CashDispenser.Denominate` في المواصفة: يحدد عدد الأوراق المطلوبة من كل درج لتلبية الطلب، ويستخدم للتحقق من أن الطلب قابل للصرف، بحسب عدد الأوراق في كل درج وحالته.

والمزيج Mix نفسه قرار عمل لا قرار تقني فقط. فالمواصفة تذكر طريقتين: أن يحدد التطبيق عدد الأوراق من كل درج بنفسه، أو أن يترك ذلك لخوارزمية الخدمة أو لجدول مزيج Mix Table محفوظ، وتعطي مثالاً بأن 100 دولار قد تكون خمس أوراق من فئة 20 أو عشر أوراق من فئة 10\. فالبنك قد يفضل أن يصرف الفئات الكبيرة أولاً ليقل عدد الأوراق وتطول مدة الأدراج قبل إعادة التعبئة، وقد يفضل في مواسم معينة أن يعطي العميل فئات أصغر لأنه سيحتاجها، وقد يضع حداً لعدد الأوراق في الدفعة الواحدة بحسب قدرة فتحة النقد. وفي مختبرنا استخدمنا أبسط خوارزمية: الفئة الأكبر أولاً بقدر ما في الدرج، فكان مزيج 600 ريال ورقة من 500 وورقة من 100، وهذا ما رآه العميل في القصة قبل العطل.

## من الصراف إلى البنك: رسائل المضيف Host

الجهة التي يكلمها الصراف عبر الشبكة تسمى المضيف Host، وهي نظام في البنك يدير صرافاته ويستقبل طلباتها، ثم يوجه كل طلب بحسب البطاقة: إذا كانت بطاقة من البنك نفسه، أجاب عنها من حساب العميل لديه، وإذا كانت من بنك آخر، أرسلها عبر الشبكة الوطنية إلى البنك المصدر. وفي السعودية تذكر [صفحة مدى لدى البنك المركزي](https://www.sama.gov.sa/en-US/payment/Pages/mada.aspx?ref=arabroot.io) أن الشبكة تصل المستخدمين عبر قنوات منها نقاط البيع والصرافات ومواقع التجارة الإلكترونية، وأن هذه القنوات كلها متصلة بنظام مدفوعات مركزي يوجه العمليات المالية التي تتم عليها إلى البنك المصدر للبطاقة. وتوجيه الرسائل بين البنوك موضوع مقال المحول المالي Payment Switch من هذه المرحلة، أما هنا فنقف عند المحادثة بين الصراف ومضيفه.

وقد يكون بين الصراف والمضيف في بعض البنوك بروتوكول خاص بالشركة المصنعة أو بالبرنامج الذي يدير الصرافات، ثم يترجمه المضيف إلى ISO 8583 نحو الشبكة. وفي مختبرنا جعلنا الصراف يتكلم ISO 8583 مباشرة لتبقى الصورة واضحة، فالمعنى واحد في الحالتين: طلب سحب يحمل البطاقة والمبلغ والرقم السري المشفر، ورد يحمل الموافقة أو الرفض، وعكس حين يحدث ما لا يتوقعه أحد.

### رسالة السحب 0200 ورمز المعالجة 01

في مقال عملية الدفع بالبطاقة رأينا التفويض 0100 الذي يحجز المبلغ دون أن يخصمه، ثم يأتي التحصيل لاحقاً. أما سحب النقد فمختلف: المال يخرج من الصراف الآن، فلا معنى لحجز ينتظر تحصيلاً بعد أيام. ولهذا تستخدم عمليات الصراف في الغالب فئة الرسائل المالية 2، أي `0200`، وهي كما يصفها [ملخص المعيار](https://en.wikipedia.org/wiki/ISO%5F8583?ref=arabroot.io) رسالة تتحقق من توفر المال وتأخذ الموافقة وتقيد مباشرة في الحساب، ويسمى هذا نظام الرسالة الواحدة Single Message System. ونوع العملية في الحقل 3، رمز المعالجة Processing Code: أول رقمين `01` يعنيان سحباً نقدياً، و`31` استعلاماً عن الرصيد، بحسب جدول رموز المعالجة في الملخص نفسه.

وهذه رسالة السحب التي يبنيها صراف المختبر لطلب 600 ريال، مطبوعة حقلاً حقلاً بالسكربت `show.py`، مع إخفاء وسط رقم البطاقة في الطباعة فقط:

```text
0200 bytes: 215 | bitmap: 7238040008A19001
    t 0200
    2 424242******4242
    3 011000
    4 000000060000
    7 1027171309
   11 000001
   12 171309
   13 1027
   22 051
   37 263000000010
   41 ATM00042
   43 ARABROOT LAB BRANCH ATM 42
   48 9F1C2B3A4D5E6F708192A3B4C5D6E7F8
   49 682
   52 3A7F1E5A8B6802A5
   64 C6D37F0B7AFF8FDD
```

في المخرج أعلاه لاحظ التالي:

- **الحقل 3 قيمته `011000`**: الرقمان الأولان 01 سحب نقدي، والرقمان التاليان 10 نوع الحساب الذي يسحب منه، وهو في الاستخدام الشائع حساب توفير، والرقمان الأخيران 00 الحساب المحول إليه ولا يوجد. وتفاصيل الرقمين الأوسطين تختلف بين الشبكات، فتأخذها من مواصفة شبكتك.
- **الحقل 22 قيمته `051`**: بحسب جدول نمط الإدخال في الملخص، 05 تعني أن البطاقة قرئت من الشريحة ICC، و1 تعني أن الجهاز يستطيع استقبال الرقم السري.
- **الحقل 52 الرقم السري**: ستة عشر رقماً ست عشرياً هي كتلة الرقم السري بعد التشفير، ولا يمكن لمن يقرأ الرسالة أن يعرف الرقم منها دون المفتاح. وفي المختبر تبنيها دالة `pin_block` بالصيغة 0 من ISO 9564 وتشفرها بمفتاح اختبار، وفي الصراف الحقيقي تبنيها لوحة EPP داخلها.
- **الحقل 43 اسم الجهاز وموقعه**: أربعون حرفاً ثابتة، وسنرى بعد قليل أنه سبب أول مشكلة واجهناها في المختبر.
- **الحقل 48 بيانات خاصة**: وضعنا فيه في المختبر رقماً عشوائياً Nonce يولده الموزع، سنشرح سببه في فقرة الحماية. وهذا اختيار المختبر، فكل شبكة تحدد ما تضعه في الحقول الخاصة.
- **الحقل 64 رمز التحقق من الرسالة MAC**: ثمانية بايتات محسوبة بمفتاح مشترك بين الصراف والمضيف على كل ما قبلها، فإذا تغير بايت واحد في الطريق لم يعد الرمز صحيحاً.

والرسالة كلها 215 بايتاً، أي ضعف رسالة التفويض التي رأيناها في مقال رسائل البنوك تقريباً، لأن الصراف يرسل ما لا ترسله صفحة دفع: الوقت المحلي والتاريخ في الحقلين 12 و13، والرقم المرجعي في الحقل 37، والرقم السري المشفر، ورمز التحقق.

### رسائل إدارة الشبكة 0800

ليست كل رسالة بين الصراف والمضيف عملية مالية. ففئة الرسائل 8، كما يذكر الملخص، لإدارة الشبكة: تسجيل الدخول Logon، واختبار الصدى Echo Test، وتبادل المفاتيح Key Exchange. وأول ما يفعله صراف المختبر عند تشغيله رسالة `0800` برمز 301 في الحقل 70، وهو اختبار الصدى، فيرد المضيف `0810` برمز 00\. وفائدة هذه الرسائل عملية جداً: الصراف الذي يرسل صدى كل دقيقة يعرف أن الخط مقطوع قبل أن يقف عميل أمامه، فيعرض «الخدمة غير متاحة» بدل أن يأخذ البطاقة والرقم ثم يفشل. وتبادل المفاتيح يجعل البنك يغير مفاتيح الصراف دورياً عبر الشبكة دون زيارة فني، وهذا موضوع مقال HSM.

## تجربة عملية: صراف ومضيف في Containers منفصلة

الآن سوف نبني المختبر. أردنا أن يكون الصراف والمضيف برنامجين منفصلين يتكلمان عبر شبكة حقيقية، لا دالتين في ملف واحد، لأن نصف مشكلات الصرافات يأتي من الشبكة نفسها: رد يتأخر، ورد يضيع، ورد يصل بعد أن يتوقف أحد عن انتظاره. والملفات خمسة في مجلد واحد:

- `common.py`: الأدوات المشتركة، وهي تأطير الرسالة بطول من بايتين قبلها، وحساب رمز MAC بالخوارزمية 3 من ISO 9797-1، وبناء كتلة الرقم السري بالصيغة 0، وتوقيع توكن الصرف. والمفاتيح كلها مفاتيح اختبار مكتوبة في الملف.
- `host.py`: المضيف، يستمع على المنفذ 8583، ويعرف بطاقتين من أرقام الاختبار المنشورة في [توثيق Stripe للاختبار](https://docs.stripe.com/testing?ref=arabroot.io) برصيدين 2,000 و300 ريال، ويطبق حداً للسحب الواحد قدره 3,000 ريال في المختبر، ويتأخر عمداً ثلاث ثوان إذا كان المبلغ 700 ريال لنختبر المهلة.
- `atm.py`: تطبيق الصراف والموزع، ينفذ سبعة سيناريوهات بالترتيب.
- `whatif.py` و`show.py`: تجارب «ماذا لو» وطباعة الرسالة حقلاً حقلاً.

ولماذا التأطير بطول من بايتين؟ لأن TCP تيار من البايتات لا رسائل منفصلة، فإذا أرسل الصراف رسالتين متتاليتين قد يقرؤهما المضيف دفعة واحدة أو على ثلاث دفعات. والحل الشائع في روابط المضيف أن يسبق كل رسالة طولها، فيقرأ الطرف الآخر بايتين، ثم يقرأ هذا العدد بالضبط من البايتات. وهذه دالتا الإرسال والاستقبال من `common.py`:

```python
async def send(writer, raw: bytes):
    writer.write(len(raw).to_bytes(2, "big") + raw)       # 2-byte length header, a common framing for host links
    await writer.drain()

async def recv(reader) -> bytes:
    n = int.from_bytes(await reader.readexactly(2), "big")
    return await reader.readexactly(n)
```

وقلب المضيف دالة التفويض، وهي تفحص بالترتيب: هل البطاقة معروفة، ثم هل الرقم السري صحيح بعد فك كتلته، ثم هل المبلغ داخل الحد، ثم هل الرصيد يكفي، وعند النجاح تخصم المبلغ وتحفظ العملية برقم الجهاز ورقم التتبع STAN لتجدها إذا جاء عكس، وتوقع توكن الصرف:

```python
async def authorize(req):
    pan, amount = req["2"], int(req["4"])
    acct = ACCOUNTS.get(pan)
    if not acct:
        return reply(req, "0210", "14")
    if pin_from_block(req["52"], pan) != acct["pin"]:
        return reply(req, "0210", "55")
    if amount > LIMIT:
        return reply(req, "0210", "61")
    if amount > acct["balance"]:
        return reply(req, "0210", "51")
    acct["balance"] -= amount
    TXNS[(req["41"], req["11"])] = {"pan": pan, "amount": amount, "reversed": False}
    nonce = req.get("48", "")
    token = e2e_token(nonce, amount) if nonce else ""
    if amount == SLOW_AMOUNT:
        await asyncio.sleep(3)
    return reply(req, "0210", "00", **{"38": f"{random.randint(0, 999999):06d}",
                                       "54": f"1002682C{acct['balance']:012d}", "48": token})
```

ورموز الرد هنا من جدول إصدار 1987 في الملخص: 14 رقم بطاقة غير صحيح، و55 رقم سري غير صحيح، و61 يتجاوز حد السحب، و51 الرصيد لا يكفي. والحقل 54 المبالغ الإضافية Additional Amounts يحمل الرصيد المتاح بعد الخصم، ليطبعه الصراف على الإيصال. ولاحظ أن المضيف يخصم قبل أن يعرف هل خرج المال فعلاً، وهذا هو جوهر القصة: الموافقة تسبق الصرف، فلا بد من طريقة لتصحيح الخصم إذا لم يحدث الصرف كما يجب.

وجانب الصراف دالة السحب، وتسلسلها كما في الصورة التالية: يسأل الموزع هل المبلغ ممكن، ثم يأخذ منه رقماً عشوائياً، ثم يرسل الطلب، ثم يصرف بالتوكن الذي عاد مع الموافقة:

![مخطط تسلسل لسحب 600 ريال بين العميل وتطبيق الصراف والموزع والمضيف: العميل يدخل البطاقة والرقم السري والمبلغ، والتطبيق يرسل Denominate للمبلغ 600 فيرد الموزع أن 500 + 100 ممكن، ثم GetCommandNonce، ثم رسالة 0200 فيها رمز السحب 01 وكتلة الرقم المشفرة والرقم العشوائي وMAC، ثم يعود 0210 برمز 00 والرصيد وتوكن الصرف وMAC، ثم Dispense ومعه التوكن، ثم Present، ثم النقد أمام العميل](https://arabroot.io/content/images/2026/10/atm-inside-03-withdrawal.webp)

الصراف يتأكد أنه يستطيع الصرف قبل أن يطلب الخصم، ويصرف بتوكن من المضيف

وشغلنا المضيف والصراف في Containers منفصلة على شبكة Docker خاصة، من Docker Image صغير مبني على `python:3.13.17-slim` مع مكتبتي pyiso8583 4.0.1 وpycryptodome 4.2.0، بهذا الـ Dockerfile:

```dockerfile
FROM python:3.13.17-slim
RUN pip install --no-cache-dir --root-user-action=ignore pyiso8583==4.0.1 pycryptodome==4.2.0
WORKDIR /lab
COPY common.py host.py atm.py whatif.py show.py ./
```

ثم الأوامر التالية من داخل مجلد المختبر:

```bash
docker build -t sfa-atm-lab:1 .
docker network create sfa-atm-net
docker run -d --name sfa-atm-host --network sfa-atm-net sfa-atm-lab:1 python host.py
docker run --rm --name sfa-atm --network sfa-atm-net sfa-atm-lab:1 python atm.py
docker logs sfa-atm-host
```

ولاحظ أننا لم ننشر أي منفذ على الجهاز المضيف، فالصراف يصل إلى المضيف باسم الـ Container `sfa-atm-host` داخل الشبكة الخاصة، وهذا يشبه الواقع: رابط الصراف بالمضيف شبكة مغلقة، لا خدمة مفتوحة على الإنترنت. والمخرج سوف يكون كما يلي:

```text
17:15:35 ATM  == echo test 0800/301 -> 0810 rc 00
17:15:35 ATM  == withdrawal 600 SAR, card ****4242
17:15:35 ATM     Denominate ok: {'unit1': 1, 'unit2': 1}
17:15:35 ATM     0210 rc 00 auth 599758 available 1400.0
17:15:35 ATM     Dispense ok: {'unit1': 1, 'unit2': 1} = 600 SAR, cash presented
17:15:35 ATM  == withdrawal 100 SAR, card ****4242
17:15:35 ATM     Denominate ok: {'unit2': 1}
17:15:35 ATM     0210 rc 55 auth - available -
17:15:35 ATM  == withdrawal 500 SAR, card ****5556
17:15:35 ATM     Denominate ok: {'unit1': 1}
17:15:35 ATM     0210 rc 51 auth - available -
17:15:35 ATM  == withdrawal 30 SAR, card ****4242
17:15:35 ATM     Denominate: notDispensable, no message sent to the host
17:15:35 ATM  == withdrawal 600 SAR, card ****4242
17:15:35 ATM     Denominate ok: {'unit1': 1, 'unit2': 1}
17:15:35 ATM     0210 rc 00 auth 648214 available 800.0
17:15:35 ATM     Dispense error cashUnitError; CashDispenser.IncompleteDispenseEvent {"currencies": {"SAR": 500}, "values": {"unit1": 1}}
17:15:35 ATM     0420 reason 22, dispensed 500.00 -> 0430 rc 00
17:15:35 ATM  == withdrawal 700 SAR, card ****4242
17:15:35 ATM     Denominate ok: {'unit1': 1, 'unit2': 2}
17:15:37 ATM     no reply in 2s for STAN 000007: reversing
17:15:38 ATM     discarded late 0210 for STAN 000007 (waiting for 000008)
17:15:38 ATM     0420 reason 68, dispensed 0.00 -> 0430 rc 00
17:15:38 ATM  == withdrawal 250 SAR, card ****4242
17:15:38 ATM     Denominate ok: {'unit2': 2, 'unit3': 1}
17:15:38 ATM     0210 rc 00 auth 612706 available 650.0
17:15:38 ATM     Dispense error cashUnitError; CashDispenser.IncompleteDispenseEvent {"currencies": {"SAR": 200}, "values": {"unit2": 2}}
17:15:38 ATM     0420 sent, reply lost on the way (simulated): repeating as 0421
17:15:38 ATM     0421 reason 22, dispensed 200.00 -> 0430 rc 00
17:15:38 ATM  == balance ****4242: 700.00
units left: {'unit1': 18, 'unit2': 37, 'unit3': 40}
```

في المخرج أعلاه لاحظ التالي، سيناريو بعد سيناريو:

- **السحب العادي**: سأل الصراف الموزع فأجاب بمزيج ورقة من الدرج الأول (500) وورقة من الثاني (100)، ثم عادت الموافقة برقم موافقة ورصيد متاح 1,400، ثم صرف وقدم النقد. وهذه الدورة كلها حدثت في الثانية نفسها.
- **الرقم السري الخاطئ**: رمز 55\. ولاحظ أن الصراف لم يعرف أن الرقم خاطئ، فهو لا يملك الرقم الصحيح أصلاً، بل أرسل الكتلة المشفرة، والمضيف هو من فكها وقارن.
- **الرصيد لا يكفي**: البطاقة الثانية رصيدها 300 فطلبت 500، فجاء الرمز 51.
- **مبلغ لا تكونه الأوراق**: 30 ريالاً، وأصغر فئة في الأدراج 50، فرفض الموزع قبل أن تخرج أي رسالة إلى المضيف. وهذا هو فائدة سؤال الموزع أولاً: لا خصم، ولا عكس، ولا شيء في سجل المضيف أصلاً.
- **عطل الموزع بعد الموافقة**: هذه قصة العميل نفسها، وسنفصلها في الفقرة التالية.
- **رد متأخر**: المضيف وافق بعد ثلاث ثوان، والصراف ينتظر ثانيتين فقط، فعكس العملية. ثم وصل الرد المتأخر فأهمله الصراف لأن رقم التتبع لا يطابق ما ينتظره.
- **ضياع رد العكس**: أرسل الصراف العكس، وضاع الرد في الطريق في المحاكاة، فكرره برسالة 0421، فعرف المضيف أنه عكس هذه العملية من قبل ولم يعد المال مرتين.

والرصيد النهائي 700 ريال، والحساب بسيط: 2,000 ناقص 600 في السحب الأول، ثم 100 خصمت ولم تصرف في العطل الأول فأعيدت، ثم 700 خصمت وأعيدت كاملة في المهلة، ثم 250 خصم منها 200 فعلاً، أي 2,000 ناقص 600 ناقص 500 ناقص 200 يساوي 700\. والأدراج تقول الشيء نفسه: درج 500 نقص ورقتين، ودرج 100 نقص ثلاث أوراق، أي 1,300 ريال خرجت فعلاً، وهي بالضبط ما نقص من الحساب.

### المشكلة التي واجهناها: حقل بطول ثابت

في التشغيل الأول لم يصل الصراف إلى رسالة واحدة، بل توقف عند بناء أول طلب سحب بهذا الخطأ:

```text
iso8583.encoder.EncodeError: Field data is 26 bytes, expecting 40: field 43
```

والسبب أن الحقل 43، اسم الجهاز وموقعه، في المواصفة التي نستخدمها ثابت الطول بأربعين حرفاً، وكتبنا فيه اسماً من 26 حرفاً. والمكتبة رفضت أن تكمله بنفسها، وهذا صحيح، لأن الإكمال بمسافات أو بأصفار أو من اليمين أو من اليسار قرار تحدده مواصفة الشبكة لا المكتبة. فأكملناه بمسافات من اليمين بالدالة `ljust(40)`، كما ترى في طباعة الرسالة أعلاه، حيث يظهر بعد الاسم فراغ طويل. والدرس لمن يكتب مضيفاً أو محاكياً: كل حقل ثابت الطول له قاعدة إكمال مكتوبة في مواصفة الشريك، فاسأل عنها ولا تخمنها، لأن الطرف الآخر قد يقرأ «اسم الجهاز» مع المسافات أو بدونها، فتختلف المطابقة بين النظامين.

## حين يتعطل الموزع بعد الموافقة

الآن نعود إلى شكوى العميل. فقد أعاد المختبر القصة نفسها: طلب 600 ريال، والمزيج ورقة من 500 وورقة من 100، والموافقة عادت بالرصيد المتاح 800، ثم سحب الموزع ورقة الخمسمئة وتعطل قبل الثانية. وفي الموزع الحقيقي قد يكون السبب ورقة ملتصقة أو مائلة أو درجاً فيه عطل ميكانيكي، وفي مختبرنا هو المعامل `jam_after=1` الذي يجعل الموزع يتوقف بعد ورقة واحدة. والسؤال الذي يجيب عنه النظام هنا: كم خرج فعلاً؟ والجواب في الحدث `IncompleteDispenseEvent` الذي أعاده الموزع: `{"SAR": 500}` من `{"unit1": 1}`، أي ورقة واحدة من الدرج الأول.

وعندها يرسل الصراف رسالة عكس `0420`، وهي من فئة الرسائل 4، وبحسب الملخص وظيفتها 2 أي إشعار Advice، والإشعار كما يصفه الملخص رسالة تخبر بأن شيئاً حدث، ولا يستطيع المستقبل أن يرفضه، بل يقبله فقط. وهذا منطقي: الصراف لا يطلب من المضيف إذناً بأن الموزع تعطل، بل يخبره بما حدث. والصورة التالية تبين ما في الرسالة:

![عطل الموزع بعد الموافقة: طلب 600 ريال بمزيج ورقة 500 وورقة 100، ثم موافقة 0210 برصيد متاح 800، ثم خرجت ورقة 500 فقط مع cashUnitError وIncompleteDispenseEvent، فيرسل الصراف رسالة عكس 0420 فيها الحقل 39 بقيمة 22 عطل في الجهاز، والحقل 90 بقيمة 0200 000005 ووقت الأصل، والحقل 95 بقيمة 000000050000 أي ما صرف فعلاً، فيعيد المضيف 600 ناقص 500، والنتيجة أعيد 100 ريال والرصيد 900 بدل 800](https://arabroot.io/content/images/2026/10/atm-inside-04-jam.webp)

العكس الجزئي: الحقل 95 يقول كم خرج فعلاً، فيعيد المضيف الفرق فقط

وفي رسالة العكس ثلاثة حقول تحمل القصة كلها:

- **الحقل 90، بيانات العملية الأصلية Original Data Elements**: 42 حرفاً تبدأ بنوع الرسالة الأصلية `0200`، ثم رقم تتبعها `000005`، ثم وقت إرسالها، ثم معرفي المؤسستين. وبهذا يعرف المضيف أي عملية يعكس، حتى لو كانت رسالة العكس نفسها تحمل رقم تتبع جديداً.
- **الحقل 95، المبالغ البديلة Replacement Amounts**: أول اثني عشر رقماً فيه المبلغ الفعلي للعملية، وهو هنا `000000050000` أي 500 ريال. وهذا ما يجعل العكس جزئياً: المضيف لا يعيد المبلغ كله، بل الفرق بين ما خصمه وما خرج فعلاً.
- **الحقل 39، سبب العكس**: وضعنا فيه 22، وهو في جدول الملخص «عطل مشتبه به» Suspected malfunction، واستخدمنا 68 «رد وصل متأخراً» في سيناريو المهلة. ورموز أسباب العكس تحددها مواصفة كل شبكة، فهذا اختيار المختبر لا قاعدة عامة.

وفي سجل المضيف ظهر السطر الذي كان مدير الفريق يريده قبل الظهر:

```text
17:15:35 HOST <- 0420 STAN 000006 amount 000000060000 rc 22
17:15:35 HOST reversal for STAN 000005: dispensed 500.00, credited back 100.00, balance now 900.00
17:15:35 HOST -> 0430 STAN 000006 rc 00
```

أي أن النظام أعاد 100 ريال آلياً في الثانية نفسها، والرصيد صار 900 بدل 800\. ولماذا رأى العميل في القصة رسالة خصم 600 إذن؟ لأن رسالة الخصم أرسلت عند الموافقة، قبل أن يحدث العطل، وإعادة المئة ظهرت بعدها حركة مستقلة في كشف الحساب، فرآها العميل خصماً ثم إعادة، لا خصماً بقيمة 500 مباشرة. وكان أول درس كتبه الفريق أن رسالة العميل يجب أن تقول ما حدث فعلاً: «صرف لك 500 ريال من 600، وأعيد الفرق إلى حسابك»، بدل «نعتذر، لا يمكن إتمام العملية» التي جعلت العميل يظن أنه لم يأخذ شيئاً أو خسر المال كله.

وقد يتساءل البعض: ماذا لو لم يعرف الموزع نفسه كم خرج؟ وهذه حالة حقيقية، فالموزع قد يتعطل وبعض الأوراق في الطريق بين المكدس والفتحة. وهنا يأتي دور أمر `CashDispenser.GetPresentStatus` في المواصفة، وهو يعيد حالة آخر محاولة تقديم، وتقول المواصفة إن هذه القيمة تبقى محفوظة حتى بعد انقطاع الكهرباء، إلى أن تبدأ محاولة تقديم جديدة. وإذا بقي الشك بعد ذلك، فلا يحسم الأمر الكود وحده، بل يحسمه فريق النقد عند فتح الخزنة وعد الأدراج ودرج المرفوضات، ومقارنة العد بالسجل، كما سنرى في فقرة إدارة النقد.

### وماذا لو لم يأخذ العميل النقد؟

حالة أخرى قريبة من العطل: صرف الموزع المبلغ كاملاً وقدمه، لكن العميل انشغل أو ابتعد، فبقيت الأوراق في الفتحة. وكثير من الموزعات تستطيع أن تسحب الأوراق إلى الداخل Retract بعد مهلة، فتنقلها إلى درج خاص. فهل يعكس الصراف العملية؟ هذه سياسة يقررها البنك لا المعيار، لأن الأوراق المسحوبة قد يكون العميل أخذ بعضها قبل أن تسحب. والمهم للمطور أن السجل يجب أن يفرق بين ثلاث حالات لا حالتين: صرف وأخذ العميل النقد، وصرف وسحبت الأوراق، وتعطل الصرف. ولكل حالة رسالة مختلفة إلى المضيف وتسوية مختلفة عند عد الخزنة.

## حين لا يصل الرد: المهلة والعكس

السيناريو السادس أخطر من العطل لأن الصراف لا يعرف فيه شيئاً: أرسل طلب السحب 700 ريال برقم تتبع 7، ومرت ثانيتان دون رد. فهل وافق المضيف وخصم؟ أم أن الطلب لم يصل أصلاً؟ أم أن المضيف رفض والرد ضاع؟ والصراف لا يستطيع أن يسأل العميل، ولا يستطيع أن يصرف وهو لا يعرف، فالحل الوحيد الآمن ما شرحناه في مقال رسائل البنوك: يرسل عكساً، والعكس آمن في كل الحالات، فإن كان المضيف خصم أعاد، وإن لم يخصم لم يحدث شيء. والصورة التالية تبين ما حدث بالضبط في سجل المختبر:

![مخططان: على اليسار انتهاء المهلة، الصراف يرسل 0200 برقم تتبع 7 ومبلغ 700، والمضيف يوافق ويخصم ثم يتأخر 3 ثوان، والصراف لا يتلقى رداً خلال ثانيتين فيرسل 0420 برقم 8 والسبب 68، ثم يصل 0210 لرقم 7 متأخراً فيهمله الصراف لأن الرقم لا يطابق 8، ثم 0430 برد 00 وإعادة 700 والرصيد يعود 900. وعلى اليمين ضياع رد العكس، 0420 برقم 10 بعد صرف 200 من 250، والمضيف يعيد 50 والرصيد 700، ثم يضيع 0430، فيكرر الصراف 0421، والمضيف يرد بأنه عكسها من قبل دون إعادة ثانية، والرصيد النهائي 700](https://arabroot.io/content/images/2026/10/atm-inside-05-timeout.webp)

المهلة تحل بالعكس، والتكرار يحل بأن يعرف المضيف ما عكسه من قبل

وفي هذا السيناريو ظهرت مشكلة كنا نتوقعها عند تصميم المختبر، ولو لم نتوقعها لظهرت خطأ غريباً: الرد المتأخر للطلب 7 لا يختفي، بل يبقى في الاتصال نفسه، فإذا قرأ الصراف «الرسالة التالية» بعد أن أرسل العكس، سيقرأ الموافقة القديمة على أنها رد العكس. ولهذا تقارن دالة `exchange` في الصراف رقم التتبع في كل رد برقم الطلب الذي تنتظره، وتهمل ما لا يطابقه، كما في هذا السطر من المخرج:

```text
17:15:38 ATM     discarded late 0210 for STAN 000007 (waiting for 000008)
```

والجزء المهم من دالة `exchange` كما يلي:

```python
            raw = await asyncio.wait_for(recv(self.r), left)
            resp = decode(raw)
            if not mac_ok(raw):                                # never act on a reply whose MAC does not verify
                log(f"   reply {resp['t']} for STAN {resp.get('11')} has a bad MAC: treated as no reply")
                continue
            if resp.get("11") == msg["11"]:
                return resp
            log(f"   discarded late {resp['t']} for STAN {resp.get('11')} (waiting for {msg['11']})")
```

ولاحظ أن الموافقة المتأخرة ليست «خطأ» يجب أن يعالج، بل حدث متوقع تماماً في أي نظام شبكي، والقاعدة البسيطة فيه: بعد أن يقرر الصراف أن الطلب انتهى وأرسل عكسه، فلا شيء يصل بعد ذلك لهذا الطلب يغير القرار. ولو صرف الصراف بناء على الموافقة المتأخرة بعد أن أرسل العكس، لخرج المال وأعيد إلى الحساب في الوقت نفسه، وهذه أسوأ نتيجة ممكنة للبنك.

ومن جهة المضيف، فإن طول المهلة قرار له ثمن في الاتجاهين. فالمهلة القصيرة تعني عكوساً أكثر لعمليات كانت ستنجح، والعميل يرى «لا يمكن إتمام العملية» ويعيد المحاولة، والمهلة الطويلة تعني عميلاً ينتظر أمام الشاشة وطابوراً خلفه. ولهذا يراقب فريق المضيف في البنوك نسبة العمليات التي تنتهي بالمهلة لكل صراف ولكل بنك مصدر، فارتفاعها في صراف واحد يشير إلى خط الاتصال، وارتفاعها في كل الصرافات مع بنك مصدر واحد يشير إلى مشكلة عند ذلك البنك أو في الطريق إليه.

## العكس المكرر 0421: لماذا يجب أن يعرف المضيف ما عكسه؟

في السيناريو السابع اجتمع العطل والشبكة: طلب العميل 250 ريالاً، فوافق المضيف، وصرف الموزع ورقتين من فئة 100 ثم تعطل، فأرسل الصراف عكساً بأن الصرف الفعلي 200، فأعاد المضيف 50\. لكن الرد على العكس ضاع في الطريق، وهذا ما حاكيناه بأن قرأ الصراف الرد وتجاهله. فماذا يفعل الصراف؟ لا يعرف هل وصل العكس، فيكرره. والمعيار يعطي لهذا رقماً خاصاً: الرقم الأخير من نوع الرسالة هو المصدر، و1 يعني تكرار المستحوذ Acquirer repeat، فتصبح الرسالة `0421`، أي «هذا العكس نفسه مرة أخرى، وقد تكون رأيته».

وهنا يظهر ما يجب أن يفعله المضيف، وهو مكتوب في دالة `reverse`: يبحث عن العملية الأصلية برقم الجهاز ورقم التتبع الأصلي من الحقل 90، فإذا وجد أنها عكست من قبل، رد بالموافقة دون أن يعيد المال مرة ثانية:

```text
17:15:38 HOST reversal for STAN 000009: dispensed 200.00, credited back 50.00, balance now 700.00
17:15:38 HOST -> 0430 STAN 000010 rc 00
17:15:38 HOST <- 0421 STAN 000011 amount 000000025000 rc 22
17:15:38 HOST reversal for STAN 000009: already reversed, duplicate ignored
17:15:38 HOST -> 0430 STAN 000011 rc 00
```

ولو كتب المضيف العكس بالطريقة البسيطة، أي «كلما وصل عكس أعد المبلغ»، لأعاد 50 ريالاً مرتين، ولكان رصيد العميل في المختبر 750 بدل 700\. وخمسون ريالاً في عملية واحدة لا تبدو مشكلة، لكن الرسائل المكررة ليست حدثاً نادراً في شبكة فيها آلاف الصرافات وخطوط اتصال متفاوتة الجودة، فتصبح المشكلة فرقاً في التسوية يبحث عنه فريق المطابقة كل يوم. وهذه هي فكرة Idempotency نفسها التي شرحناها في [مقال Idempotency](https://arabroot.io/articles/idempotency-%D9%83%D9%8A%D9%81-%D8%AA%D8%AC%D8%B9%D9%84-%D8%A7%D9%84%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%A7%D9%85%D9%86%D8%A9-%D8%B9%D9%86%D8%AF-%D8%AA%D9%83%D8%B1%D8%A7%D8%B1%D9%87%D8%A7/): العملية التي قد تتكرر يجب أن يكون لها مفتاح يعرفها، وهنا المفتاح هو رقم الجهاز مع رقم التتبع الأصلي، وتنفيذها مرتين يجب أن يعطي أثر تنفيذها مرة واحدة.

## إدارة النقد: الأدراج والعدادات والمطابقة

بعد أن حل الفريق الشكوى بقي سؤال يهم قسماً آخر في البنك: إذا كان الموزع تعطل والورقة الثانية لم تخرج، فأين هي؟ وهل يطابق ما في الخزنة ما يقوله النظام؟ وهذا عمل إدارة النقد Cash Management، وهو جزء من عمل الصرافات لا يراه العميل. فكل درج له عداد في الموزع يعرف كم ورقة يفترض أن تكون فيه، ونظام المضيف يعرف كم صرف كل صراف، وفريق نقل النقد يعد الأدراج فعلياً عند كل تعبئة. وثلاثة مصادر للرقم نفسه تعني أن أي اختلاف بينها يجب أن يفسر.

وفي مختبرنا بدأت الأدراج بعشرين ورقة من 500 وأربعين من 100 وأربعين من 50، وانتهت بثماني عشرة وسبع وثلاثين وأربعين. والفرق ورقتان من 500 وثلاث من 100، أي 1,300 ريال، وهي بالضبط مجموع ما صرف فعلاً في السيناريوهات: 600 ثم 500 ثم 200\. والمطابقة في البنك تفعل هذا الحساب نفسه لكل صراف ولكل فترة، بين العد الفعلي عند التعبئة وعدادات الموزع وسجل المضيف، وكل اختلاف يفتح تحقيقاً: هل هناك ورقة في درج المرفوضات؟ هل هناك عملية سحبت أوراقها بعد مهلة؟ هل هناك عكس لم يصل إلى المضيف؟

وفي المواصفة واجهة كاملة لهذا اسمها التخزين Storage، ومعها أوامر لبدء التبديل Exchange وإنهائه، أي اللحظة التي يفتح فيها الفريق الخزنة ليستبدل الأدراج، وتذكر المواصفة أن الموزع يرفض بعض الأوامر أثناء التبديل. والدرس العملي أن العدادات في الموزع ليست حقيقة مطلقة: فالمواصفة نفسها تقول عن أمر `CashDispenser.Reject` إن عدادات درج المرفوضات قد لا تكون موثوقة، لأنها تزيد بعدد الأوراق التي يظن الجهاز أنها هناك. فالعد البشري عند التعبئة هو المرجع الأخير، والعدادات أداة لاكتشاف الاختلاف مبكراً.

![رجلان بزي العمل أمام صراف آلي مفتوح في مركز تسوق، أحدهما يرفع الغطاء العلوي والآخر ينحني نحو الخزنة، وبجانبهما عربة عليها صناديق](https://arabroot.io/content/images/2026/10/atm-inside-08-photo-atm-replenish.webp)

فنيان يفتحان صرافاً في مركز تسوق، وحين يكون الفتح لاستبدال الأدراج تسميه المواصفة التبديل Exchange، وفيه يرفض الموزع بعض الأوامر حتى يغلق الفريق الخزنة وتعود العدادات إلى حالة معروفة. الصورة: Ingolfson، ملكية عامة Public Domain، المصدر: [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Westpac%5FATM%5FBeing%5FOpened%5FBy%5FTwo%5FGuys.jpg?ref=arabroot.io) (تم تصغيرها وتحويلها إلى WebP).

وهذا يشبه ما شرحناه في [مقال المطابقة Reconciliation](https://arabroot.io/articles/%D8%A7%D9%84%D9%85%D8%B7%D8%A7%D8%A8%D9%82%D8%A9-reconciliation-%D9%83%D8%B4%D9%81-%D8%A7%D9%84%D8%A8%D9%86%D9%83-%D9%85%D9%82%D8%A7%D8%A8%D9%84-%D9%86%D8%B8%D8%A7%D9%85%D9%83/) عن مطابقة كشف البنك مع نظامك، والفرق أن أحد المصدرين هنا ليس ملفاً، بل صندوق حديدي فيه أوراق يجب أن يعدها إنسان، ولهذا فإن تصميم السجل في الصراف يجب أن يفكر من البداية في الشخص الذي سيطابق بعد أسبوع، لا في المطور الذي يصلح الكود اليوم.

## الحماية من جهة المدافع: ماذا لو أمر أحد الموزع مباشرة؟

كل ما سبق يفترض أن الأطراف صادقة، وأن الأعطال أعطال. لكن الصراف صندوق فيه مال في مكان عام، والحاسب الذي يأمر الموزع حاسب عادي، والرسائل تمر عبر شبكة. ولهذا نتكلم هنا من جهة المدافع فقط: كيف تصمم النظام بحيث لا يكفي أن يصل أحد إلى الحاسب أو إلى الشبكة ليخرج مالاً، وكيف تكتشف المحاولة. ولن نشرح أي طريقة للهجوم، فهذا ليس موضوع المقال ولا موضوع الموقع.

### حالتان موثقتان: من الحاسب الداخلي ومن المحول

💡

في يناير 2018 نشرت Trend Micro [ملخصاً لتحذيرات](https://www.trendmicro.com/vinfo/us/security/news/cybercrime-and-digital-threats/diebold-nixdorf-ncr-corp-send-out-warnings-atm-jackpotting-attacks-us-banks?ref=arabroot.io) أرسلتها الشركتان المصنعتان Diebold Nixdorf وNCR إلى البنوك عن هجمات تفريغ الصرافات Jackpotting التي رصدت في الولايات المتحدة لأول مرة، بعد تنبيه من جهاز الخدمة السرية الأمريكي. وبحسب الملخص احتاج المهاجمون وصولاً فعلياً إلى الصراف ليضعوا برنامجاً خبيثاً في حاسبه الداخلي، يأمر الموزع بإخراج ما في الأدراج. ومن التوصيات التي ذكرها: تحديث النظام والبرامج، والسماح بقائمة محددة من البرامج فقط Whitelisting لأن الصراف جهاز بوظيفة ثابتة، ومراقبة أحداث العتاد والبرامج المتعلقة بالأمن لحظياً، وحماية حواسيب الفنيين ووسائط USB.

💡

وفي أغسطس 2020 نشرت CISA مع وزارة الخزانة الأمريكية وFBI والقيادة السيبرانية [تنبيه FASTCash 2.0](https://www.cisa.gov/news-events/cybersecurity-advisories/aa20-239a?ref=arabroot.io) عن مجموعة استهدفت سيرفرات تطبيق المحول التي تعالج رسائل ISO 8583 في البنوك، فكان البرنامج الخبيث يعترض طلبات السحب ويرد عليها بموافقات مزورة تبدو سليمة، فتصرف الصرافات النقد في عمليات منسقة، ويذكر التنبيه أن حادثة واحدة شملت أكثر من 30 دولة. ومن التوصيات للجهات التي تملك صرافات أو أجهزة نقاط بيع: أن تطلب رموز التحقق من الرسالة MAC على ردود البنك المصدر وتتحقق منها.

والحالتان تهاجمان نقطتين مختلفتين في الصورة التي رسمناها في أول المقال: الأولى الحاسب الداخلي الذي يكلم الموزع، والثانية الطريق بين الصراف والبنك المصدر. والحماية في المختبر تغطي النقطتين بطبقتين، والصورة التالية تجمع نتائج التجارب:

![جدول طبقات الحماية ونتائجها في المختبر: MAC على الطلب مع تغيير المبلغ من 100 إلى 9900 أثناء النقل فرد المضيف 0210 برمز 63، وMAC على الرد مع موافقة مزورة 0210 برمز 00 فلم يتحقق الرمز فلا صرف، وتوكن الصرف E2E مع Dispense دون توكن فكانت النتيجة invalidToken، وتوكن لمرة واحدة مع استعمال التوكن نفسه مرة ثانية فكانت invalidToken، والتوكن مربوط بالمبلغ مع توكن 100 لطلب 500 فكانت invalidToken، والمضيف يعرف ما وافق عليه مع عكس لطلب لم يصل فكان الرد 0430 برمز 25](https://arabroot.io/content/images/2026/10/atm-inside-06-defense.webp)

كل طبقة جربناها بتجربة «ماذا لو» واحدة، والنتائج من مخرج حقيقي

### الطبقة الأولى: رمز التحقق MAC على الطلب والرد

رمز التحقق من الرسالة Message Authentication Code رقم يحسبه المرسل من محتوى الرسالة كله بمفتاح سري يعرفه الطرفان فقط، ويضعه في آخرها، فيحسبه المستقبل مرة ثانية ويقارن. وفي المختبر نحسبه بالخوارزمية 3 من ISO 9797-1، وتسمى في الصناعة Retail MAC: تشفير DES بسلسلة CBC على كل الكتل بنصف المفتاح الأول، ثم خطوتان إضافيتان على الكتلة الأخيرة بالنصف الثاني ثم الأول. ولماذا لا نستخدم الهاش وحده؟ لأن من يغير المبلغ في الطريق يستطيع أن يحسب الهاش الجديد بنفسه، أما الـ MAC فيحتاج المفتاح، وقد شرحنا هذا الفرق بالتفصيل بين الهاش وHMAC في [مقال التشفير للمطورين](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%B4%D9%81%D9%8A%D8%B1-%D9%84%D9%84%D9%85%D8%B7%D9%88%D8%B1%D9%8A%D9%86-%D8%A7%D9%84%D9%87%D8%A7%D8%B4-%D9%88%D9%83%D9%84%D9%85%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%B1%D9%88%D8%B1-%D9%88%D8%A7%D9%84%D9%85%D9%81%D8%A7%D8%AA%D9%8A%D8%AD/).

وفي ملف `whatif.py` جربنا الاتجاهين. الأول أن نبني طلب سحب 100 ريال صحيحاً بالـ MAC، ثم نغير المبلغ في البايتات إلى 9,900 قبل إرسالها، كأن شيئاً في الطريق غيره. والثاني أن نبني رداً بالموافقة `0210` برمز 00 وفيه حقل MAC لكنه غير محسوب بالمفتاح، كأن أحداً يرد على الصراف بدل المضيف. والمخرج:

```text
what if: the amount is changed after the ATM computed the MAC
   host answered 0210 rc 63
what if: a forged approval (0210, rc 00) arrives without the right MAC
   MAC verifies: False -> the ATM does not dispense
```

رفض المضيف الطلب المعدل برمز 63، وهو في جدول الملخص «مخالفة أمنية» Security violation، ولم يفك كتلة الرقم السري أصلاً ولم ينظر في الرصيد، لأن رسالة لا يتحقق رمزها لا يوثق بأي حقل فيها. والصراف بدوره لم يتحقق عنده رمز الرد المزور، فدالة `exchange` تعامل الرد الذي لا يتحقق رمزه كأنه لم يصل. ولاحظ أن الحماية الثانية هي بالضبط ما أوصى به تنبيه FASTCash للجهات التي تملك صرافات: التحقق من الـ MAC على ردود البنك المصدر، فلو اكتفى الصراف بالتحقق في اتجاه واحد لما منع موافقة مزورة تأتي من الطريق.

لكن للـ MAC حداً يجب أن تعرفه: يحمي الرسالة بين طرفين يملكان المفتاح نفسه، فإذا اخترق المهاجم أحد الطرفين، مثل سيرفر المحول في حالة FASTCash، فهو يملك المفتاح ويستطيع أن يحسب رموزاً صحيحة. ولهذا يوصي التنبيه نفسه بطبقات أخرى في جهة البنك، منها التحقق من الرموز التي تولدها شريحة البطاقة في الطلب، واستخدام رموز يولدها البنك المصدر في الرد، وتقسيم الشبكة، ومراقبة المعاملات مقابل نمط متوقع من الأطراف والمبالغ والتكرار والتوقيت. والرموز التي تولدها الشريحة موضوع مقال الشريحة EMV في هذه المرحلة.

### الطبقة الثانية: توكن الصرف من طرف إلى طرف

الـ MAC يحمي الطريق بين الصراف والمضيف، لكنه لا يحمي الطريق بين التطبيق والموزع داخل الصراف نفسه، وهذا هو الطريق الذي استغلته هجمات التفريغ: برنامج على الحاسب الداخلي يرسل أمر الصرف مباشرة دون أي عملية من المضيف. وهنا يأتي ما تسميه مواصفة XFS4IoT الحماية من طرف إلى طرف End to End Security، ويقول القسم 2.7 منها إن الهدف حماية العملية كلها من المضيف الذي يملك النقد حتى عتاد الموزع، بحيث لا يكفي اختراق أي نقطة بينهما لصرف مبلغ خاطئ.

والفكرة كما تصفها المواصفة في أمر `CashDispenser.Dispense`: العتاد نفسه يولد رقماً عشوائياً Command Nonce، ويجب على من يريد الصرف أن ينشئ بهذا الرقم توكناً Token يأذن بالصرف، والجهاز يتتبع الرقم والتوكن ويرفض أي صرف بتوكن آخر، ويتتبع قيمة ما صرف بالتوكن ويرفض تجاوزها، وحين تصل القيمة المصروفة إلى قيمة التوكن يمسح الرقم العشوائي فيبطل التوكن. وتضيف المواصفة نقطة دقيقة: حين تفرض الحماية، فإن الأوامر الحساسة التي لا يعرف لها توكن تمنع من الصرف، وتضرب مثالاً بأمر اختبار الأدراج، لأنه لو بقي يصرف لاستبدل به المهاجم أمر الصرف ببساطة.

وفي مختبرنا طبقنا الفكرة بأبسط شكل: يرسل الصراف الرقم العشوائي من الموزع في الحقل 48 من طلب السحب، ويوقع المضيف عند الموافقة توكناً من هذا الرقم والعملة والمبلغ بمفتاح لا يعرفه تطبيق الصراف، ويعيده في الحقل 48 من الرد، والموزع يتحقق منه قبل أن يخرج ورقة واحدة. ولاحظ أن التطبيق في الوسط ينقل التوكن فقط ولا يستطيع أن يصنعه، وهذه هي النقطة كلها. والمخرج من `whatif.py`:

```text
what if: software on the ATM calls Dispense with no token
   command: {'type': 'command', 'name': 'CashDispenser.Dispense', 'version': '2.0', 'requestId': 2}
   result : {'errorCode': 'invalidToken'}
what if: a valid token is used a second time
   first : {'errorCode': None, 'currencies': {'SAR': 100}, 'values': {'unit2': 1}}
   second: {'errorCode': 'invalidToken'}
what if: a token for 100 is used to ask for 500
   result: {'errorCode': 'invalidToken'}
```

- **أمر صرف دون توكن** رفضه الموزع، وهذا يمثل برنامجاً على الحاسب الداخلي يأمر الموزع دون عملية من المضيف.
- **التوكن نفسه مرتين**: نجح في المرة الأولى وصرف 100، ورفض في الثانية، لأن الموزع مسح الرقم العشوائي بعد أن صرف قيمة التوكن كاملة، فلا يمكن إعادة تشغيل Replay موافقة قديمة.
- **توكن 100 لطلب 500** رفض، لأن المبلغ جزء مما وقع عليه المضيف، فلا يمكن أخذ موافقة صغيرة واستعمالها لمبلغ كبير.

والتوكن في المختبر HMAC بمفتاح مشترك للتبسيط، والمواصفة تحيل تفاصيل التوكنات وصيغها إلى مواصفة عامة للحماية من طرف إلى طرف مشتركة بين XFS 3.x وXFS4IoT، فلا تأخذ صيغة المختبر على أنها الصيغة المعتمدة. والدرس العام أوضح من الصيغة: لا تجعل أي برنامج على الحاسب الداخلي يملك وحده قرار إخراج المال، واجعل العتاد يطلب دليلاً من الجهة التي تملك المال.

### الطبقة الثالثة: المضيف يعرف ما وافق عليه

وفي آخر تجربة أرسلنا عكساً لطلب لم يصل إلى المضيف أصلاً، فرد المضيف برمز 25 «لا يوجد سجل» Unable to locate record:

```text
what if: a reversal arrives for a request the host never received
17:15:24 ATM     0420 reason 68, dispensed 0.00 -> 0430 rc 25
```

وهذا يبدو تفصيلاً، لكنه يحمي البنك من نوع آخر من الأخطاء: عكس يعيد مالاً لعملية لم تحدث. فالمضيف الذي يعيد المال لكل عكس يصله دون أن يجد العملية الأصلية، يجعل رسالة العكس نفسها طريقاً لإضافة مال إلى حساب. والمضيف في المختبر يعيد فقط ما خصمه، ولا يعيده إلا مرة واحدة، وهذان الشرطان معاً هما ما يجعل العكس آمناً.

### المراقبة: ما الذي تنظر إليه كل يوم؟

الطبقات السابقة تمنع، والمراقبة تكتشف ما لم يمنع. ومن التوصيات في المصدرين السابقين، وما رأيناه في المختبر، يمكن أن نجمع ما يراقبه فريق الصرافات في لوحة واحدة:

- **أحداث العتاد**: فتح باب الخزنة أو منطقة الخدمة في غير موعد صيانة معروف، وتغير حالة الموزع، وانقطاع الكهرباء أو الشبكة عن صراف.
- **أخطاء الحماية**: كل رد `invalidToken` من الموزع، وكل رسالة يفشل فيها الـ MAC، فهذه لا تحدث في التشغيل الطبيعي، ورسالة واحدة منها تستحق تنبيهاً.
- **نسب العكس والمهلة**: لكل صراف ولكل بنك مصدر، كما ذكرنا في فقرة المهلة.
- **نمط السحب**: عدد عمليات السحب ومبالغها وتوقيتها لكل صراف مقابل نمطه المعتاد، فتنبيه FASTCash يوصي بخط أساس Baseline للأطراف والمبالغ والتكرار والتوقيت، والعمليات المنسقة تظهر غالباً كقفزة في هذا النمط. وقد شرحنا بناء مثل هذه القواعد والنماذج في [مقال كشف الاحتيال](https://arabroot.io/articles/%D9%83%D8%B4%D9%81-%D8%A7%D9%84%D8%A7%D8%AD%D8%AA%D9%8A%D8%A7%D9%84-fraud-detection-%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%85-%D8%AA%D8%B9%D9%84%D9%85-%D8%A7%D9%84%D8%A9/).

## كيف يبدو هذا في بنك حقيقي؟

عندما جمع الفريق ما تعلمه من الشكوى، وجد أن الصراف الواحد يمر بأيدي فرق كثيرة، ولكل فريق جزء من الصورة. فريق القنوات يملك تطبيق الصراف وشاشاته ورسائله للعميل، وفريق المضيف والمحول يملك الرسائل مع الشبكة والمفاتيح ورموز الرد، وفريق النقد يملك الأدراج والتعبئة والعد، والشركة المصنعة أو مقدم الخدمة يملك العتاد ومزودات الخدمة والصيانة، وفريق أمن المعلومات يملك المراقبة والتنبيهات، وفريق الاحتيال يملك قواعد النمط والتحقيق. والشكوى الواحدة قد تحتاج سجلاً من كل فريق، فإذا لم يكن بينها رابط مشترك، ضاع نصف يوم في البحث.

والرابط المشترك في المختبر واضح: رقم الجهاز ورقم التتبع والرقم المرجعي في الحقل 37، وهي تظهر في سجل الصراف وسجل المضيف ورسالة العكس معاً. فوضع الفريق قاعدة أن كل سجل في أي نظام يتعلق بالصرافات يحمل هذه الثلاثة، وأن الرقم المرجعي يطبع على إيصال العميل، فإذا اتصل العميل يكفي أن يقرأه من الإيصال لتجد كل الفرق العملية نفسها.

والقرار الثاني كان عن الاختبار. فأغلب ما رأيناه في المختبر لا يظهر في الاختبار العادي: الموزع لا يتعطل حين تريد، والشبكة لا تتأخر حين تريد. ولهذا يحتاج فريق تطبيق الصراف إلى محاكيات للأجهزة والمضيف تسمح بحقن الأعطال عمداً، كما فعلنا بالمعامل `jam_after` والمبلغ البطيء، وقد ذكر ملخص تاريخ XFS أن أدوات اختبار XFS تسمح باختبار التطبيقات على عتاد محاكى، وبعضها يدعم اختبارات التراجع Regression الآلية. فكل إصدار جديد من التطبيق يمر على قائمة سيناريوهات الأعطال قبل أن يصل إلى صراف واحد في الميدان.

## أخطاء شائعة Common Mistakes

| الخطأ                                   | لماذا يضر                                   | البديل                                  |
| --------------------------------------- | ------------------------------------------- | --------------------------------------- |
| طلب الموافقة قبل سؤال الموزع            | خصم مبلغ لا يمكن صرفه ثم عكس لا داعي له     | Denominate أولاً، ثم الطلب              |
| عكس كامل بعد صرف جزئي                   | العميل يأخذ 500 ويستعيد 600                 | الحقل 95 بما صرف فعلاً                  |
| قراءة «الرد التالي» دون مطابقة الرقم    | موافقة قديمة تفسر على أنها رد جديد          | مطابقة STAN، وإهمال ما لا يطابق         |
| الصرف بعد إرسال العكس                   | المال خرج وأعيد إلى الحساب معاً             | القرار بعد المهلة نهائي                 |
| عكس يعيد المال كلما وصل                 | إعادة مزدوجة مع 0421                        | مفتاح العملية الأصلية، والعكس مرة واحدة |
| التحقق من MAC في اتجاه واحد             | موافقة مزورة تصل إلى الصراف                 | MAC على الطلب والرد معاً                |
| تطبيق يأمر الموزع دون إذن من المضيف     | برنامج على الحاسب الداخلي يكفي لإخراج المال | توكن من المضيف يتحقق منه العتاد         |
| رسالة «لا يمكن إتمام العملية» في كل عطل | العميل لا يعرف هل أخذ المال أم لا، فيشتكي   | رسالة تقول ما صرف وما أعيد              |
| إكمال الحقول الثابتة دون مواصفة         | اختلاف القيم بين الصراف والمضيف في المطابقة | قاعدة الإكمال من مواصفة الشريك          |

## قائمة تحقق لتطبيق صراف أو مضيف

- الصراف يسأل الموزع هل المبلغ قابل للصرف قبل أي رسالة إلى المضيف.
- كل رد يطابق برقم التتبع، وكل رد لا يتحقق رمزه MAC يعامل كأنه لم يصل.
- المهلة محددة ومراقبة، وانتهاؤها يرسل عكساً، والرد المتأخر لا يغير القرار.
- العطل أثناء الصرف يرسل عكساً بالمبلغ الذي خرج فعلاً في الحقل 95، لا عكساً كاملاً.
- المضيف يعكس العملية مرة واحدة فقط بمفتاح الجهاز ورقم التتبع الأصلي، ويرفض عكس عملية لا يعرفها.
- الموزع لا يصرف إلا بإذن من الجهة التي تملك النقد، والأوامر التي لا تحمل إذناً لا تصرف.
- أحداث الخزنة والموزع وأخطاء الحماية ترسل إلى المراقبة لحظياً، مع خط أساس لنمط السحب.
- السجل يحمل رقم الجهاز ورقم التتبع والرقم المرجعي، ويفرق بين صرف وسحب أوراق وتعطل.
- عدادات الأدراج تطابق مع سجل المضيف والعد الفعلي عند كل تعبئة.
- محاكيات الأعطال جزء من اختبار كل إصدار جديد من التطبيق.

## الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

- الصراف مجموعة أجهزة يربطها حاسب داخلي: موزع وأدراج في خزنة منفصلة، ولوحة EPP تشفر الرقم السري داخلها، وقارئ بطاقة، وسجل يحفظ كل خطوة.
- التطبيق يكلم الأجهزة بلغة XFS الموحدة، وXFS4IoT ينقلها من واجهة Windows إلى رسائل JSON عبر WebSocket، بترويسة وحمولة وأوامر مثل Denominate وDispense وPresent.
- السحب رسالة مالية 0200 برمز معالجة 01 تخصم مباشرة، ولذلك يحتاج كل ما يحدث بعد الموافقة إلى طريقة للتصحيح.
- في المختبر أعاد العكس الجزئي 100 ريال آلياً حين خرجت ورقة 500 من 600، وأعاد عكس المهلة 700 كاملة، ومنع المضيف إعادة مزدوجة مع 0421، فانتهى الحساب والأدراج على 1,300 ريال مصروفة فعلاً.
- الـ MAC على الطلب والرد يمنع التغيير والتزوير في الطريق، وتوكن الصرف من المضيف يمنع برنامجاً على الحاسب الداخلي من إخراج المال وحده، وكلاهما جربناه بنتيجة حقيقية.
- الصراف الواحد يمر بفرق كثيرة، والرقم المرجعي ورقم التتبع في كل سجل هما ما يجعل التحقيق يأخذ دقائق لا ساعات.

وفي مقالات هذه المرحلة القادمة بإذن الله نفتح الأجزاء التي مررنا عليها سريعاً هنا: الشريحة EMV وما تحسبه البطاقة في كل عملية، ووحدات أمن العتاد HSM التي تفك كتل الرقم السري وتحفظ المفاتيح، والمحول المالي الذي يوجه طلب الصراف إلى البنك المصدر الصحيح. وبقية مقالات قسم التقنية المالية والمدفوعات:

[![](https://arabroot.io/content/images/2026/10/card-payment-flow-cover-cover.webp)دليلكيف يعمل الدفع بالبطاقة؟ التفويض Authorization والتسويةمن ضغطة «ادفع» إلى آخر اعتراض: التفويض و3D Secure والتحصيل والتسوية والاسترداد والاعتراض، مع مختبر لأحداث مزود الدفع ومطابقتها.](https://arabroot.io/articles/%D9%83%D9%8A%D9%81-%D8%AA%D8%B9%D9%85%D9%84-%D8%B9%D9%85%D9%84%D9%8A%D8%A9-%D8%A7%D9%84%D8%AF%D9%81%D8%B9-%D8%A8%D8%A7%D9%84%D8%A8%D8%B7%D8%A7%D9%82%D8%A9-%D8%A7%D9%84%D8%AA%D9%81%D9%88%D9%8A%D8%B6-%D9%88%D8%A7%D9%84%D8%AA%D8%B3%D9%88%D9%8A%D8%A9-%D9%88%D8%A7%D9%84%D8%A7%D8%B9%D8%AA%D8%B1%D8%A7%D8%B6/)[![](https://arabroot.io/content/images/2026/10/iso8583-iso20022-cover-cover.webp)دليلرسائل البنوك: ISO 8583 للبطاقات وISO 20022 للتحويلاتنفكك رسالة تفويض بطاقة بايتاً بايتاً، ونبني أمر تحويل بأسماء عربية ونتحقق منه بالمخطط الرسمي، ونرى ما يتعطل في كل معيار.](https://arabroot.io/articles/%D8%B1%D8%B3%D8%A7%D8%A6%D9%84-%D8%A7%D9%84%D8%A8%D9%86%D9%88%D9%83-iso-8583-%D9%84%D9%84%D8%A8%D8%B7%D8%A7%D9%82%D8%A7%D8%AA-%D9%88-iso-20022-%D9%84%D9%84%D8%AA%D8%AD%D9%88%D9%8A%D9%84%D8%A7%D8%AA/)[![](https://arabroot.io/content/images/2026/10/emv-contactless-cover-cover.webp)دليلالشريحة EMV والدفع اللاتلامسي: ماذا يحدث في ثانيتين؟نفك بيانات الشريحة في الحقل 55 من عينة منشورة، ونعيد حساب رمز ARQC منشور خطوة خطوة، ونرى كيف يقرر الجهاز الاتصال بالبنك أو الموافقة دونه.](https://arabroot.io/articles/%D8%A7%D9%84%D8%B4%D8%B1%D9%8A%D8%AD%D8%A9-emv-%D9%88%D8%A7%D9%84%D8%AF%D9%81%D8%B9-%D8%A7%D9%84%D9%84%D8%A7%D8%AA%D9%84%D8%A7%D9%85%D8%B3%D9%8A-contactless/)[![](https://arabroot.io/content/images/2026/10/hsm-pin-cover-cover.webp)دليلوحدات أمن العتاد HSM والرقم السري PIN وإدارة المفاتيحنبني كتلة الرقم السري بمثال Mastercard المنشور داخل SoftHSM، ونرى مكونات المفاتيح وKCV والصيغة 4 وكتل TR-31، ولماذا لا يكفي HSM عام للرقم السري.](https://arabroot.io/articles/%D9%88%D8%AD%D8%AF%D8%A7%D8%AA-%D8%A7%D9%85%D9%86-%D8%A7%D9%84%D8%B9%D8%AA%D8%A7%D8%AF-hsm-%D9%88%D8%A7%D9%84%D8%B1%D9%82%D9%85-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A-pin-%D9%88%D8%A7%D8%AF%D8%A7%D8%B1%D8%A9-%D8%A7%D9%84%D9%85%D9%81%D8%A7%D8%AA%D9%8A%D8%AD/)[![](https://arabroot.io/content/images/2026/10/payment-switch-cover-cover.webp)دليلالمحول المالي Payment Switch: توجيه رسائل البطاقات مع jPOSنبني محولاً بـ jPOS يوجه طلبات مستحوذ إلى مصدرين، ونعيد قصة رفض ظهر معه حجز، ونرى ماذا يحدث حين يتأخر المصدر أو يتوقف أو يتكرر رقم التتبع.](https://arabroot.io/articles/%D8%A7%D9%84%D9%85%D8%AD%D9%88%D9%84-%D8%A7%D9%84%D9%85%D8%A7%D9%84%D9%8A-payment-switch-%D8%AA%D9%88%D8%AC%D9%8A%D9%87-%D8%B1%D8%B3%D8%A7%D8%A6%D9%84-%D8%A7%D9%84%D8%A8%D8%B7%D8%A7%D9%82%D8%A7%D8%AA-%D9%85%D8%B9-jpos/)[![](https://arabroot.io/content/images/2026/10/saudi-payments-cover-cover.webp)دليلمنظومة المدفوعات في السعودية: سريع وSARIE ومدى وسدادالأنظمة الوطنية للمدفوعات في المملكة كما تصفها مصادر البنك المركزي، وما يعنيه كل نظام للمطور، مع تحليل تقارير نقاط البيع الأسبوعية.](https://arabroot.io/articles/%D9%85%D9%86%D8%B8%D9%88%D9%85%D8%A9-%D8%A7%D9%84%D9%85%D8%AF%D9%81%D9%88%D8%B9%D8%A7%D8%AA-%D9%81%D9%8A-%D8%A7%D9%84%D8%B3%D8%B9%D9%88%D8%AF%D9%8A%D8%A9-%D8%B3%D8%B1%D9%8A%D8%B9-%D9%88-sarie-%D9%88-%D9%85%D8%AF%D9%89-%D9%88-%D8%B3%D8%AF%D8%A7%D8%AF/)

## سجل التحديثات

- أكتوبر 2026: كتابة المقال وتشغيل المختبر يوم 27 أكتوبر 2026 في Containers من python:3.13.17-slim (Python 3.13.17) مع pyiso8583 4.0.1 وpycryptodome 4.2.0، ببطاقات اختبار ومفاتيح اختبار فقط، ومراجعة مواصفة XFS4IoT (إصدار 2023-02 لدى CEN) وتنبيه CISA AA20-239A وصفحة مدى لدى البنك المركزي في التاريخ نفسه.