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.
Base URL: https://api.monime.io · Auth: Bearer token · Required headers: Monime-Space-Id, Idempotency-Key

Prerequisites

Environment variables

Create .env.local:
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:
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:
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:
import { NextResponse } from 'next/server'

export async function GET(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.
Use webhooks to reliably update your system about payment outcomes, even if the customer closes the browser before redirection.

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