# Page de paiement hébergée

Une page de paiement que nous hébergeons pour la carte et les espèces, à vos couleurs. Créer une session de paiement, ce que voit votre client, et comment une session se termine.

La page de paiement hébergée est une page que AdProMaxPay héberge pour vous. Votre serveur crée une
**session de paiement** (*checkout session*), votre client paie sur la page par carte ou en espèces,
et votre serveur apprend le résultat par un webhook. Vous ne manipulez jamais un numéro de carte, et
la même session sert pour les deux moyens de paiement.

La page :

* montre votre logo, le nom de votre entreprise, le montant et votre contact d’assistance ;
* parle français, anglais et arabe : elle suit la langue du navigateur de votre client, prend le
  français quand elle ne peut pas savoir, et propose un sélecteur de langue ;
* est pensée d’abord pour le téléphone, avec le bouton *Payer* toujours à portée ;
* porte en mode test un bandeau rayé **TEST**, pour que personne ne prenne une page de test pour
  une vraie.

Pour l’essayer de bout en bout en dix minutes, suivez le
[démarrage rapide carte](https://pay-docs.adpromax.com/fr/get-started/quickstart-card).

## La vie d’une session

Les statuts d’une session de paiement:

- `open` → `complete`: payée
- `open` → `expired`: terminée sans paiement, ou expirée par vous

Une session est `open` dès sa création. Elle devient `complete` quand son intention de paiement
réussit, et `expired` quand elle atteint son `expires_at` sans avoir été payée, ou quand vous la
faites expirer. Chaque session a une intention de paiement, nommée par son `payment_intent`.

## Créer une session

Créez une session pour chaque commande que votre client s’apprête à payer, avec
`POST /v1/checkout/sessions`. L’en-tête `Idempotency-Key` est obligatoire : voir
[Idempotence](https://pay-docs.adpromax.com/fr/api-reference/idempotency).

| Paramètre              | Ce qu’il contient                                                                                                                          |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `amount`               | Le montant, un entier en centimes : `45000` vaut 450,00 MAD.                                                                               |
| `currency`             | Toujours `mad`.                                                                                                                            |
| `payment_method_types` | Les moyens que votre client peut choisir : `card`, `cash`, ou les deux. Les espèces sont proposées à partir de 100,00 MAD.                 |
| `success_url`          | Là où la page envoie votre client après le paiement. En `https` ; `http://localhost` n’est accepté qu’en mode test.                        |
| `cancel_url`           | Là où *Retour* envoie votre client avant le paiement. Même règle.                                                                          |
| `expires_at`           | Facultatif. La fin de la session, en secondes Unix, entre 30 minutes et 24 heures à partir de maintenant. 24 heures par défaut.            |
| `customer`             | Facultatif. Un [client](https://pay-docs.adpromax.com/fr/payments/customers) (`cus_…`) de la même organisation et du même mode : le paiement apparaît dans son historique. |
| `metadata`             | Facultatif. Vos propres références, comme le numéro de commande : voir [Métadonnées](https://pay-docs.adpromax.com/fr/payments/metadata).                                  |

La réponse est la session : son `id`, son `url`, son `payment_intent` et son `status`. Conservez
l’identifiant de l’intention de paiement avec votre commande : les événements que vous recevez le
nomment.

```bash
curl https://pay-api.adpromax.com/v1/checkout/sessions \
  -u "$APX_SECRET_KEY:" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: checkout-order-2087" \
  -d '{
    "amount": 45000,
    "currency": "mad",
    "payment_method_types": ["card", "cash"],
    "success_url": "https://shop.example.com/orders/2087/thanks",
    "cancel_url": "https://shop.example.com/cart",
    "expires_at": '"$(($(date +%s) + 7200))"',
    "customer": "cus_01k7e2f9g3h6j8k2m5n7p4q1sd",
    "metadata": { "order_id": "2087" }
  }'
```

```js title="session.mjs"
const response = await fetch('https://pay-api.adpromax.com/v1/checkout/sessions', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.APX_SECRET_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': 'checkout-order-2087',
  },
  body: JSON.stringify({
    amount: 45000,
    currency: 'mad',
    payment_method_types: ['card', 'cash'],
    success_url: 'https://shop.example.com/orders/2087/thanks',
    cancel_url: 'https://shop.example.com/cart',
    // Two hours from now, in Unix seconds.
    expires_at: Math.floor(Date.now() / 1000) + 2 * 60 * 60,
    customer: 'cus_01k7e2f9g3h6j8k2m5n7p4q1sd',
    metadata: { order_id: '2087' },
  }),
})
const session = await response.json()
if (!response.ok)
  throw new Error(session.error.message)
console.log(session.payment_intent, session.url)
```

```ts title="session.ts"
interface CheckoutSession {
  id: string
  object: 'checkout.session'
  amount: number
  currency: string
  payment_intent: string
  status: string
  url: string
  expires_at: number
  customer: 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/checkout/sessions', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.APX_SECRET_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': 'checkout-order-2087',
  },
  body: JSON.stringify({
    amount: 45000,
    currency: 'mad',
    payment_method_types: ['card', 'cash'],
    success_url: 'https://shop.example.com/orders/2087/thanks',
    cancel_url: 'https://shop.example.com/cart',
    // Two hours from now, in Unix seconds.
    expires_at: Math.floor(Date.now() / 1000) + 2 * 60 * 60,
    customer: 'cus_01k7e2f9g3h6j8k2m5n7p4q1sd',
    metadata: { order_id: '2087' },
  }),
})
if (!response.ok) {
  const { error } = (await response.json()) as ApiError
  throw new Error(`${error.code}: ${error.message}`)
}
const session = (await response.json()) as CheckoutSession
console.log(session.payment_intent, session.url)
```

```python title="session.py"
import os
import time

import requests

response = requests.post(
    "https://pay-api.adpromax.com/v1/checkout/sessions",
    auth=(os.environ["APX_SECRET_KEY"], ""),
    headers={"Idempotency-Key": "checkout-order-2087"},
    json={
        "amount": 45000,
        "currency": "mad",
        "payment_method_types": ["card", "cash"],
        "success_url": "https://shop.example.com/orders/2087/thanks",
        "cancel_url": "https://shop.example.com/cart",
        # Two hours from now, in Unix seconds.
        "expires_at": int(time.time()) + 2 * 60 * 60,
        "customer": "cus_01k7e2f9g3h6j8k2m5n7p4q1sd",
        "metadata": {"order_id": "2087"},
    },
)
session = response.json()
response.raise_for_status()
print(session["payment_intent"], session["url"])
```

```php title="session.php"
<?php
$ch = curl_init('https://pay-api.adpromax.com/v1/checkout/sessions');
curl_setopt_array($ch, [
    CURLOPT_USERPWD => getenv('APX_SECRET_KEY') . ':',
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'Idempotency-Key: checkout-order-2087',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'amount' => 45000,
        'currency' => 'mad',
        'payment_method_types' => ['card', 'cash'],
        'success_url' => 'https://shop.example.com/orders/2087/thanks',
        'cancel_url' => 'https://shop.example.com/cart',
        // Two hours from now, in Unix seconds.
        'expires_at' => time() + 2 * 60 * 60,
        'customer' => 'cus_01k7e2f9g3h6j8k2m5n7p4q1sd',
        'metadata' => ['order_id' => '2087'],
    ]),
    CURLOPT_RETURNTRANSFER => true,
]);
$session = json_decode(curl_exec($ch), true);
echo $session['payment_intent'], ' ', $session['url'], PHP_EOL;
```

```ruby title="session.rb"
require 'json'
require 'net/http'

uri = URI('https://pay-api.adpromax.com/v1/checkout/sessions')
request = Net::HTTP::Post.new(uri)
request.basic_auth(ENV.fetch('APX_SECRET_KEY'), '')
request['Content-Type'] = 'application/json'
request['Idempotency-Key'] = 'checkout-order-2087'
request.body = JSON.generate(
  amount: 45000,
  currency: 'mad',
  payment_method_types: %w[card cash],
  success_url: 'https://shop.example.com/orders/2087/thanks',
  cancel_url: 'https://shop.example.com/cart',
  # Two hours from now, in Unix seconds.
  expires_at: Time.now.to_i + 2 * 60 * 60,
  customer: 'cus_01k7e2f9g3h6j8k2m5n7p4q1sd',
  metadata: { order_id: '2087' }
)

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

puts "#{session['payment_intent']} #{session['url']}"
```

```go title="session.go"
package main

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

type CheckoutSession struct {
	ID            string `json:"id"`
	PaymentIntent string `json:"payment_intent"`
	Status        string `json:"status"`
	URL           string `json:"url"`
	ExpiresAt     int64  `json:"expires_at"`
}

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

func main() {
	// Two hours from now, in Unix seconds.
	expiresAt := time.Now().Add(2 * time.Hour).Unix()
	body, err := json.Marshal(map[string]any{
		"amount":               45000,
		"currency":             "mad",
		"payment_method_types": []string{"card", "cash"},
		"success_url":          "https://shop.example.com/orders/2087/thanks",
		"cancel_url":           "https://shop.example.com/cart",
		"expires_at":           expiresAt,
		"customer":             "cus_01k7e2f9g3h6j8k2m5n7p4q1sd",
		"metadata":             map[string]string{"order_id": "2087"},
	})
	if err != nil {
		log.Fatal(err)
	}
	req, err := http.NewRequest(http.MethodPost,
		"https://pay-api.adpromax.com/v1/checkout/sessions", 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", "checkout-order-2087")

	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 session CheckoutSession
	if err := json.NewDecoder(res.Body).Decode(&session); err != nil {
		log.Fatal(err)
	}
	fmt.Println(session.PaymentIntent, session.URL)
}
```

```java title="Session.java"
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Instant;

public class Session {
    public static void main(String[] args) throws Exception {
        // Two hours from now, in Unix seconds.
        long expiresAt = Instant.now().getEpochSecond() + 2 * 60 * 60;
        String body = """
            {
              "amount": 45000,
              "currency": "mad",
              "payment_method_types": ["card", "cash"],
              "success_url": "https://shop.example.com/orders/2087/thanks",
              "cancel_url": "https://shop.example.com/cart",
              "expires_at": %d,
              "customer": "cus_01k7e2f9g3h6j8k2m5n7p4q1sd",
              "metadata": { "order_id": "2087" }
            }
            """.formatted(expiresAt);
        HttpRequest request = HttpRequest.newBuilder(
                URI.create("https://pay-api.adpromax.com/v1/checkout/sessions"))
            .header("Authorization", "Bearer " + System.getenv("APX_SECRET_KEY"))
            .header("Content-Type", "application/json")
            .header("Idempotency-Key", "checkout-order-2087")
            .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 session as JSON: read payment_intent and url with a JSON library.
        System.out.println(response.statusCode() + " " + response.body());
    }
}
```

```csharp title="Session.cs"
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/checkout/sessions");
request.Headers.Add("Idempotency-Key", "checkout-order-2087");
request.Content = JsonContent.Create(new
{
    amount = 45000,
    currency = "mad",
    payment_method_types = new[] { "card", "cash" },
    success_url = "https://shop.example.com/orders/2087/thanks",
    cancel_url = "https://shop.example.com/cart",
    // Two hours from now, in Unix seconds.
    expires_at = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 2 * 60 * 60,
    customer = "cus_01k7e2f9g3h6j8k2m5n7p4q1sd",
    metadata = new { order_id = "2087" },
});

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 session = await response.Content.ReadFromJsonAsync<CheckoutSession>(json);
Console.WriteLine($"{session!.PaymentIntent} {session.Url}");

record CheckoutSession(string Id, string PaymentIntent, string Status, string Url);
record ApiError(ApiErrorBody Error);
record ApiErrorBody(string Code, string Message);
```

```rust title="main.rs"
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
//             serde = { version = "1", features = ["derive"] }
//             serde_json = "1"
use std::time::{SystemTime, UNIX_EPOCH};

use serde::Deserialize;
use serde_json::json;

#[derive(Deserialize)]
struct CheckoutSession {
    payment_intent: String,
    url: String,
}

#[derive(Deserialize)]
struct ApiError {
    error: ErrorBody,
}

#[derive(Deserialize)]
struct ErrorBody {
    message: String,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Two hours from now, in Unix seconds.
    let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
    let expires_at = now + 2 * 60 * 60;

    let response = reqwest::blocking::Client::new()
        .post("https://pay-api.adpromax.com/v1/checkout/sessions")
        .bearer_auth(std::env::var("APX_SECRET_KEY")?)
        .header("Idempotency-Key", "checkout-order-2087")
        .json(&json!({
            "amount": 45000,
            "currency": "mad",
            "payment_method_types": ["card", "cash"],
            "success_url": "https://shop.example.com/orders/2087/thanks",
            "cancel_url": "https://shop.example.com/cart",
            "expires_at": expires_at,
            "customer": "cus_01k7e2f9g3h6j8k2m5n7p4q1sd",
            "metadata": { "order_id": "2087" }
        }))
        .send()?;
    if !response.status().is_success() {
        let failure: ApiError = response.json()?;
        return Err(failure.error.message.into());
    }
    let session: CheckoutSession = response.json()?;
    println!("{} {}", session.payment_intent, session.url);
    Ok(())
}
```

```json title="200 OK" response
{
  "id": "cs_01k7e3m5q8w2r6t9y4b7n1v5ca",
  "amount": 45000,
  "currency": "mad",
  "payment_method_types": ["card", "cash"],
  "payment_intent": "pi_01k7e3m5px4z8c2v6b9n3m7hra",
  "customer": "cus_01k7e2f9g3h6j8k2m5n7p4q1sd",
  "status": "open",
  "url": "https://pay.example.com/c/cs_01k7e3m5q8w2r6t9y4b7n1v5ca#…",
  "success_url": "https://shop.example.com/orders/2087/thanks",
  "cancel_url": "https://shop.example.com/cart",
  "expires_at": 1790854200,
  "metadata": { "order_id": "2087" },
  "livemode": false
}
```

## Envoyer votre client sur la page

Redirigez le navigateur de votre client vers l’`url` de la session avec un HTTP `303`, comme dans
[le démarrage rapide](https://pay-docs.adpromax.com/fr/get-started/quickstart-card#3-envoyer-votre-client-sur-la-page).

Gardez l’adresse entière, y compris la partie après `#`. Cette partie est l’accès de votre client à
cette session : les navigateurs ne l’envoient jamais à un serveur, elle reste donc hors des journaux.
Ne la journalisez pas, ne la raccourcissez pas, et ne l’envoyez à personne d’autre que votre client.

## Ce que voit votre client

D’abord, le montant et un choix entre les moyens de paiement que vous avez proposés.

**Par carte :**

1. Votre client choisit la carte et appuie sur *Payer*. La page prépare le paiement, puis explique
   qu’elle l’emmène sur la page carte sécurisée de notre partenaire carte. En mode test, cette page
   est notre simulation marquée TEST, qui n’accepte que les [cartes de test](https://pay-docs.adpromax.com/fr/sandbox/test-cards).
2. Il saisit sa carte sur cette page. Si sa banque demande 3-D Secure, il confirme là.
3. Il revient sur notre page, qui affiche « Confirmation auprès de votre banque… » jusqu’à la
   confirmation du paiement, puis la page de réussite.
4. Après 5 secondes, la page de réussite l’envoie sur votre `success_url`.

Si la carte est refusée, la page donne la raison et propose *Essayer une autre carte*, ou *Payer en
espèces* quand vous avez proposé les espèces et que le montant atteint 100,00 MAD. Voir
[Paiements par carte](https://pay-docs.adpromax.com/fr/payments/card-payments).

**En espèces :** votre client donne son nom complet et sa ville (un numéro de téléphone est
facultatif), puis reçoit un bon avec un code de paiement à régler chez un agent de paiement
partenaire. Voir [Paiements en espèces](https://pay-docs.adpromax.com/fr/payments/cash-payments).

Pour les deux moyens, votre client peut donner une adresse e-mail pour recevoir un reçu ; le reçu
n’est envoyé que s’il le fait.

Le lien *← Retour* de la page, qui nomme votre entreprise, envoie votre client sur votre
`cancel_url`. La session reste `open` jusqu’à son expiration : la même `url` fonctionne encore s’il
revient.

## Savoir quand c’est payé

Livrez la commande sur l’un de ces événements, jamais sur le retour à votre `success_url` :

| Événement                    | Quand                                                                     |
| ---------------------------- | ------------------------------------------------------------------------- |
| `payment_intent.succeeded`   | L’intention de paiement de la session a réussi.                           |
| `checkout.session.completed` | La session a été payée : elle nomme son `payment_intent`.                 |
| `checkout.session.expired`   | La session s’est terminée sans paiement. Libérez la commande ou le stock. |

Vous recevez aussi les autres événements de l’intention de paiement, comme
`payment_intent.payment_failed` après une carte refusée. Lisez
[Redirections et webhooks](https://pay-docs.adpromax.com/fr/payments/how-payments-work#redirections-et-webhooks--auquel-se-fier)
pour comprendre pourquoi le retour ne prouve rien.

Un code espèces que votre client a déjà reçu garde sa propre échéance, environ trois jours, même
quand l’`expires_at` de la session arrive avant : il peut encore le payer.

## Faire expirer une session

Si la commande est annulée ou que le stock est épuisé, faites expirer vous-même la session ouverte
avec `POST /v1/checkout/sessions/{id}/expire`. La session devient `expired` et son intention de
paiement non payée est annulée : votre client ne peut plus la payer. Seule une session `open` peut
expirer ainsi.

Pour relire une session, utilisez `GET /v1/checkout/sessions/{id}` ; pour les lister,
`GET /v1/checkout/sessions`.

```bash
curl -X POST \
  https://pay-api.adpromax.com/v1/checkout/sessions/cs_01k7e3m5q8w2r6t9y4b7n1v5ca/expire \
  -u "$APX_SECRET_KEY:"
```

```js title="expire.mjs"
const id = 'cs_01k7e3m5q8w2r6t9y4b7n1v5ca'
const response = await fetch(`https://pay-api.adpromax.com/v1/checkout/sessions/${id}/expire`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.APX_SECRET_KEY}` },
})
const session = await response.json()
console.log(session.status) // expired
```

```ts title="expire.ts"
interface CheckoutSession {
  id: string
  object: 'checkout.session'
  status: string
}

const id = 'cs_01k7e3m5q8w2r6t9y4b7n1v5ca'
const response = await fetch(`https://pay-api.adpromax.com/v1/checkout/sessions/${id}/expire`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.APX_SECRET_KEY}` },
})
const session = (await response.json()) as CheckoutSession
console.log(session.status) // expired
```

```python title="expire.py"
import os

import requests

session_id = "cs_01k7e3m5q8w2r6t9y4b7n1v5ca"
response = requests.post(
    f"https://pay-api.adpromax.com/v1/checkout/sessions/{session_id}/expire",
    auth=(os.environ["APX_SECRET_KEY"], ""),
)
print(response.json()["status"])  # expired
```

```php title="expire.php"
<?php
$id = 'cs_01k7e3m5q8w2r6t9y4b7n1v5ca';
$ch = curl_init("https://pay-api.adpromax.com/v1/checkout/sessions/{$id}/expire");
curl_setopt_array($ch, [
    CURLOPT_USERPWD => getenv('APX_SECRET_KEY') . ':',
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
]);
$session = json_decode(curl_exec($ch), true);
echo $session['status'], PHP_EOL; // expired
```

```ruby title="expire.rb"
require 'json'
require 'net/http'

id = 'cs_01k7e3m5q8w2r6t9y4b7n1v5ca'
uri = URI("https://pay-api.adpromax.com/v1/checkout/sessions/#{id}/expire")
request = Net::HTTP::Post.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
puts JSON.parse(response.body)['status'] # expired
```

```go title="expire.go"
package main

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

type CheckoutSession struct {
	ID     string `json:"id"`
	Status string `json:"status"`
}

func main() {
	id := "cs_01k7e3m5q8w2r6t9y4b7n1v5ca"
	req, err := http.NewRequest(http.MethodPost,
		"https://pay-api.adpromax.com/v1/checkout/sessions/"+id+"/expire", 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()

	var session CheckoutSession
	if err := json.NewDecoder(res.Body).Decode(&session); err != nil {
		log.Fatal(err)
	}
	fmt.Println(session.Status) // expired
}
```

```java title="Expire.java"
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Expire {
    public static void main(String[] args) throws Exception {
        String id = "cs_01k7e3m5q8w2r6t9y4b7n1v5ca";
        HttpRequest request = HttpRequest.newBuilder(
                URI.create("https://pay-api.adpromax.com/v1/checkout/sessions/" + id + "/expire"))
            .header("Authorization", "Bearer " + System.getenv("APX_SECRET_KEY"))
            .POST(HttpRequest.BodyPublishers.noBody())
            .build();
        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());
        // The session as JSON: its status is now "expired".
        System.out.println(response.body());
    }
}
```

```csharp title="Expire.cs"
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 = "cs_01k7e3m5q8w2r6t9y4b7n1v5ca";
using var response = await http.PostAsync(
    $"https://pay-api.adpromax.com/v1/checkout/sessions/{id}/expire", null);
var session = await response.Content.ReadFromJsonAsync<CheckoutSession>(json);
Console.WriteLine(session!.Status); // expired

record CheckoutSession(string Id, string Status);
```

```rust title="main.rs"
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
//             serde = { version = "1", features = ["derive"] }
use serde::Deserialize;

#[derive(Deserialize)]
struct CheckoutSession {
    status: String,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let id = "cs_01k7e3m5q8w2r6t9y4b7n1v5ca";
    let session: CheckoutSession = reqwest::blocking::Client::new()
        .post(format!("https://pay-api.adpromax.com/v1/checkout/sessions/{id}/expire"))
        .bearer_auth(std::env::var("APX_SECRET_KEY")?)
        .send()?
        .json()?;
    println!("{}", session.status); // expired
    Ok(())
}
```

## Votre image de marque

Réglez l’apparence de la page une fois, sur le tableau de bord sous **Paramètres → Image de marque** :

* un logo, en PNG, JPEG ou WebP, jusqu’à 512 Ko ;
* le nom affiché, ainsi que votre e-mail et votre téléphone d’assistance ;
* une couleur de bouton : si un texte blanc y serait difficile à lire, elle est assombrie jusqu’à
  devenir lisible, et la page des paramètres vous le signale ;
* un fond blanc, ou légèrement teinté de votre couleur.

La page du bon d’un paiement en espèces utilise les mêmes réglages.

## Sans aucun code

Un **lien de paiement**, créé sur le tableau de bord, ouvre la même page hébergée pour toute personne
qui a le lien. Voir [Sans code : un lien de paiement](https://pay-docs.adpromax.com/fr/get-started/quickstart-card#sans-code--un-lien-de-paiement).
