لنفرض أن لديك شركة ناشئة تدير عربات قهوة متنقلة في الفعاليات والحدائق، وكل عربة فيها شخص واحد يحضر القهوة ويستلم الدفع. وجهاز نقطة البيع يحتاج اتصالاً وشحناً واهتماماً، وفي زحمة الفعالية يتأخر الطابور عند الدفع أكثر مما يتأخر عند التحضير. فاقترح أحد المؤسسين حلاً بدا بسيطاً جداً: ملصق برمز QR على كل عربة، يمسحه العميل بتطبيق محفظته، فيدفع، وتنتهي القصة. ثم بدأت الأسئلة: ماذا يحمل هذا الرمز بالضبط؟ وهل نطبع رمزاً واحداً أم نعرض رمزاً لكل طلب؟ وكيف يعرف الموظف أن العميل دفع فعلاً، لا أنه أراه شاشة تقول «تم»؟ وماذا لو لصق أحد رمزاً آخر فوق رمزنا في الليل؟
وهذا هو موضوع المقال: الدفع برمز QR من الداخل. ما الذي يحمله الرمز، ولماذا يتبع أغلب العالم صيغة واحدة وضعتها EMVCo، والفرق بين الرمز الثابت والديناميكي، وأين يقف الرمز في المملكة بحسب تعميم البنك المركزي، ثم ما يحميه الرمز وما لا يحميه. وسوف نبني في المختبر بلغة Python مولداً وقارئاً للرموز بهذه الصيغة مع حساب CRC، ونتحقق منهما على خمسة رموز رسمية منشورة في دليل نظام Pix من البنك المركزي البرازيلي، ثم نرسم رموزنا ونقرؤها من الصورة، ونجرب ما يحدث عند التلاعب.
سوف نناقش في هذا المقال:
- نمطا الدفع برمز QR: التاجر يعرض الرمز، أو العميل يعرضه، ولماذا يختلفان في كل شيء.
- صيغة EMV للرمز الذي يعرضه التاجر: رقم وطول وقيمة، وقوالب داخل قوالب، وCRC في الآخر.
- الرمز الثابت والديناميكي، ومتى يناسب كل منهما.
- الرمز في المملكة: تعميم البنك المركزي في 2021 ومشروع المدفوعات السعودية للتشغيل البيني.
- المختبر: محلل ومولد يتحققان على رموز Pix الرسمية، ورموز لعربة القهوة تقرأ من الصورة، وأخطاء نرتكبها عمداً.
- الأمان من جهة المدافع: لماذا لا يحمي CRC من استبدال الرمز، وما الذي يحمي فعلاً.
- متى يناسبك الدفع برمز QR ومتى لا يناسبك، وأخطاء شائعة وقائمة تحقق.
من يعرض الرمز ومن يمسحه؟
«الدفع برمز QR» عبارة تجمع طريقتين مختلفتين تماماً، والصورة التالية تفرق بينهما:

- التاجر يعرض الرمز Merchant-Presented Mode واختصاراً MPM: الرمز على ملصق أو شاشة عند التاجر، فيه معرف حساب التاجر وربما المبلغ، والعميل يمسحه بتطبيقه ويؤكد الدفع. التاجر هنا لا يحتاج أي جهاز قراءة، وهذا ما جذب صاحب عربة القهوة.
- العميل يعرض الرمز Consumer-Presented Mode واختصاراً CPM: تطبيق العميل يولد رمزاً مؤقتاً يمثل وسيلة دفعه، وجهاز التاجر يمسحه ويطلب الدفع كما يفعل مع البطاقة. وهنا يحتاج التاجر جهازاً يقرأ الرمز ويتصل بنظام الدفع.
والفرق بين النمطين ليس في من يمسك الكاميرا فقط، وإنما في مكان الثقة. ففي النمط الأول الرمز عام، يراه كل من يمر بالعربة، ولا يحمل أي سر، فخطره أن يستبدل بآخر كما سنرى. وفي النمط الثاني الرمز يمثل وسيلة دفع العميل، فهو أشبه برقم بطاقة مؤقت، ولهذا يولده التطبيق لمدة قصيرة ولعملية واحدة، بنفس فكرة الرموز البديلة Tokens التي شرحناها في أمن بيانات البطاقات والترميز، فمن يصور رمز العميل لا يستطيع أن يدفع به مرة ثانية بعد انتهائه. والنتيجة أن أسئلة الأمان في النمط الأول عن سلامة الرمز المعروض، وفي الثاني عن حماية الرمز الذي يعرضه العميل من النسخ.

والجهة التي وضعت صيغة موحدة للنمطين هي EMVCo، وهي نفسها التي تدير مواصفات بطاقات الشريحة EMV، وتنشر في صفحة رموز EMV QR أن مواصفاتها تقدم قالباً موحداً لإنشاء رموز تعمل بنفس الطريقة في كل مكان للدفع بالبطاقات والحسابات، ومواصفة الرمز الذي يعرضه التاجر منشورة بالإصدار 1.1 بتاريخ 17 أبريل 2020 في صفحة المواصفة. وهذا المقال عن هذا النمط، لأنه الأكثر انتشاراً عند التجار الصغار، ولأن الرمز فيه بيانات مقروءة يمكن أن نبنيها ونفحصها بأنفسنا.
ماذا يحمل الرمز؟ رقم وطول وقيمة
الرمز نفسه مجرد صورة لنص، والمهم هو النص، ويسمى الحمولة Payload. والحمولة في صيغة EMV سلسلة من الكائنات Data Objects، كل منها رقم من خانتين ID، ثم طول من خانتين، ثم القيمة بذلك الطول. وهذه نفس فكرة TLV التي رأيناها في رمز QR في الفاتورة الإلكترونية، مع فرقين: الرقم والطول هنا نص من خانتين لا بايت واحد، والحمولة نص مقروء لا بايتات مرمزة بـ base64. ولاحظ أن رمز الفاتورة ورمز الدفع شيئان مختلفان تماماً، فالأول يثبت أن الفاتورة صدرت من نظام مسجل، والثاني يطلب دفعاً لحساب.
ولأن مواصفة EMVCo نفسها تنزل من موقعها بعد قبول شروط استخدامه، اعتمدنا في المختبر على وثيقة رسمية مفتوحة مبنية عليها: دليل معايير بدء الدفع في Pix من البنك المركزي البرازيلي، الإصدار 2.10.0، الذي يصف رمز BR Code المبني على صيغة EMV للرمز الذي يعرضه التاجر، ويطبع أمثلة كاملة بقيم CRC الخاصة بها، ويحيل إلى مواصفة EMVCo في الحواشي. ومن أهم الكائنات كما يشرحها الدليل:
| ID | الكائن | ملاحظة |
|---|---|---|
| 00 | صيغة الحمولة Payload Format Indicator | قيمته 01، وهو أول كائن دائماً |
| 01 | طريقة البدء Point of Initiation Method | اختياري، والقيمة 12 تعني ألا يبدأ بالرمز أكثر من دفعة واحدة |
| 02 إلى 51 | معلومات حساب التاجر Merchant Account Information | واحد أو أكثر، لكل نظام دفع يقبله التاجر، وفي Pix القالب 26 وأوله معرف النظام GUI br.gov.bcb.pix |
| 52 | فئة التاجر MCC | أربعة أرقام، والدليل يستخدم 0000 حين لا تحدد |
| 53 | العملة | الرمز الرقمي في ISO 4217، و986 للريال البرازيلي في أمثلة الدليل |
| 54 | المبلغ | اختياري، وإذا غاب يكتبه العميل |
| 58 و59 و60 | الدولة واسم التاجر ومدينته | BR وFulano de Tal وBRASILIA في الأمثلة |
| 62 | بيانات إضافية | قالب، ومنه 05 لمعرف العملية |
| 63 | CRC | آخر كائن، أربعة أحرف ست عشرية |
ويحدد الدليل حساب CRC في حاشية قصيرة: كثير الحدود 0x1021، والقيمة الابتدائية 0xFFFF، وترتيب الكائنات يغير CRC، ويحسب على كل الحمولة بما فيها «6304»، أي رقم الكائن 63 وطوله. وهذا النوع يعرف عادة باسم CRC-16/CCITT-FALSE. ويذكر الدليل أيضاً أن حد الطول في صيغة EMV 99 حرفاً للقالب الواحد، لأن الطول خانتان، فكل ما تريد وضعه في قالب حساب التاجر يجب أن يتسع في 99 حرفاً.
الرمز الثابت والرمز الديناميكي
الكائن 01 يقول هل يستخدم الرمز مرة واحدة أم مرات كثيرة، وهذا أهم قرار تصميمي في المشروع كله. ويذكر دليل Pix القيمة 12 للرمز الذي لا يبدأ به أكثر من دفعة، وفي المختبر استخدمنا القيمة 11 للرمز الثابت، فلا يوجد كائن آخر يغير بين النوعين:
- الرمز الثابت Static: يطبع مرة واحدة ويلصق، فيه حساب التاجر فقط غالباً، والعميل يكتب المبلغ بنفسه. وهو أرخص وأبسط، ولا يحتاج شاشة ولا اتصالاً عند التاجر. ولكن العميل قد يكتب مبلغاً خاطئاً، والتاجر لا يعرف أي دفعة لأي طلب، وكل الدفعات تصل بنفس المرجع.
- الرمز الديناميكي Dynamic: يولد لكل طلب على شاشة، وفيه المبلغ ومرجع الطلب، ويستخدم مرة واحدة. فالعميل لا يكتب شيئاً، والتاجر يطابق كل دفعة بطلبها، ولكنه يحتاج شاشة ونظاماً يولد الرمز، أي جهازاً من نوع ما مرة أخرى.
وفي القصة اختار الفريق الاثنين: رمز ثابت مطبوع على العربة للحالات البسيطة، وجهاز لوحي صغير يعرض رمزاً ديناميكياً لكل طلب في أوقات الذروة، لأن المطابقة بالمرجع توفر ساعات في آخر اليوم. وهذا الاختيار نفسه ما سوف نبنيه في المختبر.

الرمز نفسه: من أين جاء، ولماذا يتحمل الخدش؟
قبل الحمولة، يفيد أن تعرف ما هو الرمز المربع نفسه، لأن خصائصه تحدد ما تستطيع أن تطبعه على عربة في الشمس. فشركة DENSO WAVE، التي كانت قسماً في DENSO لتطوير قارئات الباركود، تروي في صفحة تاريخ الرمز أنها أعلنت رمز QR سنة 1994 بطلب من مستخدمين أرادوا باركوداً يحمل معلومات أكثر، وأن QR اختصار Quick Response لأن الفكرة كانت سرعة القراءة، وأنها نشرت مواصفاته ليستخدمه أي أحد، واحتفظت ببراءة الاختراع وأعلنت أنها لن تستخدمها. وهذا القرار هو ما جعل الرمز متاحاً لكل تطبيق وطابعة، ولهذا نستطيع أن نولده ونقرؤه بمكتبات مفتوحة المصدر كما فعلنا.
ويتكون الرمز من مربعات صغيرة تسمى Modules، وتذكر صفحة إصدارات الرمز أن الإصدارات من 1 إلى 40، يبدأ الأول بـ 21 في 21 مربعاً، وكل إصدار يضيف أربعة مربعات في الضلع، حتى 177 في 177 في الإصدار 40. وهذا ما رأيناه بالضبط في المختبر: الإصدار 6 بـ 41 مربعاً، والإصدار 7 بـ 45، والإصدار 8 بـ 49. والمكتبة تختار أصغر إصدار يتسع للحمولة، فكل حرف إضافي في الحمولة قد يرفع الإصدار ويصغر المربعات.
والخاصية الثانية تصحيح الأخطاء Error Correction بخوارزمية Reed-Solomon، التي تسمح بقراءة الرمز حتى لو اتسخ أو تلف جزء منه. وتشرح صفحة تصحيح الأخطاء أن هناك أربعة مستويات L وM وQ وH، وأن رفع المستوى يزيد القدرة على التصحيح ويزيد حجم الرمز معاً، وأن المستوى M بنسبة 15% هو الأكثر استخداماً، وأن Q أو H قد تختار للبيئات التي يتسخ فيها الرمز. واخترنا في المختبر M، أما لملصق خارجي على عربة فالمستوى الأعلى يستحق التجربة، مع العلم أنه يكبر الرمز، فالقرار مفاضلة بين التحمل وحجم المربع، ولا يحسمه إلا اختبار على الملصق الحقيقي في المكان الحقيقي.
الرمز في المملكة: تعميم 2021
في المملكة لا تترك الصيغة لكل مقدم خدمة. ففي تعميم البنك المركزي عن تقنية رمز الاستجابة السريع بتاريخ 21 سبتمبر 2021 الموافق 14/2/1443هـ، والنسخة الإنجليزية منه موصوفة بأنها مترجمة، يذكر البنك المركزي أن فريق المدفوعات السعودية يعمل على مشروع لتطوير بيئة الدفع بتقنية رمز QR، يهدف إلى تعزيز العمليات وفق مبدأ التشغيل البيني Interoperability لأنظمة الدفع بين المشاركين في القطاع، ومنهم البنوك وشركات التقنية المالية المرخصة. ويؤكد التعميم أن كل الجهات التي أطلقت الخدمة سابقاً يجب أن تلتزم بالمتطلبات والمعايير الفنية والتجارية لبيئة الدفع المرتبطة بالمشروع، وأن تنفذ إجراءات الاختبار بإكمال الاعتماد الفني مع المدفوعات السعودية قبل نهاية 2021.
وهذا يعني ثلاثة أشياء عملية لعربة القهوة. الأول أن المواصفة التي تعتمدها المدفوعات السعودية لحمولة الرمز هي المرجع، لا صيغة يخترعها مقدم الخدمة، وهي ليست منشورة للعامة، فلم نستخدمها في المختبر، والمعرف الذي في رموزنا مثال لا يتبع أي نظام حقيقي. والثاني أن التاجر الصغير لا يبني الرمز بنفسه عادة، وإنما يحصل عليه من مزود الخدمة المرخص الذي يتعامل معه، بنكاً كان أو شركة تقنية مالية، وهو المسؤول عن الاعتماد. والثالث أن فكرة التشغيل البيني هي أن يقرأ الرمز أكثر من تطبيق، فالعميل لا يحتاج تطبيقاً بعينه لكل تاجر. وما يفيد المهندس من المختبر هو فهم البنية والفحوص والمخاطر، لأنها نفسها في أي نظام يتبع صيغة EMV، وهي ما يجب أن يسأل عنه المزود قبل أن يعتمد عليه.
المختبر: محلل ومولد بصيغة EMV
كتبنا الملف emvqr.py بثلاث دوال: بناء الحمولة، وتحليلها، وحساب CRC. وشغلنا كل المختبر في Container من python:3.13.17-slim. وهذه الدوال:
def crc16(data: bytes) -> str:
"""CRC-16 with polynomial 0x1021 and initial value 0xFFFF (no reflection, no final XOR)"""
crc = 0xFFFF
for b in data:
crc ^= b << 8
for _ in range(8):
crc = ((crc << 1) ^ 0x1021) if crc & 0x8000 else (crc << 1)
crc &= 0xFFFF
return f"{crc:04X}"
def tlv(id_, value):
if len(value) > 99:
raise ValueError(f"ID {id_}: {len(value)} characters, the length field has two digits")
return f"{id_}{len(value):02d}{value}"
def build(objects):
"""objects: list of (id, value or list of (id, value)); ID 63 is added last over everything before it"""
body = "".join(tlv(i, v if isinstance(v, str) else "".join(tlv(a, b) for a, b in v)) for i, v in objects)
body += "6304"
return body + crc16(body.encode("utf-8"))
def parse(s, nested=True):
out, i = [], 0
while i < len(s):
id_, ln = s[i:i + 2], s[i + 2:i + 4]
if not (id_.isdigit() and ln.isdigit()):
raise ValueError(f"position {i}: expected two digits for ID and two for length, found {s[i:i + 4]!r}")
v = s[i + 4:i + 4 + int(ln)]
if len(v) < int(ln):
raise ValueError(f"position {i}: ID {id_} says {ln} characters but only {len(v)} remain")
out.append((id_, parse(v, False) if nested and id_ in NESTED else v))
i += 4 + int(ln)
return outفي الكود أعلاه لاحظ التالي:
- CRC بايتاً بايتاً: لكل بايت ندخله في الجزء الأعلى من المسجل، ثم ثماني دورات، في كل منها إذا كانت الخانة العليا 1 نزيح ونطبق 0x1021، وإلا نزيح فقط. ولا يوجد قلب للخانات ولا XOR نهائي، وهذا ما يميز هذا النوع عن أنواع CRC-16 أخرى بنفس كثير الحدود، والخطأ في هذا التفصيل يعطي CRC صحيح الشكل خاطئ القيمة.
- «6304» داخل الحساب: نضيف رقم الكائن 63 وطوله 04 قبل حساب CRC، ثم نلصق النتيجة، كما يقول الدليل.
- القوالب: الكائنات من 26 إلى 51، و62 و64 و80، قيمتها نفسها سلسلة كائنات، فنحللها مرة ثانية.
- الأخطاء برسائل مفيدة: إذا قال الطول 13 ولم يبق إلا 7، يقول المحلل أين بالضبط، لأن هذا ما تحتاجه عندما يشكو تاجر أن رمزه لا يقرأ.
التحقق على خمسة رموز رسمية
قبل أن نثق في الدوال، شغلناها على الأمثلة الخمسة المطبوعة في دليل Pix، كل منها بقيمة CRC مطبوعة. ونقلنا النصوص من ملف PDF يدوياً، وهنا فائدة إضافية لـ CRC: إذا أخطأنا في نقل حرف واحد، فلن يطابق. والمخرج:
static, no amount (CRC 1D3D) 137 chars CRC printed 1D3D computed 1D3D match True
dynamic, payload by URL (CRC 64E4) 155 chars CRC printed 64E4 computed 64E4 match True
recurring only (CRC F2DA) 175 chars CRC printed F2DA computed F2DA match True
static with amount and recurrence (CRC 2875) 225 chars CRC printed 2875 computed 2875 match True
dynamic with recurrence (CRC FB42) 233 chars CRC printed FB42 computed FB42 match True
== the first example, object by object
00 01
26
00 br.gov.bcb.pix
01 123e4567-e12b-12d1-a456-426655440000
52 0000
53 986
58 BR
59 Fulano de Tal
60 BRASILIA
62
05 ***
63 1D3Dالأمثلة الخمسة طابقت من أول تشغيل، أي أن خوارزمية CRC صحيحة، وأن نقلنا للنصوص صحيح، وأن المحلل يقرأ القوالب المتداخلة كما يصفها الدليل. ولاحظ في المثال الأول أن الكائن 26 يحمل معرف النظام br.gov.bcb.pix ثم مفتاح Pix للتاجر، وأن الكائن 62 يحمل معرف العملية ***، وهي القيمة التي يذكر الدليل، نقلاً عن مواصفة EMV، أنها تعني أن التطبيق هو من يحصل على المعلومة. وفي الأمثلة الديناميكية بدل المفتاح عنوان URL، يجلب منه التطبيق بيانات الدفع من خادم المستلم، وهذا تصميم مهم سنعود إليه في فقرة الأمان.
ماذا لو استخدمت نوع CRC آخر؟
قلنا إن تفصيل القيمة الابتدائية وقلب الخانات يميز هذا النوع عن غيره، وهذا ليس كلاماً نظرياً، فمكتبات كثيرة تقدم دالة اسمها «CRC-16» أو «CRC-CCITT» وكل منها تقصد شيئاً مختلفاً. فشغلنا كثير الحدود نفسه 0x1021 بأربع طرائق على المثال الرسمي الأول:
printed in the manual: 1D3D
init 0xFFFF, not reflected (CCITT-FALSE) 1D3D
init 0x0000, not reflected (XMODEM) 4AC0
init 0x0000, reflected (KERMIT) 4889
init 0xFFFF, reflected (X-25 without final XOR) F763طريقة واحدة من الأربع تعطي القيمة المطبوعة، والثلاث الأخرى تعطي أربعة أحرف ست عشرية صحيحة الشكل تماماً. فإذا استخدم مولد الرموز عند المزود أو في نقطة بيعك واحدة منها، فكل الرموز تبدو سليمة لعينك وتفشل كلها في تطبيقات العملاء. ولهذا لا تقبل دالة CRC من أي مكتبة قبل أن تجربها على مثال رسمي معروف القيمة، كما فعلنا هنا، ولا تكتفي بأن «الاسم يشبه».
رموز عربة القهوة، من النص إلى الصورة والعكس
ثم بنينا رمزين للعربة: ثابت بحساب التاجر فقط، وديناميكي بمبلغ 18.50 ريالاً ورقم طلب ورقم الجهاز في الكائن 62. والعملة 682 وهي الرمز الرقمي للريال السعودي، والدولة SA، والحساب في الكائن 26 بمعرف نظام مخترع sa.example.wallet كما ذكرنا. ورسمنا كل رمز بمكتبة qrcode، ثم قرأناه من الصورة نفسها بمكتبة zxing-cpp، لأن الرمز الذي لا يقرأ من صورته لا قيمة لصحة نصه:
ACCOUNT = [("00", "sa.example.wallet"), ("01", "MERCHANT-000123")]
BASE = [("00", "01"), ("26", ACCOUNT), ("52", "5814"), ("53", "682"), ("58", "SA"),
("59", "ARABROOT LAB CAFE"), ("60", "EXAMPLE CITY")]
def draw(payload, name):
q = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_M, box_size=8, border=4)
q.add_data(payload); q.make(fit=True)
q.make_image().save(name)
back = zxingcpp.read_barcodes(Image.open(name))
return q.version, q.modules_count, back[0].text if back else None== static: 122 chars, CRC ok True, QR version 6 (41x41), read back from the image equal: True
00020101021126400017sa.example.wallet0115MERCHANT-0001235204581453036825802SA5917ARABROOT LAB CAFE6012EXAMPLE CITY63048B62
== dynamic: 163 chars, CRC ok True, QR version 7 (45x45), read back from the image equal: True
00020101021226400017sa.example.wallet0115MERCHANT-000123520458145303682540518.505802SA5917ARABROOT LAB CAFE6012EXAMPLE CITY62280115ORD-2026-0008710705POS03630497D6

في المخرج أعلاه لاحظ التالي:
- الثابت 122 حرفاً والديناميكي 163: المبلغ ورقم الطلب أضافا 41 حرفاً، فانتقل الرمز من الإصدار 6 بـ 41 مربعاً في الضلع إلى الإصدار 7 بـ 45 مربعاً. وكل إصدار أعلى يعني مربعات أصغر في نفس المساحة.
- الفرق الوحيد في البداية:
010211في الثابت و010212في الديناميكي، وهذا ما يقول للتطبيق هل يقبل الرمز مرات كثيرة أم مرة واحدة. - الكائن 62 قالب:
62 28ثم بداخله01 15 ORD-2026-000871و07 05 POS03، أي رقم الطلب ورقم الجهاز. وهذا ما سوف تطابق به الدفعة مع الطلب. - القراءة من الصورة:
read back from the image equal: Trueللرمزين، أي أن مستوى تصحيح الأخطاء M والحجم كافيان على الشاشة. أما على ملصق في الشمس فالسؤال مختلف كما سنرى.
والرمز الديناميكي يغير طريقة تأكيد الدفع كلها، كما تبين الصورة التالية:

وهذا يجيب عن سؤال الموظف في القصة: كيف يعرف أن العميل دفع؟ ليس بالنظر إلى شاشة جوال العميل، فلقطة شاشة لعملية قديمة تبدو مثلها تماماً، وإنما بإشعار من نظام الدفع إلى نظام العربة، يحمل رقم الطلب الذي وضعناه في الكائن 62، فتتحول حالة الطلب إلى «مدفوع» وحدها. وهذه نفس قاعدة المطابقة التي شرحناها في المطابقة: كشف البنك مقابل نظامك، ونفس قاعدة الإشعار الذي يعالج مرة واحدة بمفتاح فريد في Idempotency.
كيف يقرأ تطبيق الدافع الرمز؟
من جهة تطبيق المحفظة الذي يمسح الرمز، القراءة سلسلة خطوات، وكل خطوة مكان محتمل لخطأ أو هجوم:
- فك الصورة: الكاميرا تحول المربعات إلى نص، وتصحيح الأخطاء يغطي الخدوش الصغيرة.
- CRC: إذا لم يطابق، فالتطبيق يرفض الرمز ولا يحاول أن يخمن، لأن الحمولة تالفة.
- التحليل: الكائن 00 يجب أن يكون الأول بقيمة 01، وكل طول يجب أن يتسع في الباقي، وأي خلل يعني رفض الرمز.
- اختيار النظام: التطبيق يبحث في قوالب حساب التاجر عن معرف نظام GUI يعرفه، فإذا لم يجد شيئاً يدعمه يقول ذلك صراحة.
- معرفة المستفيد: هنا القرار الأمني المهم، فالتطبيق لا يعرض الاسم من الرمز، وإنما يسأل نظام الدفع عن صاحب الحساب أو المفتاح، كما يفعل Pix مع دليل المفاتيح.
- التأكيد: يعرض الاسم الموثوق والمبلغ، والعميل يؤكد، ثم يرسل الطلب.
وفي الرموز الديناميكية التي تحمل عنوان URL بدل الحساب، يضيف دليل Pix طبقة أقوى. فالعنوان في الرمز يكتب دون بادئة البروتوكول، والوصول إليه بعد أربع عشرة عملية تحقق، منها أن يكون النطاق مصرحاً لمقدم خدمة المستلم بإنشاء الرموز، وعبر HTTPS فقط. ثم يعود الرد بصيغة JWS بحسب RFC 7515، أي بيانات الدفع موقعة بمفتاح مقدم خدمة المستلم، فيتحقق تطبيق الدافع من التوقيع بالمفتاح العام لذلك المقدم قبل أن يعرض أي شيء، ويوصي الدليل بأن يحفظ مقدم خدمة الدافع الحمولة الموقعة في سجلاته. أي أن الرمز الديناميكي في هذا التصميم مجرد مؤشر إلى بيانات موقعة على خادم مصرح، وهذا ما ينقصه الرمز الثابت: التوقيع موجود، ولكن في الخادم لا في الصورة.
وقد يتساءل البعض: لماذا لا يكون الرمز عنوان موقع عادياً يفتح صفحة دفع، كما تفعل مطاعم كثيرة مع قوائم الطعام؟ والإجابة أن هذا ممكن، ولكنه بالضبط ما يحذر منه تنبيه IC3: رمز يفتح موقعاً يطلب بيانات تسجيل الدخول أو البطاقة، فإذا استبدل بموقع شبيه لا يلاحظ العميل الفرق. أما الحمولة بصيغة EMV فيقرؤها تطبيق المحفظة نفسه، الذي سجل العميل الدخول إليه مسبقاً، ولا يطلب منه إدخال أي بيانات، فيغلق باباً كاملاً من الاحتيال.
ماذا لو؟ أخطاء نرتكبها عمداً
تغيير المبلغ
غيرنا المبلغ في الحمولة الديناميكية من 18.50 إلى 81.50 دون أن نغير CRC، ثم غيرناه وأعدنا حساب CRC:
== a. the amount changed on the way (18.50 -> 81.50), CRC left as it was
CRC ok: False
== b. the same change, and the CRC recomputed
CRC ok: True | amount now 81.50
== c. someone else's account in ID 26, CRC recomputed: what a sticker over the real QR would carry
CRC ok: True | account [('00', 'sa.example.wallet'), ('01', 'MERCHANT-999999')] | name still ARABROOT LAB CAFEوهنا أهم درس في المقال: CRC يكشف التغيير غير المقصود، مثل خطأ في الطباعة أو قراءة خاطئة لمربع في الصورة، ولكنه لا يكشف التغيير المقصود، لأن أي أحد يعيد حسابه بالدالة نفسها في سطر واحد. فالحمولة في التجربة الثالثة تحمل حساباً آخر، واسم التاجر نفسه، وCRC صحيحاً. أي أن الرمز نفسه لا يحمل أي توقيع يثبت أن التاجر هو من أنشأه، بعكس رمز الفاتورة الإلكترونية الذي رأينا فيه توقيع ECDSA.
اسم التاجر بالعربية
== d. an Arabic merchant name in ID 59
name: 13 characters, 24 UTF-8 bytes; length field written: 5913
parsed by characters: مقهى عرب رووت | CRC ok True
a reader that counts bytes takes: مقهى عركتب المولد طول الاسم 13 لأنه يعد الحروف، والمحلل الذي يعد الحروف قرأه كاملاً، ولكن القارئ الذي يعد البايتات أخذ 13 بايتاً من 24، فقطع الاسم إلى «مقهى عر»، وكل الكائنات بعده ستنزاح. والمشكلة ليست في أي من الطريقتين وحدها، وإنما في أن يختلف المولد والقارئ. لذلك إذا احتاج نظامك اسماً عربياً في الرمز، فاقرأ في مواصفة النظام الذي تتبعه كيف يعد الطول، وما مجموعة الحروف المسموحة في كل كائن، ثم اختبر برمز عربي حقيقي على أكثر من تطبيق قبل أن تطبعه على ألف ملصق.
الرمز على ملصق صغير
== e. what if ID 26 carries a long URL instead of an account (like a dynamic Pix code)?
161 chars -> QR version 8 (49x49); at 25 mm on a table sticker one module is 0.44 mm
static 122 chars -> version 6 (41x41); one module 0.51 mmإذا طبعت الرمز على ملصق بعرض 25 مليمتراً، فكل مربع في الرمز الثابت نصف مليمتر تقريباً، وفي رمز بعنوان URL طويل 0.44 مليمتر، والحساب يشمل الهامش الأبيض الذي يحتاجه القارئ حول الرمز، أربعة مربعات من كل جهة. والفرق يبدو صغيراً، ولكن الكاميرا في الإضاءة الضعيفة ومن مسافة تحتاج مربعات أكبر، والملصق على عربة في الشمس يبهت ويخدش. لذلك أبق الرمز الثابت قصيراً بمعرف لا بعنوان طويل، واطبعه أكبر مما تظن، واختبره بأضعف جوال عند فريقك.
كم من الملصق يمكن أن يتلف؟
بقي سؤال من فقرة الرمز نفسه: ماذا تعني مستويات تصحيح الأخطاء عملياً لملصق على عربة؟ فرسمنا الرمز الثابت نفسه بالمستويات الأربعة، ثم غطينا وسطه بمربع رمادي يكبر خطوة خطوة، بعيداً عن مربعات التحديد الثلاثة في الزوايا، وحاولنا قراءته بعد كل خطوة بنفس مكتبة zxing-cpp:
payload 122 chars
level L: version 5 (37x37), still read with 4% of the symbol covered, failed at 6%
level M: version 6 (41x41), still read with 10% of the symbol covered, failed at 12%
level Q: version 7 (45x45), still read with 16% of the symbol covered, failed at 18%
level H: version 9 (53x53), still read with 20% of the symbol covered, failed at 22%في المخرج أعلاه لاحظ التالي:
- كل مستوى أعلى يتحمل أكثر: من 4% من مساحة الرمز في L إلى 20% في H، والترتيب نفسه الذي تصفه صفحة DENSO WAVE، ولكن الأرقام هنا أقل من النسب الاسمية للمستويات، لأن المربع الرمادي يغطي أيضاً أجزاء من بنية الرمز نفسها لا البيانات وحدها، ولأن النسبة الاسمية تحسب بالكلمات المرمزة لا بالمساحة.
- الثمن في الحجم: نفس الحمولة احتاجت الإصدار 5 بـ 37 مربعاً في L، والإصدار 9 بـ 53 مربعاً في H، أي أن كل مربع في H أصغر بنحو الثلث على نفس الملصق.
- القرار لعربة القهوة: ملصق في الخارج يتعرض للخدش والبقع يستحق Q على الأقل مع ملصق أكبر، أما الرمز الديناميكي على شاشة نظيفة فيكفيه M. وهذه أرقام من نوع واحد من التلف، مربع في الوسط، والخدش الحقيقي أو الانعكاس على شاشة لامعة يتصرفان بشكل مختلف، فاختبر على الملصق الحقيقي.
الأمان من جهة المدافع: استبدال الرمز
في 18 يناير 2022 نشر مركز شكاوى جرائم الإنترنت IC3 في مكتب التحقيقات الفيدرالي الأمريكي تنبيهاً عاماً عن التلاعب برموز QR، يذكر فيه أن المجرمين يستبدلون الرموز الرقمية والمطبوعة برموز ضارة، وأن الشركة قد تقدم لعملائها رمزاً يوجههم لإكمال الدفع، فيستبدله المجرم برمز معدل يحول دفعة العميل إليه، ومن نصائحه للأفراد أن يتأكدوا أن الرمز المطبوع لم يتلاعب به أحد، مثل ملصق فوق الرمز الأصلي، وأن جهات إنفاذ القانون لا تضمن استعادة الأموال بعد تحويلها. وهذا بالضبط ما رأيناه في التجربة الثالثة: حمولة صحيحة الشكل وCRC صحيح وحساب آخر.

والدفاع يأتي من أربعة أماكن، ولا يأتي أي منها من الرمز نفسه:
- الاسم الذي يراه العميل يأتي من نظام الدفع: هذا أقوى دفاع، وتصميم Pix مثال واضح عليه، فدليله يقول في حاشية إن الاسم الذي يعرض للدافع هو بالضرورة الاسم الذي يعيده الاستعلام في دليل المفاتيح DICT، وإن اسم التاجر في الكائن 59 يتجاهله الدافع. أي أن من يلصق رمزاً بحسابه سوف يظهر اسمه هو في تطبيق العميل، لا اسم المقهى. فإذا كنت تبني تطبيق الدافع، فلا تعرض أبداً الاسم المكتوب في الرمز كأنه موثوق.
- الرمز الديناميكي على شاشة التاجر: الرمز الذي يولد لكل طلب ويعيش دقائق على شاشة لا يمكن لصق شيء فوقه بسهولة، وإذا دفع العميل لرمز آخر فلن يصل إشعار بالطلب.
- فحص الملصقات: الموظف يفحص الملصق في بداية كل وردية، والملصق تحت غطاء شفاف أو داخل إطار لا يسمح بلصق آخر فوقه، ورقم أو اسم ظاهر بجانب الرمز يقارنه العميل بما يظهر في تطبيقه.
- الإشعار في نقطة البيع: إذا كان «مدفوع» يأتي من الإشعار فقط، فالعميل الذي دفع لحساب المحتال لن يظهر طلبه مدفوعاً، فيعرف الموظف فوراً أن هناك مشكلة، بدل أن يكتشفها التاجر في آخر الشهر من نقص في الإيرادات.
ومن جهة البيانات، استبدال الملصق يترك أثراً يمكن أن يراه نظام التاجر قبل أن يشتكي أحد. فالعربة التي تبيع عادة أربعين كوباً في الساعة الأولى من الفعالية، ثم تتوقف الإشعارات من رمزها الثابت فجأة بينما الطابور طويل، إشارة واضحة على أن المال يذهب إلى مكان آخر. لذلك أضاف الفريق تنبيهاً بسيطاً: إذا مرت عشرون دقيقة في ساعات العمل دون أي إشعار من رمز عربة نشطة، يصل تنبيه للمشرف ليتحقق من الملصق. وهذا التنبيه لا يحتاج أي ذكاء، ولكنه يحول الهجوم من خسارة يوم كامل إلى خسارة دقائق، ويطبق القاعدة التي تكررت في هذه السلسلة: راقب الغياب كما تراقب الحضور، لأن أخطر الأعطال هي التي لا تطلق خطأ.
ولاحظ أن الرمز في هذا الهجوم لا يحتاج أي اختراق تقني، فهو ورقة فوق ورقة، ولهذا يعتمد الدفاع على التصميم والإجراءات أكثر من التشفير. وكل حالة من هذه يجب أن تراها في بيانات نظامك أيضاً، فدفعة لم تصل لطلب عرض رمزه، أو شكوى عميل يقول إنه دفع ولم يظهر طلبه، إشارة تستحق أن تنبه عليها، كما شرحنا في كشف الاحتيال: قواعد أم تعلم آلة؟.
آخر اليوم: المطابقة والاسترداد
في نهاية أول فعالية جلس الفريق ليطابق، فوجد ثلاثة أنواع من الدفعات. الأولى من الرموز الديناميكية، وكل منها بإشعار يحمل رقم الطلب، فطابقت آلياً. والثانية من الرمز الثابت، وكلها بنفس المرجع ومبالغ يكتبها العملاء، فاحتاج الفريق أن يطابقها يدوياً بأوقات الطلبات، ووجد دفعة بـ 185 ريالاً بدل 18.50 لعميل أخطأ في الفاصلة. والثالثة دفعة واحدة في الإشعارات لا يقابلها أي طلب، لعميل دفع ثم ذهب دون أن يأخذ قهوته. وكل نوع يحتاج معالجة مختلفة في النظام:
- الدفعات المطابقة: تغلق آلياً، والمبلغ ينتقل من «مستحق من المزود» إلى «مسوى» عندما يصل ملف تسوية المزود، كما شرحنا في مقال المطابقة.
- دفعات الرمز الثابت: تذهب إلى حساب معلق Suspense Account حتى يطابقها أحد، ولا تحسب إيراداً مؤكداً قبل ذلك. وهذا سبب آخر لتقليل استخدام الرمز الثابت في أوقات الذروة.
- الاسترداد: الدفعة الزائدة أو الخاطئة لا تلغى بحذف سجل، وإنما بعملية استرداد جديدة مرتبطة بالأصلية، بالطريقة التي يتيحها نظام المزود وقواعده، وتسجل في دفتر القيود كحركة عكسية. فاسأل المزود قبل الإطلاق: كيف أسترد دفعة؟ وكم تستغرق؟ وهل يمكن الاسترداد الجزئي؟
والمهم هندسياً أن هذه المعالجة تصمم قبل الإطلاق لا بعده، لأن أول يوم مزدحم سوف يولد كل هذه الحالات معاً، والفريق الذي يكتشفها في جدول Excel في منتصف الليل لن يتذكر أي دفعة لأي طلب. ولهذا وضع الفريق في القصة قاعدة بسيطة: الرمز الثابت للطوارئ فقط، والديناميكي هو الأصل، وكل دفعة بلا طلب تظهر في شاشة التنبيهات فوراً لا في تقرير آخر اليوم.
وبقي في المطابقة طرف ثالث لا يظهر في شاشة العربة: ملف التسوية من المزود. فالإشعار يقول إن العميل دفع، وملف التسوية يقول كم وصل فعلاً إلى حساب الشركة بعد الرسوم ومتى، والاثنان لا يتطابقان سطراً بسطر دائماً، لأن المزود قد يجمع دفعات يوم كامل في تحويل واحد. لذلك صمم الفريق المطابقة على مرحلتين: كل إشعار يطابق بطلبه في نفس اللحظة، ثم كل يوم تطابق مجموعة الإشعارات بسطور ملف التسوية، والفرق بينهما يجب أن يساوي الرسوم المتفق عليها بالضبط، وأي فرق آخر تنبيه يحقق فيه أحد في نفس الأسبوع لا في نهاية السنة.
متى يناسبك الدفع برمز QR؟
بعد كل ذلك عاد فريق العربات إلى السؤال الأول: هل يكفي الرمز وحده؟ والإجابة تعتمد على أربعة أشياء:
| السؤال | الرمز يناسب | الرمز لا يناسب وحده |
|---|---|---|
| ما الذي يملكه عملاؤك؟ | أغلبهم يستخدمون تطبيقات تقرأ الرمز | كثيرون يدفعون بالبطاقة اللاتلامسية أو النقد |
| هل تحتاج مطابقة كل دفعة بطلب؟ | نعم مع رمز ديناميكي وإشعار | رمز ثابت فقط مع طلبات كثيرة |
| هل لديك شاشة واتصال؟ | نعم، فيمكن الديناميكي | لا، فالثابت وحده بمخاطره |
| من مزود الخدمة؟ | مزود مرخص معتمد لدى المدفوعات السعودية | حل بصيغة خاصة لا يقرؤه إلا تطبيق واحد |
واختار الفريق في النهاية رمزاً ديناميكياً على جهاز لوحي صغير في كل عربة، مع الإبقاء على قبول البطاقة اللاتلامسية، لأن نسبة من العملاء لن تفتح تطبيقاً لتدفع ثمن قهوة، ولأن الطابور الذي أرادوا اختصاره سوف يطول إذا اضطر العميل لفتح التطبيق والبحث عن زر المسح. أي أن الرمز وسيلة إضافية تقلل التكلفة وتسرع بعض العملاء، لا بديل كامل عن قبول البطاقات الذي شرحناه في عملية الدفع بالبطاقة وبوابات الدفع في السعودية.
وقد يتساءل المهندس: إذا كانت الصيغة مفتوحة وسهلة إلى هذا الحد، فلماذا لا نبني الرمز بأنفسنا ونتصل بأي نظام؟ والإجابة أن الرمز نصف القصة فقط، فالنصف الآخر هو الحساب الذي يشير إليه، والنظام الذي يحول المال ويرسل الإشعار، وهذه كلها خدمات مدفوعات منظمة يقدمها مزود مرخص، ويحتاج المزود نفسه اعتماداً فنياً كما يقول تعميم البنك المركزي. فالذي يبنيه فريق التاجر عادة هو نقطة البيع التي تطلب رمزاً ديناميكياً من المزود لكل طلب، وتعرضه، وتستقبل الإشعار، وتطابقه. ومعرفة البنية التي بنيناها في المختبر هي ما يجعله يكتب هذا الجزء بثقة، ويختبر رموز المزود بنفسه، ويعرف أين ينظر عندما يشكو عميل أن الرمز لا يعمل.
رمز واحد لأكثر من نظام؟
في فعالية كبيرة سأل أحد الزوار: «تطبيقي من بنك آخر، هل يقرأ رمزكم؟». وهذا سؤال التشغيل البيني الذي يتحدث عنه تعميم البنك المركزي. وصيغة EMV نفسها تسمح بأكثر من إجابة، فحاشية في دليل Pix تذكر أن كائنات معلومات حساب التاجر من 02 إلى 51 قد يوجد منها واحد أو أكثر في نفس الرمز، كل منها يحدد نظام دفع يقبله التاجر. أي أن الملصق الواحد يمكن نظرياً أن يحمل حساب التاجر في نظامين أو ثلاثة، وكل تطبيق يقرأ القالب الذي يعرفه ويتجاهل الباقي.
ولهذا الخيار ثمنان. الأول الحجم، فكل قالب إضافي عشرات الحروف، والرمز يرتفع إصداراً أو اثنين، وقد رأينا ما يفعله ذلك بحجم المربع على ملصق صغير. والثاني الغموض، فإذا قرأ تطبيق قالبين يعرفهما، فأيهما يختار؟ ومن يقرر الرسوم التي يدفعها التاجر؟ والبديل أن يكون التشغيل البيني في الخلفية، بمعرف واحد في الرمز يفهمه كل المشاركين، والتوجيه بينهم في نظام مركزي، بدل أن يحمل كل ملصق قائمة أنظمة. وهذا ما يجعل الالتزام بمواصفة المدفوعات السعودية أهم من أي قرار تتخذه أنت في حمولة الرمز: المواصفة تحدد القالب والمعرف، والنظام يتولى الباقي، ومهمتك أن تتأكد أن مزودك يتبعها، وأن تختبر رمزه بأكثر من تطبيق كما في قائمة التحقق.
أسئلة قبل أن تختار مزود الخدمة
لأن التاجر لا يبني الرمز ولا نظام الدفع بنفسه، فأهم عمل هندسي في المشروع هو تقييم المزود. وكتب فريق العربات قائمة أسئلة من كل ما رأوه في المختبر، ويستحق كل منها إجابة مكتوبة قبل التوقيع:
- الاعتماد: هل أكمل المزود الاعتماد الفني مع المدفوعات السعودية للرموز كما يطلب تعميم البنك المركزي؟ وهل يقرأ رموزه أكثر من تطبيق؟
- الرمز الديناميكي: هل توجد واجهة برمجية API تطلب منها نقطة البيع رمزاً لكل طلب بمبلغه ومرجعه؟ وما مدة صلاحية الرمز؟
- الإشعار: كيف يصل إشعار الدفع، عبر Webhook أم استعلام؟ وهل يحمل المرجع الذي وضعناه؟ وكيف أتحقق أن الإشعار من المزود فعلاً لا من أي أحد يعرف عنوان الخادم؟ وماذا يحدث إذا لم يصل ثم وصل مرتين؟
- الاسم الذي يراه العميل: هل يعرض تطبيق الدافع اسم التاجر من سجل الحساب أم من الرمز؟ فالإجابة الأولى هي التي تحمي من استبدال الملصق.
- التسوية: متى يصل المال إلى حساب التاجر؟ وما شكل ملف التسوية؟ وكم الرسوم؟ وكل ذلك مكتوب في العقد لا في عرض المبيعات.
- الاسترداد: كيف يسترد التاجر دفعة كاملة أو جزئية، ومن واجهة أم من بوابة؟
وكل سؤال من هذه يقابله اختبار يستطيع الفريق تشغيله بنفسه في بيئة الاختبار عند المزود: يطلب رمزاً ويفحصه بالمحلل الذي بنيناه، ويتحقق من CRC وبنية الكائنات، ويدفع مبلغاً تجريبياً ويقيس متى يصل الإشعار، ويرسل نفس الإشعار مرتين ليتأكد أن نظامه يعالجه مرة واحدة. وهذه الاختبارات أسرع وأصدق من قراءة وثيقة المزود، لأنها تجيب عما يفعله النظام فعلاً لا عما يقول إنه يفعله.
أخطاء شائعة
| الخطأ | لماذا يضر | البديل |
|---|---|---|
| «تم الدفع» من شاشة العميل | لقطة شاشة قديمة تبدو مثلها | الحالة من إشعار نظام الدفع برقم الطلب |
| اعتبار CRC حماية | يعاد حسابه في سطر واحد | الاسم من نظام الدفع، والرمز الديناميكي |
| عرض اسم التاجر من الكائن 59 | المحتال يكتب أي اسم | الاسم الذي يعيده نظام الدفع للحساب |
| رمز ثابت لطلبات كثيرة | لا مطابقة، والعميل يكتب المبلغ | رمز ديناميكي بمبلغ ورقم طلب |
| CRC بخوارزمية قريبة | قيمة صحيحة الشكل خاطئة | اختبار على أمثلة رسمية بقيمها |
| الطول بالحروف عند طرف وبالبايتات عند آخر | الاسم العربي يقطع والكائنات تنزاح | طريقة المواصفة، واختبار باسم عربي |
| ملصق صغير بعنوان طويل | مربعات أقل من نصف مليمتر | حمولة قصيرة وملصق أكبر |
| ملصق مكشوف بلا فحص | ملصق آخر فوقه دون أن يلاحظ أحد | فحص كل وردية وغطاء وإطار |
قائمة تحقق قبل أن تطبع أول رمز
| البند | كيف تتحقق منه |
|---|---|
| مزود مرخص ومعتمد للرموز | سألت المزود عن اعتماده لدى المدفوعات السعودية |
| الرمز يقرأ في أكثر من تطبيق | مسح فعلي بتطبيقات عملائك الشائعة |
| «مدفوع» من الإشعار فقط | نقطة البيع لا تقبل تأكيداً يدوياً بلا إشعار |
| كل دفعة مطابقة بطلب | رقم الطلب في الكائن 62 يعود في الإشعار |
| الإشعار يعالج مرة واحدة | مفتاح فريد لكل إشعار |
| الملصقات تحت الفحص | فحص كل وردية، وغطاء وإطار |
| الحجم كاف | مسح بأضعف جوال في إضاءة ضعيفة ومن مسافة |
| الاسم العربي إن وجد مختبر | رمز عربي حقيقي على أكثر من تطبيق |
| البطاقة اللاتلامسية ما زالت مقبولة | لا يخسر المتجر عميلاً لا يستخدم التطبيقات |
الخلاصة
وصلنا لنهاية الموضوع، وأهم ما فيه:
- الدفع برمز QR نمطان: التاجر يعرض الرمز MPM، أو العميل يعرضه CPM، والأول هو الأنسب للتاجر الصغير لأنه لا يحتاج جهاز قراءة.
- الحمولة في صيغة EMV كائنات برقم وطول وقيمة، وقوالب داخلها كائنات، وCRC-16 بكثير الحدود 0x1021 والقيمة الابتدائية 0xFFFF في الكائن 63.
- المحلل والمولد في المختبر طابقا CRC في الأمثلة الخمسة الرسمية من دليل Pix، ورمزا العربة قرئا من الصورة وطابقا النص، الثابت 122 حرفاً من الإصدار 6 والديناميكي 163 حرفاً من الإصدار 7.
- في المملكة يطلب تعميم البنك المركزي (سبتمبر 2021) الالتزام بمتطلبات مشروع المدفوعات السعودية للتشغيل البيني والاعتماد الفني معها، فالمرجع مواصفتها لا صيغة يخترعها المزود.
- CRC يكشف الخطأ غير المقصود ولا يكشف التلاعب: رمز بحساب آخر وCRC صحيح مر في المختبر، والدفاع في عرض الاسم من نظام الدفع، والرمز الديناميكي، وفحص الملصقات، والإشعار برقم الطلب.
- تغطية وسط الرمز أوقفت قراءته عند 6% من مساحته في المستوى L و22% في H، فالملصق الخارجي يستحق مستوى تصحيح أعلى ومساحة أكبر.
- الرمز وسيلة إضافية تقلل التكلفة، لا بديل كامل عن قبول البطاقات.
والمقالات المرتبطة:
دليلكيف يعمل الدفع بالبطاقة؟ التفويض Authorization والتسويةمن ضغطة «ادفع» إلى آخر اعتراض: التفويض و3D Secure والتحصيل والتسوية والاسترداد والاعتراض، مع مختبر لأحداث مزود الدفع ومطابقتها.
دليلبوابات الدفع Payment Gateways في السعودية والبدائل المفتوحةكيف تختار مزود الدفع في المملكة بما تنشره صفحاتهم الرسمية، وأين يقف Stripe والبرمجيات المفتوحة، مع مختبر التحويل بين مزودين.
دليلمنظومة المدفوعات في السعودية: سريع وSARIE ومدى وسدادالأنظمة الوطنية للمدفوعات في المملكة كما تصفها مصادر البنك المركزي، وما يعنيه كل نظام للمطور، مع تحليل تقارير نقاط البيع الأسبوعية.
دليلالمحافظ الرقمية Digital Wallets: الأنواع والترخيص والبناءأنواع المحافظ الرقمية وتراخيص النقود الإلكترونية في المملكة وحدودها، وحماية أموال العملاء، مع مختبر للحدود والمطابقة اليومية.
دليلالفوترة الإلكترونية فاتورة ZATCA: كيف تربط نظامكبنينا فاتورة مبسطة بصيغة XML ورمز QR بلغة Python، وتحققنا منهما بأداة الهيئة، وحسبنا الهاش بأنفسنا وجربنا الأخطاء الشائعة.سجل التحديثات
- أكتوبر 2026: كتابة المقال وتشغيل المختبر على python:3.13.17-slim (qrcode 8.2 وPillow 12.0.0 وzxing-cpp 2.3.0)، والتحقق على أمثلة دليل معايير بدء الدفع في Pix (الإصدار 2.10.0)، ومراجعة صفحات EMVCo عن رموز QR وتعميم البنك المركزي السعودي (21 سبتمبر 2021) وتنبيه IC3 (18 يناير 2022) كما ظهرت في 27 أكتوبر 2026.