Paiements
Remboursements
Rembourser un paiement par carte en totalité ou en partie, le temps que prend un remboursement, et pourquoi un paiement en espèces n’est pas remboursé en espèces.
Sur cette page
Un remboursement rend de l’argent à votre client pour un paiement réussi. Les paiements par carte peuvent être remboursés, en totalité ou en partie. Les paiements en espèces ne le peuvent pas : voir Paiements en espèces plus bas.
- Seul un paiement
succeededpeut être remboursé. - Vous pouvez faire plusieurs remboursements partiels, tant que leur total ne dépasse pas
l’
amount_receiveddu paiement. Un remboursement supérieur à ce qui reste est refusé avecamount_too_large. - Un paiement en espèces est refusé avec
refund_unsupported_payment_method, en mode test comme en production.
Rembourser un paiement par carte
Créez un remboursement avec POST /v1/refunds. L’en-tête Idempotency-Key est obligatoire, comme
sur chaque requête qui déplace de l’argent : un remboursement envoyé deux fois avec la même clé n’est
fait qu’une fois.
| Paramètre | Ce qu’il contient |
|---|---|
payment_intent | Le paiement à rembourser (pi_…). Ou envoyez charge, sa tentative réussie (ch_…). |
amount | Facultatif. En centimes. Sans lui, tout ce qui n’a pas encore été remboursé. |
reason | Facultatif. requested_by_customer, duplicate ou fraudulent. |
metadata | Facultatif. Vos propres références, comme un numéro de retour : voir Métadonnées. |
La réponse est le remboursement, d’abord pending.
curl https://pay-api.adpromax.com/v1/refunds \
-u "$APX_SECRET_KEY:" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: refund-order-1042-1" \
-d '{
"payment_intent": "pi_01k7e7v2x5z8b1d4f7h0k3n6qs",
"amount": 10000,
"reason": "requested_by_customer",
"metadata": { "return_id": "R-311" }
}'const response = await fetch('https://pay-api.adpromax.com/v1/refunds', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.APX_SECRET_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'refund-order-1042-1',
},
body: JSON.stringify({
payment_intent: 'pi_01k7e7v2x5z8b1d4f7h0k3n6qs',
amount: 10000,
reason: 'requested_by_customer',
metadata: { return_id: 'R-311' },
}),
})
const refund = await response.json()
if (!response.ok)
throw new Error(refund.error.message)
console.log(refund.id, refund.status)interface Refund {
id: string
object: 'refund'
amount: number
currency: string
payment_intent: string
reason: string | null
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/refunds', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.APX_SECRET_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'refund-order-1042-1',
},
body: JSON.stringify({
payment_intent: 'pi_01k7e7v2x5z8b1d4f7h0k3n6qs',
amount: 10000,
reason: 'requested_by_customer',
metadata: { return_id: 'R-311' },
}),
})
if (!response.ok) {
const { error } = (await response.json()) as ApiError
throw new Error(`${error.code}: ${error.message}`)
}
const refund = (await response.json()) as Refund
console.log(refund.id, refund.status)import os
import requests
response = requests.post(
"https://pay-api.adpromax.com/v1/refunds",
auth=(os.environ["APX_SECRET_KEY"], ""),
headers={"Idempotency-Key": "refund-order-1042-1"},
json={
"payment_intent": "pi_01k7e7v2x5z8b1d4f7h0k3n6qs",
"amount": 10000,
"reason": "requested_by_customer",
"metadata": {"return_id": "R-311"},
},
)
refund = response.json()
response.raise_for_status()
print(refund["id"], refund["status"])<?php
$ch = curl_init('https://pay-api.adpromax.com/v1/refunds');
curl_setopt_array($ch, [
CURLOPT_USERPWD => getenv('APX_SECRET_KEY') . ':',
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Idempotency-Key: refund-order-1042-1',
],
CURLOPT_POSTFIELDS => json_encode([
'payment_intent' => 'pi_01k7e7v2x5z8b1d4f7h0k3n6qs',
'amount' => 10000,
'reason' => 'requested_by_customer',
'metadata' => ['return_id' => 'R-311'],
]),
CURLOPT_RETURNTRANSFER => true,
]);
$refund = json_decode(curl_exec($ch), true);
echo $refund['id'], ' ', $refund['status'], PHP_EOL;require 'json'
require 'net/http'
uri = URI('https://pay-api.adpromax.com/v1/refunds')
request = Net::HTTP::Post.new(uri)
request.basic_auth(ENV.fetch('APX_SECRET_KEY'), '')
request['Content-Type'] = 'application/json'
request['Idempotency-Key'] = 'refund-order-1042-1'
request.body = JSON.generate(
payment_intent: 'pi_01k7e7v2x5z8b1d4f7h0k3n6qs',
amount: 10000,
reason: 'requested_by_customer',
metadata: { return_id: 'R-311' }
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
refund = JSON.parse(response.body)
raise refund.dig('error', 'message') unless response.is_a?(Net::HTTPSuccess)
puts "#{refund['id']} #{refund['status']}"package main
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
)
type Refund struct {
ID string `json:"id"`
Amount int64 `json:"amount"`
PaymentIntent string `json:"payment_intent"`
Status string `json:"status"`
}
type APIError struct {
Error struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
func main() {
body, err := json.Marshal(map[string]any{
"payment_intent": "pi_01k7e7v2x5z8b1d4f7h0k3n6qs",
"amount": 10000,
"reason": "requested_by_customer",
"metadata": map[string]string{"return_id": "R-311"},
})
if err != nil {
log.Fatal(err)
}
req, err := http.NewRequest(http.MethodPost,
"https://pay-api.adpromax.com/v1/refunds", 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", "refund-order-1042-1")
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 refund Refund
if err := json.NewDecoder(res.Body).Decode(&refund); err != nil {
log.Fatal(err)
}
fmt.Println(refund.ID, refund.Status)
}import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Refund {
public static void main(String[] args) throws Exception {
String body = """
{
"payment_intent": "pi_01k7e7v2x5z8b1d4f7h0k3n6qs",
"amount": 10000,
"reason": "requested_by_customer",
"metadata": { "return_id": "R-311" }
}
""";
HttpRequest request = HttpRequest.newBuilder(
URI.create("https://pay-api.adpromax.com/v1/refunds"))
.header("Authorization", "Bearer " + System.getenv("APX_SECRET_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", "refund-order-1042-1")
.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 refund as JSON: read its id and status with a JSON library.
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/refunds");
request.Headers.Add("Idempotency-Key", "refund-order-1042-1");
request.Content = JsonContent.Create(new
{
payment_intent = "pi_01k7e7v2x5z8b1d4f7h0k3n6qs",
amount = 10000,
reason = "requested_by_customer",
metadata = new { return_id = "R-311" },
});
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 refund = await response.Content.ReadFromJsonAsync<Refund>(json);
Console.WriteLine($"{refund!.Id} {refund.Status}");
record Refund(string Id, long Amount, string PaymentIntent, string Status);
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 Refund {
id: String,
status: 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/refunds")
.bearer_auth(std::env::var("APX_SECRET_KEY")?)
.header("Idempotency-Key", "refund-order-1042-1")
.json(&json!({
"payment_intent": "pi_01k7e7v2x5z8b1d4f7h0k3n6qs",
"amount": 10000,
"reason": "requested_by_customer",
"metadata": { "return_id": "R-311" }
}))
.send()?;
if !response.status().is_success() {
let failure: ApiError = response.json()?;
return Err(failure.error.message.into());
}
let refund: Refund = response.json()?;
println!("{} {}", refund.id, refund.status);
Ok(())
}{
"id": "re_01k7e7w3y6a9c2e5g8j1m4p7rt",
"object": "refund",
"amount": 10000,
"currency": "mad",
"payment_intent": "pi_01k7e7v2x5z8b1d4f7h0k3n6qs",
"charge": "ch_01k7e7v3c6e9g2j5m8p1r4t7wb",
"status": "pending",
"reason": "requested_by_customer",
"metadata": { "return_id": "R-311" },
"created": 1790863800,
"livemode": false
}La vie d’un remboursement
- pending → succeeded: exécuté
- pending → failed: refusé
- pending → canceled: vous annulez
Un remboursement est pending jusqu’à son exécution, puis succeeded. Il finit failed si le
réseau carte le refuse, et canceled si vous l’annulez avant.
- En mode test, un remboursement réussit en quelques secondes.
- En production, un remboursement par carte peut être traité manuellement : notre équipe
l’exécute auprès du réseau carte. Il reste
pendingjusqu’à ce que ce soit fait, ce qui peut prendre quelques jours ouvrés. Une foissucceeded, la banque de votre client prend encore son propre temps pour faire apparaître l’argent sur sa carte.
Les statuts sont les mêmes dans les deux cas : votre code ne change pas d’un mode à l’autre.
Annuler un remboursement
Un remboursement pending qui n’a pas encore été exécuté peut être annulé avec
POST /v1/refunds/{id}/cancel. Il devient canceled, et son montant peut être remboursé de nouveau
plus tard.
curl -X POST https://pay-api.adpromax.com/v1/refunds/re_01k7e7w3y6a9c2e5g8j1m4p7rt/cancel \
-u "$APX_SECRET_KEY:"const id = 're_01k7e7w3y6a9c2e5g8j1m4p7rt'
const response = await fetch(`https://pay-api.adpromax.com/v1/refunds/${id}/cancel`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.APX_SECRET_KEY}` },
})
const refund = await response.json()
console.log(refund.status) // canceledinterface Refund {
id: string
object: 'refund'
status: string
}
const id = 're_01k7e7w3y6a9c2e5g8j1m4p7rt'
const response = await fetch(`https://pay-api.adpromax.com/v1/refunds/${id}/cancel`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.APX_SECRET_KEY}` },
})
const refund = (await response.json()) as Refund
console.log(refund.status) // canceledimport os
import requests
refund_id = "re_01k7e7w3y6a9c2e5g8j1m4p7rt"
response = requests.post(
f"https://pay-api.adpromax.com/v1/refunds/{refund_id}/cancel",
auth=(os.environ["APX_SECRET_KEY"], ""),
)
print(response.json()["status"]) # canceled<?php
$id = 're_01k7e7w3y6a9c2e5g8j1m4p7rt';
$ch = curl_init("https://pay-api.adpromax.com/v1/refunds/{$id}/cancel");
curl_setopt_array($ch, [
CURLOPT_USERPWD => getenv('APX_SECRET_KEY') . ':',
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
]);
$refund = json_decode(curl_exec($ch), true);
echo $refund['status'], PHP_EOL; // canceledrequire 'json'
require 'net/http'
id = 're_01k7e7w3y6a9c2e5g8j1m4p7rt'
uri = URI("https://pay-api.adpromax.com/v1/refunds/#{id}/cancel")
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'] # canceledpackage main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
)
type Refund struct {
ID string `json:"id"`
Status string `json:"status"`
}
func main() {
id := "re_01k7e7w3y6a9c2e5g8j1m4p7rt"
req, err := http.NewRequest(http.MethodPost,
"https://pay-api.adpromax.com/v1/refunds/"+id+"/cancel", 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 refund Refund
if err := json.NewDecoder(res.Body).Decode(&refund); err != nil {
log.Fatal(err)
}
fmt.Println(refund.Status) // canceled
}import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class CancelRefund {
public static void main(String[] args) throws Exception {
String id = "re_01k7e7w3y6a9c2e5g8j1m4p7rt";
HttpRequest request = HttpRequest.newBuilder(
URI.create("https://pay-api.adpromax.com/v1/refunds/" + id + "/cancel"))
.header("Authorization", "Bearer " + System.getenv("APX_SECRET_KEY"))
.POST(HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
// The refund as JSON: its status is now "canceled".
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"));
var id = "re_01k7e7w3y6a9c2e5g8j1m4p7rt";
using var response = await http.PostAsync(
$"https://pay-api.adpromax.com/v1/refunds/{id}/cancel", null);
var refund = await response.Content.ReadFromJsonAsync<Refund>(json);
Console.WriteLine(refund!.Status); // canceled
record Refund(string Id, string Status);// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
// serde = { version = "1", features = ["derive"] }
use serde::Deserialize;
#[derive(Deserialize)]
struct Refund {
status: String,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let id = "re_01k7e7w3y6a9c2e5g8j1m4p7rt";
let refund: Refund = reqwest::blocking::Client::new()
.post(format!("https://pay-api.adpromax.com/v1/refunds/{id}/cancel"))
.bearer_auth(std::env::var("APX_SECRET_KEY")?)
.send()?
.json()?;
println!("{}", refund.status); // canceled
Ok(())
}Événements et soldes
| Événement | Quand |
|---|---|
refund.created | Un remboursement a été créé. |
refund.updated | Un remboursement a changé de statut, par exemple vers succeeded. |
refund.failed | Un remboursement a échoué. |
charge.refunded | Une tentative a été remboursée, en totalité ou en partie. |
- L’
amount_refundedde l’intention de paiement augmente quand un remboursement réussit. L’intention de paiement restesucceeded, même remboursée en totalité. - Un remboursement échoué ou annulé libère son montant : vous pouvez le rembourser de nouveau.
- Un remboursement réussi est pris sur votre solde ; il apparaît comme une ligne de remboursement sous Soldes. Nos frais sur le paiement ne sont pas rendus : un paiement remboursé en totalité laisse votre solde plus bas de ces frais.
Depuis le tableau de bord
Sur la page d’un paiement, Rembourser ouvre un formulaire :
- le montant, prérempli avec ce qui reste à rembourser, avec Total et Partiel ;
- une raison : doublon, fraude, demande du client, ou autre avec une note ;
- en production, une dernière confirmation avant l’envoi du remboursement.
La page Remboursements liste chaque remboursement avec son statut, son paiement et son auteur.
Qui peut rembourser sur le tableau de bord dépend du rôle : Propriétaire, Administrateur, Finance et Support le peuvent. Le Support peut rembourser jusqu’à un plafond par remboursement, 1 000,00 MAD par défaut, que le formulaire affiche avant la saisie. Pour les autres rôles, le bouton est désactivé et dit pourquoi. Voir Équipe et rôles. Une clé d’API n’est pas limitée par les rôles : elle agit pour toute l’organisation.
Paiements en espèces
Un paiement en espèces réussi ne peut pas être remboursé par AdProMaxPay : ni en espèces, ni sur une carte.
Pourquoi. Votre client a remis l’argent en espèces chez un agent. Il n’y a ni carte ni compte sur lesquels le rendre.
Ce qui se passe. Le paiement reste succeeded et son montant reste dans votre solde. Sur le
tableau de bord, Rembourser est désactivé sur un paiement en espèces et dit pourquoi ; l’API
répond refund_unsupported_payment_method, en mode test comme en production.
Ce que vous faites. Réglez-le directement avec votre client : un virement bancaire, un avoir, ou des espèces dans votre magasin. Enregistrez-le dans vos propres comptes. Annoncez votre politique à vos clients avant qu’ils choisissent les espèces.
Un code espèces qui n’a pas encore été payé n’est pas remboursé mais annulé : voir Annuler un code dont vous ne voulez plus.