أخطاء API
مغلّف الخطأ اللي بيرجعه كل استدعاء API فاشل. أنواع الأخطاء الثلاثة، والحقول اللي بيحملوها، ونمط المعالِج.
الصفحة دي بتغطّي الأخطاء المتزامنة اللي XPay بيرجّعها: لما السيرفر بتاعك يعمل استدعاء API ويرجعله رد غير 2xx، إيه اللي في الجسم، وإزاي تتعامل معاه.
لحالات فشل الدفع غير المتزامنة (رفض بطاقة بيقع على lastPaymentError بتاع Payment Intent)، ده مكان مختلف تمامًا. شوف أخطاء الدفع.
المغلّف
كل رد خطأ API ليه نفس الشكل:
{
"error": {
"type": "invalid_request_error",
"code": "parameter_missing",
"message": "Missing required parameter: lineItems",
"param": "lineItems",
"doc_url": "https://docs.xpay.app/integrate/errors/api-error-codes#parameter_missing"
},
"request_id": "req_3STkwmFGhGHoO0IX13BRo5iU"
}| الحقل | النوع | المعنى |
|---|---|---|
error.type | string | واحد من الفئات الثلاثة تحت. تفرّع على ده الأول. |
error.code | string | null | معرّف الخطأ المحدّد. ثابت عبر الرسائل والترجمات. استخدمه هو، مش الرسالة، للتحكم في المسار. |
error.message | string | إنجليزي مقروء للبني آدمين. لسجلاتك وواجهة فريقك. ما تعرضوش حرفيًا لعملائك. |
error.param | string | null | حقل جسم الطلب اللي فشل (زي lineItems[0].price، customerDetails.email). موجود في أخطاء التحقّق. |
error.doc_url | string | null | رابط مباشر لصف الكود ده في مرجع أكواد أخطاء API. |
request_id | string | الـ req_* ID للطلب الفاشل. سجّله دايمًا. استخدمه علشان تلاقي الطلب في Workbench ← السجلات. |
code، وparam، وdoc_url ممكن يكونوا غايبين. التلاتة التانيين موجودين دايمًا.
لو تقدر تتحمّل تفرّع واحد بس في المعالِج بتاعك، تفرّع على type، مش على code أو حالة الـ HTTP.
قائمة الأنواع ثابتة عبر الإصدارات؛ وقائمة الأكواد بتكبر على مدار الوقت وبتتضاف أسباب أخطاء جديدة.
الأنواع الثلاثة
| النوع | حالة HTTP | معناه إيه | المعالِج المعتاد |
|---|---|---|---|
invalid_request_error | 400، 404، 409 | الطلب اترفض بسبب حاجة من ناحيتك: حقل ناقص أو غلط، أو مورد مش موجود، أو حالة متعارضة. | ما تعيدش المحاولة. صلّح الطلب. اعرض الرسالة لمين تسبّب فيها (فريقك، الفورم بتاعك). |
authentication_error | 401 | مفتاح الـ API أو توقيع الـ HMAC ناقص، أو غير صالح، أو معطّل، أو بصيغة غلط. | ما تعيدش المحاولة. اتأكد من XPAY_SECRET_KEY في بيئتك، ومن حالة تفعيل المفتاح في لوحة التحكم، ومن إنك بتبعت Authorization: Bearer <key>. |
api_error | 500 | مشكلة غير متوقعة من ناحية السيرفر عند XPay. | أعِد المحاولة بتراجع أسّي. لو استمرت، اذكر request_id للدعم. |
لما تعيد محاولة كتابة (POST أو PATCH)، ابعت Idempotency-Key علشان إعادة المحاولة ترجّع النتيجة الأصلية بدل ما تكرّر العملية. شوف عدم التكرار (idempotency).
نمط المعالجة
المعالِج هو try / catch واحد. جوّه الـ catch، افصل الرد، وتفرّع على error.type، وضيّق على error.code للعدد القليل من الأكواد اللي بتهمّك، وسجّل كل حاجة، واعرض اللي مناسب.
async function callXPay<T>(path: string, body: unknown): Promise<T> {
const res = await fetch(`https://api.xpay.app${path}`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.XPAY_SECRET_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (res.ok) return res.json() as Promise<T>;
// Read the canonical envelope. Every XPay error response has `error.type`.
const payload = await res.json().catch(() => null);
const xpayError = payload?.error;
const requestId = payload?.request_id;
if (!xpayError?.type) {
// Transport-level failure: not XPay-shaped. Treat as a client-side bug.
throw new Error(`Non-XPay response: ${res.status}`);
}
// Always log first, branch second.
log.error("xpay api error", {
requestId,
type: xpayError.type,
code: xpayError.code,
param: xpayError.param,
message: xpayError.message,
docUrl: xpayError.doc_url,
});
switch (xpayError.type) {
case "invalid_request_error":
// Your request is wrong. Don't retry. Bubble up to whoever sent it.
throw new BadRequest(xpayError);
case "authentication_error":
// Your secret key is wrong. Page yourself; don't retry.
throw new ConfigError(xpayError);
case "api_error":
// XPay is having a moment. Retry with backoff.
throw new RetryableError(xpayError);
default:
// Unknown type. Treat as retryable to be safe.
throw new RetryableError(xpayError);
}
}import os, requests
def call_xpay(path: str, body: dict) -> dict:
res = requests.post(
f"https://api.xpay.app{path}",
headers={
"Authorization": f"Bearer {os.environ['XPAY_SECRET_KEY']}",
"Content-Type": "application/json",
},
json=body,
timeout=30,
)
if res.ok:
return res.json()
payload = res.json() if res.content else {}
xpay_error = payload.get("error") or {}
request_id = payload.get("request_id")
if not xpay_error.get("type"):
# Transport-level failure: not XPay-shaped.
raise RuntimeError(f"Non-XPay response: {res.status_code}")
log.error(
"xpay api error",
extra={
"request_id": request_id,
"type": xpay_error.get("type"),
"code": xpay_error.get("code"),
"param": xpay_error.get("param"),
"message": xpay_error.get("message"),
"doc_url": xpay_error.get("doc_url"),
},
)
error_type = xpay_error["type"]
if error_type == "invalid_request_error":
raise BadRequest(xpay_error)
if error_type == "authentication_error":
raise ConfigError(xpay_error)
if error_type == "api_error":
raise RetryableError(xpay_error)
raise RetryableError(xpay_error) # unknown type, treat as retryableالتضييق على code
type بيقرّر التفرّع العريض. code بيقرّر الرسالة المحدّدة اللي بتعرضها. شوية أكواد تستاهل معاملة خاصة في واجهتك؛ والباقي يقدر يستخدم error.message زائد رابط "اعرف أكتر" لـ error.doc_url.
if (xpayError.type === "invalid_request_error") {
switch (xpayError.code) {
case "resource_missing":
// The ID you sent doesn't exist. Likely your code, not the user's.
return show("That record was deleted or never existed.");
case "checkout_session_expired":
// The session is past expiresAt. Create a new one.
return await createNewSession();
case "price_sold_out":
// Stock ran out between session creation and payment.
return show("Sorry, that item is sold out.");
case "merchant_not_activated":
// Your account isn't approved for live mode yet.
return show("Your account needs activation. Contact support.");
default:
// Generic invalid_request_error. Surface the message.
return show(xpayError.message);
}
}للقائمة الكاملة بالأكواد اللي ممكن تشوفها، شوف أكواد أخطاء API.
اللي ما تعملوش
- ما تعيدش محاولة
invalid_request_error. نفس الطلب هيفشل بنفس الطريقة. صلّح المدخلات وحاول تاني، بـIdempotency-Keyجديد لو بتستخدم واحد. - ما تعيدش محاولة
authentication_errorكمان. المفتاح الغلط مفتاح غلط. إعادة المحاولة عشر مرات بتنتج بس عشر سطور في السجل بتقول كده. - ما تعرضش
error.messageلعملائك النهائيين حرفيًا. هو إنجليزي وتقني وممكن يحتوي معرّفات داخلية. استخدمه لسجلاتك وواجهة فريق الدعم بتاعك. ابنِ النص الموجّه للعميل بناءً علىerror.code. - ما تتجاهلش
error.codeوتفصلerror.messageبدلًا منه. نص الرسالة بيتغير؛ والكود ثابت. التفرّع على الرسالة هش. - ما تبلعش الأخطاء بصمت. سجّل
request_idجنب كل خطأ بتمسكه دايمًا. من غيره، تصحيح الأخطاء في Workbench ← السجلات بيبقى تمرير لانهائي. - ما تفترضش إن الرد بشكل XPay. الرد من غير
error.typeهو فشل على مستوى النقل (بروكسي معطّل، أو نوع محتوى غلط، أو حمولة كبيرة أوي). اعتبره خطأ من ناحية العميل لازم يتصلّح، مش تفرّعًا وقت التشغيل.
رايح فين بعد كده
أكواد أخطاء API
القائمة الكاملة لقيم error.code، متجمّعة حسب المجال، مع وصف من سطر لكل واحد.
أخطاء الدفع
المكان التاني: إزاي تتعامل مع عملية دفع فاشلة لعميل عن طريق lastPaymentError.
لوحة السجلات
دوّر على أي استدعاء فاشل بـ request_id وشوف الطلب، والرد، وحقول الخطأ كاملين.
مقدمة الأخطاء
رجوع لأول مجموعة الأخطاء. خريطة المكانين.