Payin test cases
The payments to run before you go live with Ozow, what each one delivers, and a handler that survives all of them.
On this page12 sections
- What arrives, and what it says
- A handler that passes every test below
- Core test cases
- Test 1: Successful payment
- Test 2: Cancelled payment
- Test 3: Failed payment
- Test 4: Signature verification
- Test 5: The same outcome twice
- Test 6: Transaction status check
- Optional test cases
- Test 7: Standalone button
- Test 8: Customer Identity Verification
Build with AI 1 package
A build package is every page for one task, with the API operations they use. Copy the prompt into a coding assistant, or hand it the package itself: slim links to each page, full inlines all of them in one document.
- Take a paymentEverything needed to take a payment end to end with One API, from credentials through the hosted page to the webhook that confirms it, and the test cases that prove each outcome before you go live.
These test cases are recommended but not mandatory for go-live. Run at least the core ones before you accept real payments. The optional ones apply only if you have built the feature they test.
Building on the Payments API instead? Use Payin test cases: Payments API; the statuses and the field names are different.
Testing environment
PayinPayin A payment made by a consumer to a merchant. The direction most of this site is about: money coming in. Its counterpart is a payout, which sends money out and is not tied to any payment anyone made you. integrations can be tested directly in production. Any test transactions settle into your configured bank account. To test without moving real money, staging credentials are available on request: contact your account manager or support@ozow.com.
What arrives, and what it says
A One API payment tells you its outcome on a
transaction.complete webhookWebhook A URL of yours that Ozow calls when something happens, rather than you polling to find out. The call carries no credential of yours and arrives at a public URL, so authenticate it before acting on it: a hash field on the Payments API, a Svix signature on One API., and also on the
standard Ozow notification when you set notifyUrl on the payment request. Configure both and both
fire, which is what Test 5 exists for.
The webhook carries one envelope whatever happened, and
data.status is one of four values:
data. |
The transaction was |
|---|---|
Successful |
completed |
Incomplete |
created and not taken further |
Pending |
pending, or under investigation |
Error |
anything else, including cancelled, abandoned and voided |
There is no Cancelled to test for. A cancellation arrives as Error, and data.reason says
which kind of failure it was. A handler that switches on Cancelled never runs that branch.
A handler that passes every test below
The tests are all the same handler seen from different angles: verify, then read the status, then
update the order once. This is that handler, written against svix 2.x.
import { Webhook } from "svix";
// From Get Webhook Secret. Keep it out of source control.
const webhook = new Webhook(process.env.OZOW_WEBHOOK_SECRET);
app.post(
"/ozow/webhook",
express.raw({ type: "application/json" }),
(req, res) => {
try {
// Verify against the raw body. Parsing first and re-serialising changes
// the bytes and the signature will not match. Test 4.
webhook.verify(req.body, {
"svix-id": req.headers["svix-id"],
"svix-timestamp": req.headers["svix-timestamp"],
"svix-signature": req.headers["svix-signature"],
});
} catch {
// Rejected, logged, and never acted on.
return res.status(400).send("invalid signature");
}
// `verify` returns nothing on svix 2.x: it throws on a bad signature and
// that is the whole result. Parse after it, never before. On svix 1.x it
// returned the parsed body, so a handler carried over from that version
// reads `undefined` here and fails after it has already acknowledged.
const event = JSON.parse(req.body);
// Acknowledge first. Ozow retries anything that is not a 2xx, and a slow
// database is not a reason to be sent the same event again.
res.sendStatus(200);
const { type, data } = event;
if (type !== "transaction.complete") return;
// The same event can arrive more than once, and a payment can also notify
// twice when `notifyUrl` is set alongside the webhook. Test 5.
if (!claimOnce(data.id)) return;
switch (data.status) {
case "Successful":
fulfilOrder(data.id);
break;
case "Pending":
// Not an outcome. Leave the order alone and wait for the next event.
break;
case "Incomplete":
case "Error":
// Everything that is not a payment: cancelled, abandoned, voided, failed.
failOrder(data.id, data.reason);
break;
default:
// A value this code has never seen. Do not guess what it means.
alertOps(`unknown status ${data.status} on ${data.id}`);
}
},
);
claimOnce is whatever makes the update happen once in your system: a unique constraint on the
transaction id, a row lock, or a conditional write. Deciding by "have I seen this id" in memory does
not survive two instances.
Important
Update the order from this handler, never from the browser returning to your success URL. A customer who closes the tab still has to end up with the right order state.
Core test cases
Test 1: Successful payment
Verify that your integration handles a completed payment end to end.
Steps
- Create a payment with
POST /payments - Complete the payment on the Ozow payment page with a valid payment method
- Verify that your endpoint receives the webhook
- Verify that the Svix signature validates
- Verify that the order is updated
- Verify that the customer reaches your success URL
Expected outcomes
typeistransaction.completeanddata.statusisSuccessful- Order credited and fulfilled once
- Customer redirected to the success URL
Test 2: Cancelled payment
Verify that a cancellation does not credit the order.
Steps
- Create a payment
- Cancel it on the Ozow payment page
- Verify that your endpoint receives the webhook
- Verify that the order is not credited
- Verify that the customer reaches your cancel URL
Expected outcomes
data.statusisError, notCancelled, with the detail indata.reason- Order not credited
- Customer redirected to the cancel URL
Test 3: Failed payment
Verify that a failure does not credit the order.
Steps
- Create a payment
- Attempt a payment that fails
- Verify that your endpoint receives the webhook
- Verify that the order is not credited
- Verify that the customer reaches your error URL
Expected outcomes
data.statusisError, with the reason indata.reason- Order not credited
- Customer redirected to the error URL
Test 4: Signature verification
Verify that verification actually rejects something.
Steps
- Complete a successful test payment and keep the delivery
- Verify it with your implementation and confirm it passes
- Change one byte of the body, or one character of
svix-signature, and replay it - Confirm the tampered delivery fails verification and is not processed
Expected outcomes
- The genuine delivery passes
- The tampered delivery is rejected, logged, and updates nothing
- Verification runs against the raw body, not a re-serialised object
Test 5: The same outcome twice
Verify that one payment updates one order once.
Note
Ozow retries a delivery your endpoint did not acknowledge, and a payment with both a
webhook and notifyUrl reports twice by design. Both look like a duplicate to your handler.
Steps
- Complete a successful test payment
- Deliver the same event to your endpoint a second time
- If you have set
notifyUrlas well, confirm what that notification does to the same order
Expected outcomes
- The second delivery is recognised and changes nothing
- The order is credited once
- Nothing throws
Test 6: Transaction status check
Verify that you can ask, rather than wait.
Steps
- Complete a test payment and note the payment id
- Call
GET /payments/{id}/transactions - Compare the status with the outcome you were sent
Expected outcomes
- The call returns the transaction
- Its status matches the payment's outcome
Note
This endpoint returns the transaction's own status, which is the full set on the
statuses page, not the four the webhook maps to. A payment the webhook reports
as Complete reads Successful here. Compare without case.
Optional test cases
Run these only if you have built the feature.
Test 7: Standalone button
Verify that a button opens the payment method it names.
Steps
- Build the standalone button with the correct
institutionId - Create a payment with that field set
- Verify that the Ozow payment page opens on that payment method
Expected outcomes
- The named payment method is shown
- The customer is routed to it without choosing again
Test 8: Customer Identity Verification
Run this only if your business is in a high-risk industry and Customer Identity VerificationCustomer Identity Verification Checking that the payment instrument belongs to the natural person making the payment. Ozow requires it for merchants it has classified as high-risk, on Pay by Bank, Absa Pay, Capitec Pay, Nedbank Direct EFT, FNB Payment Requests and PayShap Request, and can disable those methods where it is not implemented correctly. is required.
Steps
- Create a payment with a verified customer identity in
payer.identity - Verify that the page offers only payment methods linked to that identity
- Verify that unlinked payment methods are hidden
- Create a payment with no identity and verify which methods are offered
Expected outcomes
- Only linked payment methods are offered when an identity is passed
- The behaviour without an identity is what you expect for your account
In the API reference
4 entries
Last updated