Nylon PayNylon Pay

Payment Events

Event-driven payment status tracking with on, once, and wait

PaymentInstance Events

The PaymentInstance returned by collectPayment() emits events as the payment progresses through its lifecycle.

const payment = await nylonpay.collectPayment({
  /* ... */
});

payment.on('processing', handleProcessing);
payment.on('success', handleSuccess);
payment.on('failed', handleFailed);
payment.on('cancelled', handleCancelled);
payment.on('error', handleError);

Event Reference

EventWhen It Fires
processingPayment accepted and in flight (awaiting customer confirmation). Fires once, right after initiation — even when the payment resolves before the first status poll
successPayment completed successfully
failedPayment failed (customer rejected or timeout)
cancelledPayment cancelled by customer or system
errorServer-side initiation failure (auth, limit, provider, network, timeout) — transaction never started

EventData

Every handler receives an EventData object:

type EventData = {
  event: PaymentEvent;
  reference: string;
  transaction?: Transaction;
  error?: string;
  category?: SdkErrorCategory;
  retryable?: boolean;
  timestamp: string;
};
  • event, the event that fired ("processing", "success", etc.)
  • reference, the transaction reference — always present, on every event
  • transaction, full transaction record. Present on terminal status events (success, failed, cancelled). The processing event can fire before the full record is fetched — use reference there
  • error, error message. Present on the "error" event
  • category, machine-readable error category. Present on the "error" event
  • retryable, whether re-invoking the same operation may succeed. Present on the "error" event
  • timestamp, ISO 8601 timestamp of when the event was emitted
payment.on('error', ({ error, category, retryable }) => {
  console.error('Payment error:', error, 'category:', category, 'retryable:', retryable);
});

on(event, handler)

Registers a handler that fires every time the event occurs.

payment.on('success', ({ transaction }) => {
  console.log('Transaction ID:', transaction.id);
});

once(event, handler)

Registers a handler that fires only once, then automatically removes itself.

payment.once('success', ({ transaction }) => {
  // This runs only on first success
  if (transaction.metadata) {
    fulfillOrder(transaction.metadata.orderId);
  }
});

off(event, handler)

Removes a previously registered handler.

const handler = ({ transaction }) => console.log(transaction.status);

payment.on('success', handler);
// ... later
payment.off('success', handler);

Chaining

Event methods return the PaymentInstance, enabling chaining.

payment
  .on('success', handleSuccess)
  .on('failed', handleFailed)
  .on('cancelled', handleCancelled)
  .on('error', handleError);

wait()

Returns a Promise<Transaction | null> that resolves when the payment reaches a terminal state. Resolves with the Transaction on success, or null on failure, cancellation, or lifecycle error. Never rejects.

const tx = await payment.wait();
if (tx) {
  // Payment succeeded
  console.log('Payment succeeded:', tx.id);
} else {
  // Payment failed, cancelled, or error
  console.error('Payment did not succeed');
}

Status Polling

The PaymentInstance tracks status transitions by polling the backend until the payment reaches a terminal state — you don't manage the loop yourself. Polling is single-flight (only one request in flight at a time) and adds a small random jitter to each interval, so many concurrent payments don't synchronise into a burst against the status endpoint. Polling stops automatically on any terminal event, or when the attempt/duration caps are reached.

Tune the cadence and limits with maxPollAttempts and maxPollDurationMs (see Configuration).

Express Route Handler Example

import express from 'express';
import { createNylonPay } from '@nile-squad/nylonpay-ts';

const app = express();
const nylonPay = createNylonPay({
  /* config */
});

app.post('/api/pay', async (req, res) => {
  const { amount, phoneNumber, orderId } = req.body;
  // References must be valid UUIDs — use the auto-generated one or supply your own
  const reference = crypto.randomUUID();

  try {
    const payment = await nylonPay.collectPayment({
      amount,
      currency: 'UGX',
      customer: {
        name: 'Customer Name',
        phoneNumber,
      },
      description: 'Product purchase',
      reference,
      metadata: { orderId },
    });

    // Respond immediately, process async
    res.json({ reference, status: 'initiated' });

    // Handle outcome asynchronously
    payment
      .on('success', ({ transaction }) => {
        updateOrderStatus(orderId, 'paid');
      })
      .on('failed', () => {
        updateOrderStatus(orderId, 'payment_failed');
      })
      .on('error', (data) => {
        console.error('Payment error:', data.error);
      });
  } catch (err) {
    // Initiation error, check err.category
    res.status(400).json({ error: err.category, message: err.message });
  }
});

When to Use Events vs wait()

ScenarioApproach
HTTP API with immediate responseUse on() handlers, respond 202 Accepted
Background job/processorUse on() for state updates
CLI or script or simple flowUse await payment.wait()
Need specific state onlyUse once()

Transaction Payload

type Transaction = {
  id: string;
  reference: string;
  amount: number;
  currency: Currency;
  status: TransactionStatus;
  type: TransactionType;
  method: PaymentMethod;
  description: string;
  operatorTid?: string | null;
  phone: string;
  email: string | null;
  failureReason: string | null;
  metadata: Record<string, string>;
  mode: TransactionMode;
  createdAt: string;
  updatedAt: string;
};

type TransactionStatus =
  | 'pending'
  | 'processing'
  | 'successful'
  | 'failed'
  | 'cancelled';

On this page