Skip to content

About

Flexpay provides a convenient way to handle payments using various mobile money platforms such as Airtel Money, Orange Money, Afrimoney, and M-Pesa. It works with , A mobile money payment aggregator in the Republic Democratic of Congo.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

9 stars

Watchers

1 watching

Forks

Repository files navigation

FlexPay TypeScript

A TypeScript client for FlexPay corporate mobile and card collections, transaction checks, callback parsing, and legacy payouts. Contact FlexPay for merchant credentials and their API documentation.

These examples use the version 4 API. Read the version 4 upgrade guide when upgrading an existing integration.

Requirements and installation

Node.js 24 or later is required. The package ships ESM, CommonJS, and TypeScript declarations. Install version 4 with:

npm install @ngandu-dev/flexpay@^4

Configuration

A client requires credentials and a BaseUrl value with exactly three service prefixes: payment, check, and payout. Choose the production preset explicitly:

import { BaseUrl, Client, Credential } from "@ngandu-dev/flexpay";

const flexpay = new Client(
  new Credential({ merchant: "merchant_code", token: "authorization_token" }),
  BaseUrl.production(),
);

BaseUrl.production() selects these versioned prefixes:

Property Prefix Operation path
payment https://corporateapi.flexpay.cd/api/rest/v1 /paymentService for mobile and card
check https://apicheck.flexpaie.com/api/rest/v1 /check/{orderNumber}
payout https://backend.flexpay.cd/api/rest/v1 /merchantPayOutService

For sandbox integrations, use the FlexPay sandbox and pass its deployment URL to BaseUrl.sandbox(). It sets payment, check, and payout to that URL:

const sandbox = new Client(
  new Credential({ merchant: "TESTMERCHANT", token: "test-token" }),
  BaseUrl.sandbox("https://your-flexpay-sandbox.example/api/rest/v1"),
);

Prefixes must be absolute HTTP(S) URLs without credentials, query strings, fragments, backslashes, or interior whitespace/control characters. Surrounding whitespace and trailing slashes are removed. BaseUrl and Credential validate and freeze their inputs using Zod schemas. Credentials must be nonblank; their contents are preserved. Unknown fields are rejected. Later caller mutations cannot change the validated objects. There is no environment flag or default network choice. An optional third constructor argument accepts a Fetch-compatible function for HTTP integration and tests. It must honor the supplied abort signal and redirect: "manual", without adding retries. The client sets bearer authentication, disables redirects, and bounds each HTTP attempt to 30 seconds. The bearer token never enters payment JSON.

Mobile collection

import { MobileRequest } from "@ngandu-dev/flexpay";

const mobile = await flexpay.mobile(new MobileRequest({
  amount: 10,
  reference: "invoice-123",
  currency: "USD",
  callbackUrl: "https://merchant.example/callback",
  phone: "243999999999",
}));

if (!mobile.isAccepted()) {
  // Record mobile.message and mobile.code.value for reconciliation.
}

isAccepted() reports provider code zero. It does not confirm that the customer paid or guarantee that an order number was supplied. Preserve the returned orderNumber for status checks.

Card collection

import { CardRequest } from "@ngandu-dev/flexpay";

const card = await flexpay.card(new CardRequest({
  amount: 10,
  reference: "invoice-124",
  currency: "USD",
  callbackUrl: "https://merchant.example/callback",
  approveUrl: "https://merchant.example/approved",
  cancelUrl: "https://merchant.example/cancelled",
  declineUrl: "https://merchant.example/declined",
}));

if (card.isAccepted() && card.url !== null && card.url !== "") {
  // Use card.url to continue checkout under your application's redirect policy.
}

Mobile and card requests send the corporate callback_url field. Card return URLs use approve_url, cancel_url, and decline_url. Immutable request objects contain no merchant or token. The transport adds the merchant during HTTP encoding. Amounts must be finite and positive; references and URL fields must be nonblank. Mobile and payout phone numbers must contain exactly 12 digits. Request schemas reject unknown fields and check URL fields for nonblank content; they do not validate reachability or callback delivery.

Payout

import { PayoutRequest } from "@ngandu-dev/flexpay";

const payout = await flexpay.payout(new PayoutRequest({
  amount: 10,
  reference: "payout-123",
  currency: "USD",
  callbackUrl: "https://merchant.example/payout-callback",
  phone: "243999999999",
}));

payout.isAccepted();

Payout retains the existing endpoint and JSON format, including numeric type: 1 and callbackUrl. All POST operations are sent once. Only GET transaction checks retry transient failures, at most three retries after the first attempt, for transport failures and HTTP 5xx. Backoff is 500, 1000, then 2000 milliseconds. An integer-seconds Retry-After can extend that delay up to two seconds. Longer delays, HTTP dates, and malformed values stop automatic retries. HTTP 4xx and malformed HTTP 2xx responses are not retried. Reconcile an ambiguous POST result before resubmitting it.

Transaction checks and callbacks

if (mobile.orderNumber === null || mobile.orderNumber.trim() === "") {
  throw new Error("FlexPay did not return an order number");
}

const check = await flexpay.check(mobile.orderNumber);
const successful = check.isSuccessful();
const lookupCode = check.code.value;
const transactionCode = check.transaction?.status.value;

Use the provider's order number for checks. The client encodes it as one path segment and rejects blank values and dot-only path segments. CheckResponse.isSuccessful() requires both response code zero and nested transaction status zero. A missing transaction returns false. Malformed nested transactions, including a missing currency, throw NetworkException with type invalid_response.

Callback parsing does not require a client or credentials:

import { CallbackResponse } from "@ngandu-dev/flexpay";

// incomingBody is the unknown JSON body received by your callback endpoint.
const callback = CallbackResponse.fromPayload(incomingBody);
callback.isSuccessful();
callback.providerReference; // provider_reference, when supplied

The callback parser validates the supplied data; it does not authenticate the sender or authorize fulfillment. Invalid callback data reports a Zod validation error. Reporting fields include reference, orderNumber, providerReference, amount, amountCustomer, phone, currency, createdAt, and channel when present.

Response codes and transaction statuses are always immutable Status values. Read their numeric value; isSuccessful() recognizes zero and isFailure() recognizes one. Other codes satisfy neither predicate and remain observable without assigning them a pending or terminal meaning. Response objects and nested transactions are frozen. Callback success requires code zero.

Development

bun install --frozen-lockfile
bun run format
bun run quality
bun run test:coverage
bun audit

quality checks formatting, types, tests, builds, and package exports. Automated tests use injected Fetch implementations and never submit provider payments.

Contributing and security

See CONTRIBUTING.md for the contribution workflow and SECURITY.md for private vulnerability reporting. Contributions follow the Code of Conduct. Released under the MIT License.

About

Flexpay provides a convenient way to handle payments using various mobile money platforms such as Airtel Money, Orange Money, Afrimoney, and M-Pesa. It works with , A mobile money payment aggregator in the Republic Democratic of Congo.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

9 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages