شرح خوارزمية لوهن (Luhn): فحص Mod 10 باستخدام JavaScript وPython
فريق Visatk · · 9 دقائق قراءة · Read in English
شرح خوارزمية لوهن (Luhn أو mod 10) خطوة بخطوة مع مثال محلول وكود JavaScript وPython، ونظرة واضحة على ما تثبته الخوارزمية وما لا تثبته.

في كل مرة تخبرك فيها صفحة الدفع فورًا بأن "رقم البطاقة غير صحيح" قبل أن تضغط زر الدفع، تكون هناك عملية حسابية بسيطة تعود إلى خمسينيات القرن الماضي هي التي تقوم بالمهمة. خوارزمية لوهن (Luhn)، وتُعرف أيضًا باسم خوارزمية mod 10، هي مجموع التحقق المستخدم في الرقم الأخير من جميع أرقام بطاقات الدفع تقريبًا. وهي بسيطة بما يكفي لحسابها يدويًا، وسريعة بما يكفي لتعمل مع كل ضغطة مفتاح، لكنها كثيرًا ما يُساء فهمها: فنجاح الرقم في فحص Luhn لا يعني شيئًا عن كون البطاقة حقيقية أو فعّالة أو مملوكة للشخص الذي يستخدمها.
يشرح هذا الدليل طريقة عمل الخوارزمية خطوة بخطوة، ويستعرض مثالًا محلولًا بالكامل، ويقدّم تطبيقات جاهزة للاستخدام الفعلي بلغتي JavaScript وPython، ويوضّح بدقة ما تثبته Luhn وما لا تثبته.
ما هي خوارزمية لوهن (Luhn)؟ #
ابتكر خوارزمية لوهن العالِم هانز بيتر لوهن (Hans Peter Luhn) الذي كان يعمل في IBM، وحصل على براءة اختراعها عام 1960. وهي معادلة بسيطة لمجموع التحقق صُممت لاكتشاف الأخطاء غير المقصودة عند كتابة الأرقام أو قراءتها بصوت عالٍ أو نسخها. ويحددها معيار ISO/IEC 7812-1 بوصفها طريقة حساب رقم التحقق لرقم الحساب الأساسي للبطاقة (PAN)، كما تُستخدم في معرّفات أخرى، مثل أرقام IMEI في الهواتف المحمولة وأنظمة مختلفة لأرقام الهوية الوطنية وأرقام الحسابات.
الرقم الأخير في رقم البطاقة هو رقم التحقق، ويُختار بحيث يحقق الرقم كاملًا معادلة Luhn. فإذا كُتب رقم واحد بشكل خاطئ، تفشل المعادلة في جميع الحالات تقريبًا، ويستطيع النموذج التنبيه إلى الخطأ فورًا دون الاتصال بأي جهة.
كيف تعمل الخوارزمية #
للتحقق من صحة رقم يتضمن رقم التحقق الخاص به بالفعل:
- ابدأ من الرقم الموجود في أقصى اليمين (رقم التحقق) واتجه يسارًا، ورقّم المواضع 1، 2، 3 وهكذا.
- ضاعف قيمة كل رقم يقع في موضع زوجي (الرقم الثاني والرابع والسادس من اليمين، وهكذا).
- إذا نتج عن المضاعفة عدد أكبر من 9، فاطرح منه 9. وهذا يعادل جمع رقميه معًا (مثلًا: 16 تصبح 1 + 6 = 7، و16 − 9 = 7).
- اجمع كل الأرقام: المعدّلة منها وغير المعدّلة.
- إذا كان المجموع من مضاعفات العدد 10 (أي أن باقي قسمة المجموع على 10 يساوي 0)، فالرقم صحيح.
لحساب رقم التحقق لرقم لا يحتوي عليه بعد، طبّق المضاعفة نفسها على الأرقام الأساسية كما لو أن رقم التحقق مضاف بالفعل في الموضع 1 (أي أن الرقم الموجود في أقصى يمين الأرقام الأساسية هو الذي يُضاعَف)، ثم اجمع النتيجة، واختر الرقم الذي يرفع المجموع إلى أقرب مضاعف تالٍ للعدد 10. وبصيغة معادلة: check = (10 − (sum mod 10)) mod 10.
مثال محلول: التحقق من الرقم 79927398713 #
الرقم 79927398713 مثال كلاسيكي يرد في المراجع التعليمية. لنتحقق منه خطوة بخطوة.
| الموضع من اليمين | الرقم | مضاعفة؟ | بعد المضاعفة | القيمة المضافة |
|---|---|---|---|---|
| 1 | 3 | لا | – | 3 |
| 2 | 1 | نعم | 2 | 2 |
| 3 | 7 | لا | – | 7 |
| 4 | 8 | نعم | 16 | 7 (16 − 9) |
| 5 | 9 | لا | – | 9 |
| 6 | 3 | نعم | 6 | 6 |
| 7 | 7 | لا | – | 7 |
| 8 | 2 | نعم | 4 | 4 |
| 9 | 9 | لا | – | 9 |
| 10 | 9 | نعم | 18 | 9 (18 − 9) |
| 11 | 7 | لا | – | 7 |
المجموع: 3 + 2 + 7 + 7 + 9 + 6 + 7 + 4 + 9 + 9 + 7 = 70. وبما أن 70 mod 10 = 0، فالرقم صحيح.
حساب رقم التحقق للأرقام الأساسية نفسها #
لنفترض الآن أن لديك الأرقام الأساسية 7992739871 فقط وتحتاج إلى حساب رقم التحقق. احذف صف الرقم "3" من الجدول أعلاه: مجموع القيم المتبقية هو 70 − 3 = 67. ثم (10 − (67 mod 10)) mod 10 = (10 − 7) mod 10 = 3. إذن رقم التحقق هو 3، وهو مطابق للرقم الأصلي.
مثال ثانٍ: رقم بطاقة تجريبية شائع #
يُستخدم الرقم التجريبي واسع الانتشار 4111 1111 1111 1111 في بيئات الاختبار (Sandbox) الخاصة بالدفع، وهو مكوّن من 16 رقمًا. بالعدّ من اليمين، الأرقام الثمانية في المواضع الزوجية هي سبعة أرقام 1 والرقم 4 في البداية. وبالمضاعفة نحصل على سبعة أرقام 2 (مجموعها 14) مضافًا إليها 8، أي 22. أما الأرقام الثمانية في المواضع الفردية فكلها 1، ومجموعها 8. المجموع الكلي 22 + 8 = 30، وهو من مضاعفات 10، لذا ينجح الرقم في الفحص. يمكنك التحقق من أي رقم فورًا باستخدام حاسبة Luhn لدينا.
التطبيق بلغة JavaScript #
تحذف هذه النسخة المسافات والشرطات، وترفض أي محرف ليس رقمًا، ثم تتحقق من مجموع التحقق. وهي تعالج الأرقام من اليمين إلى اليسار باستخدام مبدّل (toggle)، مما يتجنب أي التباس في الإزاحة بمقدار موضع واحد بسبب طول البطاقة.
/**
* Returns true if the input passes the Luhn (mod 10) check.
* Accepts spaces and dashes as separators.
*/
function isLuhnValid(input) {
const digits = String(input).replace(/[\s-]/g, "");
if (!/^\d+$/.test(digits) || digits.length < 2) return false;
let sum = 0;
let double = false;
for (let i = digits.length - 1; i >= 0; i--) {
let d = digits.charCodeAt(i) - 48;
if (double) {
d *= 2;
if (d > 9) d -= 9;
}
sum += d;
double = !double;
}
return sum % 10 === 0;
}
/**
* Computes the Luhn check digit for a payload (number without check digit).
*/
function luhnCheckDigit(payload) {
const digits = String(payload).replace(/[\s-]/g, "");
if (!/^\d+$/.test(digits)) throw new Error("Digits only");
let sum = 0;
let double = true; // payload's rightmost digit will sit in position 2
for (let i = digits.length - 1; i >= 0; i--) {
let d = digits.charCodeAt(i) - 48;
if (double) {
d *= 2;
if (d > 9) d -= 9;
}
sum += d;
double = !double;
}
return (10 - (sum % 10)) % 10;
}
console.log(isLuhnValid("7992 7398 713")); // true
console.log(isLuhnValid("4111-1111-1111-1111")); // true
console.log(isLuhnValid("4111111111111112")); // false
console.log(luhnCheckDigit("7992739871")); // 3
التطبيق بلغة Python #
import re
def is_luhn_valid(value: str) -> bool:
"""Return True if value passes the Luhn (mod 10) check."""
digits = re.sub(r"[\s-]", "", str(value))
if not digits.isdigit() or len(digits) < 2:
return False
total = 0
for index, char in enumerate(reversed(digits)):
d = int(char)
if index % 2 == 1: # every second digit from the right
d *= 2
if d > 9:
d -= 9
total += d
return total % 10 == 0
def luhn_check_digit(payload: str) -> int:
"""Compute the Luhn check digit for a payload without its check digit."""
digits = re.sub(r"[\s-]", "", str(payload))
if not digits.isdigit():
raise ValueError("Digits only")
total = 0
for index, char in enumerate(reversed(digits)):
d = int(char)
if index % 2 == 0: # payload's rightmost digit is doubled
d *= 2
if d > 9:
d -= 9
total += d
return (10 - total % 10) % 10
assert is_luhn_valid("7992 7398 713")
assert is_luhn_valid("4111-1111-1111-1111")
assert not is_luhn_valid("4111111111111112")
assert luhn_check_digit("7992739871") == 3
لاحظ أن الدالة str.isdigit() في Python تقبل أيضًا بعض محارف الأرقام غير ASCII. إذا كنت تستقبل مدخلات من مصادر غير موثوقة، فاستخدم فحصًا أكثر صرامة مثل re.fullmatch(r"[0-9]+", digits).
أخطاء شائعة في التطبيق #
- المضاعفة من اليسار. نمط المضاعفة مرتبط بالطرف الأيمن من الرقم. والبدء من اليسار يعطي نتائج خاطئة للأرقام ذات العدد الفردي من الخانات، مثل أرقام American Express المكوّنة من 15 رقمًا.
- التعامل مع الرقم كعدد صحيح. أرقام البطاقات المكوّنة من 16 إلى 19 رقمًا قد تتجاوز نطاق الأعداد الصحيحة الآمن في JavaScript، كما تختفي الأصفار البادئة. تعامل معها دائمًا كنصوص (strings).
- نسيان حذف الفواصل. يلصق المستخدمون الأرقام مع مسافات وشرطات. وحّد التنسيق أولًا، ثم تحقق من الصحة.
- الاعتماد على Luhn وحدها للتحقق. اجمع بينها وبين فحص بادئة العلامة التجارية للبطاقة وطول الرقم لتقديم تجربة مستخدم أفضل.
- تسجيل الرقم كاملًا أثناء تصحيح الأخطاء. لا تسجّل أرقام PAN كاملة أبدًا. أخفِ كل شيء باستثناء ما يسمح به نطاق الامتثال لديك.
ما الذي تكتشفه خوارزمية Luhn #
صُممت Luhn لاكتشاف الأخطاء غير المقصودة، وهي بارعة في ذلك.
| نوع الخطأ | مثال | هل يُكتشف؟ |
|---|---|---|
| أي خطأ في رقم واحد | 4111 → 4171 | نعم، دائمًا |
| معظم حالات تبديل رقمين متجاورين | 12 → 21 | نعم |
| تبديل 09 و90 | 09 → 90 | لا |
| الأخطاء التوأمية 22↔55 و33↔66 و44↔77 | 33 → 66 | لا |
| أخطاء عشوائية متعددة | تغيّر عدة أرقام | يُكتشف نحو 9 من كل 10 بالصدفة |
توجد أنظمة أقوى لرقم التحقق، مثل خوارزميتي Verhoeff وDamm، اللتين تكتشفان جميع أخطاء الرقم الواحد وجميع حالات تبديل الأرقام المتجاورة. لكن Luhn ما زالت مستخدمة في المدفوعات لأنها بسيطة وسريعة ومتجذرة بعمق في المعايير والبنية التحتية.
ما الذي لا تثبته خوارزمية Luhn #
هذا هو القسم الأهم للتجار ومحللي الاحتيال. الرقم الذي ينجح في فحص Luhn:
- ليس بالضرورة بطاقة حقيقية. فنحو سلسلة واحدة من كل عشر سلاسل أرقام عشوائية بطول معين تنجح في الفحص بالصدفة، ويمكن لأي شخص حساب رقم تحقق صحيح لأي أرقام أساسية.
- ليس بالضرورة فعّالًا. فالحسابات المغلقة والمنتهية والمحظورة تظل أرقام التحقق فيها صحيحة.
- لا يعني أنه ملك للشخص الذي يكتبه. فأرقام البطاقات المسروقة تنجح في فحص Luhn بلا أي مشكلة.
- لا يؤكد الجهة المُصدِرة. فخوارزمية Luhn لا تخبرك بشيء عن رقم BIN. استخدم أداة فحص BIN لمعرفة البنك المُصدِر والعلامة التجارية ونوع البطاقة والدولة.
- لا يؤكد توفر الرصيد. فطلب التفويض لدى الجهة المُصدِرة وحده هو ما يؤكد إمكانية الخصم من الحساب.
ولهذا السبب أيضًا يستطيع المحتالون تنفيذ "هجمات BIN": إذ يولّدون أرقامًا صحيحة وفق Luhn ضمن رقم BIN معروف ويجربونها على صفحات الدفع. فالتحقق عبر Luhn يستبعد الأخطاء المطبعية، لا المهاجمين. والتصدي لهجمات تخمين الأرقام يتطلب حدودًا لمعدل المحاولات وحماية من الروبوتات ومصادقة، وليس فحوصات أقوى من جهة العميل.
موقع Luhn ضمن مراحل التحقق من البطاقة #
تستخدم سلسلة التحقق المدروسة لإدخال البطاقة خوارزمية Luhn كخطوة مبكرة منخفضة التكلفة:
- توحيد التنسيق – احذف المسافات والشرطات.
- تحديد العلامة التجارية – استخدم نطاقات البادئات للتعرف على Visa وMastercard وAmex وغيرها، وحدد الطول المتوقع للرقم وطول رمز CVV.
- فحص الطول – تأكد من أن الطول صحيح للعلامة التجارية المكتشفة.
- تشغيل Luhn – اكتشف الأخطاء المطبعية فورًا واعرض رسالة واضحة وودية.
- الترميز (Tokenization) – أرسل الرقم إلى الحقول الآمنة أو حزمة SDK الخاصة بمزوّد الدفع، مع إبقاء أرقام PAN الخام بعيدًا عن خوادمك قدر الإمكان.
- التفويض – اترك القرار الفعلي للجهة المُصدِرة، مع استخدام AVS وCVV و3-D Secure حسب الحاجة.
تجمع أداة التحقق من بطاقة الائتمان لدينا بين تحديد العلامة التجارية وفحص الطول وفحص Luhn في مكان واحد، وهي مفيدة للتحقق السريع من منطق التطبيق الخاص بك.
اختبار التطبيق الخاص بك #
أنشئ مجموعة اختبارات صغيرة تتضمن قيمًا معروف أنها صحيحة وأخرى معروف أنها خاطئة:
- أرقام البطاقات التجريبية المنشورة من مزوّد الدفع لديك، ويجب أن تنجح جميعها.
- كل رقم من تلك الأرقام بعد زيادة رقمه الأخير بواحد، ويجب أن تفشل جميعها.
- أرقام تحتوي على مسافات وشرطات ومسافات بيضاء في البداية أو النهاية.
- مدخلات غير رقمية، ونصوص فارغة، ونصوص طويلة جدًا.
- أرقام ذات طول فردي، مثل أرقام American Express التجريبية المكوّنة من 15 رقمًا، لاكتشاف أخطاء المضاعفة التي تبدأ من اليسار.
لاختبارات الواجهة الأمامية بكميات كبيرة، يُنتج مولّد أرقام البطاقات التجريبية أرقامًا صحيحة التنسيق لأغراض التطوير فقط. وهي تنجح في فحص Luhn وفحص البادئة، لكنها غير مرتبطة بأي حساب حقيقي ولا يمكن استخدامها في الشراء.
الأسئلة الشائعة #
هل خوارزمية لوهن ميزة أمان؟
هل تستخدم جميع بطاقات الدفع خوارزمية لوهن؟
لماذا نطرح 9 بعد المضاعفة؟
هل تخبرني خوارزمية Luhn بالبنك الذي أصدر البطاقة؟
هل أتحقق باستخدام Luhn من جهة العميل أم من جهة الخادم؟
#خوارزمية Luhn#خوارزمية mod 10#التحقق من البطاقات#JavaScript#Python
متخصصون في المدفوعات ومكافحة الاحتيال وأدوات المطورين.


