Payments
Create#
const payment = await cash.payments.create({
amount: 250, // rand, two decimals at most
reference: "order_1042", // your own order number, 1 to 100 characters
email: "customer@example.co.za", // optional
description: "Order 1042", // optional; 50 characters at most on Netcash
metadata: { order: "1042" }, // optional; values become strings
method: "card", // optional; steers routing
gateway: "yoco", // optional; skips routing
return_url: "https://example.co.za/paid", // optional; overrides config.returnUrl
});Then send the customer to pay:
if (payment.checkout_url) {
// Yoco: redirect to the link
} else if (payment.checkout_form) {
// Netcash: send { action, fields } to the browser and post it as a form (see the Netcash page)
}The payment is stored before any gateway hears of it, so a notification can never arrive for a payment callCash does not know.
Amounts#
amount is rand: 250 is R250.00 and 99.5 is R99.50. Anything that is not a positive number with at most two decimals throws amount_invalid before a gateway is called. If your app stores cents, divide by 100 going in.
References#
Your reference decides what happens when the same order is paid for twice:
| The last payment with this reference is… | What create does |
|---|---|
| paid | Throws reference_used (409). Use a new reference. |
| pending, under 30 minutes old, same gateway, same amount | Returns that same payment. A double click opens one checkout. |
| pending, under 30 minutes old, same gateway, different amount | Throws reference_used. Use a new reference to charge a different amount. |
| pending and older, or on another gateway | Creates a new payment. |
| failed | Creates a new payment. A failure frees the reference. |
Routing#
routes in the config says which gateways to try, in order, for each way of paying:
routes: { card: ["yoco", "netcash"], default: ["netcash"] }How a payment picks its gateway:
gatewayon the call wins, and skips routing.- Otherwise
routes[method]when you pass amethod. - Otherwise
routes.default. - Otherwise every gateway you configured, in the order you listed them.
- With no real gateways, the test gateway.
Failover#
Only a gateway that is down hands over to the next one in the route: a network error, a timeout (15 seconds), a 5xx or a 429. Those throw gateway_down, which is retryable.
A gateway that refuses the payment (a 4xx, gateway_rejected) is not tried elsewhere, because the next one would most likely refuse it too. The payment is left failed with the reason in failure, and the error is thrown.
Failover only helps if you are approved on more than one gateway. That is mostly agencies and bigger shops.
Get, list, update#
const p = await cash.payments.get("pay_…"); // not_found if there is no such payment
const recent = await cash.payments.list({ status: "paid", gateway: "yoco", limit: 50 });
const owed = await cash.payments.list({ undelivered: true }); // payments with events your handler has not taken yet
await cash.payments.update("pay_…", { metadata: { shipped: "yes" } }); // merges into metadatalist returns newest first, 100 at most unless you set limit.
The payment object#
| Field | Meaning |
|---|---|
id | pay_ and 16 letters and digits |
status | pending, paid or failed |
amount | Rand |
currency | Always ZAR |
reference | Your order number |
email | Or null |
gateway | yoco, netcash or test |
mode | test or live |
method | card, instant_eft, bank_eft, cash, voucher, other, or null until paid |
verified | true once the gateway itself confirmed the payment, never on a notification's word |
checkout_url | Yoco's link, or null |
checkout_form | Netcash's { action, fields }, or null |
metadata | Your data, as strings |
refunded | Rand refunded so far (succeeded refunds only) |
refunds | Each refund: id, amount, status, failure |
gateway_ref | The reference the gateway knows this payment by (CC…) |
gateway_id | Yoco's checkout id, or Netcash's RequestTrace |
failure | Why it failed, or null |
created_at, paid_at, failed_at | ISO timestamps |