Skip to main content

TypeScript SDK

The Papi TypeScript SDK (@ibonia/papi-api-client) provides a typed client for the Papi payment API. It handles HTTP communication, serialization, and model types so you can focus on your business logic. It runs on Node.js 18 or later.


Installation

The SDK is hosted on the Ibonia package repository. Add the registry to your .npmrc, then install the package.

Registry

@ibonia:registry=https://package-repository.ibonia.com/repository/ibonia-npm/

Yarn

yarn add @ibonia/papi-api-client

npm

npm install @ibonia/papi-api-client

The SDK depends on axios (~1.7.9), which is installed automatically.


Core classes

ExportKindDescription
ConfigurationclassClient configuration. Holds the base path; create it once and share it.
PaymentLinksApiclassMethods for creating payment links and reading them back.
PaymentsApiclassMethod for checking the status of a payment by Papi's reference.
PaymentLinkRequesttypeRequest body for creating a payment link.
PaymentLinkResponsetypeResponse returned after a payment link is created.
PaymentLinkStatusResponsetypeA payment link read back by reference, with its status and the payment outcome. Same field names as PaymentResponse, except Papi's reference is papiPaymentReference.
PaymentResponsetypePayload sent by Papi to your notification endpoint.
ErrorResponsetypeError body returned by the API.

Every method returns an AxiosResponse, and every Papi body is an envelope. The payload is therefore always response.data.data.


Usage

1
Initialize the client

Create one Configuration and pass it to the API classes you need.

import { Configuration, PaymentLinksApi, PaymentsApi } from '@ibonia/papi-api-client';

const config = new Configuration({ basePath: 'https://app.papi.mg' });

const links = new PaymentLinksApi(config);
const payments = new PaymentsApi(config);
2
Build a payment link request

The fields amount, clientName, reference, and description are required. notificationUrl is optional but strongly recommended.

import { PaymentLinkRequest } from '@ibonia/papi-api-client';

const request: PaymentLinkRequest = {
amount: 15000,
clientName: 'Client Name',
reference: 'ORDER-123',
description: 'Payment for Order #123',
notificationUrl: 'https://yourapp.com/payment-notify',
validDuration: 2, // link expires after 2 hours
};

To restrict the link to one provider, use the provider enum:

import { PaymentLinkRequestProviderEnum } from '@ibonia/papi-api-client';

request.provider = PaymentLinkRequestProviderEnum.Mvola;
3
Create the payment link

Call createPaymentLink with your API key (from the dashboard) and the request.

const res = await links.createPaymentLink(apiKey, request);
const link = res.data.data; // PaymentLinkResponse

const paymentUrl = link.paymentLink; // redirect the user here
const notifToken = link.notificationToken; // store this for verification
const myReference = link.paymentReference; // your own reference, echoed back

Redirect the customer to paymentUrl. After the payment, Papi will call your notificationUrl with the result.

Calling createPaymentLink again with the same reference while the link is still live returns that same link (same URL, same token), so a retry is safe. The same reference with a different amount or currency answers 409.

try {
const res = await links.createPaymentLink(apiKey, request);
} catch (err: any) {
const status = err.response?.status; // 409, 401, 400…
const message = err.response?.data?.error?.message;
console.error(status, message);
}
4
Handle the payment notification

Expose a POST endpoint that reads a PaymentResponse body. Papi calls this endpoint after every payment attempt. Verify the notification before doing anything else.

import express from 'express';
import { PaymentResponse, PaymentResponsePaymentStatusEnum } from '@ibonia/papi-api-client';

const app = express();
app.use(express.json());

app.post('/payment-notify', (req, res) => {
const notification = req.body as PaymentResponse;

// Values stored when the link was created
const expectedToken = 'xyz789';
const expectedReference = 'ORDER-123';

const tokenMatches = notification.notificationToken === expectedToken;
const refMatches = notification.merchantPaymentReference === expectedReference;

if (!tokenMatches || !refMatches) {
return res.status(403).end();
}

switch (notification.paymentStatus) {
case PaymentResponsePaymentStatusEnum.Success:
// Mark the order as paid
break;
case PaymentResponsePaymentStatusEnum.Failed:
// Handle the failure
break;
case PaymentResponsePaymentStatusEnum.Pending:
// Wait for the final notification
break;
}

res.status(200).end();
});
5
Read the payment link back

A notification is sent once. If your endpoint was down or the call was lost, ask Papi directly: getPaymentLink reads the link back by your merchant reference (the reference you sent at creation, merchantPaymentReference in notifications) and returns the same outcome the notification would have carried.

import { PaymentLinkStatusResponseLinkStatusEnum } from '@ibonia/papi-api-client';

const res = await links.getPaymentLink(apiKey, 'ORDER-123');
const link = res.data.data; // PaymentLinkStatusResponse

switch (link.linkStatus) {
case PaymentLinkStatusResponseLinkStatusEnum.Paid:
confirmOrder(link.papiPaymentReference); // paymentStatus is SUCCESS
break;
case PaymentLinkStatusResponseLinkStatusEnum.Active:
// still payable; paymentStatus tells you about the last attempt
break;
case PaymentLinkStatusResponseLinkStatusEnum.Expired:
case PaymentLinkStatusResponseLinkStatusEnum.Disabled:
issueNewLinkOrCancel();
break;
}

getPaymentLink answers 404 when no link of your shop carries that reference, and 401 when the API key is wrong.

6
Check a payment status

When you already hold Papi's reference for the payment — paymentReference on a notification, or papiPaymentReference on a link read back — you can ask for the payment itself. This call takes no API key.

