Skip to main content

3. Integrate Papi to your web application

Integrate Papi into your application, including handling payment flows, notifications, and environment‑specific settings.


Environments

Papi provides a single production endpoint. For testing, use the test mode flags described below.

  • Production (live transactions): https://app.papi.mg/dashboard/api/payment-links

Payment Flow Overview

The payment process consists of the following steps:

  1. Get your API Key from the dashboard.
  2. Create a payment link: Send an API request to generate a unique payment link for the customer.
  3. Redirect the customer: Use the returned paymentLink to send the customer to the secure payment page.
  4. Process the payment: The customer completes the payment on the secure page.
  5. Receive a notification: After the payment, Papi calls your notificationUrl with the final status.
  6. Verify and handle the result: Check the notificationToken and paymentReference to confirm authenticity and update your application.

Step‑by‑Step Guide to Implementing Payment Integration

This guide walks you through integrating the payment API step by step, from setting up redirection pages to handling notifications.

1
Create pages for redirection

You need two pages on your website where customers will be sent after payment:

  • Success URL – The page shown after a successful payment (e.g., order confirmation).
  • Failure URL – The page shown after a failed payment (e.g., an error message with a retry option).

Take note of both URLs – you will need them when creating the payment link.

2
Create the notification endpoint (Callback API)

The notification endpoint is an API you implement to receive automatic status updates from Papi.
It must accept POST requests.

Example notification body sent by Papi

{
"paymentStatus": "SUCCESS",
"paymentMethod": "MVOLA",
"currency": "MGA",
"amount": 15000,
"fee": 500,
"clientName": "Client Name",
"description": "Payment for Order #123",
"merchantPaymentReference": "MERCHANT-0001",
"paymentReference": "ORDER-123",
"notificationToken": "xyz789",
"message": "Payment completed successfully.",
"payerEmail": "customer@example.com",
"payerPhone": "+261340000000"
}

Explanation of notification fields

FieldTypeDescription
paymentStatusstringSUCCESS, PENDING, or FAILED.
paymentMethodstringThe method used (MVOLA, ARTEL_MONEY, ORANGE_MONEY, BRED).
currencystringCurrency code (always MGA).
amountintegerAmount paid.
feeintegerTransaction fee deducted.
clientNamestringCustomer's name as provided.
descriptionstringThe payment description you sent.
merchantPaymentReferencestringInternal reference from the payment provider.
paymentReferencestringYour unique reference for this payment (from the reference field).
notificationTokenstringToken returned when you created the payment link – use it to verify authenticity.
messagestringAdditional human‑readable information.
payerEmailstringCustomer's email (if provided).
payerPhonestringCustomer's phone number (if provided).

Verifying the notification

To ensure the notification is genuine, verify that:

  • paymentReference matches the reference you sent.
  • notificationToken matches the one you received in the payment‑link creation response.

If both match, the notification is authentic and you can safely update your database.

3
Prepare the request body and headers for creating a payment link

You will send a POST request to generate a payment link.
The body must include the URLs you created in Steps 1 and 2, along with payment details.

Endpoint

POST https://app.papi.mg/dashboard/api/payment-links

Headers

{
"Content-Type": "application/json",
"Token": "<YOUR_API_KEY>"
}

Request body example

{
"amount": 15000.0,
"clientName": "Client Name",
"reference": "ORDER-123",
"description": "Payment for Order #123",
"successUrl": "https://yourapp.com/payment-success",
"failureUrl": "https://yourapp.com/payment-failure",
"notificationUrl": "https://yourapp.com/payment-notify",
"validDuration": 60,
"provider": "MVOLA",
"payerEmail": "customer@example.com",
"payerPhone": "+261340000000",
"testReason": "Internal QA",
"isTestMode": false
}

Request fields explained

