# Create and style hosted checkout pages

> Add a checkout domain, design the default style with a live preview, create a payment page for an amount, copy its link and follow its returns.

Source: https://www.coritan.com/docs/organizations/commerce/checkout-pages/

In the dashboard:

- /dashboard/organizations/…/commerce/checkout-pages: https://www.coritan.com/dashboard/organizations

**Checkout pages** is where your store creates a payment page for one amount, with no products and no cart. A page is served on a domain of your own and styled by you, down to its CSS, and nothing on it names Coritan. The customer sees the amount and a billing form, then pays on your own MAEF parent site, the *payment site*. Nobody enters a card number on the page.

The section has three cards. **Checkout domains** holds the names that serve your pages. **Default style** designs how every page looks, with a preview. **Pages** creates pages and lists them. Each step below has the matching API call beside it, and [Create hosted checkout pages with the Merchant API](/docs/organizations/storefront/commerce-checkout-pages/) describes every field.

A hosted checkout page makes no store order, no invoice and no entry on your balance. You learn that a page is paid from its status, and you refund at your payment site.

## Before you begin

- Coritan's staff turn checkout pages on for a store. Until they do, the page shows **Checkout pages are not turned on for this store**, and its **Open a ticket** asks [support](https://www.coritan.com/dashboard/support) to turn them on.
- You need the Billing role or higher to open **Checkout pages**. Adding a domain, saving the style and creating or cancelling a page needs the Admin or Owner role. A member below that sees a note that changing these needs an owner or admin.
- Add at least one payment page for the mode and currency you charge in, and turn it on, as [Take payments through your MAEF parent site](/docs/organizations/commerce/payments/) describes. You do not need to turn on payment pages at checkout.
- Have a hostname you can add DNS records to, such as `pay.example.com`. It serves checkout pages only, so never use the address of your storefront. It can be on a different domain from the rest of your site.
- Register your checkout host with your payment site, the way you registered your storefront. The payment site must send customers back to the checkout host. We have not tested this against a live payment site, so make a small test payment first. [Tell the payment site where to send the customer](/docs/organizations/storefront/commerce-checkout-pages/#tell-the-payment-site-where-to-send-the-customer) says why.

## Add a checkout domain

1. In the [dashboard](https://www.coritan.com/dashboard/organizations), open the organization, then **Commerce**, then **Money**, then **Checkout pages**.
2. On the **Checkout domains** card, select **Add domain…**. When the card has no domains yet, the same button is in its empty state.
3. Enter the **Domain**, such as `pay.example.com`, without `https://` or a path. Select **Add domain**.
4. The domain opens in a drawer with its **DNS records**. Publish them at the DNS provider of the domain:
   - A `TXT` record at `_coritan-checkout.<your domain>`. Its value is a token we made for this domain. It proves you own the name.
   - A `CNAME` record at your domain, when the table lists one. It points the name at us. The table shows it only when our staff have set a target for it.
5. Select **Verify now**. A new record can take a while to reach us, so if the drawer says the record is missing, wait a few minutes and try again.

API: `POST /commerce/checkout-domains` with `{"hostname": "pay.example.com"}`, then `POST /commerce/checkout-domains/{hostname}/verify`.

The table shows each domain's **Status**: **Waiting for DNS** until the record is found, then **Verified**. After a domain is verified, we order its certificate. Its **Certificate** shows **Being issued**, then **Active**, and **After it is verified** before that. Pages open on the domain once the certificate is **Active**.

You can add up to 10 domains. The menu on a row has **Show DNS records**, **Verify now** and **Remove domain…**. Keep the records in place, because pages stop loading on the domain if they go.

We refuse a name that is not a hostname, a name the platform or App Deployment uses, your storefront's domain or address, and a name another organization holds. The message under **Domain** says which.

### Remove a domain

1. Open the row's menu and select **Remove domain…**.
2. Type the domain's name, then select **Remove domain**.

Pages made on the domain stop loading, and a customer who opens one sees "Page not found". While a customer started to pay on a page there in the last 30 minutes, we refuse, and you remove the domain once that payment is done.

API: `DELETE /commerce/checkout-domains/{hostname}`.

## Design the default style

The **Default style** card sets how every new page looks. A page keeps the style it was made with, so changing the default moves no page you made already.

1. On the **Default style** card, change what you want. The preview on the right draws the page a customer sees, in the style on screen, before you save. Choose **Form**, **Processing**, **Paid**, **Failed**, **Expired** or **Cancelled** under **Page state** to see each page.
2. Under **Brand**, choose the **Theme**, the **Corners**, the **Font** and the **Layout**. A font is a name for a set of fonts the customer's device already has: the page downloads none.
3. Enter the colours as hex values such as `#0F2A57`: **Accent** for the button and the links, **Background** behind the card, **Card** behind the words and **Text**. Leave one empty for its default. We change a text or link colour that would be hard to read.
4. Enter the **Logo address**, an `https` address. We read the image once and serve it from the checkout page itself. A logo we cannot read is left out.
5. Under **Words**, change the **Heading**, **Message**, **Amount label**, **Billing title**, **Button label**, **Success title**, **Success message** and **Footer text**. An empty one uses the words the field shows as its placeholder. An empty message or footer is left out of the page.
6. Select **Add a link** to add up to 3 footer links, each with a label of up to 30 characters and an address that starts with `https://` or `mailto:`.
7. Under **Advanced**, write your own **Custom CSS** of up to 20,000 characters. The **Class names to style** tip lists the classes you can target. The CSS cannot load files or fonts, so `url()`, `@import` and `@font-face` are refused.
8. Select **Save style**. A message under a field says what to fix. The card's footer says how many changes are waiting, **Discard** drops them, and **Reset to defaults…** clears the saved style after you select **Reset style**.

API: `PATCH /commerce/store` with `{"settings": {"checkout_style": {...}}}`, and `null` clears the style. `POST /commerce/checkout-pages/preview` draws the preview. [Style the page](/docs/organizations/storefront/commerce-checkout-pages/#style-the-page) lists every key, the CSS we accept, the class names and the CSS variables.

When the CSS has something we refuse, the preview stops and the message names the first rule, property or value. Remove it and the preview follows.

## Create a page

1. On the **Pages** card, select **New checkout page…**. The button is in the card's header, or in its empty state when you have no pages.
2. Enter the **Amount**, in the currency you choose, such as `25.00`, and choose the **Currency** from the store's.
3. Optionally, enter a **Reference** of up to 255 characters, such as an invoice number. It comes back in the API and in events.
4. Optionally, enter a **Description** of up to 120 characters. It names the payment line on your payment site. The customer never sees it on the checkout page.
5. Enter the **Return address**, which is required. It is where the customer came from, such as the invoice page on your site. We send the customer back there when they have paid or choose to leave. It is an `https` address of up to 2,000 characters, with no spaces and no sign-in details.
6. Optionally, enter a **Success address**. A customer who has paid goes there instead of to the return address.
7. Optionally, enter a **Cancel address**. A customer who leaves without paying goes there instead of to the return address.
8. Choose the **Domain** when you have more than one verified domain. With one, the dialog shows it.
9. Set **Expires after**, a whole number of hours from 1 to 720. It is 24 unless you change it.
10. Choose the **Mode**. **Test** uses your payment page for test payments. **Live** uses your live payment page, and is available once the store is live.
11. Optionally, select **Style for this page only** and enter a JSON object of style keys, such as `{"accent_color": "#0f766e", "heading": "Pay your invoice"}`. It wins over the default style for this page.
12. Select **Create page**.

API: `POST /commerce/checkout-pages`, with `return_url` always and `success_url` and `cancel_url` when you fill them in. The dashboard sends the amount in minor units, such as `2500`. A request with no `return_url` answers `422`. [Send the customer back](/docs/organizations/storefront/commerce-checkout-pages/#send-the-customer-back) lists each field.

The **Checkout page created** dialog shows the page's address. Copy it and send it to your customer, or select **Open page** to see it. The page is in the **Pages** list with the status **Open**.

To copy the address later, open the row's menu and select **Copy link**, or **Open page**.

### Where the customer goes

| Situation | The customer goes to |
| --- | --- |
| The customer pays | The success address, or the return address when you left it empty. We redirect them at once, so they see no receipt. |
| The customer opens a paid page again | A receipt with a "Continue" link to the same address. |
| The customer leaves from the billing form | The "Cancel and go back" link goes to the cancel address, or to the return address when you left it empty. |
| The page is confirming, failed, held for review, expired or cancelled | A "Return to site" link to the return address. |

We add `checkout_page=<id>&status=<status>` to each address. Your server must not mark anything paid from that query, because anyone can type it. Read the page's status or take the `commerce.checkout_page.paid` event, as [Confirm the payment on your server](/docs/organizations/storefront/commerce-checkout-pages/#confirm-the-payment-on-your-server) describes.

## Follow a page

The **Pages** table lists each page with its **Status**, **Amount**, **Domain**, when it was paid or expires, and when it was **Created**. Filter it by **All**, **Open**, **Processing**, **Paid**, **Failed**, **Cancelled** or **Expired**. **Load more** shows older pages.

| Status | What it means |
| --- | --- |
| **Open** | No customer has started to pay. |
| **Processing** | The customer sent the form and we are waiting for your payment site to send them back. |
| **Paid** | Your payment site reported the payment. |
| **Failed** | The payment did not go through. The customer can open the page and try again. |
| **Cancelled** | You cancelled the page. |
| **Expired** | The page's time passed. |

API: `GET /commerce/checkout-pages`, filtered by `status`.

### View returns

1. Open the row's menu and select **View returns…**, or select the row.
2. The drawer shows the page's **Address**, **Status**, **Amount**, **Reference**, **Return address**, **Customer email**, **Payment attempts** and, once paid, the **Order at the payment site**. It also shows the **Success address** and the **Cancel address** when the page has them.
3. Under **Returns**, read what the payment site sent back, newest first. **Older** loads more.

An **Unmatched** return is a payment that could not pay the page: it came after you cancelled the page, a second time after the page was paid, or for another amount. Refund it at your payment site. A return that is **Processed** changed the page, and **Ignored** changed nothing.

API: `GET /commerce/checkout-pages/{checkout_page_id}/events`.

### Cancel a page

1. Open the row's menu and select **Cancel page…**.
2. Select **Cancel page** to confirm, or **Keep page** to leave it.

A customer who opens the page then sees that it was cancelled, with a "Return to site" link to the return address. You can cancel an open, failed or expired page, and a processing page whose customer last tried more than 30 minutes ago. Before that, the dashboard says a customer is paying on it.

> [!CAUTION]
> A customer who pays after you cancel a page is still charged by your payment site. The return appears under **Returns** as **Unmatched**, and you refund the payment there.

API: `POST /commerce/checkout-pages/{checkout_page_id}/cancel`.

## Refund a payment

Hosted checkout pages move no money, so you refund at your payment site. Nothing here records a refund, because a page is not a store order.

## Result

- **Checkout domains** shows your domain as **Verified**, with its certificate **Active**.
- The **Pages** list shows each page you create, and its link opens a page in your style, with the amount and the billing form.
- A customer who pays comes back to your checkout host, the page becomes **Paid**, the customer lands on your success address or return address, and **View returns…** shows the return as **Processed**. Your server learns of it from the page's status or the `commerce.checkout_page.paid` event, as [Confirm the payment on your server](/docs/organizations/storefront/commerce-checkout-pages/#confirm-the-payment-on-your-server) describes.

## Troubleshooting

**The Checkout pages section** is for owners, admins and billing
: Your role is below Billing. Ask an owner to change it.

**Checkout pages are not turned on for this store**
: Coritan's staff turn checkout pages on. Select **Open a ticket** to ask. Until then you can read the page, but no domain or page can be added and the style cannot be saved.

**Add domain…** or **New checkout page…** is missing
: Only owners and admins see them, once staff have turned checkout pages on. **Add domain…** also goes when you have 10 domains. **New checkout page…** needs a verified domain.

**Verify now** says the record is missing
: We did not find the `TXT` record at `_coritan-checkout.<your domain>` with the token the drawer shows. Check the name and the value at your DNS provider, wait a few minutes, and try again. You can check a domain 12 times an hour.

The certificate stays **Being issued**
: The name must point at us before the certificate can be issued. Publish the `CNAME` record when the table lists one, and check again in a few minutes. When it lists none, ask [support](https://www.coritan.com/dashboard/support) where to point the name.

Creating a page says there is no payment page
: The store has no payment page that is turned on for that mode and currency. **Open Payments** in the message takes you to [Payments](/docs/organizations/commerce/payments/), where you add one or turn it on.

**Return address** says "Enter the address the customer came from."
: The field is required. Enter an `https` address of up to 2,000 characters, with no spaces and no sign-in details. The **Success address** and **Cancel address** take the same kind of address and can stay empty.

A customer paid but did not land on your site
: Open **View returns…**. A page that is still **Processing** has not had your payment site's return. Once the page is **Paid**, the customer goes to the success address, or to the return address when you left it empty.

**Live** is not available
: Your store is not live yet. Use **Test**, or take the store live as [Take your store live](/docs/organizations/commerce/merchant-profile/) describes.

The preview says a field is not valid
: A colour is not a `#` and six hex digits, an address does not start with `https://`, or a text is too long. The message under the field says which.

A customer paid and the page stays **Processing**
: We have not had your payment site's return. **View returns…** shows an empty list when none arrived. Check that the payment site sends customers back to your checkout host and not to your storefront. A payment site that sends customers only to the address it holds for your storefront needs your checkout host registered with it.

A return is **Unmatched**
: The payment could not pay the page. Refund it at your payment site, and check the return's reason in the API's events.

Removing a domain says a customer is paying
: A customer started to pay on a page there in the last 30 minutes. Remove the domain once that payment is done.

## Related

- [Create hosted checkout pages with the Merchant API](/docs/organizations/storefront/commerce-checkout-pages/)
- [Take payments through your MAEF parent site](/docs/organizations/commerce/payments/)
- [Run your store from the Commerce tab](/docs/organizations/commerce/)
- [Receive organization webhooks](/docs/organizations/webhooks/)

## With the API

`GET https://api.coritan.com/api/v1/orgs/{org_slug}/commerce/checkout-pages` lists the store's pages and `POST` creates one. The domains are under `/commerce/checkout-domains`.

```bash
curl "https://api.coritan.com/api/v1/orgs/acme/commerce/checkout-pages?status=open" \
  -H "Authorization: Bearer $CORITAN_TOKEN"
```
