Skip to main content

Java SDK

The Papi Java SDK (papi-api-client) provides a typed client for the Papi payment API. It handles HTTP communication, serialization, and model classes so you can focus on your business logic.


Installation

The SDK is hosted on the Ibonia package repository. Add the repository and the dependency to your project.

Maven

<repositories>
<repository>
<id>ibonia-repo-group</id>
<url>https://package-repository.ibonia.com/repository/ibonia-mvn-group</url>
</repository>
</repositories>

<dependencies>
<dependency>
<groupId>com.ibonia.papi</groupId>
<artifactId>papi-api-client</artifactId>
<version>1.0.3</version>
</dependency>
</dependencies>

Gradle

repositories {
maven { url 'https://package-repository.ibonia.com/repository/ibonia-mvn-group' }
}

dependencies {
implementation 'com.ibonia.papi:papi-api-client:1.0.3'
}

Core classes

ClassPackageDescription
ApiClientcom.ibonia.papi.apiclientBase HTTP client. Instantiate once and share.
PaymentLinksApicom.ibonia.papi.apiclient.apiMethods for creating payment links and reading them back.
PaymentsApicom.ibonia.papi.apiclient.apiMethod for checking the status of a payment by Papi's reference.
PaymentLinkRequestcom.ibonia.papi.apiclient.modelRequest body for creating a payment link.
PaymentLinkResponsecom.ibonia.papi.apiclient.modelResponse returned after a payment link is created.
PaymentLinkStatusResponsecom.ibonia.papi.apiclient.modelA payment link read back by reference, with its status and the payment outcome. Same field names as PaymentResponse, except Papi's reference is papiPaymentReference.
PaymentResponsecom.ibonia.papi.apiclient.modelPayload sent by Papi to your notification endpoint.

Usage

1
Initialize the client

Create one ApiClient instance (it is thread-safe) and pass it to the API classes you need. Its default base path is https://app.papi.mg. In a Spring application, do this in the constructor so the API instances are ready when your bean is created.

import com.ibonia.papi.apiclient.ApiClient;
import com.ibonia.papi.apiclient.api.PaymentLinksApi;
import com.ibonia.papi.apiclient.api.PaymentsApi;

ApiClient apiClient = new ApiClient(); // base path: https://app.papi.mg
PaymentLinksApi paymentLinksApi = new PaymentLinksApi(apiClient);
PaymentsApi paymentsApi = new PaymentsApi(apiClient);
2
Build a payment link request

Construct a PaymentLinkRequest using the fluent builder. The fields amount, description, clientName, and reference are required. notificationUrl is optional but strongly recommended.

import com.ibonia.papi.apiclient.model.PaymentLinkRequest;
import java.net.URI;

PaymentLinkRequest request = new PaymentLinkRequest()
.amount(15000.0)
.description("Payment for Order #123")
.clientName("John Doe")
.reference("ORDER-123")
.payerEmail("john.doe@example.com")
.payerPhone("+261340000000")
.notificationUrl(new URI("https://yourapp.com/api/payment-notifications"))
.validDuration(60); // link expires after 60 hours
3
Create the payment link

Call createPaymentLink with your API key (from the dashboard) and the request. The method returns a wrapper — call .getData() to get the actual PaymentLinkResponse.

import com.ibonia.papi.apiclient.model.PaymentLinkResponse;

PaymentLinkResponse response = paymentLinksApi
.createPaymentLink("YOUR_API_KEY", request)
.getData();

String paymentUrl = response.getPaymentLink(); // redirect the user here
String notifToken = response.getNotificationToken(); // store this for verification

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 throws an ApiException whose getCode() is 409.

4
Handle the payment notification

Expose a POST endpoint that accepts a PaymentResponse body. Papi calls this endpoint after every payment attempt.

import com.ibonia.papi.apiclient.model.PaymentResponse;

@PostMapping("/api/payment-notifications")
public ResponseEntity<?> handleNotification(
@RequestBody @Valid PaymentResponse notification) {

// Verify authenticity before doing anything else
boolean tokenMatches = notifToken.equals(notification.getNotificationToken());
boolean refMatches = "ORDER-123".equals(notification.getMerchantPaymentReference());

if (!tokenMatches || !refMatches) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}

switch (notification.getPaymentStatus()) {
case SUCCESS -> handleSuccess(notification);
case FAILED -> handleFailure(notification);
case PENDING -> handlePending(notification);
}

return ResponseEntity.ok().build();
}
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 com.ibonia.papi.apiclient.model.PaymentLinkStatusResponse;

PaymentLinkStatusResponse link = paymentLinksApi
.getPaymentLink("YOUR_API_KEY", "ORDER-123")
.getData();

switch (link.getLinkStatus()) {
case PAID -> confirmOrder(link.getPapiPaymentReference()); // paymentStatus is SUCCESS
case ACTIVE -> {} // still payable; paymentStatus tells you about the last attempt
case EXPIRED, DISABLED -> issueNewLinkOrCancel();
}

getPaymentLink throws an ApiException whose getCode() is 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 — getPaymentReference() on a notification, or getPapiPaymentReference() on a link read back — you can ask for the payment itself. This call takes no API key.

import com.ibonia.papi.apiclient.api.PaymentsApi;
import com.ibonia.papi.apiclient.model.PaymentResponse;

PaymentResponse payment = paymentsApi
.getPaymentStatus("mvola", papiPaymentReference)
.getData();

