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.
Node.js 24 or later is required. The package ships ESM, CommonJS, and TypeScript declarations. Install version 4 with:
npm install @ngandu-dev/flexpay@^4A 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.
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.
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.
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.
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 suppliedThe 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.
bun install --frozen-lockfile
bun run format
bun run quality
bun run test:coverage
bun auditquality checks formatting, types, tests, builds, and package exports. Automated tests use injected
Fetch implementations and never submit provider payments.
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.