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

# Checkout Node SDK

> @meru.app/checkout-node is the server-side SDK for Meru Checkout. Create checkout sessions and reconcile their status from your backend with your secret key.

`@meru.app/checkout-node` is the server-side SDK for Meru Checkout. Use it on your
backend to create checkout sessions and reconcile their status.

<Warning>
  This SDK holds your **secret key** (`sk_live_…`). Use it **only on your backend**.
  The browser/React side never sees it — it only receives the session `id`/`url`.
</Warning>

## Install

```bash theme={null}
npm install @meru.app/checkout-node
```

<Note>
  Requires **Node 18+** (uses the global `fetch`). Zero dependencies.
</Note>

## Construct a client

```ts theme={null}
import { MeruCheckout } from "@meru.app/checkout-node";

const meru = new MeruCheckout(process.env.MERU_SECRET_KEY);
```

`apiKey` falls back to `process.env.MERU_SECRET_KEY`, so you can also construct with
no arguments. Pass an options object to override defaults:

```ts theme={null}
const meru = new MeruCheckout({
  apiKey: process.env.MERU_SECRET_KEY,
  baseUrl: "https://checkout-api.meru.com", // default
  timeoutMs: 20000, // default
});
```

## Create a session

<Steps>
  <Step title="Create the session">
    Call `meru.checkouts.create(...)`. The returned session includes a `url` to redirect
    the customer to, and an `id` to hand to the [React SDK](/api-reference/checkout/react-sdk).

    ```ts theme={null}
    const session = await meru.checkouts.create({
      amountFiat: 50,
      reference: "order_123",
      paymentMethod: "qr-bolivia",
      successUrl: "https://your-site.com/success",
      cancelUrl: "https://your-site.com/cancel",
    });

    // Redirect the customer to session.url, or send session.id to the React SDK.
    console.log(session.id, session.url);
    ```
  </Step>

  <Step title="Expose it from your backend">
    A typical Express handler creates the session and returns the `id`/`url` to your
    frontend.

    ```ts theme={null}
    import express from "express";
    import { MeruCheckout } from "@meru.app/checkout-node";

    const app = express();
    const meru = new MeruCheckout(process.env.MERU_SECRET_KEY);

    app.post("/api/checkout", express.json(), async (req, res) => {
      const session = await meru.checkouts.create({
        amountFiat: 50,
        reference: req.body.orderId,
        paymentMethod: "qr-bolivia",
      });

      res.json({ id: session.id, url: session.url });
    });
    ```
  </Step>
</Steps>

<Card title="Embed the session you created here" icon="react" href="/api-reference/checkout/react-sdk">
  Pass `session.id` to `@meru.app/checkout-react` to embed or redirect on the
  frontend.
</Card>

## Idempotency

Pass an `idempotencyKey` to make session creation safe to retry. Retrying with the
same key returns the **original** session instead of creating a duplicate — useful
when a network error leaves you unsure whether the first call succeeded.

<Steps>
  <Step title="Create with an idempotency key">
    ```ts theme={null}
    const session = await meru.checkouts.create(
      { amountFiat: 50, reference: "order_123" },
      { idempotencyKey: "order_123" },
    );
    ```
  </Step>

  <Step title="Retry safely">
    If the call fails or times out, retry with the **same** key. The SDK sends an
    `Idempotency-Key` header (scoped to your account), so you get the same session back
    rather than a second charge.

    ```ts theme={null}
    const retried = await meru.checkouts.create(
      { amountFiat: 50, reference: "order_123" },
      { idempotencyKey: "order_123" },
    );

    // retried.id === session.id
    ```
  </Step>
</Steps>

<Note>
  Use a stable, unique value per logical order (for example, your order id) as the
  idempotency key.
</Note>

## Reconcile status

Read a session with `meru.checkouts.retrieve(id)` and treat `succeeded` as paid.

```ts theme={null}
const session = await meru.checkouts.retrieve("cmqx8r2f00012abcde34fghij");

if (session.status === "succeeded") {
  // Fulfill the order.
}
```

<Warning>
  This server-side check is the **authoritative** confirmation. The React SDK's
  `success` event is for UX only — always verify with `retrieve` (or your own
  reconciliation) before fulfilling anything of value.
</Warning>

## Errors

Any non-2xx response throws a `MeruApiError` with a `.status` (HTTP status number)
and `.body` (the parsed error payload).

```ts theme={null}
import { MeruCheckout, MeruApiError } from "@meru.app/checkout-node";

const meru = new MeruCheckout(process.env.MERU_SECRET_KEY);

try {
  const session = await meru.checkouts.create({ amountFiat: 50 });
} catch (err) {
  if (err instanceof MeruApiError) {
    console.error("Meru API error", err.status, err.body);
  } else {
    throw err; // network/timeout or unexpected error
  }
}
```

## Configuration

