Paiements

Paiements en espèces

Les bons, le montant à payer, les échéances, le minimum de 100 MAD, où vos clients paient, et que faire quand un code expire.

Ouvrir en Markdown
Sur cette page
  1. Le parcours d’un paiement en espèces
  2. Les règles
  3. Le bon
  4. Où vos clients paient
  5. Quand tout ne se passe pas comme prévu
  6. Annuler un code dont vous ne voulez plus
  7. Les espèces ne sont pas remboursées
  8. Depuis le tableau de bord
  9. Votre propre page
  10. Bonnes pratiques

Avec les espèces, votre client reçoit un code de paiement, sur un bon, et le règle en espèces chez un agent de paiement partenaire avant une échéance. Vous apprenez le résultat par un webhook, comme pour une carte. Les espèces vous permettent de vendre à des clients qui n’ont pas de carte, ou qui préfèrent ne pas s’en servir en ligne.

Pour l’essayer en quelques minutes, suivez le démarrage rapide espèces.

Le parcours d’un paiement en espèces

Un paiement en espèces, du code à l’argent
  • requires_payment_method → requires_action: code émis
  • requires_action → succeeded: payé chez un agent, confirmé
  • requires_action → requires_payment_method: expiré ou refusé
  • requires_action → canceled: vous annulez
  1. Votre client choisit les espèces sur la page hébergée, ou vous confirmez une intention de paiement en espèces. Il reçoit un bon, et l’intention de paiement passe en requires_action.
  2. Il se rend chez un agent de paiement partenaire, donne le code, paie en espèces et garde son reçu.
  3. Une vérification du statut voit le paiement : l’intention de paiement devient succeeded et vous recevez payment_intent.succeeded. En production, la vérification a lieu toutes les 2 minutes : l’événement arrive donc en général dans les minutes qui suivent le paiement ; en mode test, elle a lieu tout de suite après un paiement simulé.

Tant que le paiement n’est pas confirmé, l’intention de paiement reste en requires_action, parfois pendant des jours.

Les règles

  • Un paiement en espèces vaut au moins 100,00 MAD (10000 centimes), en mode test comme en production. Un montant inférieur est refusé avec amount_too_small.
  • Le nom complet et la ville de votre client sont obligatoires. La page hébergée les demande ; avec l’API, vous envoyez payer.name et payer.city à la confirmation. Un numéro de téléphone est facultatif.
  • La devise est mad, et les montants sont des entiers en centimes, comme partout dans l’API.

Le bon

Le bon se trouve dans le next_action.display_voucher de l’intention de paiement :

ChampCe qu’il contient
codeLe code de paiement que votre client donne à l’agent. Affichez-le tel quel : n’en changez jamais les caractères.
amount_to_payCe que votre client remet : votre montant plus un petit droit de timbre, à la charge de votre client (0,25 MAD en mode test). Affichez ce chiffre tel que le bon le donne ; ne le calculez jamais. Le droit n’est pas pris sur votre amount.
expires_atL’échéance, en secondes Unix : environ trois jours après l’émission du code. Affichez-la à l’heure du Maroc.
slip_urlUn bordereau imprimable.
hosted_voucher_urlNotre page du bon, que vous pouvez envoyer à votre client.

Notre page du bon montre le code avec un bouton Copier, le montant à remettre, l’échéance à l’heure du Maroc, où et comment payer, et des boutons pour l’imprimer, le partager par WhatsApp ou SMS, et J’ai payé : vérifier maintenant. Elle est à vos couleurs (Paramètres → Image de marque) et fonctionne sur n’importe quel téléphone.

Où vos clients paient

Chez un agent de paiement partenaire : votre client donne le code, paie en espèces le montant à remettre avant l’échéance, et garde le reçu de l’agent. La page du bon indique quels agents acceptent le code et comment en trouver un à proximité.

En mode test, aucun agent n’accepte un code : vous simulez le paiement à la place, sur la page du bon, sur le tableau de bord ou avec les assistants de test. Chaque issue est décrite dans Scénarios de test en espèces.

Quand tout ne se passe pas comme prévu

Ce qui arriveCe que vous recevezCe que vous faites
L’échéance passe sans paiementpayment_intent.payment_failed, avec last_payment_error.code à voucher_expired ; retour à requires_payment_methodLibérez la commande ou le stock, ou laissez votre client obtenir un nouveau code : sur la page hébergée tant que la session est ouverte, ou en confirmant de nouveau l’intention de paiement.
Le réseau de paiement refuse le codepayment_intent.payment_failed avec voucher_refusedLa même chose : votre client peut obtenir un nouveau code.
Le code est mis en attente par le réseauRien, jusqu’à ce qu’il soit payé ou nonAttendez.
Votre client paie un autre montant, ou paie après l’échéance ou après votre annulationreview.opened ; rien n’est créditéAttendez : une personne de notre côté examine le cas. Voir Quand quelque chose ne colle pas.

Annuler un code dont vous ne voulez plus

Si la commande est annulée avant que votre client paie, annulez l’intention de paiement avec POST /v1/payment_intents/{id}/cancel. Le code ne peut plus être payé ; vous recevez payment_intent.canceled.

