# 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.

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](https://pay-docs.adpromax.com/fr/get-started/quickstart-cash).

## 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 :

| Champ                | Ce qu’il contient                                                                                                                                                                                                                              |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `code`               | Le code de paiement que votre client donne à l’agent. Affichez-le tel quel : n’en changez jamais les caractères.                                                                                                                               |
| `amount_to_pay`      | Ce 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_at`         | L’échéance, en secondes Unix : environ trois jours après l’émission du code. Affichez-la à l’heure du Maroc.                                                                                                                                   |
| `slip_url`           | Un bordereau imprimable.                                                                                                                                                                                                                       |
| `hosted_voucher_url` | Notre 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](https://pay-docs.adpromax.com/fr/sandbox/test-helpers). Chaque issue
est décrite dans [Scénarios de test en espèces](https://pay-docs.adpromax.com/fr/sandbox/cash-scenarios).

## Quand tout ne se passe pas comme prévu

| Ce qui arrive                                                                          | Ce que vous recevez                                                                                                      | Ce que vous faites                                                                                                                                                                            |
| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| L’échéance passe sans paiement                                                         | `payment_intent.payment_failed`, avec `last_payment_error.code` à `voucher_expired` ; retour à `requires_payment_method` | Libé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 code                                                   | `payment_intent.payment_failed` avec `voucher_refused`                                                                   | La même chose : votre client peut obtenir un nouveau code.                                                                                                                                    |
| Le code est mis en attente par le réseau                                               | Rien, jusqu’à ce qu’il soit payé ou non                                                                                  | Attendez.                                                                                                                                                                                     |
| Votre client paie un autre montant, ou paie après l’échéance ou après votre annulation | `review.opened` ; rien n’est crédité                                                                                     | Attendez : une personne de notre côté examine le cas. Voir [Quand quelque chose ne colle pas](https://pay-docs.adpromax.com/fr/payments/how-payments-work#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.

```bash
curl -X POST \
  https://pay-api.adpromax.com/v1/payment_intents/pi_01k7e6t4v8x2z5b9c3d7f1g6hq/cancel \
  -u "$APX_SECRET_KEY:"
```

```js title="cancel.mjs"
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
```

```ts title="cancel.ts"
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
```

```python title="cancel.py"
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 title="cancel.php"
<?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
```

```ruby title="cancel.rb"
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
```

```go title="cancel.go"
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
}
```

```java title="Cancel.java"
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());
    }
}
```

```csharp title="Cancel.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 = "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);
```

```rust title="main.rs"
// 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](https://pay-docs.adpromax.com/fr/payments/refunds#paiements-en-espèces) 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](https://pay-docs.adpromax.com/fr/get-started/quickstart-cash#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.
