Embedded checkout

Open Borga checkout as a modal on your own page with borga.js, using a publishable key and a per-session client secret.

Embedded checkout keeps the payer on your page. Your server creates a session in embedded mode and hands two values to the browser: the session id and a one-time client_secret. The borga.js script opens a modal that loads the checkout, and calls back into your page when the payment completes.

Under the hood the modal loads checkout.borga.is in an iframe, and the card fields live in the card processor's iframe inside it. Your page never sees card data, and neither does Borga: the PCI boundary is the same as hosted checkout.

  1. Create a publishable key

    Under Developers, create a publishable key and add the origins your checkout pages run on, for example https://shop.example.is. Origins are scheme and host only, no path. localhost and 127.0.0.1 are always allowed for development.

  2. Create an embedded session on your server

    Use your secret key. Set mode to embedded and origin to the page that will open the modal. The response includes a client_secret (pcs_…) that is only ever returned once.

    Node.jsts
    // POST /api/checkout  (your server)
    export async function POST(request: Request) {
      const { cartId } = (await request.json()) as { cartId: string };
      const amount = await totalForCart(cartId);
    
      const session = await borga.paymentSessions.create({
        amount,
        currency: "ISK",
        mode: "embedded",
        origin: "https://yoursite.is", // the page that will open the modal
        external_reference: cartId,
      });
    
      // Only these two values go to the browser. Never the secret key.
      return Response.json({
        sessionId: session.id,
        clientSecret: session.client_secret,
      });
    }
    
    declare function totalForCart(cartId: string): Promise<number>;

    Return only id and client_secret to the browser. The amount is locked on the server; nothing the browser sends can change it.

  3. Open the modal in the browser

    Load borga.js from https://js.borga.is/v1/borga.js, construct a client with your publishable key, and call checkout.open with the two values from your server.

    HTML + JShtml
    <script src="https://js.borga.is/v1/borga.js" async></script>
    <button id="pay">Pay 12.900 kr.</button>
    
    <script>
      const borga = new Borga("pk_test_…");
    
      document.getElementById("pay").addEventListener("click", async () => {
        // Ask your server for a session (see the server snippet).
        const { sessionId, clientSecret } = await fetch("/api/checkout", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ cartId: "cart_123" }),
        }).then((r) => r.json());
    
        borga.checkout.open({
          sessionId,
          clientSecret,
          onComplete: ({ paymentId }) => {
            window.location.href = `/thank-you?payment=${paymentId}`;
          },
          onCancel: () => console.log("Payer closed the modal"),
          onError: (err) => console.error(err.message),
        });
      });
    </script>

    open() returns a handle with close() if you need to dismiss the modal yourself. Pressing Escape or clicking the backdrop closes it and fires onCancel.

  4. Fulfil from the webhook

    onComplete fires when the checkout reports success and is the right moment to move the payer to a confirmation page. Ship the order from the payment.succeeded webhook, which fires whether or not the browser is still open.

Callbacks

CallbackWhenPayload
onCompleteThe payment succeeded{ paymentId, status: "succeeded" }
onCancelThe payer closed the modal without payingnone
onErrorThe session could not load, or the payment failed in a way the payer cannot retry{ message, code? }

Creating sessions from the browser

A publishable key can also create embedded sessions directly from the browser by calling POST /v1/payment_sessions with Authorization: Bearer pk_test_…. The browser's Origin header must be in the key's allowed origins, and only mode: "embedded" is accepted. Since the amount then comes from the browser, use this only when the server-side amount is verified later, for example by comparing the webhook payload against your order before fulfilling.

3-D Secure

Most challenges complete inside the modal. When an issuer insists on a full-page redirect, the checkout returns to checkout.borga.is/…/3ds-return and the modal continues automatically. You do not need to handle this.

Content Security Policy

If your site sets a CSP, allow these origins on the page that opens checkout:

script-src  'self' https://js.borga.is;
frame-src   https://checkout.borga.is;
connect-src 'self' https://api.borga.is;

Add https://*.straumur.is to frame-src and connect-src if you see the card form blocked; the card processor loads its own iframe inside the checkout page.

React

@borga/react wraps the script loader in a hook, useBorgaCheckout, that returns open and close. It loads borga.js once per page and is marked "use client". The package is built from the Borga monorepo alongside @borga/node; until it is on npm, install it from the repository or copy the hook, which is about 80 lines with no dependencies beyond React.

Framework-agnostic

borga.js is a plain script that sets window.Borga. Vue, Svelte, Angular and vanilla pages use it the same way as the HTML example above. Load it only in the browser: importing it during server rendering throws, because it touches document.