الأخطاء
معالجة الأخطاء
كائن الخطأ، وأنواع الأخطاء السبعة، والرموز التي تصادفها أولًا، وكيف تعالج كلًّا منها.
في هذه الصفحة
عندما يفشل طلب، تجيب واجهة 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_error | 400، 404، 409 | الطلب خاطئ: معامل ناقص أو غير معروف أو غير صالح، أو كائن أو عنوان غير موجود، أو إجراء لا تسمح به حالة الكائن. | صحّح الطلب. إرساله من جديد دون تغيير يفشل بالطريقة نفسها. |
authentication_error | 401 | لا مفتاح، أو مفتاح لا يعمل. | تحقّق من المفتاح ومن وضعه. |
permission_error | 403 | المفتاح يعمل لكن لا يحق له هذا. | انظر الرمز: مفتاح للوضع المباشر قبل التفعيل، أو أداة اختبار مساعدة بمفتاح للوضع المباشر… |
card_error | 402 | رُفضت بطاقة. | اعرض لعميلك رسالة ودعه يجرّب بطاقة أخرى. |
idempotency_error | 400، 409 | أُعيد استخدام Idempotency-Key لطلب آخر، أو ما زال طلبها الأول قيد التنفيذ. | انظر عدم التكرار (idempotency). |
rate_limit_error | 429 | طلبات كثيرة جدًا في وقت قصير. | انتظر مهلة Retry-After، ثم أعد المحاولة. انظر حدود المعدل. |
api_error | 500، 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. تجد قائمة الأسباب في
كيف تعمل المدفوعات.
قراءة خطأ في كودك
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
endpackage 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. احتفظ به في سجلاتك الخاصة،
وقدّمه عندما تراسل الدعم: فهو أسرع طريق إلى الجواب.