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

# Collect with Next.js

> Collect payments with the Dollr API from Next.js Route Handlers or Server Actions.

<Note>
  **Invoices vs orders:** These guides walk through **invoices** (formal billing, line items, publish). The collect flow is the same for **orders** — use `/v1/orders/*` instead of `/v1/invoices/*` and set `source_type: "ORDER"` on the checkout session. See [Orders](/api/orders) and [Parties & counterparties](/concepts/parties-and-counterparties).

  **Hosted checkout:** To skip party, invoice, session, and execute steps entirely, see [Hosted checkout](/guides/hosted-checkout).
</Note>

<Info>
  **Fastest path for Next.js:** [Hosted checkout](/guides/hosted-checkout) — create a checkout source server-side and redirect to `url`. No Stripe or payment-account setup required.
</Info>

Call the Dollr API from **server-only** code for API-embedded flows. Never expose your Client Secret in the browser.

<Note>
  **API Reference:** [Obtain token](/api-reference/jwt/client-obtain-token) · [Collect](/api-reference/executions/collect)
</Note>

## Prerequisites

* Next.js 14+ (App Router)
* Environment variables in `.env.local` (not `NEXT_PUBLIC_` except Stripe publishable key for card UI):

```
DOLLR_CLIENT_ID=...
DOLLR_CLIENT_SECRET=...
```

## Option A — Hosted checkout (recommended)

```typescript theme={null}
// app/actions/checkout.ts
"use server";

const BASE = "https://api.heydollr.app";

async function dollrHeaders() {
  const { access_token } = await fetch(`${BASE}/v1/jwt/client/obtain/token`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      client_id: process.env.DOLLR_CLIENT_ID,
      client_secret: process.env.DOLLR_CLIENT_SECRET,
    }),
  }).then((r) => r.json());
  return { Authorization: `Bearer ${access_token}`, "Content-Type": "application/json" };
}

export async function createHostedCheckout() {
  const headers = await dollrHeaders();
  const checkout = await fetch(`${BASE}/v1/checkouts/create`, {
    method: "POST",
    headers,
    body: JSON.stringify({
      mode: "HOSTED",
      source_kind: "ORDER",
      party_name: "Amara Kamara",
      party_phone: "231771234567",
      currency: "USD",
      items: [{ name: "T-Shirt", currency: "USD", amount: 25 }],
      success_url: `${process.env.SITE_URL}/checkout/success`,
      cancel_url: `${process.env.SITE_URL}/checkout/cancel`,
    }),
  }).then((r) => r.json());

  return checkout.url; // redirect customer here
}
```

On your success page, verify server-side with `GET /v1/status/source` before fulfilling — see [Hosted checkout](/guides/hosted-checkout#fulfillment-security).

## Option B — API-embedded collection

<Steps>
  <Step title="Route Handler — obtain token">
    ```typescript theme={null}
    // app/api/dollr/token/route.ts
    const BASE = "https://api.heydollr.app";

    export async function POST() {
      const res = await fetch(`${BASE}/v1/jwt/client/obtain/token`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          client_id: process.env.DOLLR_CLIENT_ID,
          client_secret: process.env.DOLLR_CLIENT_SECRET,
        }),
        cache: "no-store",
      });
      const data = await res.json();
      return Response.json({ access_token: data.access_token });
    }
    ```
  </Step>

  <Step title="Server Action — full collect flow">
    Follow [Quick Start](/quickstart) steps: party → counterparty → invoice → publish → session → payment account → execute. Or use [Collect with card](/guides/collect-with-card) for Stripe Elements.
  </Step>

  <Step title="Poll from a Route Handler">
    ```typescript theme={null}
    // app/api/dollr/status/[referenceId]/route.ts
    export async function GET(
      _req: Request,
      context: { params: Promise<{ referenceId: string }> }
    ) {
      const { referenceId } = await context.params;
      const headers = await dollrHeaders();
      const res = await fetch(
        `https://api.heydollr.app/v1/status/collection/${referenceId}`,
        { headers, cache: "no-store" }
      );
      return Response.json(await res.json());
    }
    ```
  </Step>
</Steps>

<Warning>
  Do not expose `DOLLR_CLIENT_SECRET` to Client Components. Use Route Handlers or Server Actions only.
</Warning>

## Try it yourself

<CardGroup cols={3}>
  <Card title="Hosted checkout" icon="window-maximize" href="/guides/hosted-checkout">
    Mobile money and card on a Dollr-hosted page — fastest path.
  </Card>

  <Card title="Orders" icon="box" href="/api/orders">
    Same collect flow with `source_type: ORDER` and `/v1/orders/*`.
  </Card>

  <Card title="Direct checkout" icon="cart-shopping" href="/guides/collect-via-checkout">
    One API call to create source — API-embedded flow.
  </Card>
</CardGroup>

## Related

* [Hosted checkout](/guides/hosted-checkout) · [Orders](/api/orders)
* [Collect via checkout](/guides/collect-via-checkout)
* [Choose your integration](/guides/choose-integration)
* [Error handling](/guides/error-handling)
* [Sessions & executions](/concepts/sessions-and-executions)
