# Checkout

> Send customers to the hosted checkout page, and handle their return safely.

Checkout is Devzity's hosted payment page. You never build a payment form.

1. Your server creates a Payment and receives `checkout_url`.
2. You send the customer there — an HTTP redirect on the web, or opening the URL from a mobile app.
3. The customer chooses bKash or Nagad, enters their number if needed, and pays.
4. If you supplied a `return_url`, checkout offers a **Return to merchant** button (or **Return to app** for native links). It never redirects automatically.
5. Your server learns the outcome from a [webhook](https://docs.devzity.com/v1/webhooks.md) or by retrieving the payment.

## `return_url`

Use an absolute `https://` URL for websites, or your app's link for native apps: a custom scheme (`myapp://payment-return`, `com.example.shop://done`) or an https App Link / Universal Link. `javascript:`, `data:`, `file:` and `intent:` are always rejected. Any query parameters you include are preserved.

Devzity appends two parameters:

| Parameter | Value |
| --- | --- |
| `devzity_payment_id` | The Payment id. |
| `devzity_status` | `PAID` or `CANCELLED`. |

> **Warning:** **Never fulfil an order because the return URL said `PAID`.** The return status is a hint, never proof of payment. Anyone can open your return URL with `devzity_status=PAID` in it. When the customer returns, look up the payment with your own backend (`GET /v1/payments/{id}`), or rely on the `payment.paid` webhook, before you fulfil anything. The SDKs name it `statusHint` to make this hard to forget.

Correct handling, in any language:

```text
on return with devzity_status=PAID:
    refresh the order/payment from YOUR backend   ← never fulfil here
```

Your backend confirms with `GET /v1/payments/{id}`, or by having received the verified `payment.paid` webhook.

## If the customer never comes back

Closing the tab, pressing back, or a killed app does **not** mean the payment failed — they may have already paid. Don't mark anything failed and don't tell the customer to pay again; wait for the webhook, or ask your backend for the payment's status.

## Test checkout

A test payment's checkout shows a **TEST MODE — No real money will be moved** banner, a fake receiving number (`01XXXXXXXXX`), and buttons to simulate success or cancellation. See [Testing](https://docs.devzity.com/v1/testing.md).
