# Cash payments

Vouchers, the amount to pay, deadlines, the 100 MAD minimum, where your customers pay, and what to do when a code expires.

With cash, your customer receives a **payment code**, on a voucher, and pays it in cash at a partner
payment agent before a deadline. You learn the result from a webhook, as for a card. Cash lets you
sell to customers who have no card, or prefer not to use one online.

To try it in a few minutes, follow the [cash quickstart](https://pay-docs.adpromax.com/en/get-started/quickstart-cash).

## How a cash payment moves

A cash payment, from the code to the money:

- `requires_payment_method` → `requires_action`: code issued
- `requires_action` → `succeeded`: paid at an agent, confirmed
- `requires_action` → `requires_payment_method`: expired or refused
- `requires_action` → `canceled`: you cancel

1. Your customer chooses cash on the hosted page, or you confirm a payment intent with cash. They
   receive a voucher, and the payment intent is `requires_action`.
2. They go to a partner payment agent, give the code, pay in cash and keep their receipt.
3. A status check sees the payment: the payment intent becomes `succeeded` and you receive
   `payment_intent.succeeded`. In live mode the check runs every 2 minutes, so the event usually
   comes within minutes of the payment; in test mode it runs at once after a simulated payment.

Until the payment is confirmed, the payment intent stays `requires_action`, possibly for days.

## The rules

* A cash payment is at least **100.00 MAD** (`10000` centimes), in test mode as in live mode. A
  smaller amount is refused with `amount_too_small`.
* Your customer's **full name and city** are required. The hosted page asks for them; with the API
  you send `payer.name` and `payer.city` when you confirm. A phone number is optional.
* The currency is `mad`, and amounts are integers in centimes, as everywhere in the API.

## The voucher

The voucher is in the payment intent's `next_action.display_voucher`:

| Field                | What it holds                                                                                                                                                                                                             |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `code`               | The payment code your customer gives at the agent. Show it as it is: never change its characters.                                                                                                                         |
| `amount_to_pay`      | What your customer hands over: your amount plus a small stamp fee, which your customer pays (0.25 MAD in test mode). Show this figure as the voucher gives it; never compute it. The fee is not taken from your `amount`. |
| `expires_at`         | The deadline, in Unix seconds: about three days after the code is issued. Show it in Morocco time.                                                                                                                        |
| `slip_url`           | A printable slip.                                                                                                                                                                                                         |
| `hosted_voucher_url` | Our voucher page, which you can send to your customer.                                                                                                                                                                    |

Our voucher page shows the code with a *Copy* button, the amount to hand over, the deadline in
Morocco time, where and how to pay, and buttons to print it, share it by WhatsApp or SMS, and *I've
paid: check now*. It is in your colours (**Settings → Branding**) and works on any phone.

## Where your customers pay

At a **partner payment agent**: your customer gives the code, pays the amount to hand over in cash,
before the deadline, and keeps the agent's receipt. The voucher page says which agents accept the
code and how to find one nearby.

In test mode no agent accepts a code: you simulate the payment instead, on the voucher page, on the
dashboard or with the [test helpers](https://pay-docs.adpromax.com/en/sandbox/test-helpers). Every outcome is listed in
[Test cash scenarios](https://pay-docs.adpromax.com/en/sandbox/cash-scenarios).

## When it does not go to plan

| What happens                                                                        | What you receive                                                                                                            | What you do                                                                                                                                                |
| ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The deadline passes unpaid                                                          | `payment_intent.payment_failed`, with `last_payment_error.code` set to `voucher_expired`; back to `requires_payment_method` | Free the order or the stock, or let your customer get a new code: on the hosted page while the session is open, or by confirming the payment intent again. |
| The payment network refuses the code                                                | `payment_intent.payment_failed` with `voucher_refused`                                                                      | The same: your customer can get a new code.                                                                                                                |
| The code is put on hold at the network                                              | Nothing until it is paid or not                                                                                             | Wait.                                                                                                                                                      |
| Your customer pays another amount, or pays after the deadline or after you canceled | `review.opened`; nothing is credited                                                                                        | Wait: a person on our side looks at it. See [When something does not add up](https://pay-docs.adpromax.com/en/payments/how-payments-work#when-something-does-not-add-up).                  |

## Cancel a code you no longer want

If the order is canceled before your customer pays, cancel the payment intent with
`POST /v1/payment_intents/{id}/cancel`. The code can no longer be paid; you receive
`payment_intent.canceled`.

If your customer pays it anyway, nothing is credited: the payment gets a review, and a person on our
side looks at it. Tell your customer when you cancel.

```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(())
}
```

## Cash is not refunded

A succeeded cash payment cannot be refunded through AdProMaxPay, neither in cash nor to a card: see
[Refunds](https://pay-docs.adpromax.com/en/payments/refunds#cash-payments) for why, and what to do instead.

## From the dashboard

You can also take a cash payment without code:

* **Create → Cash voucher**: an amount from 100.00 MAD, a description, a customer if you want, your
  customer's name and city, and a phone number if you have it. The payment's page then lets you
  share the voucher by WhatsApp, SMS or a link.
* **Payment links** can offer cash next to card: each person who opens the link chooses.

## Your own page

You can show the code on your own page rather than ours: create a payment intent, confirm it with
cash, then display `next_action.display_voucher`. See
[Show the code on your own page](https://pay-docs.adpromax.com/en/get-started/quickstart-cash#show-the-code-on-your-own-page).

## Good practice

* Hold the order or the stock until the deadline, then free it on `voucher_expired`.
* Show `amount_to_pay` and the deadline exactly as the voucher gives them, with the time zone.
* Do not promise an instant confirmation: tell your customer the order is confirmed once their
  payment is.
* Remind your customer before the deadline if you can: most unpaid codes simply expire.
