# Hosted checkout

Take a payment on a checkout page Vezmo hosts for you, with one server call and a redirect, covering the session fields, the customer details, expiry, theme, success and cancel redirects, app return links, your order number and Idempotency-Key, fulfilling from the webhook and the session limit.

Source: https://vezmo.com/docs/developers/hosted-checkout

## Overview

Hosted checkout is the simplest way to take a payment from your own site. Your server creates a checkout session, you
send your customer to its `url`, they pay on a page Vezmo hosts, and they come back to your site. You write no checkout
page, and card details never touch your servers.

New to the API? [Take your first test payment](/docs/developers/quickstart) walks through it end to end.

## How it works

1. Your server creates a session with `POST /merchant/secure-payments` and gets back a `url`.
2. You redirect your customer to that `url`.
3. They pay on the checkout, which shows your business name and the order.
4. The checkout sends them to your `successUrl` (or `cancelUrl`), and Vezmo sends your server a webhook event.
5. Your server ships the order on the `payment.success` event.

## Create a session

Your server creates a session with `POST /merchant/secure-payments`, sending its access token as `Authorization: Bearer`
and the token, and, if you like, your order number as the `Idempotency-Key` header (see [Your order number](#your-order-number)). The body
holds the order and the customer, as below. Request and response examples:
[Secure payments](https://user.vezmo.com/docs/v1/secure-payments).

The main fields (every field and its limits are in the
[secure payments reference](https://user.vezmo.com/docs/v1/secure-payments)):

- **title**:Required, up to 255 characters. The order's name on the checkout, and on the payment in VezmoPay.
- **amount**:Required. More than 0, at most two decimal places, in major units (`129.00` is $129.00). Your server sets it; the customer can't change it.
- **currency**:Optional, `USD` when left out.
- **description**:Optional, up to 2,000 characters.
- **client**:Optional: your customer. When you send it, `name`, `email`, `country` (a two-letter code like `US`) and `postalCode` are required, or the call answers `400`. `phone`, `company`, `line1`, `line2`, `city` and `state` are optional; a full address helps fraud checks.
- **successUrl and cancelUrl**:Optional. Where the checkout sends the customer afterwards. Both must start with `https://`. See [Redirects](#redirects).
- **ttlMinutes**:Optional. How long the `url` works: 5 to 1440 minutes, 30 when left out.
- **theme**:Optional. `light` (when left out), `dark`, or `auto` to follow the customer's device.
- **descriptor**:Optional, up to 64 characters. Your own label for where the session came from, like a storefront's name. It shows as **Origin** on the payment in VezmoPay instead of the title. The customer never sees it.
- **saveCard**:Optional. Send `true` only when your customer agreed to save their card. With a `client` email, a successful card payment saves the card for their next payment with you, and you get `payment_method.saved`.
- **app.returnUrl**:Optional. For a checkout opened from your mobile app. See [Mobile apps](#mobile-apps).

The answer holds the payment in `data.payment` (its `id`, and the status `INITIATED`) and the checkout in
`data.securePayment`: its `url` and `expiresAt`, when the link stops working.

Send the customer to `data.securePayment.url`, from your server or from your page. The link needs no key or token on
your side.

Fields the endpoint doesn't know are ignored, not refused, so check the spelling of the optional ones.

## Your order number

A session has no field for your own order number or other data of yours (a `metadata` or `orderId` field is ignored).
Two ways to tie a payment to your order:

- **Store `data.payment.id`** with your order when you create the session. The same id comes back on the redirect, in
  every `payment.*` event (`data.paymentId`) and from `GET /merchant/payment/:id`.
- **Use your order number as the `Idempotency-Key`** header (1 to 255 printable ASCII characters; anything else answers
  `400`). Then a retry can't open a second session for the same order.

How `Idempotency-Key` answers:

| You send | The API answers |
| --- | --- |
| The same key and the same body again | The same payment, with a new `url`. Use it when a call timed out or a link expired. |
| The same key with another amount, currency, title or description | `422` "Idempotency key was reused with different parameters". |
| The same key after the order was paid | `409` "Payment is in terminal state CAPTURED; use a new idempotency key to start a new payment." The order is paid: never retry it under a new key. |

Keys are kept per teammate whose API key makes the call, across test and live together. An order number you used as a
key in a test run answers `422` in live, or `409` if that test payment went through: that `409` is about the test
payment, not your live order. Give test runs their own keys, like `test-1042`.

## Redirects

When the payment is done, the checkout sends the customer to your pages with the outcome added to the address:

| Outcome | Where the customer goes |
| --- | --- |
| A card or wallet payment succeeded | `successUrl?paymentId=…&status=success` |
| A bank payment was submitted (it settles in a few business days) | `successUrl?paymentId=…&status=pending` |
| The payment failed or was declined | `cancelUrl?status=failed` |

- An address that already has a query string keeps it; the new values are added.
- Without a `cancelUrl`, a declined customer stays on the checkout and can try another card.
- Without a `successUrl`, the checkout shows **Payment successful** and tells the customer they can close the page.
- The addresses must start with `https://`; anything else answers `400`. They don't need to be in **Trusted origins**
  (those are for the embedded checkout).

**Ship on the webhook, not on the redirect:**
Anyone can open your success address. Use it to show the right page, and ship the order only when your server
receives `payment.success` for that `paymentId`, or after `GET /merchant/payment/:id` reads `"state": "succeeded"`.

## Expiry

The `url` works for `ttlMinutes` (30 by default), until the `expiresAt` time in the response. Your customer can open it
as often as they like until then.

- Once the payment is done, the link only says **This payment is already complete** and takes nothing more.
- After `expiresAt`, it says **This payment link expired** and asks the customer for a fresh checkout link.

When a link expires before the customer pays, create the session again. With the same `Idempotency-Key` and body you get
the same payment with a fresh `url`.

## Theme

`theme` sets light, dark or `auto` (it follows the customer's device). The checkout shows your business name, and the
first letter of it as its mark.

## What your customer sees

(Screenshot: The hosted checkout for Order #1001, $49.99 USD, for ada@example.com, with Northwind Audio's name and the orange Test mode, no real money moves mark: Card or Bank, the card number, expiry date and security code fields, Save this card for one-tap checkout, billing details with the name, country and ZIP code filled in from the session, and the Pay $49.99 button.)

- The order: your `title`, the amount and the currency, and the customer's email when you sent `client`.
- The ways to pay you have turned on that fit the payment, such as cards, Apple Pay and Google Pay, and US bank
  payments. 3D Secure runs inside the checkout when the bank asks for it.
- **Billing details**: the name, country and address you sent in `client` are filled in. The customer completes the
  rest.
- In test mode, an orange **Test mode** mark.

## Mobile apps

To open the checkout from your iPhone or Android app, add the app and its return link under **Developers › Mobile apps**
first, then send `app.returnUrl` with that link (or a path under it, like `acme://vezmo/orders/1042`). After paying, the
customer goes back to your app, whatever the outcome, with `status` (`success`, `pending` or `failed`) and, unless the
payment failed, `paymentId`.

A link that isn't registered answers `400`, with a message that says to add the app under Developers › Mobile apps.
See [Checkout for apps](https://user.vezmo.com/docs/v1/mobile/checkout-for-apps).

## Fulfil from the webhook

Subscribe your endpoint to at least `payment.success` and `payment.failed` ([Webhooks](/docs/developers/webhooks)). When
`payment.success` arrives:

1. Check the signature.
2. Find your order by `data.paymentId`.
3. Check `data.livemode` is `true` (in live), and that `data.amount` and `data.currency` match the order.
4. Ship the order, once: the same event can arrive more than once, so remember its `id`.

A bank payment sends `payment.processing` first and `payment.success` when it settles, a few business days later. Don't
ship on `processing`.

## Limits

- Creating sessions allows **60 a minute from one server address** (and at most 10 a second, 30 in 10 seconds). Past
  that, the call answers `429`: wait a few seconds and retry with backoff. Create a session when the customer is ready to
  pay, not ahead of time.
- Every call also counts toward your API key's own limit. See [Rate limits](/docs/developers/rate-limits).
- While VezmoPay isn't approved for your business, a live key can't create sessions (`403`). A test key can, once your
  business profile is filled in.

## Questions

### Do I need my site in Trusted origins?

No. Trusted origins are for the embedded checkout, which runs inside your page. The hosted checkout is its own page.

### Can I test the redirect on my computer?

The addresses must be `https://` with a real domain name: `http://localhost` is refused. Use an https tunnel to your
computer, or leave the URLs out and watch the webhook.

### The customer closed the checkout without paying

Nothing is charged. The payment reads `"state": "pending"`, and stays so when the link expires. Send them the same `url`
while it works, or create the session again.

## Related

- [Take your first test payment](/docs/developers/quickstart)
- [Choose how to take payments](/docs/developers/integration-options)
- [Webhooks](/docs/developers/webhooks)
- [Hosted checkout in the API reference](https://user.vezmo.com/docs/v1/hosted-checkout)
