# Checkout

Your customer needs to see where to pay, how much, and whether it worked.
Send them to the hosted checkout page (`checkout_url`), or show it all on
[your own payment page](#your-own-payment-page), in your own design: the
payment your server creates already carries everything the page needs.
`checkout_url` is only for the hosted page.

## Hosted checkout

When the gateway has a hosted checkout configured, every payment carries a
`checkout_url`. Redirect the customer there, open it in a new tab, or put it
in an email:

```js
const payment = await payitgo.payments.create({ order_id: "ORD-1001", amount_usd: "49.90" });
res.redirect(payment.checkout_url); // https://pay.example.com/pay/6f1e2a9c4d7b3e05a8c1f2d4
```

What the page does:

1. If you created the payment without a `token`, it lists the coins on offer
   and lets the customer choose one. The payment then gets its address and its
   15 minutes.
2. It shows the amount (`crypto_amount` and `token`), the deposit address with
   a QR code and a copy button, the XRP destination tag where there is one, and
   a countdown to `expires_at`. For a payment
   [priced in your own currency](/docs/payments#prices-in-other-currencies),
   the page can show that price (`price_amount` and `price_currency`, such as
   "€45.50") next to the dollar amount.
3. It updates live as the payment moves through `partially_paid`, `detecting`
   and `confirmed`, and shows a "Test payment" banner in test mode.
4. When the payment is **confirmed**, it offers a button to your
   `success_url`. When it closes unpaid (`expired`, `underpaid` or `failed`), it
   offers a link back to your `cancel_url`.

Neither link is followed automatically, and neither is proof of payment:
fulfil orders from the [`payment.confirmed` webhook](/docs/webhooks), never
from a customer landing on `success_url`.

## Your own payment page

You do not have to send customers to the hosted page. **The payment your
server creates is the page's data**: `payments.create` (`POST /v1/payments`)
answers with the amount, coin, network, deposit address, QR code payload,
XRP destination tag and deadline, so your server renders them in your own
design straight from that response. No second call, and no checkout URL.

| The customer… | Your server calls | And renders |
|---|---|---|
| …pays in a coin you already know (you chose it, or they picked it on your site from [`tokens.list({amount_usd})`](/docs/payments#list-tokens) before you created the payment) | `payments.create({…, token: "USDT_TRC20"})` | The payment it returns |
| …picks the coin on your payment page | `payments.create({…})` without a `token`, then shows its `token_options`; once they pick, [`payments.selectToken(payment.id, "USDT_TRC20")`](/docs/payments#choose-the-coin-later) | The payment `selectToken` returns |

Then follow the payment until it is paid:

- **Your server** hears about every change from its [webhook](/docs/webhooks),
  and can read the payment at any time with `payments.retrieve(id)`
  (`GET /v1/payments/{id}`). **Fulfil the order from the `payment.confirmed`
  webhook**, never from what the page shows: the page is for the customer.
- **The page** shows progress live with the [socket](#follow-it-live), using
  the payment's `checkout_token` from the same response (no extra call): each
  event carries the whole payment, so the page needs no other request.

Your secret key stays on your server throughout. A page with no server of
its own (a static site, a Flutter app) uses the
[public checkout API](#the-public-checkout-api) instead.

### What to show

| Show | From | Notes |
|---|---|---|
| The exact amount | `crypto_amount` and `token`, e.g. "49.9 USDT" | The amount must **arrive** in full: the customer's wallet or exchange fee comes on top. An exchange that takes its withdrawal fee out of the amount makes the payment arrive short |
| The network | `chain` (names below), e.g. "on Tron (TRC-20)" | Show it next to the amount, prominently: the same coin exists on several networks |
| The address | `deposit_address`, with a copy button | One address per payment: never reuse it for another order |
| A QR code | `qr_payload`, drawn as a QR code | The [deposit address alone](/docs/payments#qr-codes), so exchange apps accept it; the customer types the amount |
| The destination tag (XRP only) | `destination_tag` | **Required**: show it as clearly as the address, with a copy button. Without it the payment cannot be matched |
| The deadline | A countdown to `expires_at` | 15 minutes from when the coin was set; the rate is locked until then |
| What is still missing | `crypto_amount` − `received_amount` | Only while `partially_paid`: "Send the remaining 19.9 USDT to the same address" |
| A fee note | `network_fee_note` | Optional, one line about the network |
| Your price in your currency | `price_amount` and `price_currency`, e.g. "€45.50", next to the dollars due (`amount_due_usd`) | Only on a payment [priced in another currency](/docs/payments#prices-in-other-currencies). For reference: the customer still sends `crypto_amount`, and `amount_due_usd` includes the fees when you pass them on |

Network names, as `network` gives them:

| `chain` | A token on it | The chain's own coin |
|---|---|---|
| `tron` | Tron (TRC-20): `USDT_TRC20` | Tron: `TRX` |
| `ethereum` | Ethereum (ERC-20): `USDT_ERC20`, `USDC_ERC20` | Ethereum: `ETH` |
| `bsc` | BNB Smart Chain (BEP-20): `USDT_BEP20`, `USDC_BEP20` | BNB Smart Chain: `BNB` |
| `bitcoin`, `litecoin`, `dogecoin` | | Bitcoin, Litecoin, Dogecoin: `BTC`, `LTC`, `DOGE` |
| `xrp` | | XRP Ledger: `XRP` |

The payment's `network` (and each of its `token_options`') says it for you:
the token standard is a token's, so a chain's own coin is named by the
chain alone.

In test mode (`"livemode": false`) show a "test payment" notice. Nothing
watches a test payment's address, so real coins sent to it are never seen:
the payment is paid by the outcome you
[simulate](#simulate-a-test-payment) instead.

### What each status means on the page

| `status` | What to show |
|---|---|
| `pending` | The amount, network, address, QR code and the countdown |
| `partially_paid` | "We received 30 of 49.9 USDT. Send the remaining 19.9 USDT to the same address before the time runs out." Keep the countdown |
| `detecting` | "Payment received, confirming…" with `confirmations` of `confirmations_required` ("7 of 20"), from the socket's events ([how many are needed](/docs/currencies)). Stop the countdown: the customer must not pay again |
| `confirmed` | "Paid." Offer a link back to your order page |
| `expired` | "This payment expired." Offer to start again: create a new payment for the same `order_id` |
| `underpaid` | "Only 30 of 49.9 USDT arrived before the deadline." Point the customer to your support: what arrived is settled to you, so you can refund it or agree on the rest |
| `failed` | "The payment could not be confirmed." Point the customer to your support |

Keep the socket open after the countdown ends: money that arrives up to 24
hours after `expires_at` can still complete the payment
([late payments](/docs/payments#late-payments)), and the socket then sends
`payment.detecting` and `payment.confirmed`.

### A complete example

A server that creates the payment and renders your page (Node.js with
Express; the [`qrcode`](https://www.npmjs.com/package/qrcode) package draws
the QR code), and the page's script that follows the payment live:

```js
// server.js: npm install express @payitgo/node qrcode
import express from "express";
import QRCode from "qrcode";
import { Payitgo } from "@payitgo/node";

const payitgo = new Payitgo(process.env.PAYITGO_SECRET_KEY, {
  webhookUrl: "https://shop.example/payitgo/ipn", // where this server hears about payments
});
const app = express();

app.get("/orders/:id/pay", async (req, res) => {
  const order = await findOrder(req.params.id); // your own order lookup
  const p = await payitgo.payments.create({
    order_id: order.id,
    amount_usd: order.totalUsd, // "49.90"
    token: "USDT_TRC20",        // or create it without one, show p.token_options,
                                // then render payitgo.payments.selectToken(p.id, choice)
  });
  res.render("pay", {           // your template, your design
    amount: p.crypto_amount,
    coin: p.token.split("_")[0], // "USDT"
    network: { tron: "Tron (TRC-20)", ethereum: "Ethereum (ERC-20)", bsc: "BNB Smart Chain (BEP-20)" }[p.chain] ?? p.chain,
    address: p.deposit_address,
    tag: p.destination_tag,      // XRP only
    qr: await QRCode.toDataURL(p.qr_payload), // <img src="…">
    expiresAt: p.expires_at,
    checkoutToken: p.checkout_token, // for the live updates below
    test: !p.livemode,
  });
});
```

Calling it again for the same order while its payment is open returns that
same payment, so a reload shows the same address and amount
([safe retries](/docs/payments#safe-retries)).

```html
<!-- pay.html: a Mustache-style template; render the values above with your own engine and markup -->
<div id="pay">
  <p>Send exactly <b>{{amount}} {{coin}}</b> on <b>{{network}}</b></p>
  <img src="{{qr}}" alt="QR code" width="220" height="220">
  <code id="address">{{address}}</code> <button onclick="navigator.clipboard.writeText('{{address}}')">Copy</button>
  <p>Time left: <span id="countdown"></span></p>
  <p id="state"></p>
</div>
<script src="https://cdn.socket.io/4.8.1/socket.io.min.js"></script>
<script>
  const expiresAt = new Date("{{expiresAt}}");
  const state = document.getElementById("state");
  const tick = setInterval(() => {
    const s = Math.max(0, Math.floor((expiresAt - Date.now()) / 1000));
    document.getElementById("countdown").textContent = `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
  }, 1000);

  const socket = io("https://api.payitgo.co", { transports: ["websocket"] });
  socket.on("connect", async () => { // again after every reconnect
    const r = await socket.emitWithAck("subscribe", "{{checkoutToken}}");
    if (r.ok) show(r.payment);
  });
  socket.on("payment:update", (e) => show(e.payment)); // the whole payment after each change

  function show(p) {
    switch (p.status) {
      case "partially_paid": state.textContent = `Received ${p.received_amount}. Send the remaining ${(p.crypto_amount - p.received_amount).toFixed(6)} to the same address.`; break;
      case "detecting": clearInterval(tick); state.textContent = `Payment received, confirming (${p.confirmations} of ${p.confirmations_required})…`; break;
      case "confirmed": clearInterval(tick); state.textContent = "Paid. Thank you!"; socket.disconnect(); break;
      case "expired": state.textContent = "This payment expired."; break; // keep listening: it can still be paid late
      case "underpaid": state.textContent = `Only ${p.received_amount} arrived in time. Please contact us.`; break;
      case "failed": state.textContent = "The payment could not be confirmed. Please contact us."; socket.disconnect(); break;
    }
  }
</script>
```

The same in any stack: create the payment on your server, show the fields
from [What to show](#what-to-show), and follow it with the socket. Your
server marks the order paid when its webhook receives `payment.confirmed`.

## The public checkout API

You only need this for a payment page with **no server of its own** behind it
(a static site, a Flutter app): with a server, everything above comes from
`payments.create` and `payments.selectToken`. Everything the hosted page uses
is public and keyed by the payment's
`checkout_token` (also the last part of `checkout_url`). These routes
need no API key, so your page can call them straight from the browser: they
accept requests from any website (CORS). The token only reveals the payer's
view of one payment.

| Method | Path | What it does |
|---|---|---|
| `GET` | [`/checkout/{checkout_token}`](#read-the-payment) | The payer's view of the payment |
| `POST` | [`/checkout/{checkout_token}/token`](#choosing-the-coin) | The customer chooses the coin |
| `POST` | [`/checkout/{checkout_token}/simulate`](#simulate-a-test-payment) | Plays an outcome on a test payment |
| `GET` | [`/tokens`](#every-token) | Every token payitgo supports, without an account |
| socket.io | [`subscribe`, `payment:update`](#follow-it-live) | Live updates: the whole payment on every change |

In a Flutter app, use the Dart library's
[`PayitgoCheckout`](/docs/sdks#flutter-apps), which wraps these calls; in a
React app, the [React checkout widget](/docs/sdk-react).

### Read the payment

`GET /checkout/{checkout_token}` · no authentication

```bash
curl -s https://api.payitgo.co/checkout/6f1e2a9c4d7b3e05a8c1f2d4
```

`200 OK` with the [payment object](/docs/payments#the-payment-object), minus
your private fields: `merchant_id`, `metadata`, `customer_email`,
`webhook_url`, `checkout_token` and `checkout_url` are left out. A payment
[priced in another currency](/docs/payments#prices-in-other-currencies) also
carries `price_amount`, `price_currency` and `price_rate`.

```json
{
  "id": "8f3a2c1e9b7d4a6f05e1c2d3",
  "order_id": "ORD-1001",
  "status": "pending",
  "deposit_address": "TNVfZ8m3qR6xW2pL9kC4hJ7bD5sYt1uAeG",
  "token": "USDT_TRC20",
  "chain": "tron",
  "livemode": false,
  "crypto_amount": "49.9",
  "received_amount": "0",
  "usd_amount": "49.9",
  "amount_due_usd": "49.9",
  "fees_paid_by_customer": false,
  "network_fee": "3.212009",
  "network_fee_usd": "3.22",
  "settlement": "direct",
  "received_usd": "0",
  "rate_locked": "1",
  "deposits": [],
  "expires_at": "2026-10-10T09:15:00.112Z",
  "created_at": "2026-10-10T09:00:00.111Z",
  "network_fee_note": "Final in about a minute.",
  "qr_payload": "TNVfZ8m3qR6xW2pL9kC4hJ7bD5sYt1uAeG",
  "description": "2 x Acme T-shirt",
  "success_url": "https://acme.example/thanks",
  "cancel_url": "https://acme.example/cart"
}
```

Show the customer:

| Show | From |
|---|---|
| The amount to send | `crypto_amount` and `token` (for example "49.9 USDT on Tron") |
| Where to send it | `deposit_address`, and `qr_payload` as a QR code |
| The XRP tag | `destination_tag`, marked as required |
| The deadline | A countdown to `expires_at` |
| A note on fees and timing | `network_fee_note` |
| Progress | `status`, and `received_amount` against `crypto_amount` while `partially_paid` |
| The price in the merchant's currency (when there is one) | `price_amount` and `price_currency` ("€45.50"), next to the dollars due, `amount_due_usd` |

| Status | Message | Cause |
|---|---|---|
| `404` | `payment not found` | No payment has this checkout token |

### Choosing the coin

A payment created without a `token` has `"token": ""`, no address, and
`token_options`: the coins you accept that the amount is large enough for,
each a [TokenInfo](/docs/payments#the-tokeninfo-object) with its
`network_fee_usd` (and `network_fee_bps` for a coin that is converted) and,
when you pass the fees on, its `amount_due_usd` (what the customer pays in
that coin, everything included, so they can pick the cheapest). Show them, and
post the customer's choice:

`POST /checkout/{checkout_token}/token` · no authentication

| Field | Type | Required | Description |
|---|---|---|---|
| `token` | string | yes | One of the payment's `token_options[].token`, such as `USDT_TRC20` |

```bash
curl -s https://api.payitgo.co/checkout/b28d4f61e9a03c75d2e8f1a6/token \
  -H 'Content-Type: application/json' \
  -d '{"token": "USDT_TRC20"}'
```

`200 OK` with the payment, now with its coin, address, locked rate and a
deadline 15 minutes from now:

```json
{
  "id": "c41e7a9d2b05f3e8a6d17c90",
  "order_id": "ORD-1002",
  "status": "pending",
  "deposit_address": "TJr7Wq2nX5kP8sB3vH6mC9dF4gL1tYzRaU",
  "token": "USDT_TRC20",
  "chain": "tron",
  "livemode": false,
  "crypto_amount": "25",
  "received_amount": "0",
  "usd_amount": "25",
  "amount_due_usd": "25",
  "fees_paid_by_customer": false,
  "network_fee": "3.211899",
  "network_fee_usd": "3.22",
  "settlement": "direct",
  "received_usd": "0",
  "rate_locked": "1",
  "deposits": [],
  "expires_at": "2026-10-10T09:16:48.029135472Z",
  "created_at": "2026-10-10T09:00:00.832Z",
  "network_fee_note": "Final in about a minute.",
  "qr_payload": "TJr7Wq2nX5kP8sB3vH6mC9dF4gL1tYzRaU"
}
```

The coin can be chosen once.

| Status | Message | Cause |
|---|---|---|
| `400` | `token is required` | No `token` in the body, or the body is not JSON |
| `400` | `token is not one of this payment's options` | A coin not in `token_options` |
| `400` | `minimum payment in … is $… right now (it is converted on its way to the merchant, and the exchange takes no less)` | The coin needs a bigger payment than this one: it is converted through the exchange on its way to the merchant, which takes no less, and the merchant pays the fees or hides such coins (`hide_below_minimum`). Offer the other coins |
| `400` | `this payment can no longer be paid` | The payment is no longer `pending`, or its hour to choose has passed |
| `404` | `payment not found` | No payment has this checkout token |
| `409` | `the coin for this payment has already been chosen` | A second choice |
| `429` | `rate limit exceeded` | More than 5 choices a second from one IP address |

### Simulate a test payment

`POST /checkout/{checkout_token}/simulate` · no authentication · test payments only

A test payment moves no coins: it is paid by an outcome you choose, and
payitgo plays its deposits, status changes and webhooks a few seconds apart
([Test mode](/docs/testing#simulate-a-payment)). Your server can start one
with its key (`POST /v1/payments/{id}/simulate`); this route lets the payment
page itself do it, for example to put "simulate" buttons on a test payment
while you build your integration.

| Field | Type | Required | Description |
|---|---|---|---|
| `outcome` | string | yes | `confirmed`, `overpaid`, `partially_paid`, `underpaid`, `expired`, `failed`, `wrong_asset` or `paid_late` ([what each does](/docs/testing#outcomes)) |

```bash
curl -s https://api.payitgo.co/checkout/b28d4f61e9a03c75d2e8f1a6/simulate \
  -H 'Content-Type: application/json' \
  -d '{"outcome": "confirmed"}'
```

`200 OK` with the payment, as [`GET /checkout/{checkout_token}`](#read-the-payment)
returns it, with its simulation started:

```json
{
  "id": "c41e7a9d2b05f3e8a6d17c90",
  "order_id": "ORD-1002",
  "status": "pending",
  "deposit_address": "TJr7Wq2nX5kP8sB3vH6mC9dF4gL1tYzRaU",
  "token": "USDT_TRC20",
  "chain": "tron",
  "livemode": false,
  "simulation": { "outcome": "confirmed", "state": "running" },
  "crypto_amount": "25",
  "received_amount": "0",
  "…": "…"
}
```

The payment then moves as it would for real coins: follow it with the
[socket](#follow-it-live). A `payment.simulation` event comes when the
simulation starts (`simulation.state` `running`), a status event for each step
played (`payment.detecting`, `payment.confirmed`…), and `payment.simulation`
again when it is over (`done`): enable your buttons again then. Each event
carries the whole payment, `simulation` included.

The payment must be `pending` or `partially_paid`, have its coin (except for
`expired`), and run no other simulation.

| Status | Message | Cause |
|---|---|---|
| `400` | `outcome is required` | No `outcome` in the body, or the body is not JSON |
| `400` | `simulate must be one of: confirmed, overpaid, …` | An unknown outcome |
| `400` | `only test payments can be simulated: use a test key` | A live payment |
| `400` | `choose the payment's coin first` | No coin yet, for anything but `expired` |
| `400` | `this payment is confirmed: only a pending or partially paid payment can be simulated` | The payment is closed or confirming |
| `400` | `a BTC address only receives BTC: wrong_asset needs a coin on Tron, Ethereum or BNB Smart Chain` | `wrong_asset` on a coin that has no other |
| `404` | `payment not found` | No payment has this checkout token |
| `409` | `a simulation is already running for this payment` | Wait for its `done` |
| `429` | `rate limit exceeded` | More than 5 requests a second from one IP address |

### Every token

`GET /tokens?mode=test` · no authentication

Every token payitgo supports, in live mode or (with `mode=test`) test mode,
priced for a default account. It is a catalogue for a public page. For what
**your** customers may pay with, use the payment's `token_options` or
[`GET /v1/tokens`](/docs/payments#list-tokens) with your key.

```bash
curl -s 'https://api.payitgo.co/tokens?mode=test'
```

`200 OK`: `{"tokens": [TokenInfo, …]}`, the same shape as
[`GET /v1/tokens`](/docs/payments#list-tokens). Test mode lists the same coins
as live.

### Follow it live

The gateway serves [socket.io](https://socket.io) at `https://api.payitgo.co/socket.io`
(use `socket.io-client` 4.x). Connect to the default namespace and emit
`subscribe` with the checkout token, with an acknowledgement: it answers with
the payment as it is. Then every change arrives as a `payment:update` event
carrying the whole payment:

```js
import { io } from "socket.io-client";

const socket = io("https://api.payitgo.co", { transports: ["websocket"] });

socket.on("connect", async () => {                     // again after every reconnect
  const r = await socket.emitWithAck("subscribe", checkoutToken);
  if (r.ok) render(r.payment);
  else console.error(r.error);                         // "payment not found", "checkout token required"
});
socket.on("payment:update", (e) => render(e.payment)); // e.type says what happened
```

| You emit | With | The acknowledgement |
|---|---|---|
| `subscribe` | the checkout token | `{"ok": true, "payment": {…}}`: the payment as [`GET /checkout/{checkout_token}`](#read-the-payment) returns it. An unknown token: `{"ok": false, "error": "payment not found"}`, then the socket is disconnected |
| `unsubscribe` | the checkout token | `{"ok": true}`: no more events for that payment |

One socket can follow several payments: each `subscribe` adds one. Right after
`subscribe` you also get a `payment:update` of type `payment.snapshot` with the
current state.

A `payment:update` event, when the customer's deposit arrived (shortened):

```json
{
  "type": "payment.detecting",
  "payment": {
    "id": "5bdca6322582804b50abaadb",
    "order_id": "RT-1",
    "status": "detecting",
    "deposit_address": "TK5aUacx7EGYu2bGqWZiezvLxxzcSVYsSC",
    "token": "USDT_TRC20",
    "chain": "tron",
    "livemode": false,
    "crypto_amount": "20",
    "received_amount": "20",
    "tx_id": "34a5405206fe1dcf6002d738643f4109c30f4e86fdd075ff95ee88ca1828e231",
    "deposits": [
      { "tx_id": "34a5405206fe1dcf6002d738643f4109c30f4e86fdd075ff95ee88ca1828e231", "amount": "20", "counted": true, "confirmations": 0, "confirmations_required": 20, "status": "pending", "simulated": true, "…": "…" }
    ],
    "expires_at": "2026-10-12T16:37:33.759Z",
    "confirmations": 0,
    "confirmations_required": 20,
    "…": "…"
  },
  "id": "5bdca6322582804b50abaadb",
  "livemode": false,
  "order_id": "RT-1",
  "status": "detecting",
  "tx_id": "34a5405206fe1dcf6002d738643f4109c30f4e86fdd075ff95ee88ca1828e231",
  "crypto_amount": "20",
  "token": "USDT_TRC20",
  "chain": "tron"
}
```

| Field | Type | Description |
|---|---|---|
| `type` | string | What happened (below) |
| `payment` | object | **The whole payment after the change**, as [`GET /checkout/{checkout_token}`](#read-the-payment) returns it. Render from it |
| `id`, `livemode`, `order_id`, `status` | | The payment's id, mode, your order id and its [status](/docs/payments#lifecycle) |
| `tx_id`, `confirmations`, `crypto_amount`, `token`, `chain`, `confirmed_at` | | The event's older, flat form. Here `crypto_amount` is the amount **received** so far (in `payment` it is the amount asked for). Read `payment` instead |

| `type` | When |
|---|---|
| `payment.snapshot` | Right after `subscribe`: the payment as it is |
| `payment.coin_chosen` | The coin was chosen: the payment now has its address, amount and a new `expires_at` |
| `payment.partially_paid`, `payment.detecting`, `payment.confirmed`, `payment.underpaid`, `payment.expired`, `payment.failed`, `payment.wrong_asset`, `payment.late_deposit` | The [webhook](/docs/webhooks) events of the same name |
| `payment.confirmations` | A deposit collected confirmations: show "7 of 20" from `payment.confirmations` and `payment.confirmations_required` |
| `payment.deposit` | A deposit became final, or failed, without changing the status |
| `payment.simulation` | A test payment's [simulation](#simulate-a-test-payment) started, and again when it is over (`payment.simulation.state`) |

Render any type you do not know from its `payment` like the others.

socket.io-client reconnects by itself after a network drop; a drop of under
2 minutes replays the events missed. Subscribe again on every `connect` (as
above): it brings the current state either way. Keep the socket open while the
page is open, also after `expired` and `underpaid` (late payments).

If WebSockets are blocked, drop the `transports` option: socket.io then uses
HTTP long-polling, which the gateway serves too. Only where both are blocked,
poll [`GET /checkout/{checkout_token}`](#read-the-payment) every few seconds
as a last resort.

## Payment links in email or chat

`checkout_url` works anywhere. Remember the deadlines: a payment created with
a coin gives the customer 15 minutes, one created without a coin gives them an
hour to choose and then 15 minutes. Create the payment when the customer is
ready to pay, not when the invoice is drafted.
