الأخطاء

معالجة الأخطاء

كائن الخطأ، وأنواع الأخطاء السبعة، والرموز التي تصادفها أولًا، وكيف تعالج كلًّا منها.

فتح بصيغة Markdown
في هذه الصفحة
  1. كائن الخطأ
  2. أنواع الأخطاء
  3. الرموز التي تصادفها أولًا
  4. الطلبات
  5. المفاتيح والصلاحيات
  6. المدفوعات وعدم التكرار والحدود
  7. قراءة خطأ في كودك
  8. معرّفات الطلبات

عندما يفشل طلب، تجيب واجهة API بحالة HTTP قيمتها 400 أو أكثر مع كائن خطأ. يقرر كودك ما يفعله بناءً على type وcode في الخطأ؛ أما message فللأشخاص.

كائن الخطأ

الاستجابة404 Not Found
{
  "error": {
    "type": "invalid_request_error",
    "code": "resource_missing",
    "message": "No such payment_intent: 'pi_01k7d9x2c5v8b1n4m7q0w3e6rt'",
    "doc_url": "https://pay-docs.adpromax.com/errors#resource_missing",
    "request_id": "req_01k7d9x3f6g9h2j5k8m1n4p7qs"
  }
}
الحقلما يحمله
typeأحد الأنواع السبعة أدناه: الصنف العام للفشل.
codeالسبب الدقيق، ثابت مع الوقت: فرّع كودك بحسبه.
decline_codeللبطاقة المرفوضة: سبب البنك، مثل insufficient_funds.
paramالمعامل المخالف، على شكل مسار (metadata.order_id)، إن وُجد.
messageشرح موجّه لشخص، بلغة طلبك (الإنجليزية أو الفرنسية أو العربية، حسب ترويسة Accept-Language). قد يتغير: لا تفرّع كودك بحسبه أبدًا.
doc_urlالصفحة التي تشرح الرمز.
request_idمعرّف الطلب (req_…)، ويوجد أيضًا في ترويسة Request-Id لكل استجابة.

أنواع الأخطاء

typeالحالةما حدثما تفعله
invalid_request_error400، 404، 409الطلب خاطئ: معامل ناقص أو غير معروف أو غير صالح، أو كائن أو عنوان غير موجود، أو إجراء لا تسمح به حالة الكائن.صحّح الطلب. إرساله من جديد دون تغيير يفشل بالطريقة نفسها.
authentication_error401لا مفتاح، أو مفتاح لا يعمل.تحقّق من المفتاح ومن وضعه.
permission_error403المفتاح يعمل لكن لا يحق له هذا.انظر الرمز: مفتاح للوضع المباشر قبل التفعيل، أو أداة اختبار مساعدة بمفتاح للوضع المباشر…
card_error402رُفضت بطاقة.اعرض لعميلك رسالة ودعه يجرّب بطاقة أخرى.
idempotency_error400، 409أُعيد استخدام Idempotency-Key لطلب آخر، أو ما زال طلبها الأول قيد التنفيذ.انظر عدم التكرار (idempotency).
rate_limit_error429طلبات كثيرة جدًا في وقت قصير.انتظر مهلة Retry-After، ثم أعد المحاولة. انظر حدود المعدل.
api_error500، 503فشل شيء من جهتنا، أو هو غير متاح للحظات.أعد المحاولة بقيمة Idempotency-Key نفسها؛ وإذا استمر الخطأ، فراسل الدعم مع request_id.

الرموز التي تصادفها أولًا

الطلبات

