Erreurs
Gérer les erreurs
L’objet d’erreur, les sept types d’erreur, les codes que vous rencontrerez d’abord, et comment traiter chacun.
Sur cette page
Quand une requête échoue, l’API répond un statut HTTP de 400 ou plus et un objet d’erreur.
Votre code décide quoi faire d’après le type et le code de l’erreur ; le message est pour les
personnes.
L’objet d’erreur
{
"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"
}
}| Champ | Ce qu’il contient |
|---|---|
type | L’un des sept types ci-dessous : la grande famille de l’échec. |
code | La raison précise, stable dans le temps : c’est sur lui qu’on branche. |
decline_code | Pour une carte refusée : la raison de la banque, comme insufficient_funds. |
param | Le paramètre en cause, sous forme de chemin (metadata.order_id), quand il y en a un. |
message | Une explication pour une personne, dans la langue de votre requête (anglais, français ou arabe, d’après l’en-tête Accept-Language). Il peut changer : ne branchez jamais dessus. |
doc_url | La page qui explique le code. |
request_id | L’identifiant de la requête (req_…), aussi dans l’en-tête Request-Id de chaque réponse. |
Types d’erreur
type | Statut | Ce qui s’est passé | Que faire |
|---|---|---|---|
invalid_request_error | 400, 404, 409 | La requête est fausse : un paramètre manquant, inconnu ou invalide, un objet ou une adresse qui n’existe pas, une action que l’état de l’objet ne permet pas. | Corrigez la requête. La renvoyer telle quelle échoue de la même façon. |
authentication_error | 401 | Pas de clé, ou une clé qui ne marche pas. | Vérifiez la clé et son mode. |
permission_error | 403 | La clé marche mais ne peut pas faire ceci. | Voyez le code : une clé de production avant l’activation, un assistant de test avec une clé de production… |
card_error | 402 | Une carte a été refusée. | Montrez un message à votre client et laissez-le essayer une autre carte. |
idempotency_error | 400, 409 | Une Idempotency-Key a servi pour une autre requête, ou sa première requête s’exécute encore. | Voir Idempotence. |
rate_limit_error | 429 | Trop de requêtes en peu de temps. | Attendez le délai Retry-After, puis réessayez. Voir Limites de débit. |
api_error | 500, 503 | Quelque chose a échoué de notre côté, ou est indisponible un instant. | Réessayez avec la même Idempotency-Key ; si cela persiste, écrivez au support avec le request_id. |
Les codes que vous rencontrerez d’abord
Requêtes
code | Ce qui s’est passé |
|---|---|
parameter_missing | Un paramètre obligatoire est absent ; param le nomme. |
parameter_unknown | Un paramètre que la requête n’accepte pas ; param le nomme. Vérifiez son orthographe. |
parameter_invalid | Un paramètre a un mauvais type ou une mauvaise valeur ; param le nomme. Un champ texte qui ressemble à un numéro de carte est refusé ainsi aussi : les numéros de carte ne passent jamais par l’API. Une liste demandée avec starting_after et ending_before à la fois l’est aussi. |
resource_missing | Aucun objet avec cet identifiant, dans ce mode. S’il existe dans l’autre mode, le message le dit : vous avez sans doute utilisé la mauvaise clé. |
unrecognized_request_url | Aucune route de l’API n’a cette méthode et cette adresse ; le message nomme les deux. Vérifiez le chemin et la méthode HTTP. |
api_version_invalid | L’en-tête X-AdProMaxPay-Version, ou l’api_version d’un endpoint de webhook, nomme une version qui n’existe pas. Le message liste celles qui existent. |
amount_too_small | Le montant est sous le minimum du moyen de paiement : 100,00 MAD pour les espèces, 1,00 MAD pour une carte. |
amount_too_large | Un remboursement dépasse ce qui reste remboursable sur le paiement. |
payment_method_unavailable | Le moyen de paiement ne peut pas servir pour votre compte dans ce mode. |
payment_intent_unexpected_state | Le statut de l’intention de paiement ne permet pas cette action, comme confirmer un paiement déjà réussi. |
payment_intent_busy | Une autre requête confirme ou modifie ce paiement en ce moment. Rien n’a été modifié : renvoyez la requête dans un instant. |
charge_unexpected_state | Le statut de la tentative ne permet pas cet assistant de test, comme terminer une tentative déjà réussie. |
refund_unsupported_payment_method | Un paiement en espèces ne peut pas être remboursé. |
url_not_allowed | Une adresse de webhook qui n’est pas en https, porte un nom d’utilisateur ou un mot de passe, n’est pas publique, ou dont l’hôte ne se résout pas. |
webhook_endpoint_limit_reached | L’organisation a déjà 16 endpoints de webhook dans ce mode. |
webhook_endpoints_busy | Deux modifications de vos endpoints se sont croisées (409). Renvoyez la requête. |
idempotency_key_missing | Une requête qui fait circuler de l’argent a été envoyée sans Idempotency-Key. |
invalid_request | Une requête refusée sans code plus précis ; le message dit pourquoi. |
Clés et permissions
code | Ce qui s’est passé |
|---|---|
api_key_missing | La requête ne portait aucune clé. |
api_key_invalid | La clé n’existe pas. Vérifiez qu’il ne manque pas un caractère. |
api_key_revoked | La clé a été révoquée. Utilisez-en une autre. |
api_key_expired | La clé a été renouvelée et son chevauchement est terminé. Utilisez la nouvelle clé. |
secret_key_required | Une clé publiable a servi là où il faut une clé secrète. |
live_mode_not_activated | Une clé de production a servi avant l’activation du compte. |
account_inactive | Le compte est suspendu ou fermé. |
test_mode_only | Un assistant de test a été appelé avec une clé de production. |
authentication_required | Un 401 sans code plus précis ; le message dit pourquoi. |
permission_denied | Un 403 sans code plus précis ; le message dit pourquoi. |
Paiements, idempotence et limites
code | Ce qui s’est passé |
|---|---|
card_declined | La carte a été refusée ; decline_code peut dire pourquoi. |
idempotency_key_reused | Cette Idempotency-Key a servi pour une autre requête. |
idempotency_in_progress | La première requête avec cette Idempotency-Key s’exécute encore. |
idempotency_error | Un refus lié à l’Idempotency-Key sans code plus précis ; le message dit pourquoi. |
rate_limit_exceeded | Trop de requêtes ; le message nomme la limite atteinte et l’attente. |
test_daily_cap_reached | Le plafond quotidien du mode test est atteint, jusqu’à minuit, heure du Maroc. |
api_error | Une erreur de notre côté. |
Un paiement que votre client fait sur la page hébergée ne fait pas échouer votre appel d’API quand
la carte est refusée : votre client le voit et peut réessayer, et vous voyez la raison dans le
last_payment_error de l’intention de paiement et dans l’événement payment_intent.payment_failed.
Les raisons sont listées dans Le fonctionnement des paiements.
Lire une erreur dans votre code
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(())
}Identifiants de requête
Chaque réponse, succès ou échec, porte un en-tête Request-Id (req_…). Les journaux de requêtes
du tableau de bord retrouvent une requête par lui, avec ce que vous avez envoyé et ce que l’API a
répondu. Gardez-le dans vos propres journaux, et donnez-le quand vous écrivez au support : c’est le
chemin le plus court vers la réponse.