if (payment.getPaymentStatus() == PaymentResponse.PaymentStatusEnum.SUCCESS) {
confirmOrder(payment.getMerchantPaymentReference());
}

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 throws an ApiException whose getCode() is 400.


PaymentLinkRequest fields

FieldTypeRequiredDescription
amountdoublePayment amount (minimum 300 MGA).
clientNameStringCustomer's full name.
referenceStringYour unique identifier for this payment.
descriptionStringShort payment description (max 255 chars).
notificationUrlURIEndpoint that receives payment status notifications. Strongly recommended; without it, use getPaymentLink to read the outcome.
payerEmailStringCustomer's email address.
payerPhoneStringCustomer's phone number.
successUrlURIRedirect URL after a successful payment.
failureUrlURIRedirect URL after a failed payment.
validDurationintLink validity in hours (default: 1).
providerStringLock to one provider: MVOLA, AIRTEL_MONEY, ORANGE_MONEY, 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.

PaymentResponse fields

FieldGetterDescription
paymentStatusgetPaymentStatus()PaymentResponse.PaymentStatusEnum: SUCCESS, FAILED, or PENDING.
paymentMethodgetPaymentMethod()Provider used: MVOLA, AIRTEL_MONEY, ORANGE_MONEY, BRED.
currencygetCurrency()Always MGA.
displayCurrencygetDisplayCurrency()The currency the payer saw on the form (always MGA today).
amountgetAmount()Amount paid.
estimatedAmountgetEstimatedAmount()The amount expressed in displayCurrency (equal to amount while only MGA is supported).
feegetFee()Transaction fee deducted.
clientNamegetClientName()Customer's name.
descriptiongetDescription()Payment description.
merchantPaymentReferencegetMerchantPaymentReference()Your reference from the original request.
notificationTokengetNotificationToken()Token from the original payment link response — use to verify authenticity.
paymentReferencegetPaymentReference()Papi's reference for this payment (a UUID). Pass it to getPaymentStatus.
messagegetMessage()Failure reason, when there is one.
payerEmailgetPayerEmail()Customer email (if provided).
payerPhonegetPayerPhone()Customer phone (if provided).

PaymentLinkStatusResponse fields

FieldGetterDescription
linkStatusgetLinkStatus()PaymentLinkStatusResponse.LinkStatusEnum: ACTIVE, EXPIRED, PAID, or DISABLED. PAID wins over DISABLED and EXPIRED.
paymentStatusgetPaymentStatus()SUCCESS, PENDING, or FAILED — same values as the notification. null while nobody has attempted to pay.
paymentMethodgetPaymentMethod()Provider the payer went through. null until a payment attempt exists.
currencygetCurrency()Always MGA.
displayCurrencygetDisplayCurrency()Currency shown to the payer.
amountgetAmount()Amount of the link.
clientNamegetClientName()Customer's name.
descriptiongetDescription()Payment description.
merchantPaymentReferencegetMerchantPaymentReference()Your reference from the original request.
papiPaymentReferencegetPapiPaymentReference()Papi's reference for the payment attempt (the notification's paymentReference). null until one exists.
notificationTokengetNotificationToken()Token returned when the link was created.
messagegetMessage()Failure reason of the payment attempt, when there is one.
payerEmailgetPayerEmail()Customer email (if provided).
payerPhonegetPayerPhone()Customer phone (if provided).
paymentLinkgetPaymentLink()The payment URL.
shortLinkgetShortLink()Short form of the payment URL, when generated.
linkCreationDateTimegetLinkCreationDateTime()Creation time, epoch milliseconds.
linkExpirationDateTimegetLinkExpirationDateTime()Expiry time, epoch milliseconds.
isTestModegetIsTestMode()Whether the link was flagged as a test.

Full example (Spring Boot)

The example below shows a complete Spring Boot service that creates a payment link and handles the callback notification — the same pattern used in production.

import com.ibonia.papi.apiclient.ApiClient;
import com.ibonia.papi.apiclient.api.PaymentLinksApi;
import com.ibonia.papi.apiclient.model.PaymentLinkRequest;
import com.ibonia.papi.apiclient.model.PaymentLinkResponse;
import com.ibonia.papi.apiclient.model.PaymentResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.net.URI;

@Service
public class PaymentService {

private final PaymentLinksApi paymentLinksApi;

@Value("${app.domain}")
private String appDomain;

public PaymentService() {
this.paymentLinksApi = new PaymentLinksApi(new ApiClient());
}

public String createPaymentLink(String apiKey, double amount,
String reference, String customerName,
String email, String phone) throws Exception {
PaymentLinkRequest request = new PaymentLinkRequest()
.amount(amount)
.description("Payment " + reference)
.clientName(customerName)
.reference(reference)
.payerEmail(email)
.payerPhone(phone)
.notificationUrl(new URI(appDomain + "/api/payment-notifications"))
.validDuration(60);

PaymentLinkResponse response = paymentLinksApi
.createPaymentLink(apiKey, request)
.getData();

// Persist response.getNotificationToken() alongside the order reference
// so you can verify it when the notification arrives.

return response.getPaymentLink();
}

public void handleNotification(PaymentResponse notification) {
String storedToken = lookupNotificationToken(
notification.getMerchantPaymentReference());

if (!storedToken.equals(notification.getNotificationToken())) {
throw new SecurityException("Notification token mismatch");
}

if (notification.getPaymentStatus() == PaymentResponse.PaymentStatusEnum.SUCCESS) {
confirmOrder(notification.getMerchantPaymentReference());
}
}

// ... lookupNotificationToken and confirmOrder implementations
}