import { PaymentResponsePaymentStatusEnum } from '@ibonia/papi-api-client';

const res = await payments.getPaymentStatus('mvola', papiPaymentReference);
const payment = res.data.data; // PaymentResponse

if (payment.paymentStatus === PaymentResponsePaymentStatusEnum.Success) {
confirmOrder(payment.merchantPaymentReference);
}

The first argument is the provider the payment went through, as a string: 'mvola', 'airtel-money', 'orange-money', or 'bred-card'. An unknown payment reference answers 400.


PaymentLinkRequest fields

FieldTypeRequiredDescription
amountnumberPayment amount (minimum 300 MGA).
clientNamestringCustomer's full name.
referencestringYour unique identifier for this payment.
descriptionstringShort payment description (max 255 chars).
notificationUrlstringEndpoint that receives payment status notifications. Strongly recommended; without it, use getPaymentLink to read the outcome.
payerEmailstringCustomer's email address.
payerPhonestringCustomer's phone number.
successUrlstringRedirect URL after a successful payment.
failureUrlstringRedirect URL after a failed payment.
validDurationnumberLink validity in hours (default: 1).
providerPaymentLinkRequestProviderEnumLock to one provider: Mvola, AirtelMoney, OrangeMoney, or Bred.
displayCurrencystringCurrency shown to the payer (MGA only, default MGA).
isTestModebooleantrue to flag the transaction as a test in the dashboard.
testReasonstringReason displayed in the dashboard when test mode is on.

successUrl and failureUrl go together: send both or neither.


PaymentResponse fields

FieldTypeDescription
paymentStatusPaymentResponsePaymentStatusEnumSuccess, Pending, or Failed.
paymentMethodstringProvider used: MVOLA, AIRTEL_MONEY, ORANGE_MONEY, BRED.
currencystringAlways MGA.
displayCurrencystringThe currency the payer saw on the form (always MGA today).
amountnumberAmount paid.
estimatedAmountnumberThe amount expressed in displayCurrency (equal to amount while only MGA is supported).
feenumberTransaction fee deducted.
clientNamestringCustomer's name.
descriptionstringPayment description.
merchantPaymentReferencestringYour reference from the original request.
paymentReferencestringPapi's reference for this payment (a UUID). Pass it to getPaymentStatus.
notificationTokenstringToken from the original payment link response — use it to verify authenticity.
messagestringFailure reason, when there is one.
payerEmailstringCustomer email (if provided).
payerPhonestringCustomer phone (if provided).

PaymentLinkStatusResponse fields

FieldTypeDescription
linkStatusPaymentLinkStatusResponseLinkStatusEnumActive, Expired, Paid, or Disabled. Paid wins over Disabled and Expired.
paymentStatusPaymentResponsePaymentStatusEnumSuccess, Pending, or Failed — same values as the notification. null while nobody has attempted to pay.
paymentMethodstringProvider the payer went through. null until a payment attempt exists.
currencystringAlways MGA.
displayCurrencystringCurrency shown to the payer.
amountnumberAmount of the link.
clientNamestringCustomer's name.
descriptionstringPayment description.
merchantPaymentReferencestringYour reference from the original request.
papiPaymentReferencestringPapi's reference for the payment attempt (the notification's paymentReference). null until one exists.
notificationTokenstringToken returned when the link was created.
messagestringFailure reason of the payment attempt, when there is one.
payerEmailstringCustomer email (if provided).
payerPhonestringCustomer phone (if provided).
paymentLinkstringThe payment URL.
shortLinkstringShort form of the payment URL, when generated.
linkCreationDateTimenumberCreation time, epoch milliseconds.
linkExpirationDateTimenumberExpiry time, epoch milliseconds.
isTestModebooleanWhether the link was flagged as a test.

Full example (Express)

The example below shows a complete Express application that creates a payment link and handles the callback notification.

import express from 'express';
import {
Configuration,
PaymentLinksApi,
PaymentLinkRequest,
PaymentResponse,
PaymentResponsePaymentStatusEnum,
} from '@ibonia/papi-api-client';

const API_KEY = process.env.PAPI_API_KEY!;
const APP_DOMAIN = process.env.APP_DOMAIN!;

const config = new Configuration({ basePath: 'https://app.papi.mg' });
const links = new PaymentLinksApi(config);

const app = express();
app.use(express.json());

app.post('/checkout', async (req, res) => {
const { amount, reference, clientName, email, phone } = req.body;

const request: PaymentLinkRequest = {
amount,
clientName,
reference,
description: `Payment ${reference}`,
payerEmail: email,
payerPhone: phone,
notificationUrl: `${APP_DOMAIN}/payment-notify`,
validDuration: 2,
};

try {
const created = await links.createPaymentLink(API_KEY, request);
const link = created.data.data;

// Persist link.notificationToken alongside the order reference
// so you can verify it when the notification arrives.
await storeNotificationToken(reference, link.notificationToken);

res.json({ paymentLink: link.paymentLink });
} catch (err: any) {
res.status(err.response?.status ?? 500).json({
message: err.response?.data?.error?.message ?? 'Unexpected error',
});
}
});

app.post('/payment-notify', async (req, res) => {
const notification = req.body as PaymentResponse;

const storedToken = await lookupNotificationToken(
notification.merchantPaymentReference,
);

if (storedToken !== notification.notificationToken) {
return res.status(403).end();
}

if (notification.paymentStatus === PaymentResponsePaymentStatusEnum.Success) {
await confirmOrder(notification.merchantPaymentReference);
}

res.status(200).end();
});

app.listen(3000);

// ... storeNotificationToken, lookupNotificationToken and confirmOrder implementations