# Accept a payment

Collect money from a customer with an M-Pesa STK Push: what to send, what comes back, and what the customer sees.

Charge a customer. Your server decides the amount, the customer's handset rings, they enter their M-Pesa PIN, and the funds settle to your till.

## Send the push

```ts title="charge.ts"
import { Paylod } from "@paylod/node";

const paylod = new Paylod(process.env.PAYLOD_API_KEY!);

const outcome = await paylod.collectAndWait({
  amount: 1500,                     // whole KES, from YOUR order record
  phone: "0712345678",
  accountReference: "INV-2041",     // YOUR correlation id — comes back on the result
  description: "Order #2041",
  metadata: { orderId: "2041" },    // opaque, stored with the payment
});

if (outcome.paid) {
  await fulfil("2041", outcome.receipt);
} else {
  await notify(outcome.message);
}
```

`collectAndWait()` sends the push, then polls until the payment settles. `outcome.paid` is the only branch you need: on the other side, `outcome.message` is a decoded sentence you can show the customer.

Need the `paymentId` straight away instead of waiting? Use `collect()`, which returns as soon as the prompt is on the handset. See [Handle the result](/docs/guides/handle-the-result).

## What M-Pesa insists on

- **Whole shillings.** `amount` is an integer, 1 to 150,000. Decimals are rejected.
- **A Kenyan number.** `0712…`, `254712…` and `+254712…` all work; the SDK normalises them.
- **One prompt at a time.** A customer already looking at a prompt cannot be sent another.

`accountReference` is capped at 12 characters, `description` at 64. Both are validated locally, before the request leaves your process.

## What `accountReference` actually is

It is a **correlation id you choose** — an invoice or order number. paylod stores it on the payment and hands it back to you as `accountRef` on `GET /status/:id` and on the webhook, so it is how you tie an M-Pesa payment to the order in your own database.

What it is *not* is a message to the customer. Safaricom only surfaces it on a **Paybill** (`CustomerPayBillOnline`), where it is the account number the payer is paying to. On a **Till / Buy Goods** shortcode (`CustomerBuyGoodsOnline`) Safaricom ignores it for display: the payer sees your business name and the amount, and nothing else — exactly what you see paying at any Kenyan shop. Most small merchants are on a Till, so assume the customer will never read it.

> [!warn] Never put anything the customer needs to see into `accountReference`. `description` is what shows up on the STK prompt. And never put anything *secret* in it either — on a Paybill it is visible to the payer.

## Idempotency is automatic

The SDK generates an `Idempotency-Key` for every call, so a network retry can never double-charge.

To retry the *same* charge later, persist the key and pass it back:

```ts title="retry.ts"
const ack = await paylod.collect({ amount: 1500, phone: "0712345678" });
await db.orders.update("2041", { idempotencyKey: ack.idempotencyKey });

// Later. Same key, same body: replays the original response, no second prompt.
await paylod.collect({
  amount: 1500,
  phone: "0712345678",
  idempotencyKey: ack.idempotencyKey,
});
```

## Never take the amount from the client

> [!warn] The amount must come from your own order record, on your own server. If a browser or mobile app holds both the API key and the amount, a payer can intercept the request and pay less. That is the one way to be undercut, and it is entirely avoidable. See [Secure integration](/docs/guides/security).

## Next

[Handle the result](/docs/guides/handle-the-result) covers webhooks, and what to check before you fulfil.
