التوثيق
الدمجالأخطاء

أخطاء الدفع

لما عملية دفع لعميل تفشل، الخطأ بيقعد على حقل lastPaymentError بتاع Payment Intent. الشكل، والـ adviceCode، وإزاي تقسّم النص بين عميلك وفريقك.

الصفحة دي بتغطّي مكان الفشل غير المتزامن. لما عملية دفع لعميل تفشل (بطاقة مرفوضة، أو انتهاء مهلة المعالج، أو بطاقة منتهية، إلخ)، الخطأ ما بيرجعش كرد HTTP للسيرفر بتاعك. بيعيش على حقل lastPaymentError بتاع الـ Payment Intent لباقي عمره.

للأخطاء المتزامنة اللي بترجع من استدعاءات الـ API بتاعتك إنت، شوف أخطاء API.

فين الخطأ بيعيش

عملية الدفع الفاشلة بتسيب ثلاثة آثار مختلفة:

  • paymentIntent.lastPaymentError: التفصيل المنظَّم الكامل. اقراه لكل اللي محتاج تعرضه في واجهتك التشغيلية، أو تقرّر استراتيجية إعادة المحاولة، أو تبني النص الموجّه للعميل.
  • charge.failureCode وcharge.failureMessage: نفس الخطأ الرئيسي، مربوط بالـ Charge المحدّد اللي فشل. مفيد لما تربط تنفيذ الطلب بالـ Charge.
  • webhook الـ charge.failed: بيتطلق في اللحظة اللي محاولة الـ charge بتفشل فيها. اشترك فيه لو محتاج تتفاعل مع حالات الفشل في الوقت الفعلي.

أكمل بيانات بتبقى على lastPaymentError. لو معالِجك استقبل charge.failed، جيب الـ Payment Intent علشان تاخد الحقول المنظَّمة الموصوفة تحت.

شكل lastPaymentError

{
  "type": "card_error",
  "code": "card_declined",
  "declineCode": "insufficient_funds",
  "networkDeclineCode": "51",
  "message": "Your card has insufficient funds.",
  "merchantMessage": "Declined: insufficient funds. Customer should use a different payment method.",
  "adviceCode": "try_again_later",
  "docUrl": "https://docs.xpay.app/integrate/errors/decline-codes#insufficient_funds",
  "param": null,
  "chargeId": "ch_test_AbC123",
  "paymentMethodType": "card",
  "paymentMethod": { "card": { "brand": "mastercard", "last4": "0008" } },
  "processorCode": "...",
  "processorMessage": "..."
}
الحقلالمعنى
typeفئة عريضة: card_error، وpayment_method_error (BNPL، أو محفظة، أو كشك، أو تحويل بنكي)، وprocessor_error، وapi_error.
codeسبب الخطأ المحدّد. من قائمة أكواد أخطاء الدفع.
declineCodeلرفض البطاقات، سبب البنك المُصدِر. من قائمة أكواد الرفض. null لحالات الفشل اللي مش رفض.
networkDeclineCodeالكود من 2-4 أرقام اللي رجع من شبكة البطاقة (مثلًا 51 لعدم كفاية الرصيد في ISO 8583). المعنى بيعتمد على ماركة البطاقة. null لما الشبكة ما قدمتش واحد.
messageرسالة قصيرة آمنة للعرض للعميل.
merchantMessageرسالة تفصيلية لفريق الدعم بتاعك، بتشمل سياق العميل ما المفروضش يشوفه.
adviceCodeاللي تعمله بعد كده: confirm_card_data، أو try_again_later، أو do_not_try_again. أكتر حقل قابل للتنفيذ.
docUrlرابط مباشر للصف المطابق في مرجع الأكواد.
paramحقل جسم الطلب اللي فشل، لو منطبق. عادةً null على المكان ده.
chargeIdالـ ch_* ID بتاع الـ Charge اللي فشل.
paymentMethodTypeنوع طريقة الدفع (card، valu، fawry، إلخ).
paymentMethodلقطة لطريقة الدفع وقت الفشل (الماركة، آخر 4 أرقام للبطاقات).
processorCode، processorMessageأكواد ورسائل خام من المعالج، غير مترجمة. للتصحيح العميق بس.

code بيكون متظبّط دايمًا. declineCode وnetworkDeclineCode بيتظبّطوا على رفض البطاقات؛ والباقي بيتعبّى بناءً على اللي XPay يقدر يستخرجه من رد المعالج.

مساحات الأكواد الثلاثة

lastPaymentError بيحمل لحد ثلاثة أكواد مختلفة لنفس الفشل. كل واحد هو مصدر الحقيقة لسؤال مختلف:

الحقلالسؤال اللي بيرد عليهفين تلاقي الأوصاف
codeالفشل ده كان من أي نوع؟أكواد أخطاء الدفع
declineCodeليه البنك المُصدِر رفض؟ (البطاقات بس)أكواد الرفض
networkDeclineCodeأنهي كود خام رجّعته شبكة البطاقة؟خاص بالماركة؛ مرّره للدعم لو احتجت

لأغلب المعالِجات، تفرّع على adviceCode الأول، واعرض merchantMessage في واجهتك التشغيلية، واستخدم code أو declineCode لاختيار النص الموجّه للعميل.