Construct the client with an options object to override defaults.

<ParamField path="apiKey" type="string">
  Your Checkout secret key (`sk_live_…`). Falls back to
  `process.env.MERU_SECRET_KEY`.
</ParamField>

<ParamField path="baseUrl" type="string" default="https://checkout-api.meru.com">
  Checkout API base URL. Override for sandbox or self-hosted environments.
</ParamField>

<ParamField path="timeoutMs" type="number" default="20000">
  Request timeout in milliseconds.
</ParamField>

<ParamField path="fetch" type="typeof fetch">
  Custom `fetch` implementation. Defaults to the global `fetch`.
</ParamField>

```ts theme={null}
const meru = new MeruCheckout({
  apiKey: process.env.MERU_SECRET_KEY,
  baseUrl: "https://checkout-api.sandbox.meru.com",
});
```

## API reference

| Method                                    | HTTP                 | Returns                        |
| ----------------------------------------- | -------------------- | ------------------------------ |
| `meru.checkouts.create(params, options?)` | `POST /v1/checkouts` | `CheckoutSession` (with `url`) |
| `meru.checkouts.retrieve(id)`             | `GET /checkouts/:id` | `CheckoutSession`              |

### `CreateCheckoutParams`

<ParamField path="amountFiat" type="number">
  Amount in fiat. Required for `crypto-exchanges`; optional for `qr-bolivia` (the
  form can capture it).
</ParamField>

<ParamField path="fiatCurrency" type="string" default="USD">
  ISO currency code for `amountFiat`.
</ParamField>

<ParamField path="paymentMethod" type="'crypto-exchanges' | 'qr-bolivia'">
  Force a payment method. Omit to let the customer choose.
</ParamField>

<ParamField path="token" type="'USDC' | 'USDT'">
  Stablecoin to receive for crypto payments.
</ParamField>

<ParamField path="description" type="string">
  Description shown on the checkout.
</ParamField>

<ParamField path="reference" type="string">
  Your order id. Echoed back on the session.
</ParamField>

<ParamField path="clientReference" type="string">
  External partner identifier.
</ParamField>

<ParamField path="successUrl" type="string">
  URL to redirect to after a confirmed payment.
</ParamField>

<ParamField path="cancelUrl" type="string">
  URL to redirect/return to on cancel.
</ParamField>

<ParamField path="metadata" type="Record<string, unknown>">
  Arbitrary key/value pairs, echoed back on reads.
</ParamField>

### `CheckoutSession`

<ResponseField name="id" type="string">
  The session identifier.
</ResponseField>

<ResponseField name="url" type="string">
  The hosted checkout URL. Returned by `create` only.
</ResponseField>

<ResponseField name="reference" type="string">
  Your order id, echoed back.
</ResponseField>

<ResponseField name="description" type="string">
  The description shown on the checkout.
</ResponseField>

<ResponseField name="amountFiat" type="string | null">
  The fiat amount as a string, or `null`.
</ResponseField>

<ResponseField name="fiatCurrency" type="string">
  ISO currency code for `amountFiat`.
</ResponseField>

<ResponseField name="paymentMethod" type="'mesh' | 'qr_bolivia' | null">
  The selected method, or `null` when the customer still has to choose.
</ResponseField>

<ResponseField name="availableMethods" type="string[]">
  Methods offered for this session.
</ResponseField>

<ResponseField name="status" type="string">
  One of `created`, `pending`, `processing`, `succeeded`, `failed`, `expired`.
</ResponseField>

<ResponseField name="txHash" type="string | null">
  On-chain transaction hash for crypto payments, or `null`.
</ResponseField>

<ResponseField name="qr" type="object | null">
  QR payment details. `null` until a QR is generated. See the
  [`qr` object](/api-reference/checkout/payment-status#the-qr-object).
</ResponseField>

<ResponseField name="customer" type="object">
  Customer details captured on the checkout page (`name`, `email`, `document`).
</ResponseField>

<ResponseField name="createdAt" type="string">
  ISO-8601 creation timestamp.
</ResponseField>

<ResponseField name="expiresAt" type="string">
  ISO-8601 expiration timestamp.
</ResponseField>

<ResponseField name="paidAt" type="string | null">
  ISO-8601 timestamp when payment succeeded, or `null`.
</ResponseField>

## Related

<CardGroup cols={2}>
  <Card title="React SDK" icon="react" href="/api-reference/checkout/react-sdk">
    Embed or redirect to the session on the frontend.
  </Card>

  <Card title="Create a session (REST)" icon="plus" href="/api-reference/checkout/create-session">
    The underlying `POST /v1/checkouts` reference.
  </Card>

  <Card title="Payment status (REST)" icon="magnifying-glass" href="/api-reference/checkout/payment-status">
    The underlying `GET /checkouts/{id}` reference.
  </Card>
</CardGroup>
