# Framework examples

## Next.js (App Router)

**The client, server only:**

```ts
// lib/cash.ts (import this only from route handlers and other server code)
import { CallCash } from "callcash";
import { firestoreStore } from "callcash/firestore";
import { db } from "./firebase-admin";

export const cash = new CallCash({
  gateways: {
    yoco: { secretKey: process.env.YOCO_SECRET_KEY!, webhookSecret: process.env.YOCO_WEBHOOK_SECRET },
    netcash: { serviceKey: process.env.NETCASH_PAYNOW_KEY!, test: process.env.NETCASH_TEST === "1" },
  },
  routes: { card: ["yoco", "netcash"], default: ["netcash"] },
  store: firestoreStore(db),
  returnUrl: `${process.env.APP_URL}/paid`,
});
```

**Start a payment:**

```ts
// app/api/pay/route.ts
import { NextResponse } from "next/server";
import { isCallCashError } from "callcash";
import { cash } from "@/lib/cash";

export async function POST(req: Request) {
  const { orderId } = await req.json();
  const order = await loadOrder(orderId); // your code: the price comes from your database, never the browser

  try {
    const p = await cash.payments.create({ amount: order.total, reference: order.id, email: order.email, metadata: { order: order.id } });
    return NextResponse.json(p.checkout_url ? { url: p.checkout_url } : { form: p.checkout_form });
  } catch (e) {
    if (isCallCashError(e)) return NextResponse.json({ error: e.code }, { status: e.status });
    throw e;
  }
}
```

**In the browser,** redirect to `url`, or post `form` (the `postToNetcash` function on the [Netcash page](https://callcash.riverrr.co.za/docs/netcash#taking-a-payment)).

**The webhooks:**

```ts
// app/api/yoco/webhook/route.ts
import { cash } from "@/lib/cash";
import { onCash } from "@/lib/on-cash";

export async function POST(req: Request) {
  return (await cash.webhooks.handle("yoco", req, onCash)).response;
}
```

```ts
// app/api/netcash/notify/route.ts
import { cash } from "@/lib/cash";
import { onCash } from "@/lib/on-cash";

export async function POST(req: Request) {
  return (await cash.webhooks.handle("netcash", req, onCash)).response;
}
```

**The handler:**

```ts
// lib/on-cash.ts
import type { CashEvent } from "callcash";

export async function onCash(event: CashEvent) {
  if (event.type === "payment.paid") await markOrderPaid(event.reference); // a no-op if already paid
}
```

**The return page** shows "confirming your payment" and reads the order's status from your database. It never marks anything paid.

## Express

`webhooks.handle` takes a standard `Request`. Give Express the raw body on the webhook routes and build one:

```ts
import express from "express";
import { cash } from "./cash.js";
import { onCash } from "./on-cash.js";

const app = express();

function toRequest(req: express.Request): Request {
  const headers = new Headers();
  for (const [k, v] of Object.entries(req.headers)) if (typeof v === "string") headers.set(k, v);
  return new Request(`https://${req.get("host")}${req.originalUrl}`, { method: "POST", headers, body: req.body });
}

async function send(res: express.Response, response: Response) {
  res.status(response.status).type("application/json").send(await response.text());
}

// raw body: the Yoco signature is checked against the exact bytes
app.post("/webhooks/yoco", express.raw({ type: "*/*" }), async (req, res) => {
  await send(res, (await cash.webhooks.handle("yoco", toRequest(req), onCash)).response);
});

app.post("/webhooks/netcash", express.raw({ type: "*/*" }), async (req, res) => {
  await send(res, (await cash.webhooks.handle("netcash", toRequest(req), onCash)).response);
});

app.post("/pay", express.json(), async (req, res) => {
  const order = await loadOrder(req.body.orderId);
  const p = await cash.payments.create({ amount: order.total, reference: order.id });
  res.json(p.checkout_url ? { url: p.checkout_url } : { form: p.checkout_form });
});
```

Register the webhook routes **before** any global `express.json()`, or the body will already be parsed and the Yoco signature will not match.
