> ## Documentation Index
> Fetch the complete documentation index at: https://docs.borga.is/llms.txt
> Use this file to discover all available pages before exploring further.

# Add hosted checkout to your website

> Redirect customers to Borga's PCI-safe hosted checkout page using payment sessions. No card data touches your server or affects your PCI scope.

Hosted checkout lets you accept payments without handling any card data yourself. You create a payment session from your server, redirect the customer to the resulting URL on `checkout.borga.is`, and Borga handles the entire checkout experience — including card entry, 3D Secure authentication, Apple Pay, Google Pay, and krafa. When the customer finishes, Borga sends them back to a URL you specify. Your integration never sees raw card numbers, which significantly reduces your PCI scope.

<Steps>
  <Step title="Create a hosted payment session">
    From your server, create a payment session with `mode: "hosted"`. The `return_url` is required — it is where Borga redirects the customer after checkout completes (successfully or not). The `cancel_url` is where the customer lands if they click the back button in the checkout UI.

    <CodeGroup>
      ```bash curl theme={null}
      curl --request POST \
        --url https://api.borga.is/v1/payment_sessions \
        --header "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \
        --header "X-Merchant-Id: mer_YOUR_MERCHANT_ID" \
        --header "Content-Type: application/json" \
        --data '{
          "mode": "hosted",
          "amount": 12900,
          "currency": "ISK",
          "customer_email": "buyer@example.is",
          "external_reference": "order_5678",
          "return_url": "https://yoursite.is/order/complete",
          "cancel_url": "https://yoursite.is/cart",
          "enabled_methods": ["card", "apple_pay", "google_pay", "krafa"],
          "locale": "is"
        }'
      ```

      ```javascript Node.js theme={null}
      const session = await fetch('https://api.borga.is/v1/payment_sessions', {
        method: 'POST',
        headers: {
          'Authorization': 'Bearer sk_test_YOUR_SECRET_KEY',
          'X-Merchant-Id': 'mer_YOUR_MERCHANT_ID',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          mode: 'hosted',
          amount: 12900,
          currency: 'ISK',
          customer_email: 'buyer@example.is',
          external_reference: 'order_5678',
          return_url: 'https://yoursite.is/order/complete',
          cancel_url: 'https://yoursite.is/cart',
          enabled_methods: ['card', 'apple_pay', 'google_pay', 'krafa'],
          locale: 'is',
        }),
      }).then(r => r.json());
      ```
    </CodeGroup>

    The response includes a `url` field and a session `id`:

    ```json theme={null}
    {
      "id": "pses_01HXYZ9876ABCDEF",
      "url": "https://checkout.borga.is/pses_01HXYZ9876ABCDEF",
      "mode": "hosted",
      "status": "open",
      "amount": 12900,
      "currency": "ISK",
      "expires_at": "2026-04-29T11:00:00Z"
    }
    ```

    <Note>
      Hosted checkout sessions expire after one hour. If the customer does not complete checkout within that window, create a new session. The `expires_at` field tells you when the session will become invalid.
    </Note>
  </Step>

  <Step title="Redirect the customer">
    Once you have the session, redirect the customer's browser to `session.url`. Do this server-side with an HTTP redirect or client-side by setting `window.location.href`.

    ```javascript Node.js (Express) theme={null}
    app.post('/checkout', async (req, res) => {
      const session = await createPaymentSession(req.body);
      res.redirect(303, session.url);
    });
    ```

    ```javascript Browser theme={null}
    // After receiving session.url from your server
    window.location.href = session.url;
    ```

    The customer is now on `checkout.borga.is`. They see a Borga-branded checkout form with the payment methods you enabled. You do not need to do anything further until they return.
  </Step>

  <Step title="Handle the return">
    When checkout completes — whether the payment succeeded, failed, or was declined — Borga redirects the customer to your `return_url`. Always verify the payment status server-side before taking any action; never trust the redirect alone.

    ```javascript Node.js (Express) theme={null}
    app.get('/order/complete', async (req, res) => {
      // The payment ID may be passed as a query param if you appended it to return_url,
      // or you can look it up via external_reference.
      const paymentId = req.query.payment_id;

      const payment = await fetch(
        `https://api.borga.is/v1/payments/${paymentId}`,
        {
          headers: {
            'Authorization': `Bearer ${process.env.BORGA_SECRET_KEY}`,
            'X-Merchant-Id': process.env.BORGA_MERCHANT_ID,
          },
        }
      ).then(r => r.json());

      if (payment.status === 'succeeded') {
        // Fulfil the order
        res.render('order-success', { payment });
      } else {
        // Show the customer an appropriate message
        res.render('order-pending', { payment });
      }
    });
    ```

    <Tip>
      Append the payment ID to your `return_url` as a query parameter when creating the session (e.g. `https://yoursite.is/order/complete?payment_id=pay_01HXYZ`) so your return handler can retrieve it without additional lookups.
    </Tip>
  </Step>

  <Step title="Handle cancellation">
    If the customer clicks **Back** or closes the checkout before completing payment, Borga redirects them to your `cancel_url`. Use this URL to return the customer to your cart or product page so they can try again.

    ```javascript Node.js (Express) theme={null}
    app.get('/cart', (req, res) => {
      // Restore the cart state and let the customer retry
      res.render('cart', { message: 'Your payment was not completed. You can try again below.' });
    });
    ```

    No payment is created when a customer cancels — there is nothing to clean up on the Borga side.
  </Step>
</Steps>

## Session options reference

| Field                 | Required | Description                                                                |
| --------------------- | -------- | -------------------------------------------------------------------------- |
| `mode`                | Yes      | Set to `"hosted"` for redirect-based checkout.                             |
| `return_url`          | Yes      | Where to redirect after checkout completes or fails.                       |
| `cancel_url`          | No       | Where to redirect if the customer cancels.                                 |
| `amount`              | No       | Required if not attaching an existing `payment` id.                        |
| `currency`            | No       | Defaults to `ISK`.                                                         |
| `payment`             | No       | Attach to an existing payment object instead of creating a new one.        |
| `customer`            | No       | Customer ID to associate with the session and any saved payment method.    |
| `customer_email`      | No       | Pre-fill the email field in the checkout form.                             |
| `external_reference`  | No       | Your own reference ID, carried through to the resulting payment.           |
| `enabled_methods`     | No       | Restrict which methods appear, e.g. `["card", "krafa"]`. Omit to show all. |
| `save_payment_method` | No       | If `true`, the card used is saved to the customer for future charges.      |
| `locale`              | No       | Checkout UI language. Use `"is"` for Icelandic or `"en"` for English.      |

## Locale support

Pass a `locale` value to present the checkout UI in the customer's language. Borga currently supports `"is"` (Icelandic, default) and `"en"` (English). The locale affects all text in the checkout form, including error messages and payment method labels.

<Note>
  Hosted mode and embedded mode are distinct. In hosted mode you redirect the customer away from your site to `checkout.borga.is`. In embedded mode, Borga renders a checkout component directly inside your page using `borga.js` — your site's origin must be added to **Settings → Embed Origins** in the dashboard. See the [Payments concept guide](/concepts/payments) for an overview of both modes.
</Note>

## Next steps

* [Accept a payment](/guides/accept-payment) — create the payment object before the session
* [Webhooks](/guides/webhooks) — receive `payment.succeeded` events asynchronously instead of relying on the return redirect
* [Subscriptions](/guides/subscriptions) — set up recurring billing using saved payment methods