codeما حدث
parameter_missingمعامل إلزامي غائب؛ ويذكره param.
parameter_unknownمعامل لا يقبله الطلب؛ ويذكره param. تحقّق من كتابته.
parameter_invalidلمعامل نوع أو قيمة خاطئة؛ ويذكره param. يُرفض بهذه الطريقة أيضًا أي حقل نصي يشبه رقم بطاقة: لا تمر أرقام البطاقات عبر API أبدًا. وكذلك قائمة تُطلب بـ starting_after وending_before معًا.
resource_missingلا يوجد كائن بهذا المعرّف، في هذا الوضع. وإذا كان موجودًا في الوضع الآخر، تذكر الرسالة ذلك: استخدمت على الأرجح المفتاح الخطأ.
unrecognized_request_urlلا يوجد مسار في API بهذه الطريقة وهذا العنوان؛ وتذكرهما الرسالة. تحقّق من المسار ومن طريقة HTTP.
api_version_invalidتذكر ترويسة X-AdProMaxPay-Version، أو api_version في نقطة استقبال webhook، إصدارًا غير موجود. وتسرد الرسالة الإصدارات الموجودة.
amount_too_smallالمبلغ أقل من الحد الأدنى لطريقة الدفع: 100.00 MAD للدفع نقدًا، و1.00 MAD للبطاقة.
amount_too_largeاسترداد يتجاوز ما بقي قابلًا للاسترداد من الدفعة.
payment_method_unavailableلا يمكن استخدام طريقة الدفع لحسابك في هذا الوضع.
payment_intent_unexpected_stateحالة نية الدفع (payment intent) لا تسمح بهذا الإجراء، مثل تأكيد دفعة سبق أن نجحت.
payment_intent_busyطلب آخر يؤكد هذه الدفعة أو يعدّلها الآن. لم يتغير شيء: أعد إرسال الطلب بعد لحظات.
charge_unexpected_stateحالة المحاولة لا تسمح بأداة الاختبار المساعدة هذه، مثل إتمام محاولة سبق أن نجحت.
refund_unsupported_payment_methodلا يمكن استرداد دفعة نقدية.
url_not_allowedعنوان webhook ليس https، أو يحمل اسم مستخدم أو كلمة مرور، أو ليس عامًا، أو يذكر مضيفًا لا يمكن حل اسمه.
webhook_endpoint_limit_reachedتملك المؤسسة 16 نقطة استقبال webhook في هذا الوضع من قبل.
webhook_endpoints_busyالتقى تغييران لنقاط استقبالك في اللحظة نفسها (409). أرسل الطلب من جديد.
idempotency_key_missingأُرسل طلب يحرّك أموالًا دون Idempotency-Key.
invalid_requestطلب مرفوض دون رمز أدق؛ وتوضح الرسالة السبب.

المفاتيح والصلاحيات

codeما حدث
api_key_missingلم يحمل الطلب أي مفتاح.
api_key_invalidالمفتاح غير موجود. تحقّق من عدم نقصان أي حرف.
api_key_revokedأُبطل المفتاح. استخدم مفتاحًا آخر.
api_key_expiredجُدِّد المفتاح وانتهت فترة التداخل الخاصة به. استخدم المفتاح الجديد.
secret_key_requiredاستُخدم مفتاح قابل للنشر حيث يلزم مفتاح سري.
live_mode_not_activatedاستُخدم مفتاح للوضع المباشر قبل تفعيل الحساب.
account_inactiveالحساب موقوف أو مغلق.
test_mode_onlyاستُدعيت أداة اختبار مساعدة بمفتاح للوضع المباشر.
authentication_requiredرد 401 دون رمز أدق؛ وتوضح الرسالة السبب.
permission_deniedرد 403 دون رمز أدق؛ وتوضح الرسالة السبب.

المدفوعات وعدم التكرار والحدود

codeما حدث
card_declinedرُفضت البطاقة؛ وقد يوضح decline_code السبب.
idempotency_key_reusedاستُخدمت قيمة Idempotency-Key هذه لطلب آخر.
idempotency_in_progressالطلب الأول بقيمة Idempotency-Key هذه ما زال قيد التنفيذ.
idempotency_errorرفض يخص Idempotency-Key دون رمز أدق؛ وتوضح الرسالة السبب.
rate_limit_exceededطلبات كثيرة جدًا؛ وتذكر الرسالة الحد الذي بُلغ ومدة الانتظار.
test_daily_cap_reachedبُلغ السقف اليومي لوضع الاختبار، حتى منتصف الليل بتوقيت المغرب.
api_errorخطأ من جهتنا.