Si votre client le paie quand même, rien n’est crédité : le paiement fait l’objet d’un examen, et une personne de notre côté s’en occupe. Prévenez votre client quand vous annulez.

  • curl
  • Node.js
  • TypeScript
  • Python
  • PHP
  • Ruby
  • Go
  • Java
  • C#
  • Rust
cancel.mjscancel.tscancel.pycancel.phpcancel.rbcancel.goCancel.javaCancel.csmain.rs
curl -X POST \
  https://pay-api.adpromax.com/v1/payment_intents/pi_01k7e6t4v8x2z5b9c3d7f1g6hq/cancel \
  -u "$APX_SECRET_KEY:"
const id = 'pi_01k7e6t4v8x2z5b9c3d7f1g6hq'
const response = await fetch(`https://pay-api.adpromax.com/v1/payment_intents/${id}/cancel`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.APX_SECRET_KEY}` },
})
const intent = await response.json()
console.log(intent.status) // canceled
interface PaymentIntent {
  id: string
  object: 'payment_intent'
  status: string
}

const id = 'pi_01k7e6t4v8x2z5b9c3d7f1g6hq'
const response = await fetch(`https://pay-api.adpromax.com/v1/payment_intents/${id}/cancel`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.APX_SECRET_KEY}` },
})
const intent = (await response.json()) as PaymentIntent
console.log(intent.status) // canceled
import os

import requests

intent_id = "pi_01k7e6t4v8x2z5b9c3d7f1g6hq"
response = requests.post(
    f"https://pay-api.adpromax.com/v1/payment_intents/{intent_id}/cancel",
    auth=(os.environ["APX_SECRET_KEY"], ""),
)
print(response.json()["status"])  # canceled
<?php
$id = 'pi_01k7e6t4v8x2z5b9c3d7f1g6hq';
$ch = curl_init("https://pay-api.adpromax.com/v1/payment_intents/{$id}/cancel");
curl_setopt_array($ch, [
    CURLOPT_USERPWD => getenv('APX_SECRET_KEY') . ':',
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
]);
$intent = json_decode(curl_exec($ch), true);
echo $intent['status'], PHP_EOL; // canceled
require 'json'
require 'net/http'

id = 'pi_01k7e6t4v8x2z5b9c3d7f1g6hq'
uri = URI("https://pay-api.adpromax.com/v1/payment_intents/#{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'] # canceled
package main

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

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

func main() {
	id := "pi_01k7e6t4v8x2z5b9c3d7f1g6hq"
	req, err := http.NewRequest(http.MethodPost,
		"https://pay-api.adpromax.com/v1/payment_intents/"+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 intent PaymentIntent
	if err := json.NewDecoder(res.Body).Decode(&intent); err != nil {
		log.Fatal(err)
	}
	fmt.Println(intent.Status) // canceled
}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Cancel {
    public static void main(String[] args) throws Exception {
        String id = "pi_01k7e6t4v8x2z5b9c3d7f1g6hq";
        HttpRequest request = HttpRequest.newBuilder(
                URI.create("https://pay-api.adpromax.com/v1/payment_intents/" + 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 payment intent 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 = "pi_01k7e6t4v8x2z5b9c3d7f1g6hq";
using var response = await http.PostAsync(
    $"https://pay-api.adpromax.com/v1/payment_intents/{id}/cancel", null);
var intent = await response.Content.ReadFromJsonAsync<PaymentIntent>(json);
Console.WriteLine(intent!.Status); // canceled

record PaymentIntent(string Id, string Status);
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
//             serde = { version = "1", features = ["derive"] }
use serde::Deserialize;

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

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let id = "pi_01k7e6t4v8x2z5b9c3d7f1g6hq";
    let intent: PaymentIntent = reqwest::blocking::Client::new()
        .post(format!("https://pay-api.adpromax.com/v1/payment_intents/{id}/cancel"))
        .bearer_auth(std::env::var("APX_SECRET_KEY")?)
        .send()?
        .json()?;
    println!("{}", intent.status); // canceled
    Ok(())
}

Les espèces ne sont pas remboursées

Un paiement en espèces réussi ne peut pas être remboursé par AdProMaxPay, ni en espèces ni sur une carte : voir Remboursements pour savoir pourquoi, et que faire à la place.

Depuis le tableau de bord

Vous pouvez aussi encaisser en espèces sans code :

  • Créer → Bon espèces : un montant à partir de 100,00 MAD, une description, un client si vous le souhaitez, le nom et la ville de votre client, et un numéro de téléphone si vous l’avez. La page du paiement vous permet ensuite de partager le bon par WhatsApp, SMS ou lien.
  • Les liens de paiement peuvent proposer les espèces à côté de la carte : chaque personne qui ouvre le lien choisit.

Votre propre page

Vous pouvez afficher le code sur votre propre page plutôt que sur la nôtre : créez une intention de paiement, confirmez-la en espèces, puis affichez next_action.display_voucher. Voir Afficher le code sur votre propre page.

Bonnes pratiques

  • Réservez la commande ou le stock jusqu’à l’échéance, puis libérez-les sur voucher_expired.
  • Affichez amount_to_pay et l’échéance exactement comme le bon les donne, avec le fuseau horaire.
  • Ne promettez pas de confirmation immédiate : dites à votre client que la commande est confirmée une fois son paiement confirmé.
  • Si vous le pouvez, rappelez l’échéance à votre client : la plupart des codes impayés expirent simplement.