Skip to content

استكشاف الأخطاء وإصلاحها ​

لا يصلني الـ webhook ​

التشخيص ​

  1. لوحة التحكم ← Webhooks ← السجلات: هل يظهر الطلب في القائمة؟
    • لا ← لم يُطلَق الحدث. تأكّد من أن المعاملة وصلت إلى success/failed/cancelled/expired. افرض استعلامًا عبر POST/v1/payments/{id}/verify.
    • نعم، مع http_status غير 2xx ← رفض خادمك الطلب. راجع «نقطة الاستقبال ترد بـ 4xx/5xx».
    • نعم، مع error_message: "timeout" ← استغرق خادمك أكثر من 15 ثانية. راجع «انتهاء المهلة».

نقطة الاستقبال ترد بـ 4xx/5xx ​

http_statusالسبب المعتاد
400تحليل الجسم: يقرأ خادمك الحمولة قبل التحقق من التوقيع
401 / 403وسيط مصادقة يحجب الطلبات الخارجية: استثنِ /webhooks/soamtar
404مسار خاطئ: تحقّق من الرابط الدقيق في لوحة التحكم
422تحقق إطار العمل: اقبل الحمولة كما هي
500خطأ في التطبيق: راجع سجلاتك
502 / 503الوكيل العكسي متوقف أو التطبيق لم يبدأ

الحل: استثنِ مسار الـ webhook من حماية CSRF (مثل withoutMiddleware([VerifyCsrfToken::class]) في Laravel) ومن وسيط المصادقة لديك.

انتهاء المهلة ​

يجب أن يرد معالجك بـ 2xx في أقل من 15 ثانية. للمعالجة الطويلة:

php
// Bad
public function webhook(Request $request) {
    $this->processOrder($request);     // 30 seconds
    $this->sendEmail($request);
    return response('ok');
}

// Good
public function webhook(Request $request) {
    ProcessSoamtarWebhookJob::dispatch($request->all());
    return response('ok');             // immediate
}

يتلقى خادمي الـ webhook خمس مرات ​

هذه إعادة محاولة، وهي متوقعة. الأسباب:

  1. ترد بـ 2xx لكن بعد أكثر من 15 ثانية ← سجّلت SOAMTAR انتهاء المهلة
  2. ترد برمز غير 2xx
  3. ترد بـ 2xx لكن الاستجابة لا تصل إلى SOAMTAR أبدًا (مشكلة شبكة)

الحل: أزل التكرار من جهتك بالاعتماد على الزوج (event، data.id).

sql
CREATE UNIQUE INDEX uq_inbound_event ON your_webhook_log(event, transaction_id);

توقيع webhook غير صالح ​

العَرَض ​

يفشل التحقق من HMAC رغم استخدامك السرّ الصحيح.

السبب 1: تعديل الجسم قبل التحقق ​

تحلّل Express وSymfony وLaravel الـ JSON تلقائيًا. يجب أن تتحقق من التوقيع على الجسم الخام، قبل أي تحليل.

js
// Bad
app.use(express.json())   // parses JSON
app.post('/webhook', (req, res) => {
  verify(req.body)        // req.body is already an object, not the raw body
})

// Good
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  verify(req.body.toString())   // raw
  const event = JSON.parse(req.body.toString())
})

السبب 2: ترميز HMAC ​

قارن القيم الست عشرية بأحرف صغيرة على الجانبين (تُعيد hash_hmac في PHP أحرفًا صغيرة؛ تحقّق من ذلك في Node). استخدم hash_equals / crypto.timingSafeEqual وليس ===.

السبب 3: طابع زمني منتهٍ ​

يُرفض |now - t| > 300s. تحقّق من ساعة خادمك (NTP). لا توسّع هامش التسامح لمعالجة عمليات تسليم قديمة: أعد إرسالها من Dashboard → Webhook logs → Resend (تُوقَّع كل محاولة من جديد بطابع زمني حديث) أو استخدم استعلام verify.

تعارض Idempotency-Key ​

العَرَض ​

409 idempotency_conflict عند إعادة إرسال الطلب.

التشخيص ​

bash
curl -X POST .../v1/payments \
  -H "Idempotency-Key: abc-123" \
  -d '{"merchant_ref":"ORDER-7","amount":"150.00","currency":"SLE"}'
# 201

curl -X POST .../v1/payments \
  -H "Idempotency-Key: abc-123" \
  -d '{"merchant_ref":"ORDER-7","amount":"150.00","currency":"SLE","description":"oops"}'
# 409 — different body