الدفعة التي يجريها عميلك في الصفحة المستضافة لا تُفشل استدعاءك لـ API عند رفض البطاقة: يرى عميلك ذلك ويستطيع المحاولة من جديد، وترى أنت السبب في last_payment_error ضمن نية الدفع وفي الحدث payment_intent.payment_failed. تجد قائمة الأسباب في كيف تعمل المدفوعات.

قراءة خطأ في كودك

  • curl
  • Node.js
  • TypeScript
  • Python
  • PHP
  • Ruby
  • Go
  • Java
  • C#
  • Rust
errors.mjserrors.tserrors.pyerrors.phperrors.rberrors.goErrors.javaErrors.csmain.rs
curl -i https://pay-api.adpromax.com/v1/payment_intents/pi_01k7d9x2c5v8b1n4m7q0w3e6rt \
  -u "$APX_SECRET_KEY:"
const id = 'pi_01k7d9x2c5v8b1n4m7q0w3e6rt'
const response = await fetch(`https://pay-api.adpromax.com/v1/payment_intents/${id}`, {
  headers: { Authorization: `Bearer ${process.env.APX_SECRET_KEY}` },
})
if (!response.ok) {
  const { error } = await response.json()
  if (error.code === 'resource_missing')
    console.log('No such payment here:', error.message)
  else
    console.log(error.type, error.code, error.request_id)
}
interface ApiError {
  error: {
    type: string
    code: string
    message: string
    request_id: string
    doc_url: string
    param?: string
  }
}

const id = 'pi_01k7d9x2c5v8b1n4m7q0w3e6rt'
const response = await fetch(`https://pay-api.adpromax.com/v1/payment_intents/${id}`, {
  headers: { Authorization: `Bearer ${process.env.APX_SECRET_KEY}` },
})
if (!response.ok) {
  const { error } = (await response.json()) as ApiError
  if (error.code === 'resource_missing')
    console.log('No such payment here:', error.message)
  else
    console.log(error.type, error.code, error.request_id)
}
import os

import requests

payment_intent_id = "pi_01k7d9x2c5v8b1n4m7q0w3e6rt"
response = requests.get(
    f"https://pay-api.adpromax.com/v1/payment_intents/{payment_intent_id}",
    auth=(os.environ["APX_SECRET_KEY"], ""),
)
if not response.ok:
    error = response.json()["error"]
    if error["code"] == "resource_missing":
        print("No such payment here:", error["message"])
    else:
        print(error["type"], error["code"], error["request_id"])
<?php
$id = 'pi_01k7d9x2c5v8b1n4m7q0w3e6rt';
$ch = curl_init('https://pay-api.adpromax.com/v1/payment_intents/' . $id);
curl_setopt($ch, CURLOPT_USERPWD, getenv('APX_SECRET_KEY') . ':');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$body = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
    $error = $body['error'];
    if ($error['code'] === 'resource_missing') {
        echo 'No such payment here: ', $error['message'], PHP_EOL;
    } else {
        echo $error['type'], ' ', $error['code'], ' ', $error['request_id'], PHP_EOL;
    }
}
require 'json'
require 'net/http'

id = 'pi_01k7d9x2c5v8b1n4m7q0w3e6rt'
uri = URI("https://pay-api.adpromax.com/v1/payment_intents/#{id}")
request = Net::HTTP::Get.new(uri)
request.basic_auth(ENV.fetch('APX_SECRET_KEY'), '')

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
  http.request(request)
end
unless response.is_a?(Net::HTTPSuccess)
  error = JSON.parse(response.body)['error']
  if error['code'] == 'resource_missing'
    puts "No such payment here: #{error['message']}"
  else
    puts "#{error['type']} #{error['code']} #{error['request_id']}"
  end
end
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
)

type APIError struct {
	Error struct {
		Type      string `json:"type"`
		Code      string `json:"code"`
		Message   string `json:"message"`
		RequestID string `json:"request_id"`
	} `json:"error"`
}

