Paiements

Clients

Enregistrez qui vous paie, reliez ses paiements à lui, et retrouvez-le par son e-mail.

Ouvrir en Markdown
Sur cette page
  1. Créer un client
  2. Relier un paiement à un client
  3. Retrouver un client par son e-mail
  4. Toutes les requêtes client
  5. Sur le tableau de bord
  6. Ne collectez que le nécessaire

Un client (customer) enregistre qui vous paie : un e-mail, un nom, un numéro de téléphone et vos propres références. Il est facultatif : un paiement fonctionne sans client. Avec un client, le tableau de bord regroupe ses paiements sur une même page, et vous le retrouvez par son e-mail.

Un client appartient à une organisation et à un mode. Un client créé avec une clé de test n’existe qu’en mode test.

Créer un client

Envoyez ce que vous savez ; chaque champ est facultatif :

  • email ;
  • name ;
  • phone, au format international avec l’indicatif du pays : +212612345678 ;
  • metadata : vos propres références, comme votre numéro de compte pour cette personne. Voir Métadonnées.

Une Idempotency-Key est acceptée, pas obligatoire. Avec elle, une requête envoyée deux fois crée un seul client.

La réponse est le client, avec son identifiant (cus_…). Conservez-le avec votre propre fiche de la personne.

  • curl
  • Node.js
  • TypeScript
  • Python
  • PHP
  • Ruby
  • Go
  • Java
  • C#
  • Rust
customer.mjscustomer.tscustomer.pycustomer.phpcustomer.rbcustomer.goCustomer.javaCustomer.csmain.rs
curl https://pay-api.adpromax.com/v1/customers \
  -u "$APX_SECRET_KEY:" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "name": "Salma Idrissi",
    "phone": "+212612345678",
    "metadata": { "account_id": "8841" }
  }'
const response = await fetch('https://pay-api.adpromax.com/v1/customers', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.APX_SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    email: '[email protected]',
    name: 'Salma Idrissi',
    phone: '+212612345678',
    metadata: { account_id: '8841' },
  }),
})
const customer = await response.json()
if (!response.ok)
  throw new Error(customer.error.message)
console.log(customer.id)
interface Customer {
  id: string
  object: 'customer'
  email: string | null
  name: string | null
  phone: string | null
  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/customers', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.APX_SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    email: '[email protected]',
    name: 'Salma Idrissi',
    phone: '+212612345678',
    metadata: { account_id: '8841' },
  }),
})
if (!response.ok) {
  const { error } = (await response.json()) as ApiError
  throw new Error(`${error.code}: ${error.message}`)
}
const customer = (await response.json()) as Customer
console.log(customer.id)
import os

import requests

response = requests.post(
    "https://pay-api.adpromax.com/v1/customers",
    auth=(os.environ["APX_SECRET_KEY"], ""),
    json={
        "email": "[email protected]",
        "name": "Salma Idrissi",
        "phone": "+212612345678",
        "metadata": {"account_id": "8841"},
    },
)
customer = response.json()
response.raise_for_status()
print(customer["id"])
<?php
$ch = curl_init('https://pay-api.adpromax.com/v1/customers');
curl_setopt_array($ch, [
    CURLOPT_USERPWD => getenv('APX_SECRET_KEY') . ':',
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode([
        'email' => '[email protected]',
        'name' => 'Salma Idrissi',
        'phone' => '+212612345678',
        'metadata' => ['account_id' => '8841'],
    ]),
    CURLOPT_RETURNTRANSFER => true,
]);
$customer = json_decode(curl_exec($ch), true);
echo $customer['id'], PHP_EOL;
require 'json'
require 'net/http'

uri = URI('https://pay-api.adpromax.com/v1/customers')
request = Net::HTTP::Post.new(uri)
request.basic_auth(ENV.fetch('APX_SECRET_KEY'), '')
request['Content-Type'] = 'application/json'
request.body = JSON.generate(
  email: '[email protected]',
  name: 'Salma Idrissi',
  phone: '+212612345678',
  metadata: { account_id: '8841' }
)

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
  http.request(request)
end
customer = JSON.parse(response.body)
raise customer.dig('error', 'message') unless response.is_a?(Net::HTTPSuccess)

puts customer['id']
package main

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

type Customer struct {
	ID    string `json:"id"`
	Email string `json:"email"`
	Name  string `json:"name"`
}

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

