callCash()by riverrr
Get early access
Open as .md

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#

TypeScript
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.

URLWhat to put there
AcceptThe page customers land on after paying, for example https://example.co.za/paid
DeclineThe page for a declined payment
RedirectWhere Netcash sends customers in other cases
NotifyYour 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#

TypeScript
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:

TypeScript
// 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:

FieldValue
m1Your 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.
m2The software vendor key
p2callCash's own reference for this payment (gateway_ref, CC and 16 letters and digits)
p3Your description, or your reference, cut to 50 characters
p4The amount in rand, two decimals
BudgetY
m4The callCash payment id (comes back as Extra1)
m5Your reference, cut to 50 characters (Extra2)
m9The customer's email, when you gave one
m10ref=<your reference>

The Notify route#

TypeScriptapp/api/netcash/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.