func main() {
	id := "pi_01k7d9x2c5v8b1n4m7q0w3e6rt"
	req, err := http.NewRequest(http.MethodGet,
		"https://pay-api.adpromax.com/v1/payment_intents/"+id, nil)
	if err != nil {
		log.Fatal(err)
	}
	req.SetBasicAuth(os.Getenv("APX_SECRET_KEY"), "")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()

	if res.StatusCode >= 400 {
		var failure APIError
		if err := json.NewDecoder(res.Body).Decode(&failure); err != nil {
			log.Fatal(err)
		}
		if failure.Error.Code == "resource_missing" {
			fmt.Println("No such payment here:", failure.Error.Message)
		} else {
			fmt.Println(failure.Error.Type, failure.Error.Code,
				failure.Error.RequestID)
		}
	}
}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Errors {
    // The JDK has no JSON parser: a small regex reads one field of the error.
    static String field(String json, String name) {
        Matcher match = Pattern
            .compile("\"" + name + "\"\\s*:\\s*\"([^\"]*)\"")
            .matcher(json);
        return match.find() ? match.group(1) : "";
    }

    public static void main(String[] args) throws Exception {
        String id = "pi_01k7d9x2c5v8b1n4m7q0w3e6rt";
        HttpRequest request = HttpRequest.newBuilder(
                URI.create("https://pay-api.adpromax.com/v1/payment_intents/" + id))
            .header("Authorization", "Bearer " + System.getenv("APX_SECRET_KEY"))
            .GET()
            .build();
        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() >= 400) {
            String error = response.body();
            if (field(error, "code").equals("resource_missing")) {
                System.out.println(
                    "No such payment here: " + field(error, "message"));
            } else {
                System.out.println(field(error, "type") + " "
                    + field(error, "code") + " " + field(error, "request_id"));
            }
        }
    }
}
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;

var json = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower };
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Bearer", Environment.GetEnvironmentVariable("APX_SECRET_KEY"));

var id = "pi_01k7d9x2c5v8b1n4m7q0w3e6rt";
using var response = await http.GetAsync($"https://pay-api.adpromax.com/v1/payment_intents/{id}");
if (!response.IsSuccessStatusCode)
{
    var failure = await response.Content.ReadFromJsonAsync<ApiError>(json);
    var error = failure!.Error;
    if (error.Code == "resource_missing")
        Console.WriteLine($"No such payment here: {error.Message}");
    else
        Console.WriteLine($"{error.Type} {error.Code} {error.RequestId}");
}

record ApiError(ApiErrorBody Error);
record ApiErrorBody(string Type, string Code, string Message, string RequestId);
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
//             serde = { version = "1", features = ["derive"] }
use serde::Deserialize;

#[derive(Deserialize)]
struct ApiError {
    error: ErrorBody,
}

#[derive(Deserialize)]
struct ErrorBody {
    r#type: String,
    code: String,
    message: String,
    request_id: String,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let id = "pi_01k7d9x2c5v8b1n4m7q0w3e6rt";
    let response = reqwest::blocking::Client::new()
        .get(format!("https://pay-api.adpromax.com/v1/payment_intents/{id}"))
        .bearer_auth(std::env::var("APX_SECRET_KEY")?)
        .send()?;
    if !response.status().is_success() {
        let ApiError { error } = response.json()?;
        if error.code == "resource_missing" {
            println!("No such payment here: {}", error.message);
        } else {
            println!("{} {} {}", error.r#type, error.code, error.request_id);
        }
    }
    Ok(())
}

معرّفات الطلبات

تحمل كل استجابة، نجاحًا كانت أو فشلًا، ترويسة Request-Id (req_…). تعثر سجلات الطلبات في لوحة التحكم على الطلب به، مع ما أرسلته وما أجابت به واجهة API. احتفظ به في سجلاتك الخاصة، وقدّمه عندما تراسل الدعم: فهو أسرع طريق إلى الجواب.