Handling Payment Notifications

Learn how to set up and handle EcobankPay webhook notifications

Notification Options

Once the customer pays or cancels an invoice, depending on your site setup, EcobankPay can do two things:

  • Send an email informing you of the payment or cancellation
  • Send a notification to an IPN (instant payment notification) URL for your application to take some automated action

How IPN Notifications Work

Here's how the IPN notification process works:

  1. EcobankPay calls your IPN URL

    Once an invoice is paid or cancelled, EcobankPay does a GET to your IPN URL with the invoice_id parameter. Suppose your IPN url ishttps://ipn_url.ecobankpay.com.gh/notifyand a customer pays or cancels invoice with invoice ID AA123, EcobankPay will perform the GET request:

    https://ipn_url.ecobankpay.com.gh/notify?invoice_id=AA123

    This call is meant to prompt your application that an event of interest has occurred with respect to this invoice.

  2. Your application queries transaction status

    Your application will then make the GET call:

    https://pgw.paywithonline.com/v1/gateway/json_status_chk?invoice_id=AA123&merchant_key=YOUR_MERCHANT_KEY

    to receive details of the event.

  3. EcobankPay responds with transaction details

    EcobankPay responds to the query in the step above with a JSON object. Details of the response object are described as part of the status check endpoint.

Sample Payment Notification Request

Request:

Endpoint:

https://pgw.paywithonline.com/v1/gateway/json_status_chk

Method:

GET
ParameterRequiredDescription
merchant_keyYYour unique assigned Merchant Key
invoice_idYInvoice Id of the particular transaction whose details to check

Best Practices for Webhook Handling

  • Acknowledge receipt: Your webhook endpoint should return a 2xx HTTP status code as quickly as possible to acknowledge receipt of the webhook notification.
  • Process asynchronously: Process the webhook data asynchronously after acknowledging receipt, especially if the processing involves time-consuming operations.
  • Implement retry logic: Design your system to handle potential duplicate notifications, as webhook delivery may be retried in case of temporary failures.
  • Secure your endpoint: Ensure your webhook endpoint is properly secured, validate the incoming data, and verify that the notification is authentic.
  • Handle all transaction status types: Your webhook handler should be designed to process all possible transaction statuses (paid, cancelled, failed, etc.).
  • Proper error logging: Implement comprehensive error logging to troubleshoot issues with webhook processing.

Implementing the Webhook Handler

Below is a simplified code example of how you might implement a webhook handler in a Node.js Express application:

const express = require('express');
const axios = require('axios');
const app = express();

// Webhook endpoint to receive EcobankPay notifications
app.get('/api/webhook/ecobankpay', async (req, res) => {
  try {
    // Get the invoice ID from the request
    const { invoice_id } = req.query;

    if (!invoice_id) {
      return res.status(400).send('Missing invoice_id parameter');
    }

    // Acknowledge receipt immediately
    res.status(200).send('Webhook received');

    // Process asynchronously
    processPaymentNotification(invoice_id).catch(err => {
      console.error('Error processing payment notification:', err);
    });
  } catch (error) {
    console.error('Webhook handler error:', error);
    // Still return 200 to acknowledge receipt
    if (!res.headersSent) {
      res.status(200).send('Webhook received with errors');
    }
  }
});

async function processPaymentNotification(invoiceId) {
  const MERCHANT_KEY = process.env.ECOBANKPAY_MERCHANT_KEY;

  // Query EcobankPay for transaction status
  const statusUrl = `https://pgw.paywithonline.com/v1/gateway/json_status_chk?invoice_id=${invoiceId}&merchant_key=${MERCHANT_KEY}`;

  const response = await axios.get(statusUrl);
  const paymentData = response.data;

  // Process based on transaction status
  switch(paymentData.status) {
    case 'paid':
      // Update order status, send confirmation email, etc.
      await updateOrderStatus(invoiceId, 'paid', paymentData);
      break;

    case 'cancelled':
      // Handle cancelled payment
      await updateOrderStatus(invoiceId, 'cancelled', paymentData);
      break;

    case 'failed':
      // Handle failed payment
      await updateOrderStatus(invoiceId, 'failed', paymentData);
      break;

    default:
      console.log(`Unhandled payment status: ${paymentData.status} for invoice ${invoiceId}`);
  }
}

async function updateOrderStatus(invoiceId, status, paymentData) {
  // Your implementation to update order in your database
  console.log(`Updated order ${invoiceId} to status: ${status}`);
}

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});