FieldTypeRequiredDescription
clientNamestringCustomer's name.
amountnumberPayment amount (minimum 300).
referencestringYour unique identifier for this payment (e.g., order ID).
descriptionstringShort description (max 255 chars).
successUrlstringURL for redirection after success (must be http(s)://).
failureUrlstringURL for redirection after failure (must be http(s)://).
notificationUrlstringYour endpoint that receives payment notifications (http(s)://).
validDurationintegerLink validity in minutes (default: 1, must be > 0).
providerstringRestrict to one provider: MVOLA, ARTEL_MONEY, ORANGE_MONEY, BRED.
payerEmailstringCustomer's email address.
payerPhonestringCustomer's phone number.
testReasonstringReason for using test mode (appears in dashboard).
isTestModebooleanSet to true to enable test mode (see "Test Mode" section below).
4
Send the POST request to retrieve the payment link

Make the request with the body and headers from Step 3.
If successful, you receive a response like this:

{
"data": {
"amount": 15000.0,
"currency": "MGA",
"linkCreationDateTime": 1723850012,
"linkExpirationDateTime": 1723853612,
"paymentLink": "https://pay.papi.mg/payment/abc123",
"clientName": "Client Name",
"paymentReference": "ORDER-123",
"description": "Payment for Order #123",
"successUrl": "https://yourapp.com/payment-success",
"failureUrl": "https://yourapp.com/payment-failure",
"notificationUrl": "https://yourapp.com/payment-notify",
"payerEmail": "customer@example.com",
"payerPhone": "+261340000000",
"notificationToken": "xyz789",
"testReason": "Internal QA",
"isTestMode": false
}
}

Important response fields

FieldDescription
paymentLinkThe URL where the customer must be redirected to pay.
notificationTokenKeep this – you will need it to verify future notifications.
5
Redirect the user to the payment URL

Choose how you want to send the customer to the payment page. Two flows are supported – pick the one that fits your UX.

Option A — Standard redirect

Extract the paymentLink from the response and redirect the customer:

  • Web applications: Open the link in a new browser tab, or perform an HTTP redirect.
  • Mobile apps: Use a WebView or the device's default browser.
    Tip: Some platforms reset WebViews when the app goes to the background – handle this carefully to avoid losing state.

Once redirected, the customer completes the payment on Papi's secure page. After the transaction, they are sent back to your successUrl or failureUrl, and your notificationUrl receives the final status.

Payment choice screen


Option B — In-app pop-up window

Keep the customer on your site by opening the paymentLink in a popup window and listening for a postMessage event from Papi when the payment is confirmed.

1. Open the payment popup

Open the URL returned by the server in a new popup window. This must happen inside a user-gesture handler (e.g. a form submit event) — otherwise the browser will block the popup. We will use JavaScript to demonstrate, but the same logic applies in any frontend framework.

var paymentWindow = window.open(
paymentLink, // URL from the previous step
'payment-form-window', // reusable window name
'width=500,height=700' // popup dimensions
);

Keep a reference to paymentWindow — you will need it to verify that the message comes from exactly this popup.

2. Listen for payment success

Once the popup is open, register a message listener on the parent window. Papi will post { type: 'PAYMENT_STATUS' } when the payment is confirmed.

let PAPI_ORIGIN  = 'https://payment-form.papi.mg';

let paymentMessageHandler = null;
let paymentSuccess = false;

function teardownPaymentMessageListener() {
if (paymentMessageHandler) {
window.removeEventListener('message', paymentMessageHandler);
paymentMessageHandler = null;
}
}

function onPaymentSuccessMessage () {
// Your success handling logic here
}

function setupPaymentMessageListener() {
teardownPaymentMessageListener();

paymentMessageHandler = function (event) {
// 1. Reject anything not from the Papi origin
if (event.origin !== PAPI_ORIGIN) return;
// 2. Reject anything not from the popup we opened
if (event.source !== paymentWindow) return;
// 3. Only react to PAYMENT_STATUS payloads
if (!event.data || event.data.type !== 'PAYMENT_STATUS') return;
// 4. Don't double-process
if (paymentSuccess) return;

paymentSuccess = true;
teardownPaymentMessageListener();
onPaymentSuccessMessage();
};

window.addEventListener('message', paymentMessageHandler);
}

Call setupPaymentMessageListener() immediately after window.open() so no message is missed between the two calls:

paymentWindow = window.open(paymentLink, 'payment-form-window', 'width=500,height=700');
setupPaymentMessageListener();

3. Security guards

Three checks must all pass before acting on a message:

CheckWhy it matters
event.origin === PAPI_ORIGINRejects messages from any other domain. Must be an exact match — scheme + host, no trailing slash.
event.source === paymentWindowRejects messages from unrelated iframes or tabs that happen to share the origin.
event.data.type === 'PAYMENT_STATUS'Ignores other postMessage traffic on the same page (analytics, widgets, etc.).

4. Teardown

Always remove the listener once you're done — on success, on cancel, or when the user closes the popup:

// On success → handled automatically inside the listener above

// On cancel (user clicks "Annuler")
teardownPaymentMessageListener();
if (paymentWindow && !paymentWindow.closed) { paymentWindow.close(); }
paymentWindow = null;

Common pitfalls

SymptomCause
Listener never firesPAPI_ORIGIN mismatch (http vs https, trailing slash, port).
Listener fires for unrelated messagesMissing event.source === paymentWindow guard.
Redirect happens twiceMissing paymentSuccess flag or listener not torn down after first fire.
event.source is nullPopup closed before sending the message — ensure the message is sent prior to window.close().

Test Mode

Papi offers two ways to test your integration:

1. isTestMode flag in the request

  • Set "isTestMode": true in the request body.
  • The transaction is marked as a test in your dashboard, but real money is still moved.
  • Useful for end‑to‑end testing with real providers (except for mobile money, which does not support non‑real test transactions).

2. Application Test Mode (cards only)

  • In your shop's settings inside the dashboard, enable Test Mode.
  • Use the following test card details:
    • Card number: 4000 0000 0000 5126
    • Expiry date: 01/2028
    • CVV: 123
  • This simulates a card payment without moving real funds.

Example Workflow Summary

  1. Obtain your API Key from the dashboard (Avatar icon → Boutiques → select shop → Developer tab).
  2. Create a payment link by sending a POST request with the required fields.
  3. Redirect the customer to the returned paymentLink.
  4. Receive a notification on your notificationUrl when the payment status changes.
  5. Verify the notification using paymentReference and notificationToken.
  6. Update your records and inform the customer.

By following these steps, you can securely accept online payments with Papi. Always test thoroughly using test mode before going live.