Start here
Official SDKs
Libraries for PHP, Node.js, Python and Java. They sign every request, add idempotency keys, check webhook signatures and give you one error type to branch on, so you only write the calls.
Install
| Language | Install | Needs | |
|---|---|---|---|
| PHP | composer require afropin/afropin-php |
PHP 8.0+, curl | Packagist · GitHub |
| Node.js | npm install @afropin/afropin |
Node 18+ | npm · GitHub |
| Python | pip install afropin |
Python 3.8+ | PyPI · GitHub |
| Java | com.afropin:afropin-java:1.0.0 |
Java 11+ | Maven Central · GitHub |
None of them has dependencies beyond the language itself. Each one covers every endpoint, for brands and resellers, and every reference page in these docs shows its call in all four languages under "With an SDK".
For Maven, add the dependency to your pom.xml:
<dependency>
<groupId>com.afropin</groupId>
<artifactId>afropin-java</artifactId>
<version>1.0.0</version>
</dependency>
Set up a client
Create it with an API key ID and secret. Your key's mode decides whether you work with test or live records; the address is the same for both. Test and live
$afropin = new AfroPin\Client(getenv('AFROPIN_KEY_ID'), getenv('AFROPIN_SECRET'));
import { AfroPin } from '@afropin/afropin';
const afropin = new AfroPin(process.env.AFROPIN_KEY_ID, process.env.AFROPIN_SECRET);
import os
from afropin import Client
afropin = Client(os.environ["AFROPIN_KEY_ID"], os.environ["AFROPIN_SECRET"])
AfroPin afropin = new AfroPin(System.getenv("AFROPIN_KEY_ID"), System.getenv("AFROPIN_SECRET"));
Your first call: check and redeem a PIN
Check what a customer's PIN can pay, then take the payment. Take gift cards at checkout
$check = $afropin->brand->pins->check(['pin_code' => $code, 'amount' => 2500]);
if ($check['can_redeem']) {
$afropin->brand->pins->redeem(['pin_code' => $code, 'amount' => 2500, 'external_reference' => $saleId]);
}
const check = await afropin.brand.pins.check({ pin_code: code, amount: 2500 });
if (check.can_redeem) {
await afropin.brand.pins.redeem({ pin_code: code, amount: 2500, external_reference: saleId });
}
check = afropin.brand.pins.check({"pin_code": code, "amount": 2500})
if check["can_redeem"]:
afropin.brand.pins.redeem({"pin_code": code, "amount": 2500, "external_reference": sale_id})
Map<String, Object> check = afropin.brand().pins().check(Map.of("pin_code", code, "amount", 2500));
if (Boolean.TRUE.equals(check.get("can_redeem"))) {
afropin.brand().pins().redeem(Map.of("pin_code", code, "amount", 2500, "external_reference", saleId));
}
Parameters and response fields are the API's own, exactly as the reference pages describe them. Python method names use snake_case, for example top_up; in Java, cancelling a cashout is voidCashout, as void is a keyword there.
Errors
A failed call raises one error type carrying the API's error code, its message, per-field messages and the HTTP status. Branch on the code: the message is for people and may change. Error codes
AfroPin\AfroPinException: $e->errorCode, $e->fields, $e->statusAfroPinError: error.code, error.fields, error.statusAfroPinError: error.code, error.fields, error.statusAfroPinException: getCode(), getFields(), getStatus()When the API can't be reached, the code is connection_failed and the status is 0.
Lists
List calls return a page you can loop over, with paging holding page, pages and total. Ask for the next page with page in the parameters.
const page = await afropin.brand.payments.list({ limit: 50 });
for (const item of page) { /* ... */ }
if (page.hasMore) {
const next = await afropin.brand.payments.list({ limit: 50, page: page.paging.page + 1 });
}
Idempotency and retries
Each request is sent with a new Idempotency-Key; you can pass your own instead. The SDKs never retry for you. If a sale or top-up times out, send it again with the same external_reference: it either goes through once, or fails with duplicate_external_reference.
Webhooks
Each SDK checks the webhook-secret header against the raw body and returns the event, or raises invalid_signature. Pass the body exactly as received, before any JSON parsing. Events and webhooks
$event = AfroPin\Webhook::parse(file_get_contents('php://input'), $_SERVER['HTTP_WEBHOOK_SECRET'] ?? '', $webhookSecret);
import { parseWebhook } from '@afropin/afropin';
app.post('/afropin/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const event = parseWebhook(req.body, req.header('webhook-secret'), process.env.AFROPIN_WEBHOOK_SECRET);
res.sendStatus(200);
});
from afropin import webhooks
event = webhooks.parse(request.get_data(), request.headers.get("webhook-secret"), WEBHOOK_SECRET)
Map<String, Object> event = Webhooks.parse(rawBody, request.getHeader("webhook-secret"), webhookSecret);
Without an SDK
Any language can call the API directly: it is HMAC-signed JSON over HTTPS. Signing requests