Paiements
Métadonnées
Ajoutez vos propres références aux paiements, sessions, clients et remboursements, et retrouvez-les dans les webhooks et sur le tableau de bord.
Sur cette page
Les métadonnées (metadata) sont un ensemble de clés et de valeurs à vous sur un objet : un numéro de commande, un identifiant de panier, la boutique ou le canal d’où vient une vente. AdProMaxPay les enregistre et les rend, inchangées, partout où l’objet apparaît. Elles ne changent jamais le fonctionnement d’un paiement.
Ajouter des métadonnées à un paiement
Envoyez metadata, un objet de chaînes, quand vous créez l’intention de paiement. Vos références
reviennent alors dans les réponses de l’API et dans chaque événement sur ce paiement : quand votre
webhook reçoit payment_intent.succeeded, data.object.metadata.order_id nomme la commande à
livrer.
curl https://pay-api.adpromax.com/v1/payment_intents \
-u "$APX_SECRET_KEY:" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1045-create" \
-d '{
"amount": 25000,
"currency": "mad",
"payment_method_types": ["card", "cash"],
"metadata": { "order_id": "1045", "channel": "whatsapp" }
}'const response = await fetch('https://pay-api.adpromax.com/v1/payment_intents', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.APX_SECRET_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'order-1045-create',
},
body: JSON.stringify({
amount: 25000,
currency: 'mad',
payment_method_types: ['card', 'cash'],
metadata: { order_id: '1045', channel: 'whatsapp' },
}),
})
const intent = await response.json()
if (!response.ok)
throw new Error(intent.error.message)
console.log(intent.id, intent.metadata.order_id)interface PaymentIntent {
id: string
object: 'payment_intent'
amount: number
currency: string
status: string
metadata: Record<string, string>
}
interface ApiError {
error: { type: string, code: string, message: string, request_id: string }
}
const response = await fetch('https://pay-api.adpromax.com/v1/payment_intents', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.APX_SECRET_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'order-1045-create',
},
body: JSON.stringify({
amount: 25000,
currency: 'mad',
payment_method_types: ['card', 'cash'],
metadata: { order_id: '1045', channel: 'whatsapp' },
}),
})
if (!response.ok) {
const { error } = (await response.json()) as ApiError
throw new Error(`${error.code}: ${error.message}`)
}
const intent = (await response.json()) as PaymentIntent
console.log(intent.id, intent.metadata.order_id)import os
import requests
response = requests.post(
"https://pay-api.adpromax.com/v1/payment_intents",
auth=(os.environ["APX_SECRET_KEY"], ""),
headers={"Idempotency-Key": "order-1045-create"},
json={
"amount": 25000,
"currency": "mad",
"payment_method_types": ["card", "cash"],
"metadata": {"order_id": "1045", "channel": "whatsapp"},
},
)
intent = response.json()
response.raise_for_status()
print(intent["id"], intent["metadata"]["order_id"])<?php
$ch = curl_init('https://pay-api.adpromax.com/v1/payment_intents');
curl_setopt_array($ch, [
CURLOPT_USERPWD => getenv('APX_SECRET_KEY') . ':',
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Idempotency-Key: order-1045-create',
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => 25000,
'currency' => 'mad',
'payment_method_types' => ['card', 'cash'],
'metadata' => ['order_id' => '1045', 'channel' => 'whatsapp'],
]),
CURLOPT_RETURNTRANSFER => true,
]);
$intent = json_decode(curl_exec($ch), true);
echo $intent['id'], ' ', $intent['metadata']['order_id'], PHP_EOL;require 'json'
require 'net/http'
uri = URI('https://pay-api.adpromax.com/v1/payment_intents')
request = Net::HTTP::Post.new(uri)
request.basic_auth(ENV.fetch('APX_SECRET_KEY'), '')
request['Content-Type'] = 'application/json'
request['Idempotency-Key'] = 'order-1045-create'
request.body = JSON.generate(
amount: 25000,
currency: 'mad',
payment_method_types: %w[card cash],
metadata: { order_id: '1045', channel: 'whatsapp' }
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
intent = JSON.parse(response.body)
raise intent.dig('error', 'message') unless response.is_a?(Net::HTTPSuccess)
puts "#{intent['id']} #{intent['metadata']['order_id']}"package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
)
type PaymentIntent struct {
ID string `json:"id"`
Status string `json:"status"`
Metadata map[string]string `json:"metadata"`
}
type APIError struct {
Error struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
func main() {
metadata := map[string]string{"order_id": "1045", "channel": "whatsapp"}
body, err := json.Marshal(map[string]any{
"amount": 25000,
"currency": "mad",
"payment_method_types": []string{"card", "cash"},
"metadata": metadata,
})
if err != nil {
log.Fatal(err)
}
req, err := http.NewRequest(http.MethodPost,
"https://pay-api.adpromax.com/v1/payment_intents", bytes.NewReader(body))
if err != nil {
log.Fatal(err)
}
req.SetBasicAuth(os.Getenv("APX_SECRET_KEY"), "")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "order-1045-create")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
if res.StatusCode >= 400 {
var apiErr APIError
json.NewDecoder(res.Body).Decode(&apiErr)
log.Fatal(apiErr.Error.Message)
}
var intent PaymentIntent
if err := json.NewDecoder(res.Body).Decode(&intent); err != nil {
log.Fatal(err)
}
fmt.Println(intent.ID, intent.Metadata["order_id"])
}import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Metadata {
public static void main(String[] args) throws Exception {
// Your own references, as strings.
String metadata = """
{ "order_id": "1045", "channel": "whatsapp" }""";
String body = """
{
"amount": 25000,
"currency": "mad",
"payment_method_types": ["card", "cash"],
"metadata": %s
}
""".formatted(metadata);
HttpRequest request = HttpRequest.newBuilder(
URI.create("https://pay-api.adpromax.com/v1/payment_intents"))
.header("Authorization", "Bearer " + System.getenv("APX_SECRET_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", "order-1045-create")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
throw new IllegalStateException(response.body());
}
// The payment intent as JSON: its metadata comes back as you sent it.
System.out.println(response.statusCode() + " " + response.body());
}
}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"));
using var request = new HttpRequestMessage(
HttpMethod.Post, "https://pay-api.adpromax.com/v1/payment_intents");
request.Headers.Add("Idempotency-Key", "order-1045-create");
request.Content = JsonContent.Create(new
{
amount = 25000,
currency = "mad",
payment_method_types = new[] { "card", "cash" },
metadata = new { order_id = "1045", channel = "whatsapp" },
});
using var response = await http.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
var failure = await response.Content.ReadFromJsonAsync<ApiError>(json);
throw new InvalidOperationException(failure?.Error.Message);
}
var intent = await response.Content.ReadFromJsonAsync<PaymentIntent>(json);
Console.WriteLine($"{intent!.Id} {intent.Metadata["order_id"]}");
record PaymentIntent(string Id, string Status, Dictionary<string, string> Metadata);
record ApiError(ApiErrorBody Error);
record ApiErrorBody(string Code, string Message);// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
// serde = { version = "1", features = ["derive"] }
// serde_json = "1"
use std::collections::HashMap;
use serde::Deserialize;
use serde_json::json;
#[derive(Deserialize)]
struct PaymentIntent {
id: String,
metadata: HashMap<String, String>,
}
#[derive(Deserialize)]
struct ApiError {
error: ErrorBody,
}
#[derive(Deserialize)]
struct ErrorBody {
message: String,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let response = reqwest::blocking::Client::new()
.post("https://pay-api.adpromax.com/v1/payment_intents")
.bearer_auth(std::env::var("APX_SECRET_KEY")?)
.header("Idempotency-Key", "order-1045-create")
.json(&json!({
"amount": 25000,
"currency": "mad",
"payment_method_types": ["card", "cash"],
"metadata": { "order_id": "1045", "channel": "whatsapp" }
}))
.send()?;
if !response.status().is_success() {
let failure: ApiError = response.json()?;
return Err(failure.error.message.into());
}
let intent: PaymentIntent = response.json()?;
println!("{} {}", intent.id, intent.metadata["order_id"]);
Ok(())
}Les limites
- Jusqu’à 50 clés sur un objet.
- Une clé fait au plus 40 caractères ; une valeur au plus 500 caractères.
- Les valeurs sont des chaînes uniquement. Envoyez un nombre sous forme de texte :
"1045", pas1045.
Une requête qui dépasse ces limites est refusée avec 400, parameter_invalid, et param nomme la
clé en cause, comme metadata.order_id. Rien n’est créé. Lisez param pour trouver la clé : le
message est une phrase générale et ne dit pas quelle limite est dépassée. Dans cette réponse, la
clé fait plus de 40 caractères.
{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"param": "metadata.customer_reference_from_our_previous_system",
"message": "This value is not valid.",
"doc_url": "https://pay-docs.adpromax.com/errors#parameter_invalid",
"request_id": "req_01k7e5s3d7f1g5h9j3k7m1n5pz"
}
}Les objets qui prennent des métadonnées
| Objet | Les fixer | Les changer |
|---|---|---|
| Intention de paiement | POST /v1/payment_intents | POST /v1/payment_intents/{id}, tant que le paiement est requires_payment_method |
| Session de paiement | POST /v1/checkout/sessions | — |
| Client | POST /v1/customers | POST /v1/customers/{id}, à tout moment |
| Remboursement | POST /v1/refunds | — |
| Endpoint de webhook | POST /v1/webhook_endpoints | POST /v1/webhook_endpoints/{id} |
Une modification des métadonnées d’un endpoint de webhook est fusionnée avec ce qu’il contient :
les clés envoyées sont posées, les autres restent, et une clé envoyée avec une valeur vide ("")
est retirée.
Les métadonnées restent là où vous les mettez
Les métadonnées appartiennent à l’objet sur lequel vous les fixez, et ne sont jamais copiées vers un
autre. Les metadata d’une session de paiement sont sur la session et dans ses événements
checkout.session.* ; l’intention de paiement que la session crée ne les porte pas.
Avec la page de paiement hébergée, conservez l’identifiant de l’intention de paiement avec votre
commande, comme le fait le démarrage rapide carte, ou lisez la
session quand checkout.session.completed arrive.
Où vous les voyez
- Dans l’API : chaque réponse qui contient l’objet.
- Dans les webhooks : le
data.objectde l’événement porte les métadonnées de l’objet telles qu’elles étaient au moment de l’événement. - Sur le tableau de bord : la page d’un paiement montre ses métadonnées, en lecture seule, et la liste des paiements peut être filtrée par métadonnées. Les métadonnées d’un client se modifient sur la page du client.
Ce qui ne va jamais dans les métadonnées
Les métadonnées servent aux références, pas aux secrets. Toute votre équipe les voit sur le tableau de bord, et vos endpoints de webhook les reçoivent.
- Pas de numéros de carte. Les numéros de carte ne transitent jamais par l’API : une valeur qui
ressemble à un numéro de carte est refusée avec
parameter_invalid. - Pas de mots de passe, de clés ni de jetons.
- Pas de données personnelles sensibles, comme des numéros d’identité ou des informations de santé. Pour savoir qui a payé, utilisez un client et gardez le reste dans vos propres systèmes, sous votre identifiant.