# Integration steps

> 🧪 **Beta.** Hosted Payment Fields is in Beta and available on request.


## 1. Create a card session (backend)

Create a session exactly as you do for the Cashier, with a single card method. HPF uses a single-card-method session:

```bash
curl -X POST "https://api.omno.com/v3/public/cashier/session" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "currency": "EUR",
    "paymentTypes": ["CARD"],
    "customer": { "externalUserId": "3183818381183" }
  }'
```

Return the `sessionId` to your frontend. **HPF does not use the Cashier token.**

## 2. Add four containers to your checkout page

Each is a plain element you style however you like — Omno mounts an iframe inside it.

```html
<label>Cardholder name</label>
<div id="om-cardholder"></div>

<label>Card number</label>
<div id="om-card-number"></div>

<label>Expiry</label>
<div id="om-expiry"></div>

<label>CVV</label>
<div id="om-cvv"></div>

<button id="pay" disabled>Pay</button>
```

> ℹ️ **Sizing:** each container must be tall enough for an input control (≈ 48–56px). The mounted iframe fills its container (`height: 100%`), so a container that's too short clips the input.


## 3. Initialise the SDK and mount the fields

```typescript
import CashierSDK, { CashierEmitEvent } from "@omno-payment/checkout-js";

const cashier = new CashierSDK({ baseUrl: "https://checkout.omno.com" });

const fields = cashier.mountFields({
  sessionId,
  fields: {
    cardholder: { containerId: "om-cardholder" },
    cardNumber: { containerId: "om-card-number" },
    expiry:     { containerId: "om-expiry" },
    cvv:        { containerId: "om-cvv" },
  },
});
```

No `token` is passed — see the [`mountFields` reference](/hosted-payment-fields/mount-fields).

## 4. Enable Pay on field validity

```typescript
const validity = { cardNumber: false, expiry: false, cvv: false, cardholder: false };

cashier.on(CashierEmitEvent.FIELD_VALIDITY_CHANGE, ({ field, valid }) => {
  validity[field] = valid;
  payButton.disabled = !Object.values(validity).every(Boolean);
});
```

See [Events](/hosted-payment-fields/events).

## 5. Submit and handle the result

`submit()` orchestrates everything on the Omno origin; the card values never reach your code.

```typescript
payButton.addEventListener("click", () => {
  fields.submit({ saveCard: false /*, amount: 49.99 */ });
});

cashier.on(CashierEmitEvent.PAYMENT_SUCCESS, (data) => {/* show success */});
cashier.on(CashierEmitEvent.PAYMENT_FAILED,  (data) => {/* show decline */});
cashier.on(CashierEmitEvent.PAYMENT_PENDING, (data) => {/* show spinner */});
```

## 6. Reconcile by webhook

Reconcile the final outcome server-side via your Omno [webhook](/guides/concepts/webhooks), exactly as for the Cashier. SDK events are UI-only.

## 7. Register your checkout domain

Field routes are served with a `frame-ancestors` policy. Ask your Omno integration team to register your checkout domain before testing, or the browser will block the field iframes.

## Build it →

- [`mountFields` reference](/hosted-payment-fields/mount-fields) · [Styling](/hosted-payment-fields/styling) · [Events](/hosted-payment-fields/events) · [Limitations](/hosted-payment-fields/limitations)