# Netcash

Netcash Pay Now takes cards, instant EFT, bank EFT, cash at a shop and vouchers. It works differently from Yoco: your customer's browser posts a form to Netcash, and Netcash tells your server afterwards with a notification that is **not signed**.

## What you need

- A Netcash account with **Pay Now** switched on. Netcash only gives you test mode once the account exists.
- Your **Pay Now service key** (in the Netcash dashboard).
- Access to **NetConnector** in your Netcash login. If you cannot see it, ask your Netcash advisor to enable it for your user.

## Configure

```ts
export const cash = new CallCash({
  gateways: {
    netcash: { serviceKey: process.env.NETCASH_PAYNOW_KEY!, test: process.env.NETCASH_TEST === "1" },
  },
  store,
});
```

Set `test: true` while "Make test mode active" is ticked in your Netcash dashboard, so payments are marked `mode: "test"`. If you are a Netcash ISV, add `vendorKey`; otherwise Netcash's default software vendor key is used.

## The four URLs

Netcash does not take return addresses from the payment. You type four URLs into the dashboard: **Account profile → NetConnector → Pay Now**.

| URL | What to put there |
|---|---|
| Accept | The page customers land on after paying, for example `https://example.co.za/paid` |
| Decline | The page for a declined payment |
| Redirect | Where Netcash sends customers in other cases |
| Notify | Your Netcash webhook route, for example `https://example.co.za/api/netcash/notify` |

**Leave out any question mark.** callCash sends `ref=<your reference>` in the `m10` field, and Netcash adds it to the Accept, Decline and Redirect addresses itself.

Without the Notify URL you never hear about cash or EFT payments that complete later.

## Taking a payment

```ts
const payment = await cash.payments.create({ amount: 250, reference: "order_1042", gateway: "netcash" });
// payment.checkout_url is null; payment.checkout_form is { action, fields }
```

Nothing is sent to Netcash at this point. `checkout_form` is the form the customer's browser must post. Send it to the browser as JSON and submit it there:

```ts
// in the browser
function postToNetcash({ action, fields }: { action: string; fields: Record<string, string> }) {
  const form = document.createElement("form");
  form.method = "POST";
  form.action = action;
  for (const [name, value] of Object.entries(fields)) {
    const input = document.createElement("input");
    input.type = "hidden";
    input.name = name;
    input.value = value;
    form.appendChild(input);
  }
  document.body.appendChild(form);
  form.submit();
}
```

The fields callCash fills in:

| Field | Value |
|---|---|
| `m1` | Your Pay Now service key. Pay Now expects it in the form; build the form on the server so the key is not in your JavaScript bundle. |
| `m2` | The software vendor key |
| `p2` | callCash's own reference for this payment (`gateway_ref`, `CC` and 16 letters and digits) |
| `p3` | Your `description`, or your reference, cut to 50 characters |
| `p4` | The amount in rand, two decimals |
| `Budget` | `Y` |
| `m4` | The callCash payment id (comes back as Extra1) |
| `m5` | Your reference, cut to 50 characters (Extra2) |
| `m9` | The customer's email, when you gave one |
| `m10` | `ref=<your reference>` |

## The Notify route

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

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

## Why every notification is checked with Netcash

The Notify post carries no signature. Anyone who learns your Notify URL could post `TransactionAccepted=true` to it. So callCash treats the post as a hint and asks Netcash directly:

1. It reads `Reference` and `RequestTrace` from the post.
2. It calls Netcash's `TransactionStatus/Check` with the `RequestTrace`.
3. It believes only Netcash's answer: the reference must be this payment's, and the amount must match. A different amount gives `payment.mismatch` and the payment is not marked paid.

If Netcash's status check cannot be reached, or does not answer in JSON, `handle` responds **503** and Netcash sends the notification again later. Nothing is marked paid on a failed check.

Netcash retries notifications until it gets a 200, so the same payment can arrive more than once. callCash turns repeats into one event.

Cash at a shop and EFT payments can notify much later than card payments, sometimes the next business day. Show the customer a pending state, not a failure.

## How the customer paid

Netcash's `Method` code is turned into callCash's `method`: 1 card, 2 bank EFT, 3 cash, 4 instant EFT, 10 voucher. Any other code becomes `other`. This list is from our own integration and is still to be confirmed against live data.

## The R100 limit

A new Pay Now service starts with a **R100 transaction limit**. Above it, the card option simply does not show on Netcash's page, and nothing errors. Test with amounts under R100, and ask your Netcash advisor to raise the limit before you launch.

## Refunds

Netcash has no refund call that callCash has verified, so `refund()` throws `refund_unsupported`. Refund in the Netcash portal, then record it. See [Refunds](https://callcash.riverrr.co.za/docs/refunds).