نمط المعالِج

الإجراء اللي بتاخده بيتقرّر بـ adviceCode، مش بـ code. قائمة الأكواد بتكبر؛ وقيم الإرشاد الثلاثة لأ.

adviceCodeمعناه إيهاللي تعمله
confirm_card_dataبيانات البطاقة على الأرجح كانت غلط (عدم تطابق CVV، أو رقم غلط، أو عدم تطابق الرمز البريدي).قول للعميل يدخّل بطاقته تاني. ما تقترحش بطاقة تانية؛ نفس البطاقة ببيانات مصحّحة على الأرجح هتشتغل.
try_again_laterمؤقت: عدم كفاية الرصيد، أو انتهاء مهلة المعالج، أو خلل شبكة.قول للعميل يحاول تاني، أو اعرض خيار "جرّب طريقة دفع تانية". آمن إعادة المحاولة مرة واحدة.
do_not_try_againرفض قاطع: بطاقة مسروقة، أو ضايعة، أو احتيال، أو منتهية.ما تعيدش محاولة نفس البطاقة. اعرض للعميل "استخدم طريقة دفع تانية". بلّغ فريقك لو المعدّل ارتفع.
function handlePaymentFailure(error: LastPaymentError) {
  // Always log first. The merchantMessage is what your support team needs.
  log.error("payment failed", {
    chargeId: error.chargeId,
    type: error.type,
    code: error.code,
    declineCode: error.declineCode,
    networkDeclineCode: error.networkDeclineCode,
    merchantMessage: error.merchantMessage,
    docUrl: error.docUrl,
  });

  // Decide UX from adviceCode.
  switch (error.adviceCode) {
    case "confirm_card_data":
      return showRetryWithCard(error.message);

    case "try_again_later":
      return showRetryWithFallback(error.message);

    case "do_not_try_again":
      return showUseDifferentMethod(error.message);

    default:
      // Missing adviceCode is rare. Treat as do_not_try_again to be safe.
      return showUseDifferentMethod(error.message);
  }
}

نص العميل مقابل نص التاجر

XPay بيرجّع رسالتين على كل حالة فشل. مش قابلين للتبديل.

الحقلالجمهورمثال
messageعميلك. مفلتر بالفعل لما هو آمن للعرض."Your card has insufficient funds."
merchantMessageفريقك (الدعم، اللوحات التشغيلية). بيشمل سياق مفيد داخليًا لكن غير مناسب للعملاء."Declined: insufficient funds. Customer should use a different payment method."

تلميح المعاملة في لوحة التحكم بيستخدم merchantMessage. ابنِ واجهتك التشغيلية بنفس الطريقة. للمكان الموجّه للعميل (صفحة الدفع بتاعتك، وإيميلات المعاملات بتاعتك)، استخدم message أو نصّك إنت المربوط بـ code / declineCode.

ما تعرضش merchantMessage للعملاء. ممكن يسرّب سياق داخلي. وما تعرضش processorCode أو processorMessage للعملاء كمان؛ دول تمرير خام من المعالج، غير مترجم.

قراءة lastPaymentError في الـ webhooks

webhook الـ charge.failed بيحمل Charge في event.data.object. الـ Charge عنده الفشل الرئيسي (failureCode، failureMessage) لكن مش التفصيل المنظَّم الكامل. علشان تقرا lastPaymentError كامل، جيب الـ Payment Intent الأب:

async function onChargeFailed(event: { data: { object: Charge } }) {
  const charge = event.data.object;

  // Quick action: the Charge alone has the headline.
  log.warn("charge failed", {
    chargeId: charge.id,
    failureCode: charge.failureCode,
    failureMessage: charge.failureMessage,
  });

  // Full detail: fetch the PI.
  const pi = await getPaymentIntent(charge.paymentIntentId);
  if (pi.lastPaymentError) {
    handlePaymentFailure(pi.lastPaymentError);
  }
}

lastPaymentError بيتظبّط على الـ PI طول عمر المورد. بيسجّل آخر محاولة فاشلة؛ ومحاولات إعادة المحاولة اللي بعدها على نفس الـ PI بتكتب فوقه.

اللي ما تعملوش

  • ما تتفرّعش على code لوحده. قائمة الأكواد بتكبر على مدار الوقت. تفرّع على adviceCode للي تعمله، بعدين استخدم code لقرارات النص الضيّقة.
  • ما تعرضش merchantMessage لعملائك. ممكن يشمل تفاصيل غير مناسبة لسطح المستهلك.
  • ما تتجاهلش adviceCode: "do_not_try_again". إعادة المحاولة بعد رفض بطاقة مسروقة أو احتيال ما بتفيدش وممكن تعلّم على حسابك عند البنك المُصدِر.
  • ما تعيدش المحاولة بشكل أعمى على try_again_later. أعِد المحاولة مرة واحدة، بعدين انقل العميل لطريقة تانية أو أنهِ المسار. التكرار على رفض do_not_honor بينتج نفس النتيجة كل مرة.
  • ما تستخدمش networkDeclineCode كتفرّعك الأساسي. هو تمرير خام بيعتمد على ماركة البطاقة. استخدم declineCode (شكل XPay المطبّع) بدلًا منه.

رايح فين بعد كده

في الصفحة دي