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.

Ouvrir en Markdown
Sur cette page
  1. L’objet d’erreur
  2. Types d’erreur
  3. Les codes que vous rencontrerez d’abord
  4. Requêtes
  5. Clés et permissions
  6. Paiements, idempotence et limites
  7. Lire une erreur dans votre code
  8. Identifiants de requête

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

Réponse404 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"
  }
}
ChampCe qu’il contient
typeL’un des sept types ci-dessous : la grande famille de l’échec.
codeLa raison précise, stable dans le temps : c’est sur lui qu’on branche.
decline_codePour une carte refusée : la raison de la banque, comme insufficient_funds.
paramLe paramètre en cause, sous forme de chemin (metadata.order_id), quand il y en a un.
messageUne 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_urlLa page qui explique le code.
request_idL’identifiant de la requête (req_…), aussi dans l’en-tête Request-Id de chaque réponse.

Types d’erreur

typeStatutCe qui s’est passéQue faire
invalid_request_error400, 404, 409La 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_error401Pas de clé, ou une clé qui ne marche pas.Vérifiez la clé et son mode.
permission_error403La 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_error402Une carte a été refusée.Montrez un message à votre client et laissez-le essayer une autre carte.
idempotency_error400, 409Une Idempotency-Key a servi pour une autre requête, ou sa première requête s’exécute encore.Voir Idempotence.
rate_limit_error429Trop de requêtes en peu de temps.Attendez le délai Retry-After, puis réessayez. Voir Limites de débit.
api_error500, 503Quelque 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

codeCe qui s’est passé
parameter_missingUn paramètre obligatoire est absent ; param le nomme.
parameter_unknownUn paramètre que la requête n’accepte pas ; param le nomme. Vérifiez son orthographe.
parameter_invalidUn 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_missingAucun 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_urlAucune 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_invalidL’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_smallLe montant est sous le minimum du moyen de paiement : 100,00 MAD pour les espèces, 1,00 MAD pour une carte.
amount_too_largeUn remboursement dépasse ce qui reste remboursable sur le paiement.
payment_method_unavailableLe moyen de paiement ne peut pas servir pour votre compte dans ce mode.
payment_intent_unexpected_stateLe statut de l’intention de paiement ne permet pas cette action, comme confirmer un paiement déjà réussi.
payment_intent_busyUne 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_stateLe statut de la tentative ne permet pas cet assistant de test, comme terminer une tentative déjà réussie.
refund_unsupported_payment_methodUn paiement en espèces ne peut pas être remboursé.
url_not_allowedUne 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_reachedL’organisation a déjà 16 endpoints de webhook dans ce mode.
webhook_endpoints_busyDeux modifications de vos endpoints se sont croisées (409). Renvoyez la requête.
idempotency_key_missingUne requête qui fait circuler de l’argent a été envoyée sans Idempotency-Key.
invalid_requestUne requête refusée sans code plus précis ; le message dit pourquoi.

Clés et permissions

codeCe qui s’est passé
api_key_missingLa requête ne portait aucune clé.
api_key_invalidLa clé n’existe pas. Vérifiez qu’il ne manque pas un caractère.
api_key_revokedLa clé a été révoquée. Utilisez-en une autre.
api_key_expiredLa clé a été renouvelée et son chevauchement est terminé. Utilisez la nouvelle clé.
secret_key_requiredUne clé publiable a servi là où il faut une clé secrète.
live_mode_not_activatedUne clé de production a servi avant l’activation du compte.
account_inactiveLe compte est suspendu ou fermé.
test_mode_onlyUn assistant de test a été appelé avec une clé de production.
authentication_requiredUn 401 sans code plus précis ; le message dit pourquoi.
permission_deniedUn 403 sans code plus précis ; le message dit pourquoi.

Paiements, idempotence et limites

codeCe qui s’est passé
card_declinedLa carte a été refusée ; decline_code peut dire pourquoi.
idempotency_key_reusedCette Idempotency-Key a servi pour une autre requête.
idempotency_in_progressLa première requête avec cette Idempotency-Key s’exécute encore.
idempotency_errorUn refus lié à l’Idempotency-Key sans code plus précis ; le message dit pourquoi.
rate_limit_exceededTrop de requêtes ; le message nomme la limite atteinte et l’attente.
test_daily_cap_reachedLe plafond quotidien du mode test est atteint, jusqu’à minuit, heure du Maroc.
api_errorUne 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
  • 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(())
}

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.