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
- Access Token (server-side) and Space ID
- Next.js 13+ with the App Router
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:
- Verify the session and update your server-side state
- Redirect the user to your success UI page
Create app/api/checkout/success/route.ts:
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.
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