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

# Create Checkout Session (Next.js App Router)

> Step-by-step guide to implement a Checkout Session endpoint using the Next.js App Router API routes

## Overview

This guide shows how to build a Next.js App Router API route that creates a Checkout Session and returns a `redirectUrl` to send your customer to Monime's hosted checkout.

<Info>
  Base URL: `https://api.monime.io` · Auth: Bearer token · Required headers: `Monime-Space-Id`, `Idempotency-Key`
</Info>

## Prerequisites

* Access Token (server-side) and Space ID
  * Create tokens: <a href="/dashboard-guide/personal-access-tokens">
    Personal Access Tokens</a>
  * Find your Space ID: <a href="/dashboard-guide/create-a-business-account">
    Create a Monime Space</a>

    (see Developer section → Space list)
* Next.js 13+ with the App Router

### Environment variables

Create `.env.local`:

```bash theme={null}
MONIME_ACCESS_TOKEN=mon_test_xxxxxxxxxxxxxxxxxxxxxxxxx
MONIME_SPACE_ID=spc-xxxxxxxxxxxxxxxx
```

Never expose tokens in the browser.

## Implement the API route

Create `app/api/checkout/route.ts`:

```ts theme={null}
import { randomUUID } from 'crypto'

type LineItem = {
  type: 'custom'
  name: string
  price: { currency: 'SLE'; value: number }
  quantity: number
  description?: string
  reference?: string
}

export async function POST(req: Request) {
  const token = process.env.MONIME_ACCESS_TOKEN!
  const spaceId = process.env.MONIME_SPACE_ID!

  const { name, orderId, lineItems, metadata, callbackState } = (await req.json()) as {
    name: string
    orderId: string
    lineItems: LineItem[]
    metadata?: Record<string, string>
    callbackState?: string
  }

  if (!Array.isArray(lineItems) || lineItems.length === 0) {
    return new Response(JSON.stringify({ error: 'lineItems is required' }), { status: 400 })
  }

  // Build redirect URLs on the server to prevent tampering
  const appUrl = process.env.APP_URL || 'https://example.com'
  const successUrl = `${appUrl}/api/checkout/success?orderId=${encodeURIComponent(orderId)}`
  const cancelUrl = `${appUrl}/checkout/cancelled?orderId=${encodeURIComponent(orderId)}`

  const res = await fetch('https://api.monime.io/v1/checkout-sessions', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Monime-Space-Id': spaceId,
      'Idempotency-Key': randomUUID(),
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ name, successUrl, cancelUrl, lineItems, metadata, callbackState }),
  })

  if (!res.ok) {
    const error = await res.text()
    return new Response(JSON.stringify({ error }), { status: 500 })
  }

  const data = await res.json()

  // TODO: Persist mapping of orderId -> checkoutSessionId for later verification
  // await db.orders.update(orderId, { checkoutSessionId: data?.result?.id })

  return Response.json({ redirectUrl: data?.result?.redirectUrl, sessionId: data?.result?.id })
}
```

Notes:

* Amount is derived from `lineItems` (`price.value × quantity`, where `value` is in SLE minor units).
* `callbackState` is optional and is echoed back via callbacks for correlation.
* Always send a new `Idempotency-Key` (e.g., UUID) per logical attempt.

## Call the endpoint from your UI

Example client-side handler using fetch:

```ts theme={null}
function toMinorUnits(amountSLE: number) {
  return Math.round(amountSLE * 100)
}

async function startCheckout() {
  const payload = {
    name: 'Order #1001',
    orderId: 'ord_1001',
    lineItems: [
      { type: 'custom', name: 'Starter bundle', price: { currency: 'SLE', value: toMinorUnits(25) }, quantity: 1 },
    ],
    metadata: { cartId: 'cart_123' },
    callbackState: 'state_cart_123',
  }

  const res = await fetch('/api/checkout', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  })

  if (!res.ok) {
    console.error('Checkout failed', await res.text())
    return
  }

  const { redirectUrl } = await res.json()
  if (redirectUrl) window.location.href = redirectUrl
}
```

## Handle successUrl with a server redirect

Point the `successUrl` to a Next.js API route that will:

1. Verify the session and update your server-side state
2. Redirect the user to your success UI page

Create `app/api/checkout/success/route.ts`:

```ts theme={null}
import { NextResponse } from 'next/server'

export async function POST(req: Request) {
  const token = process.env.MONIME_ACCESS_TOKEN!
  const spaceId = process.env.MONIME_SPACE_ID!

  const url = new URL(req.url)
  const orderId = url.searchParams.get('orderId')
  if (!orderId) return new Response('Missing orderId', { status: 400 })

  // 1) Load order and get stored checkoutSessionId
  // const order = await db.orders.findOne(orderId)
  // if (!order?.checkoutSessionId) return new Response('Order not found', { status: 404 })

  // 2) Optionally verify status with the API
  // const res = await fetch(`https://api.monime.io/v1/checkout-sessions/${order.checkoutSessionId}`, {
  //   headers: {
  //     Authorization: `Bearer ${token}`,
  //     'Monime-Space-Id': spaceId,
  //   },
  // })
  // const data = await res.json()
  // const status = data?.result?.status // e.g., 'completed'
  // await db.orders.update(orderId, { status })

  // 3) Redirect to your success UI
  const uiUrl = new URL(`/checkout/success?orderId=${encodeURIComponent(orderId)}`, process.env.APP_URL || 'https://example.com')
  return NextResponse.redirect(uiUrl)
}
```

Notes:

* You can embed extra context in the `successUrl` query (e.g., `orderId`).
* Persist the session ID when creating it, so you can look it up here and confirm status.
* Prefer webhooks for authoritative status; the success redirect is for UX continuity.

## Webhooks (recommended)

Use webhooks to reliably update your system about payment outcomes, even if the customer closes the browser before redirection.

<CardGroup cols={2}>
  <Card title="Webhook Introduction" icon="globe" href="/guide/webhook/introduction">
    How webhooks work on Monime
  </Card>

  <Card title="HMAC Verification" icon="shield-check" href="/guide/webhook/hmac-verification">
    Verify webhook signatures securely
  </Card>
</CardGroup>

## Troubleshooting

* 400 Validation: ensure `lineItems` is non-empty and URLs are absolute
* 401/403 Auth: use a valid server-side token and correct `Monime-Space-Id`
* 409 Conflict: reuse `Idempotency-Key` only to retry the same logical request

## Next steps

<CardGroup cols={2}>
  <Card title="API Reference" icon="book" href="/apis/versions/caph-2025-06-20/checkout-session/create-checkout-session">
    Request schema and responses
  </Card>

  <Card title="Full Checkout Session Guide" icon="code" href="/guide/checkout-session/create-checkout-session">
    Deep dive, options, and multi-stack examples
  </Card>
</CardGroup>
