/** * Generated by the Lango developer portal. Do not edit by hand. * Group: payments (Payments) * APIs: Collect v1.2.0, Payouts v1.0.3, Ledger v2.1.0 * Generated: 2026-09-28T11:00:55.868Z */ export class LangoApiError extends Error { status: number; error: string; requestId?: string; constructor(status: number, error: string, message: string, requestId?: string) { super(message); this.name = "LangoApiError"; this.status = status; this.error = error; this.requestId = requestId; } } export interface CreatePushRequest { /** The till or paybill number receiving the money. */ shortCode: string; /** Whole currency units. In the sandbox, 1, 2 and 3 trigger failure scenarios. */ amount: number; currency: "KES" | "TZS" | "UGX"; /** Customer's number in international format, no plus sign. */ phoneNumber: string; /** Your own reference. Shown to the customer and returned in the callback. */ reference: string; description?: string; /** Where the final result is posted. */ callbackUrl: string; } export interface SendPayoutRequest { /** Whole currency units. In the sandbox, 1 triggers the low-float scenario. */ amount: number; currency: "KES" | "TZS" | "UGX"; destination: { type: "mobile" | "bank"; phoneNumber?: string; bankCode?: string; accountNumber?: string }; /** Unique per payout. Reusing one returns 409. */ reference: string; reason?: "Salary" | "Supplier" | "Refund" | "Commission" | "Other"; callbackUrl: string; } export interface ReverseTransactionRequest { reason: string; callbackUrl: string; } export class PaymentsClient { private consumerKey: string; private consumerSecret: string; private baseUrl: string; private token: string | null = null; private tokenExpiresAt = 0; constructor(consumerKey: string, consumerSecret: string, baseUrl: string) { this.consumerKey = consumerKey; this.consumerSecret = consumerSecret; this.baseUrl = baseUrl.replace(/\/+$/, ""); } private async getToken(): Promise { if (this.token && Date.now() < this.tokenExpiresAt) return this.token; const basic = btoa(`${this.consumerKey}:${this.consumerSecret}`); const res = await fetch(`${this.baseUrl}/oauth/v1/token`, { method: "POST", headers: { authorization: `Basic ${basic}`, "content-type": "application/x-www-form-urlencoded" }, body: "grant_type=client_credentials", }); const data = (await res.json().catch(() => undefined)) as | { access_token?: string; expires_in?: number; error?: string; message?: string; requestId?: string } | undefined; if (!res.ok || !data?.access_token) { throw new LangoApiError(res.status, data?.error ?? "TOKEN_ERROR", data?.message ?? "Could not get a sandbox token.", data?.requestId); } this.token = data.access_token; this.tokenExpiresAt = Date.now() + ((data.expires_in ?? 3600) - 60) * 1000; return this.token; } private async request( method: string, path: string, query: Record | undefined, body: unknown, ): Promise { const token = await this.getToken(); const parts = Object.entries(query ?? {}) .filter(([, v]) => v !== undefined) .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`); const url = this.baseUrl + path + (parts.length ? `?${parts.join("&")}` : ""); const res = await fetch(url, { method, headers: { authorization: `Bearer ${token}`, ...(body !== undefined ? { "content-type": "application/json" } : {}), }, body: body !== undefined ? JSON.stringify(body) : undefined, }); const data = await res.json().catch(() => undefined); if (!res.ok) { const d = data as { error?: string; message?: string; requestId?: string } | undefined; throw new LangoApiError(res.status, d?.error ?? "REQUEST_FAILED", d?.message ?? res.statusText, d?.requestId); } return data; } /** Send a payment prompt */ async createPush(body: CreatePushRequest): Promise { return this.request("POST", `/collect/v1/push`, undefined, body); } /** Check a payment prompt */ async getPush(params: { checkoutId: string }): Promise { return this.request("GET", `/collect/v1/push/${encodeURIComponent(String(params.checkoutId))}`, undefined, undefined); } /** Send a payout */ async sendPayout(body: SendPayoutRequest): Promise { return this.request("POST", `/payouts/v1/send`, undefined, body); } /** Check a payout */ async getPayout(params: { payoutId: string }): Promise { return this.request("GET", `/payouts/v1/${encodeURIComponent(String(params.payoutId))}`, undefined, undefined); } /** Look up a transaction */ async getTransaction(params: { transactionId: string }): Promise { return this.request("GET", `/ledger/v2/transactions/${encodeURIComponent(String(params.transactionId))}`, undefined, undefined); } /** Reverse a transaction */ async reverseTransaction(params: { transactionId: string }, body: ReverseTransactionRequest): Promise { return this.request("POST", `/ledger/v2/transactions/${encodeURIComponent(String(params.transactionId))}/reversal`, undefined, body); } /** Read your balances */ async getBalance(params: { shortCode: string }): Promise { return this.request("GET", `/ledger/v2/balance`, { shortCode: params.shortCode }, undefined); } }