func main() {
	body, err := json.Marshal(map[string]any{
		"email":    "[email protected]",
		"name":     "Salma Idrissi",
		"phone":    "+212612345678",
		"metadata": map[string]string{"account_id": "8841"},
	})
	if err != nil {
		log.Fatal(err)
	}
	req, err := http.NewRequest(http.MethodPost,
		"https://pay-api.adpromax.com/v1/customers", bytes.NewReader(body))
	if err != nil {
		log.Fatal(err)
	}
	req.SetBasicAuth(os.Getenv("APX_SECRET_KEY"), "")
	req.Header.Set("Content-Type", "application/json")

	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 customer Customer
	if err := json.NewDecoder(res.Body).Decode(&customer); err != nil {
		log.Fatal(err)
	}
	fmt.Println(customer.ID)
}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Customer {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "email": "[email protected]",
              "name": "Salma Idrissi",
              "phone": "+212612345678",
              "metadata": { "account_id": "8841" }
            }
            """;
        HttpRequest request = HttpRequest.newBuilder(
                URI.create("https://pay-api.adpromax.com/v1/customers"))
            .header("Authorization", "Bearer " + System.getenv("APX_SECRET_KEY"))
            .header("Content-Type", "application/json")
            .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 customer as JSON: read its id with a JSON library.
        System.out.println(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 response = await http.PostAsJsonAsync("https://pay-api.adpromax.com/v1/customers", new
{
    email = "[email protected]",
    name = "Salma Idrissi",
    phone = "+212612345678",
    metadata = new { account_id = "8841" },
});
if (!response.IsSuccessStatusCode)
{
    var failure = await response.Content.ReadFromJsonAsync<ApiError>(json);
    throw new InvalidOperationException(failure?.Error.Message);
}
var customer = await response.Content.ReadFromJsonAsync<Customer>(json);
Console.WriteLine(customer!.Id);

record Customer(string Id, string? Email, string? Name, string? Phone);
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 serde::Deserialize;
use serde_json::json;

#[derive(Deserialize)]
struct Customer {
    id: 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/customers")
        .bearer_auth(std::env::var("APX_SECRET_KEY")?)
        .json(&json!({
            "email": "[email protected]",
            "name": "Salma Idrissi",
            "phone": "+212612345678",
            "metadata": { "account_id": "8841" }
        }))
        .send()?;
    if !response.status().is_success() {
        let failure: ApiError = response.json()?;
        return Err(failure.error.message.into());
    }
    let customer: Customer = response.json()?;
    println!("{}", customer.id);
    Ok(())
}
Réponse200 OK
{
  "id": "cus_01k7e3m9q2v5b8n1x4c7z0r6tw",
  "object": "customer",
  "email": "[email protected]",
  "name": "Salma Idrissi",
  "phone": "+212612345678",
  "metadata": { "account_id": "8841" },
  "created": 1790855000,
  "livemode": false
}

Relier un paiement à un client

Passez l’identifiant du client dans customer quand vous créez une intention de paiement ou une session de paiement. Le paiement apparaît alors sur la page du client dans le tableau de bord, et l’intention de paiement nomme son client.

Le client doit appartenir à votre organisation et au même mode que le paiement. Tout autre identifiant est refusé avec 400, resource_missing, et param vaut customer : un client créé avec une clé de test ne peut pas payer en production.

Pour lister les paiements d’un client, filtrez les intentions de paiement : GET /v1/payment_intents?customer=cus_….

Retrouver un client par son e-mail

GET /v1/customers liste vos clients, du plus récent au plus ancien ; avec email, seulement ceux qui ont cette adresse. Avant de créer un client, cherchez-le : vos fiches gardent ainsi un seul client par personne.

Une liste répond un objet liste : data contient les clients, has_more dit si d’autres suivent. Demandez-en jusqu’à 100 à la fois avec limit (10 par défaut), puis les suivants avec starting_after égal au dernier identifiant reçu ; ending_before va dans l’autre sens.

  • curl
  • Node.js
  • TypeScript
  • Python
  • PHP
  • Ruby
  • Go
  • Java
  • C#
  • Rust
find-customer.mjsfind-customer.tsfind_customer.pyfind_customer.phpfind_customer.rbfind_customer.goFindCustomer.javaFindCustomer.csmain.rs
EMAIL=[email protected]
CUS=$(curl -s -G https://pay-api.adpromax.com/v1/customers \
  -u "$APX_SECRET_KEY:" \
  --data-urlencode "email=$EMAIL" \
  -d limit=1 | jq -r '.data[0].id // empty')

if [ -z "$CUS" ]; then
  CUS=$(curl -s https://pay-api.adpromax.com/v1/customers \
    -u "$APX_SECRET_KEY:" \
    -H "Content-Type: application/json" \
    -d "{\"email\": \"$EMAIL\", \"name\": \"Salma Idrissi\"}" | jq -r .id)
fi
echo "$CUS"
const api = 'https://pay-api.adpromax.com/v1'
const headers = {
  'Authorization': `Bearer ${process.env.APX_SECRET_KEY}`,
  'Content-Type': 'application/json',
}
const email = '[email protected]'

const query = new URLSearchParams({ email, limit: '1' })
const found = await fetch(`${api}/customers?${query}`, { headers })
  .then(response => response.json())
let customer = found.data[0]
if (customer === undefined) {
  const response = await fetch(`${api}/customers`, {
    method: 'POST',
    headers,
    body: JSON.stringify({ email, name: 'Salma Idrissi' }),
  })
  customer = await response.json()
}
console.log(customer.id)
interface Customer {
  id: string
  object: 'customer'
  email: string | null
  name: string | null
}

interface CustomerList {
  object: 'list'
  data: Customer[]
  has_more: boolean
}

const api = 'https://pay-api.adpromax.com/v1'
const headers = {
  'Authorization': `Bearer ${process.env.APX_SECRET_KEY}`,
  'Content-Type': 'application/json',
}
const email = '[email protected]'

const query = new URLSearchParams({ email, limit: '1' })
const found = await fetch(`${api}/customers?${query}`, { headers })
  .then(response => response.json() as Promise<CustomerList>)
let customer: Customer | undefined = found.data[0]
if (customer === undefined) {
  const response = await fetch(`${api}/customers`, {
    method: 'POST',
    headers,
    body: JSON.stringify({ email, name: 'Salma Idrissi' }),
  })
  customer = (await response.json()) as Customer
}
console.log(customer.id)
import os

import requests

API = "https://pay-api.adpromax.com/v1"
AUTH = (os.environ["APX_SECRET_KEY"], "")
email = "[email protected]"

found = requests.get(
    f"{API}/customers", auth=AUTH, params={"email": email, "limit": 1}
).json()
customer = found["data"][0] if found["data"] else None
if customer is None:
    customer = requests.post(
        f"{API}/customers", auth=AUTH,
        json={"email": email, "name": "Salma Idrissi"},
    ).json()
print(customer["id"])
<?php
function api(string $method, string $path, ?array $body = null): array
{
    $ch = curl_init('https://pay-api.adpromax.com/v1' . $path);
    curl_setopt_array($ch, [
        CURLOPT_USERPWD => getenv('APX_SECRET_KEY') . ':',
        CURLOPT_CUSTOMREQUEST => $method,
        CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
        CURLOPT_RETURNTRANSFER => true,
    ]);
    if ($body !== null) {
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
    }
    $answer = json_decode(curl_exec($ch), true);
    return $answer;
}

$email = '[email protected]';
$query = http_build_query(['email' => $email, 'limit' => 1]);
$customer = api('GET', "/customers?{$query}")['data'][0] ?? null;
if ($customer === null) {
    $customer = api('POST', '/customers', [
        'email' => $email,
        'name' => 'Salma Idrissi',
    ]);
}
echo $customer['id'], PHP_EOL;
require 'json'
require 'net/http'

API = 'https://pay-api.adpromax.com/v1'
email = '[email protected]'

def call(request)
  uri = request.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
  JSON.parse(response.body)
end

uri = URI("#{API}/customers")
uri.query = URI.encode_www_form(email: email, limit: 1)
customer = call(Net::HTTP::Get.new(uri))['data'].first
if customer.nil?
  request = Net::HTTP::Post.new(URI("#{API}/customers"))
  request['Content-Type'] = 'application/json'
  request.body = JSON.generate(email: email, name: 'Salma Idrissi')
  customer = call(request)
end
puts customer['id']
package main

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

const api = "https://pay-api.adpromax.com/v1"

type Customer struct {
	ID    string `json:"id"`
	Email string `json:"email"`
}

type CustomerList struct {
	Data []Customer `json:"data"`
}

func call(method, path string, body io.Reader, into any) error {
	req, err := http.NewRequest(method, api+path, body)
	if err != nil {
		return err
	}
	req.SetBasicAuth(os.Getenv("APX_SECRET_KEY"), "")
	req.Header.Set("Content-Type", "application/json")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()
	return json.NewDecoder(res.Body).Decode(into)
}

func main() {
	email := "[email protected]"
	query := url.Values{"email": {email}, "limit": {"1"}}

	var found CustomerList
	path := "/customers?" + query.Encode()
	if err := call(http.MethodGet, path, nil, &found); err != nil {
		log.Fatal(err)
	}
	var customer Customer
	if len(found.Data) > 0 {
		customer = found.Data[0]
	} else {
		body, _ := json.Marshal(map[string]string{
			"email": email,
			"name":  "Salma Idrissi",
		})
		err := call(http.MethodPost, "/customers", bytes.NewReader(body), &customer)
		if err != nil {
			log.Fatal(err)
		}
	}
	fmt.Println(customer.ID)
}
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.regex.Pattern;

public class FindCustomer {
    static final String API = "https://pay-api.adpromax.com/v1";
    static final HttpClient HTTP = HttpClient.newHttpClient();
    // The JDK has no JSON parser: a regex reads a customer's id here.
    static final Pattern ID = Pattern.compile("\"id\"\\s*:\\s*\"(cus_[^\"]+)\"");

    static String send(HttpRequest.Builder request) throws Exception {
        request.header("Authorization", "Bearer " + System.getenv("APX_SECRET_KEY"));
        request.header("Content-Type", "application/json");
        return HTTP.send(request.build(), HttpResponse.BodyHandlers.ofString()).body();
    }

    public static void main(String[] args) throws Exception {
        String email = "[email protected]";
        String query = "email=" + URLEncoder.encode(email, StandardCharsets.UTF_8);
        String found = send(HttpRequest.newBuilder(
            URI.create(API + "/customers?" + query + "&limit=1")).GET());
        var id = ID.matcher(found);
        if (!id.find()) {
            String created = send(HttpRequest.newBuilder(URI.create(API + "/customers"))
                .POST(HttpRequest.BodyPublishers.ofString("""
                    {"email": "%s", "name": "Salma Idrissi"}
                    """.formatted(email))));
            id = ID.matcher(created);
            if (!id.find()) {
                throw new IllegalStateException(created);
            }
        }
        System.out.println(id.group(1));
    }
}
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 { BaseAddress = new Uri("https://pay-api.adpromax.com/v1/") };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Bearer", Environment.GetEnvironmentVariable("APX_SECRET_KEY"));

var email = "[email protected]";
var found = await http.GetFromJsonAsync<CustomerList>(
    $"customers?email={Uri.EscapeDataString(email)}&limit=1", json);
var customer = found!.Data.FirstOrDefault();
if (customer is null)
{
    using var response = await http.PostAsJsonAsync(
        "customers", new { email, name = "Salma Idrissi" });
    customer = await response.Content.ReadFromJsonAsync<Customer>(json);
}
Console.WriteLine(customer!.Id);

record CustomerList(List<Customer> Data, bool HasMore);
record Customer(string Id, string? Email, string? Name);
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
//             serde = { version = "1", features = ["derive"] }
//             serde_json = "1"
use reqwest::blocking::Client;
use serde::Deserialize;
use serde_json::json;

const API: &str = "https://pay-api.adpromax.com/v1";

#[derive(Deserialize)]
struct Customer {
    id: String,
}

#[derive(Deserialize)]
struct CustomerList {
    data: Vec<Customer>,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    let key = std::env::var("APX_SECRET_KEY")?;
    let email = "[email protected]";

    let found: CustomerList = client
        .get(format!("{API}/customers"))
        .bearer_auth(&key)
        .query(&[("email", email), ("limit", "1")])
        .send()?
        .json()?;
    let first = found.data.into_iter().next();
    let customer = match first {
        Some(customer) => customer,
        None => client
            .post(format!("{API}/customers"))
            .bearer_auth(&key)
            .json(&json!({ "email": email, "name": "Salma Idrissi" }))
            .send()?
            .json()?,
    };
    println!("{}", customer.id);
    Ok(())
}
Réponse200 OK
{
  "object": "list",
  "data": [
    {
      "id": "cus_01k7e3m9q2v5b8n1x4c7z0r6tw",
      "object": "customer",
      "email": "[email protected]",
      "name": "Salma Idrissi",
      "phone": "+212612345678",
      "metadata": { "account_id": "8841" },
      "created": 1790855000,
      "livemode": false
    }
  ],
  "has_more": false,
  "url": "/v1/customers"
}

Toutes les requêtes client

RequêteCe qu’elle fait
POST /v1/customersCrée un client.
GET /v1/customers/{id}Lit un client.
POST /v1/customers/{id}Met à jour un client : envoyez les champs qui changent.
DELETE /v1/customers/{id}Supprime un client. La réponse est { "id": "cus_…", "object": "customer", "deleted": true }.
GET /v1/customersListe vos clients, du plus récent au plus ancien ; email garde ceux qui ont cette adresse.
GET /v1/payment_intents?customer=cus_…Liste les paiements d’un client.

Sur le tableau de bord

  • Clients les liste avec leur e-mail, leur téléphone, leur nombre de paiements, le total payé et le dernier paiement, avec une zone de recherche.
  • Nouveau client en crée un à la main : nom, e-mail, téléphone et métadonnées.
  • La page d’un client montre ses paiements et ses métadonnées. Modifiez ses coordonnées sur place, sur la page.

Ne collectez que le nécessaire

Un client contient des données personnelles. Ne demandez à vos clients que ce dont votre activité a besoin, et gardez les secrets hors de metadata : toute votre équipe et vos endpoints de webhook peuvent les lire.