الحل:

  • لـإعادة المحاولة: المفتاح نفسه والجسم نفسه تمامًا.
  • لـطلب جديد: مفتاح جديد.

الممارسة الجيدة ​

ولّد المفتاح عندما ينقر العميل على «ادفع» واحفظه (تخزين محلي أو قاعدة بيانات) حتى النتيجة النهائية. لا تولّده من جديد في كل إعادة محاولة HTTP.

معاملة عالقة في pending ​

التشخيص ​

bash
curl -X POST https://app.so-amtar.com/v1/payments/{id}/verify \
  -H "Authorization: Bearer sk_test_xxx"

يستعلم verify من Orange Money ويزامن الحالة. إذا أكّد Orange Money، تتقدّم المعاملة ويُرسَل إليك webhook.

إذا لم يتغيّر شيء ​

  • لم يؤكد العميل طلب Orange Money برمز PIN (يبقى الطلب مفتوحًا نحو 5 دقائق): اطلب منه التحقق من هاتفه، أو ابدأ دفعة جديدة.
  • لدى Orange Money حادث: استفسر من الدعم.
  • تنتهي صلاحية المعاملة تلقائيًا عند expires_at (ساعتان افتراضيًا).
  • إذا بقيت عالقة: راسل support@so-amtar.com مع مرجع SOAMTAR (TXN-...).

استرداد دفعة Orange Money ​

استخدم POST/v1/refunds (بمفتاح يملك الصلاحية can_refund):

  1. أرسل transaction_id الخاص بالدفع؛ احذف amount لاسترداد المبلغ المتبقي أو حدّده لاسترداد جزئي.
  2. يُعاد المبلغ إلى رقم Orange Money الخاص بالدافع ويُخصم من رصيدك. تبقى عملية الاسترداد في الحالة processing حتى تأكيد Orange، وتصلك النتيجة عبر refund.success / refund.failed.
  3. إذا تلقيت refund_payer_phone_missing، فالدفع لا يحمل رقم الدافع: تواصل مع الدعم.

راجع الاسترداد (بالإنجليزية).

الرصيد المعروض لا يطابق تسوياتي ​

المكوّنمُدرج في الرصيد المعروض؟
success لم يُسوَّ بعدنعم
pendingلا
success مُسوّى سابقًالا
استرداد أو دفعة صادرةنعم (مخصوم)
اعتراض على الدفع مفتوحمحجوز في رصيد منفصل

يختلف الرصيد «المتاح» عن الرصيد «المحاسبي»؛ وتعرض لوحة التحكم كليهما.

مفتاح live لا يجد الكائن الخاص بي ​

أنت تستخدم مفتاح live على كائن أُنشئ في بيئة الاختبار (أو العكس). استخدم مفتاح الوضع المطابق: ترتبط المعرّفات بالوضع بشكل صارم.

مشكلات الاتصال بالواجهة البرمجية (SSL، انتهاء المهلة…) ​

خطأ SSL: SSL certificate problem: unable to get local issuer certificate ​

يظهر عادةً على خوادم Windows أو في تثبيتات PHP/cURL القديمة التي تفتقر إلى حزمة شهادات CA محدّثة.

الحل:

  1. نزّل الملف الرسمي cacert.pem من curl.se/ca/cacert.pem.
  2. وجّه php.ini إليه:
    ini
    curl.cainfo = "C:/chemin/vers/votre/cacert.pem"
    openssl.cafile = "C:/chemin/vers/votre/cacert.pem"
  3. أعد تشغيل خادم الويب.

خطأ: Connection Timeout ​

إذا تعذّر على خادمك الوصول إلى app.so-amtar.com:

  1. جدار الحماية: يجب السماح لخادمك بإرسال طلبات صادرة على المنفذ 443 (HTTPS).
  2. DNS: تأكّد من أن app.so-amtar.com يُحلّ من خادمك.
  3. الوكيل (Proxy): إذا كان خادمك يستخدم وكيلًا، فاضبط SDK وفقًا لذلك (أو متغيرات البيئة HTTP_PROXY).

خطأ: 401 Unauthorized (مفتاح غير صالح) ​

  1. استخدم المفتاح السرّي (sk_...) في الاستدعاءات من الخادم الخلفي، لا المفتاح العام (pk_...).
  2. تحقّق من الوضع: لا يعمل مفتاح sk_test_ إلا مع كائنات بيئة الاختبار.
  3. تأكّد من أنك لم تنسخ مسافات أو فواصل أسطر مع المفتاح.

لمعرفة المزيد ​

  • الدعم: support@so-amtar.com

SOAMTAR Payment API Documentation.