دليل بوابة USSD
يغطي هذا الدليل بوابة USSD من OmniSS7، التي تربط حوارات USSD عبر SS7/MAP مع ردود HTTP/JSON، مما يمكّن المطورين من الطرف الثالث من بناء تطبيقات USSD من خلال نقطة نهاية HTTP بسيطة.
جدول المحتويات
- نظرة عامة
- العمارة
- تمكين بوابة USSD
- التكوين
- بروتوكول ردود HTTP
- USSD المنشأ من الشبكة (Push API)
- دورة حياة الجلسة
- معالجة الأخطاء
- المقاييس والمراقبة
- خادم ردود المثال
- استكشاف الأخطاء وإصلاحها
نظرة عامة
تتعامل ب��ابة USSD مع اتجاهين من حركة مرور USSD:
- المنشأ من الهاتف المحمول (Inbound) — يقوم المشترك بالاتصال برمز قصير (مثل
*100#). تتلقى البوابة MAPprocessUnstructuredSS-Request(opcode 59)، وتقوم بإعادة توجيهه إلى رد HTTP الخاص بك، وتعيد إرسال ردك عبر SS7. - المنشأ من الشبكة (Outbound) — يقوم تطبيقك بدفع رسالة USSD إلى مشترك عبر واجهة برمجة التطبيقات REST. ترسل البوابة MAP
unstructuredSS-Request(opcode 60) عبر SS7 وتوجه رد المشترك إلى ردك.
يدعم كلا الاتجاهين حوارات متعددة الأدوار — قوائم تفاعلية حيث يرد المشترك ويتلقى مطالبات متابعة.
الخصائص الرئيسية
| الخاصية | القيمة |
|---|---|
| النقل | HTTP POST متزامن لكل دور |
| التشفير | الأبجدية الافتراضية GSM 7-bit (DCS 0x0F) وفقًا لـ 3GPP TS 23.038 |
| الحد الأقصى لطول النص | 182 حرفًا (قابل للتكوين) |
| تتبع الجلسات | UUID تم إنشاؤه بواسطة البوابة لكل حوار |
| المصادقة | ��ا شيء (يثق بشبكة SS7) |
| التوجيه | مطابقة بادئة الرمز القصير مع عناوين ردود |
مراجع 3GPP
| المواصفة | الصلة |
|---|---|
| 3GPP TS 23.090 | USSD المرحلة 2 — العمارة والإجراءات |
| 3GPP TS 24.090 | USSD المرحلة 3 — تفاصيل البروتوكول |
| 3GPP TS 29.002 | بروتوكول MAP — USSD-Arg، USSD-Res، opcodes 59/60/61 |
| 3GPP TS 23.038 | الأبجدية الافتراضية GSM 7-bit ونظام ترميز البيانات |
العمارة
تدفق المنشأ من الهاتف المحمول (Inbound)
تدفق المنشأ من الشبكة (Outbound Push)
نظرة عامة على المكونات
تمكين بوابة USSD
تتطلب بوابة USSD تمكين وضع عميل MAP، بالإضافة إلى علم الميزة الخاص بها.
config :omniss7,
map_client_enabled: true,
ussd_gateway_enabled: true
تتطلب البوابة أيضًا اتصال M3UA يعمل (انظر دليل عميل MAP لإعداد M3UA).
التكوين
معلمات بوابة USSD
config :omniss7,
ussd_gateway_enabled: true,
ussd_gateway: %{
# توجيه الرمز القصير — مطابقة أطول بادئة
routes: [
%{pattern: "*100", url: "http://balance-app:9000/ussd"},
%{pattern: "*200", url: "http://topup-app:9000/ussd"},
%{pattern: "*", url: "http://default-app:9000/ussd"}
],
# مهلات الجلسة
session_timeout_ms: 180_000, # إجمالي مدة الجلسة (3 دقائق)
turn_timeout_ms: 30_000, # الحد الأقصى للانتظار لرد المشترك لكل دور (30 ثانية)
# إعدادات ردود HTTP
http_timeout_ms: 5_000, # مهلة لـ HTTP POST إلى تطبيقك (5 ثواني)
# حدود النص
max_text_length: 182 # الحد الأقصى لـ GSM 7-bit (يتم اقتطاعه مع تحذير إذا تم تجاوزه)
}
مرجع المعلمات
| المعلمة | النوع | مطلوب | الافتراضي | الوصف |
|---|---|---|---|---|
ussd_gateway_enabled | Boolean | نعم | false | مفتاح رئيسي لميزة بوابة USSD |
ussd_gateway.routes | قائمة من الخرائط | نعم | [] | قواعد توجيه بادئة الرمز القصير. تحتوي كل إدخال على pattern (سلسلة بادئة) و url (عنوان رد). تفوز أطول بادئة. |
ussd_gateway.session_timeout_ms | عدد صحيح | لا | 180_000 | الحد الأقصى لمدة الجلسة الإجمالية بالميلي ثانية. يتم إنهاء الجلسة مع خطأ إذا تم تجاوزها. |
ussd_gateway.turn_timeout_ms | عدد صحيح | لا | 30_000 | الحد الأقصى للوقت للانتظار لرد المشترك في حوار متعدد الأدوار، بالميلي ثانية. |
ussd_gateway.http_timeout_ms | عدد صحيح | لا | 5_000 | مهلة طلب HTTP لردود تطبيقك، بالميلي ثانية. تغطي كل من وقت الاتصال ووقت الاستجابة. |
ussd_gateway.max_text_length | عدد صحيح | لا | 182 | الحد الأقصى لعدد الأحرف في سلسلة نص USSD. يتم اقتطاع النصوص التي تتجاوز هذا الحد ويتم تسجيل تحذير. |
معلمات التوجيه
كل إدخال في قائمة routes هو خريطة:
| المعلمة | النوع | مطلوب | الوصف |
|---|---|---|---|
pattern | سلسلة | نعم | بادئة الرمز القصير للمطابقة. استخدم "*" كخيار احتياطي شامل. تأخذ البادئات الأطول الأولوية. |
url | سلسلة | نعم | عنوان URL لنقطة النهاية لاستقبال ردود POST للرموز القصيرة المطابقة. |
مطابقة التوجيه
يتم مطا��قة التوجيهات بواسطة أطول بادئة أولاً. لسلسلة الاتصال *100#:
"*100"تطابق (الطول 4) — مختارة"*10"تطابق (الطول 3) — تم تخطيها، أقصر"*"تطابق (الطول 1) — احتياطي
إذا لم تتطابق أي توجيه، تعيد البوابة خطأ MAP إلى الهاتف المحمول وتقوم بتسجيل تحذير.
بروتوكول ردود HTTP
يتلقى تطبيقك طلبات HTTP POST من البوابة ويستجيب بتعليمات JSON.
الطلب من البوابة إلى تطبيقك
Content-Type: application/json
الدور الأول (بدء الجلسة):
{
"session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"msisdn": "+254712345678",
"type": "initiation",
"text": "*100#",
"turn": 1
}
الأدوار اللاحقة (رد المشترك):
{
"session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"msisdn": "+254712345678",
"type": "response",
"text": "1",
"turn": 2
}
حقول الطلب
| الحقل | النوع | الوصف |
|---|---|---|
session_id | سلسلة | UUID تم إنشاؤه بواسطة البوابة. فريد لكل حوار USSD. استخدم هذا لربط الأدوار. |
msisdn | سلسلة | MSISDN المشترك (إذا كان متاحًا من رسالة MAP). قد يكون فارغًا لبعض الشبكات. |
type | سلسلة | "initiation" للدور الأول، "response" لردود المشترك اللاحقة. |
text | سلسلة | سلسلة الاتصال (مثل *100#) عند البدء، أو إدخال المشترك (مثل 1) عند الرد. |
turn | عدد صحيح | عداد الدور يبدأ من 1. يزداد مع كل تفاعل للمشترك. |
الرد من تطبيقك
يجب على تطبيقك الرد بـ JSON يحتوي على action و text:
استمرار (عرض القائمة، الانتظار لرد المشترك):
{
"action": "continue",
"text": "1. Balance\n2. Top up\n3. Transfer"
}
إنهاء (عرض الرسالة النهائية، إغلاق الجلسة):
{
"action": "end",
"text": "Your balance is $5.00"
}
حقول الرد
| الحقل | النوع | مطلوب | الوصف |
|---|---|---|---|
action | سلسلة | نعم | "continue" للحفاظ على الجلسة مفتوحة والانتظار لرد المشترك، ��و "end" لعرض رسالة نهائية وإغلاق الجلسة. |
text | سلسلة | نعم | نص لعرضه على جهاز المشترك. الحد الأقصى للطول تحكمه max_text_length (الافتراضي 182). استخدم \n لفواصل الأسطر. |
USSD المنشأ من الشبكة (Push API)
ادفع رسالة USSD إلى مشترك من تطبيقك.
نقطة النهاية
POST /api/ussd/send
الطلب
{
"msisdn": "+254712345678",
"text": "لديك فاتورة معلقة. رد 1 للدفع.",
"callback_url": "http://billing-app:9000/ussd"
}
حقول الطلب
| الحقل | النوع | مطلوب | الوصف |
|---|---|---|---|
msisdn | سلسلة | نعم | MSISDN المشترك الوجهة بالتنسيق الدولي. |
text | سلسلة | نعم | نص USSD الأولي للعرض. مشفر كـ GSM 7-bit. |
callback_url | سلسلة | نعم | عنوان URL لاستقبال رد المشترك عبر بروتوكول الرد القياسي. |
الرد
نجاح (200 OK):
{
"session_id": "xyz-789-abc-123",
"status": "sent"
}
ردود الأخطاء:
| حالة HTTP | الجسم | السبب |
|---|---|---|
| 400 | {"error": "invalid request", "required": ["msisdn", "text", "callback_url"]} | الحقول المطلوبة مفقودة |
| 400 | {"error": "gsm7_encode_failed", ...} | النص يحتوي على أحرف غير موجودة في الأبجدية GSM 7-bit |
| 500 | {"error": "send_failed", ...} | فشل إرسال M3UA (تحقق من الاتصال) |
| 503 | {"error": "USSD gateway not enabled"} | ussd_gateway_enabled هو false |
مثال cURL
curl -X POST http://localhost:8080/api/ussd/send \
-H "Content-Type: application/json" \
-d '{
"msisdn": "+254712345678",
"text": "لديك فاتورة معلقة. رد 1 للدفع.",
"callback_url": "http://billing-app:9000/ussd"
}'
دورة حياة الجلسة
يتم تتبع كل حوار USSD كـ جلسة مع session_id فريد.
حالات الجلسة
جلسة ذات دور واحد
إذا كان ردك يعود بـ "end" في الدور الأول، فلا يتم إنشاء جلسة دائمة. ترسل البوابة MAP End مع النتيجة وتعود على الفور.
جلسة متعددة الأدوار
إذا كان ردك يعود بـ "continue"، تقوم البوابة:
- بإنشاء خادم جلسة GenServer مسجل في
UssdGateway.Registry - ترسل MAP Continue مع opcode 60 (unstructuredSS-Request) إلى الهاتف المحمول
- تنتظر رد المشترك (حتى
turn_timeout_ms) - تعيد توجيه الرد إلى ردك
- تكرر حتى يعود ردك بـ
"end"أو يحدث مهلة
سلوك المهلة
| المهلة | الافتراضي | التأثير |
|---|---|---|
| مهلة الدور | 30 ثانية | إذا لم يرد المشترك ضمن هذه النافذة، يتم إنهاء الجلسة مع خطأ MAP. |
| مهلة الجلسة | 3 دقائق | إجمالي مدة الجلسة. تنهي الجلسة بغض النظر عن الن��اط. |
| مهلة ردود HTTP | 5 ثواني | إذا لم يستجب تطبيقك في الوقت المحدد، ترسل البوابة خطأ MAP إلى الهاتف المحمول وتنهي الجلسة. |
معالجة الأخطاء
تتعامل البوابة مع الفشل بشكل سلس وتحاول دائمًا إرسال رد خطأ MAP إلى الهاتف المحمول حتى يرى المشترك رسالة ذات معنى بدلاً من مهلة الشبكة.
| السيناريو | إجراء البوابة | رمز خطأ MAP |
|---|---|---|
| مهلة ردود HTTP أو 5xx | إنهاء الجلسة، إرسال MAP End مع خطأ | 34 (systemFailure) |
| JSON غير صالح من الرد | إنهاء الجلسة، إرسال MAP End مع خطأ | 34 (systemFailure) |
نص USSD يتجاوز max_text_length | اقتطاع النص، تسجيل تحذير، الاستمرار بشكل طبيعي | N/A (truncated, not an error) |
| مهلة المشترك (لا رد) | إنهاء الجلسة، إرسال MAP End مع خطأ | 34 (systemFailure) |
| لا توجد توجيه تطابق الرمز القصير | إرسال MAP End مع خطأ، تسجيل تحذير | 34 (systemFailure) |
| تعطل خادم جلسة GenServer | تموت الجلسة، يرى المشترك مهلة الشبكة | N/A (process exit) |
| بوابة USSD غير مفعلة | إرجاع Facility Not Supported | 21 (facilityNotSupported) |
المقاييس والمراقبة
تقوم بوابة USSD بتعريض مقاييس Prometheus على نقطة النهاية القياسية /metrics (المنفذ 8080).
مقاييس USSD
المقياس: ussd_requests_total
النوع: عداد
الوصف: إجمالي طلبات USSD المعالجة
التسميات:
direction—"inbound"(المنشأ من الهاتف المحمول) أو"outbound"(المنشأ من الشبكة)
المقياس: ussd_active_sessions
النوع: مقياس
الوصف: عدد جلسات USSD النشطة حاليًا
المقياس: map_request_duration_milliseconds
النوع: هيستوجرام
الوصف: مدة عمليات إرسال USSD بالميلي ثانية
التسميات:
operation—"ussd_send"لطلبات الدفع الخارجية
استعلامات Prometheus مثال
# معدل طلبات USSD حسب الاتجاه
rate(ussd_requests_total[5m])
# الجلسات النشطة
ussd_active_sessions
# زمن الاستجابة USSD الخارجي (p95)
histogram_quantile(0.95, rate(map_request_duration_milliseconds_bucket{operation="ussd_send"}[5m]))
خادم ردود المثال
بايثون (Flask)
from flask import Flask, request, jsonify
app = Flask(__name__)
sessions = {}
@app.route('/ussd', methods=['POST'])
def ussd():
data = request.json
session_id = data['session_id']
text = data['text']
turn = data['turn']
if data['type'] == 'initiation':
sessions[session_id] = {'state': 'main_menu'}
return jsonify({
'action': 'continue',
'text': 'مرحبًا!\n1. تحقق من الرصيد\n2. شراء رصيد\n3. تحويل'
})
state = sessions.get(session_id, {}).get('state')
if state == 'main_menu':
if text == '1':
del sessions[session_id]
return jsonify({
'action': 'end',
'text': 'رصيدك هو $5.00'
})
elif text == '2':
sessions[session_id]['state'] = 'buy_airtime'
return jsonify({
'action': 'continue',
'text': 'أدخل المبلغ:'
})
else:
del sessions[session_id]
return jsonify({
'action': 'end',
'text': 'خيار غير صالح. وداعً��.'
})
elif state == 'buy_airtime':
del sessions[session_id]
return jsonify({
'action': 'end',
'text': f'لقد اشتريت رصيد بقيمة ${text}. شكرًا لك!'
})
return jsonify({'action': 'end', 'text': 'انتهت الجلسة.'})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=9000)
Node.js (Express)
const express = require('express');
const app = express();
app.use(express.json());
const sessions = new Map();
app.post('/ussd', (req, res) => {
const { session_id, text, type } = req.body;
if (type === 'initiation') {
sessions.set(session_id, { state: 'main_menu' });
return res.json({
action: 'continue',
text: 'مرحبًا!\n1. تحقق من الرصيد\n2. شراء رصيد'
});
}
const session = sessions.get(session_id);
if (!session) {
return res.json({ action: 'end', text: 'انتهت الجلسة.' });
}
if (session.state === 'main_menu' && text === '1') {
sessions.delete(session_id);
return res.json({ action: 'end', text: 'رصيدك هو $5.00' });
}
sessions.delete(session_id);
return res.json({ action: 'end', text: 'وداعًا.' });
});
app.listen(9000, () => console.log('ردود USSD على المنف�� 9000'));
استكشاف الأخطاء وإصلاحها
الاتصال بـ USSD يعود "الخدمة غير متاحة"
الأعراض: يقوم المشترك بالاتصال برمز قصير ويواجه خطأ في الشبكة على الفور.
الأسباب المحتملة:
ussd_gateway_enabledهوfalse- لا يوجد توجيه يتطابق مع الرمز القصير المتصل
- اتصال M3UA معطل
الحل:
- تحقق من
ussd_gateway_enabled: trueفي التكوين - تحقق من أن نمط التوجيه يتطابق مع الرمز القصير (تذكر تضمين
*كخيار احتياطي) - تحقق من حالة نظير M3UA في واجهة الويب (صفحة النظائر)
عدم تلقي الردود من التطبيق
الأعراض: تظهر سجلات البوابة بدء USSD لكن تطبيقك لا يتلقى أبدًا HTTP POST.
الأسباب المحتملة:
- عنوان رد غير قابل للوصول من مضيف OmniSS7
- جدار حماية يمنع HTTP الصادر من OmniSS7
- تطبيق الرد غير قيد التشغيل
الحل:
- اختبار الاتصال:
curl -v http://your-app:9000/ussdمن مضيف OmniSS7 - تحقق من قواعد جدار الحماية ��ـ HTTP الصادر
- تحقق من أن تطبيق الرد الخاص بك يستمع على المنفذ المحدد
انتهاء الجلسات بشكل مبكر
الأعراض: تنتهي الجلسات متعددة الأدوار مع "systemFailure" قبل أن يتمكن المشترك من الرد.
الأسباب المحتملة:
turn_timeout_msقصير جدًا لقاعدة المشتركين لديكhttp_timeout_msقصير جدًا لوقت معالجة تطبيقك- زمن الانتقال بين OmniSS7 وخادم الرد الخاص بك
الحل:
- زيادة
turn_timeout_ms(الافتراضي 30 ثانية يجب أن يكون كافيًا لمعظم الحالات) - زيادة
http_timeout_msإذا كان تطبيقك يحتاج إلى مزيد من وقت المعالجة - نشر خادم الرد بالقرب من OmniSS7 لتقليل زمن الانتقال
أخطاء تشفير GSM 7-bit
الأعراض: أخطاء gsm7_encode_failed في السجلات أو ردود 400 من /api/ussd/send.
الأسباب المحتملة:
- النص يحتوي على أحرف خارج الأبجدية الافتراضية GSM 7-bit (مثل الرموز التعبيرية، أحرف CJK)
الحل:
- تقييد نص USSD إلى مجموعة الأحرف الأساسية GSM: أحرف ASCII، أرقام، علامات الترقيم الشائعة، وبعض أحرف اليونانية/النوردية
- انظر 3GPP TS 23.038 القسم 6.2.1 للحصول على جدول الأحرف الكامل
الوثائق ذات الصلة
- دليل API — مرجع كامل لواجهة برمجة التطبيقات REST (جميع نقاط النهاية بما في ذلك
/api/ussd/send) - دليل عميل MAP — إعداد اتصال M3UA المطلوب لـ USSD
- مرجع التكوين — جميع معلمات التكوين
- دليل الميزات الشائعة — واجهة الويب، المراقبة، وإعداد Prometheus