# Accept payments with hosted checkout (/docs/guides/accept-payments)

A **checkout session** is a single-use Creddot payment page for one order. You create it on your server, redirect the customer, and confirm the payment. Creddot collects card details, so your servers never handle card numbers and stay out of PCI DSS scope.

## Create a session [#create-a-session]

`POST /checkout/sessions` with the `collections:create` permission.

<TypeTable
  type={{
    amountMinor: { type: 'string', description: 'Amount in kobo, e.g. "250000" for ₦2,500.00.', required: true },
    currency: { type: 'string', description: 'ISO currency code. NGN.', required: true },
    reference: { type: 'string', description: 'Your unique id for this order (max 80). Used to look the session up later.', required: true },
    description: { type: 'string', description: 'Shown to the customer on the checkout page.' },
    customerName: { type: 'string', description: 'Prefills the name; the customer is not asked for it.' },
    customerEmail: { type: 'string', description: 'Prefills the email; receipts go here.' },
    redirectUrl: { type: 'string', description: 'HTTPS page the customer returns to after paying. Sent unchanged, so include your order id in it.' },
    expiresAt: { type: 'string', description: 'ISO date-time. Defaults to 30 minutes after creation.' },
  }}
/>

Redirect the customer to `checkoutUrl` from the response.

## Session states [#session-states]

`GET /checkout/sessions/{id or your reference}` returns the session and its `status`:

| Status | Meaning |
| --- | --- |
| `OPEN` | Waiting for payment. A failed attempt keeps it open so the customer can try again. |
| `PAID` | A payment succeeded. The session no longer accepts payments. |
| `EXPIRED` | `expiresAt` passed without a successful payment. Create a new session to retry. |
| `CLOSED` | Disabled without a successful payment. |

`payment` holds the successful payment, or the latest attempt while the session is open, with its Creddot `id`, `reference`, `status`, `channel` and `feeMinor`.

## Confirm before you fulfil [#confirm-before-you-fulfil]

Fulfil the order only after **one** of these, and make fulfilment idempotent because you may see both:

1. A `transaction.success` [webhook](/docs/guides/webhooks) whose `data.merchantReference` is your order reference.
2. `GET /checkout/sessions/{reference}` returning `PAID`.

Then check that the amount and currency match what you charged.

```js title="Return page (Node.js)"
const res = await fetch(`https://api.creddot.com/api/v1/checkout/sessions/${encodeURIComponent(reference)}`, {
  headers: { 'x-api-key': process.env.CREDDOT_API_KEY, 'x-api-secret': process.env.CREDDOT_API_SECRET },
});
const session = await res.json();
if (session.status === 'PAID' && session.amountMinor === order.totalKobo && session.currency === 'NGN') {
  await markOrderPaid(order.id, session.payment.id); // safe to call twice
}
```

## Finding the payment later [#finding-the-payment-later]

Payments made through a session get a Creddot reference starting with `pl_`. You don't need to store it: `GET /transactions/by-reference/{your reference}` finds the session's payment for you.

## Good to know [#good-to-know]

- Each session takes one successful payment. Create a new session for every order or retry after expiry.
- Reusing a `reference` returns `409`. Use the same `x-idempotency-key` when retrying a create after a timeout.
- Payment methods shown on checkout follow your settings in the dashboard.
