pesafy 0.3.13 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +109 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +188 -1
- package/dist/index.d.ts +188 -1
- package/dist/index.js +107 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/errors/index.ts","../src/core/encryption/security-credentials.ts","../src/utils/http/index.ts","../src/core/auth/token-manager.ts","../src/mpesa/c2b/register-url.ts","../src/mpesa/c2b/simulate.ts","../src/mpesa/c2b/webhooks.ts","../src/mpesa/dynamic-qr/generate.ts","../src/utils/phone/index.ts","../src/mpesa/stk-push/utils.ts","../src/mpesa/stk-push/stk-push.ts","../src/mpesa/stk-push/stk-query.ts","../src/mpesa/stk-push/types.ts","../src/mpesa/transaction-status/query.ts","../src/mpesa/types.ts","../src/mpesa/index.ts","../src/mpesa/webhooks/retry.ts","../src/mpesa/webhooks/signature-verifier.ts","../src/mpesa/webhooks/webhook-handler.ts"],"names":["publicEncrypt","constants"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Pesafy error types and utilities.\n *\n * Single source of truth — no duplicate definitions.\n * Previously this file had two conflicting ErrorCode aliases and two\n * PesafyError class definitions which caused TypeScript to use an\n * unpredictable one at runtime.\n */\n\n// ── Error code union ──────────────────────────────────────────────────────────\n\nexport type ErrorCode =\n | \"AUTH_FAILED\"\n | \"INVALID_CREDENTIALS\"\n | \"INVALID_PHONE\"\n | \"ENCRYPTION_FAILED\"\n | \"VALIDATION_ERROR\"\n | \"API_ERROR\"\n | \"HTTP_ERROR\"\n | \"NETWORK_ERROR\"\n | \"REQUEST_FAILED\"\n | \"INVALID_RESPONSE\"\n | \"TIMEOUT\";\n\n// ── Error options ─────────────────────────────────────────────────────────────\n\nexport interface PesafyErrorOptions {\n code: ErrorCode;\n message: string;\n /** HTTP status code from Daraja (if applicable) */\n statusCode?: number;\n /** Raw Daraja response body (for debugging) */\n response?: unknown;\n /** Underlying caught error (network, crypto, etc.) */\n cause?: unknown;\n /** Daraja requestId from the error envelope */\n requestId?: string;\n}\n\n// ── PesafyError class ─────────────────────────────────────────────────────────\n\nexport class PesafyError extends Error {\n readonly code: ErrorCode;\n readonly statusCode: number | undefined;\n readonly response: unknown;\n readonly requestId: string | undefined;\n override readonly cause: unknown;\n\n constructor(options: PesafyErrorOptions) {\n super(options.message);\n // Ensure instanceof checks work correctly\n Object.defineProperty(this, \"name\", { value: \"PesafyError\" });\n this.code = options.code;\n this.statusCode = options.statusCode;\n this.response = options.response;\n this.requestId = options.requestId;\n this.cause = options.cause;\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, PesafyError);\n }\n }\n\n toJSON() {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n statusCode: this.statusCode,\n requestId: this.requestId,\n };\n }\n}\n\n// ── Factory ───────────────────────────────────────────────────────────────────\n\n/** Convenience factory — identical API to `new PesafyError(...)` */\nexport function createError(options: PesafyErrorOptions): PesafyError {\n return new PesafyError(options);\n}\n","/**\n * Security credential encryption for Daraja APIs that require it:\n * B2C, B2B, Transaction Status Query, Reversals, Tax Remittance.\n *\n * Algorithm (from Safaricom \"Getting Started\" docs):\n * 1. Write the unencrypted initiator password into a byte array.\n * 2. Encrypt using the M-Pesa public key certificate:\n * - RSA algorithm\n * - PKCS #1 v1.5 padding (NOT OAEP)\n * 3. Base64-encode the encrypted byte array.\n *\n * Certificate source:\n * Sandbox: https://developer.safaricom.co.ke (SandboxCertificate.cer)\n * Production: https://developer.safaricom.co.ke (ProductionCertificate.cer)\n *\n * NOTE: Use the correct certificate for each environment or credentials\n * will be rejected.\n */\n\nimport { constants, publicEncrypt } from \"node:crypto\";\nimport { PesafyError } from \"../../utils/errors\";\n\n/**\n * Encrypts `initiatorPassword` with the given PEM certificate and returns\n * the base64-encoded security credential ready to send to Daraja.\n *\n * @param initiatorPassword - Plain-text password set on the M-PESA org portal\n * @param certificatePem - Full PEM string (the .cer file contents)\n */\nexport function encryptSecurityCredential(\n initiatorPassword: string,\n certificatePem: string\n): string {\n try {\n const passwordBuffer = Buffer.from(initiatorPassword, \"utf-8\");\n\n const encrypted = publicEncrypt(\n {\n key: certificatePem,\n // RSA_PKCS1_PADDING = 1 (NOT RSA_PKCS1_OAEP_PADDING = 4)\n padding: constants.RSA_PKCS1_PADDING,\n },\n passwordBuffer\n );\n\n return encrypted.toString(\"base64\");\n } catch (error) {\n throw new PesafyError({\n code: \"ENCRYPTION_FAILED\",\n message:\n \"Failed to encrypt security credential. \" +\n \"Ensure the certificate PEM is valid and matches the environment (sandbox/production).\",\n cause: error,\n });\n }\n}\n","/**\n * HTTP client for Daraja API calls.\n *\n * Single source of truth — previously this file contained TWO conflicting\n * httpRequest implementations:\n * 1. A retry-capable version (retries, retryDelay options)\n * 2. An older timeout-based version (timeout option, no retries)\n *\n * Having both caused TypeScript to pick an unpredictable implementation.\n * The older version did NOT retry on 503 — meaning Daraja sandbox failures\n * surfaced immediately rather than being absorbed by backoff.\n *\n * This file is the SINGLE canonical export. Only export: httpRequest.\n * Never export \"httpClient\" — consumers must call httpRequest directly.\n */\n\nimport { PesafyError } from \"../errors\";\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface HttpRequestOptions {\n method: \"GET\" | \"POST\";\n headers?: Record<string, string>;\n /**\n * Request body. Will be JSON.stringify'd and sent as application/json.\n * Pass undefined (not null) to omit the body entirely.\n */\n body?: unknown;\n /**\n * Number of retry attempts on transient errors (503, 429, 502, 504, network).\n * Default: 4. Set to 0 to disable retries.\n *\n * Daraja sandbox is notoriously unstable — 503s are common under load.\n * 4 retries with exponential backoff covers the typical sandbox blip.\n */\n retries?: number;\n /**\n * Base delay in ms before the first retry. Doubles each attempt + ±25% jitter.\n * Default: 2000 (2 s).\n */\n retryDelay?: number;\n /**\n * Per-request timeout in ms. Default: 30000 (30 s).\n * The timeout applies to each individual attempt, not the total retry duration.\n */\n timeout?: number;\n}\n\nexport interface HttpResponse<T> {\n data: T;\n status: number;\n headers: Record<string, string>;\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\n/** HTTP status codes that are transient and safe to retry */\nconst RETRYABLE_STATUSES = new Set([429, 500, 502, 503, 504]);\n\n/** Promise-based sleep */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Adds ±25% jitter to avoid thundering-herd retry storms.\n * Daraja sandbox 503s are caused by sandbox overload — spreading retries\n * across a time window prevents making the overload worse.\n */\nfunction withJitter(baseMs: number): number {\n const spread = baseMs * 0.25;\n return baseMs + (Math.random() * spread * 2 - spread);\n}\n\n// ── httpRequest ───────────────────────────────────────────────────────────────\n\n/**\n * Sends an HTTP request and returns parsed JSON.\n *\n * Automatically retries on transient errors with exponential backoff + jitter.\n * Never retries on 4xx client errors — those indicate a logic bug.\n *\n * @throws PesafyError on non-retryable errors or exhausted retries\n */\nexport async function httpRequest<T = unknown>(\n url: string,\n options: HttpRequestOptions\n): Promise<HttpResponse<T>> {\n const maxRetries = options.retries ?? 4;\n const baseDelay = options.retryDelay ?? 2000;\n const timeout = options.timeout ?? 30_000;\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...options.headers,\n };\n\n // Build the fetch init once — body is immutable across retries\n const init: RequestInit = {\n method: options.method,\n headers,\n ...(options.body !== undefined\n ? { body: JSON.stringify(options.body) }\n : {}),\n };\n\n let lastError: PesafyError | null = null;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n // ── Backoff before retry (not before first attempt) ─────────────────────\n if (attempt > 0) {\n const delay = withJitter(baseDelay * Math.pow(2, attempt - 1));\n console.warn(\n `[pesafy/http] Retry ${attempt}/${maxRetries} for ${options.method} ${url} ` +\n `in ${Math.round(delay)}ms (last error: ${lastError?.message ?? \"unknown\"})`\n );\n await sleep(delay);\n }\n\n // ── Per-attempt timeout via AbortController ──────────────────────────────\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n let response: Response;\n\n try {\n response = await fetch(url, { ...init, signal: controller.signal });\n } catch (err) {\n clearTimeout(timeoutId);\n\n // AbortError = timeout\n if (err instanceof Error && err.name === \"AbortError\") {\n lastError = new PesafyError({\n code: \"TIMEOUT\",\n message: `Request to ${url} timed out after ${timeout}ms`,\n cause: err,\n });\n if (attempt < maxRetries) continue;\n throw lastError;\n }\n\n // Network-level failure: DNS, ECONNRESET, ECONNREFUSED, etc.\n lastError = new PesafyError({\n code: \"NETWORK_ERROR\",\n message: `Network error calling ${url}: ${err instanceof Error ? err.message : String(err)}`,\n cause: err,\n });\n if (attempt < maxRetries) continue;\n throw lastError;\n } finally {\n clearTimeout(timeoutId);\n }\n\n // ── Parse body regardless of status ─────────────────────────────────────\n // We always read the body so we can include Daraja's error message in\n // thrown errors instead of just \"HTTP 503\".\n let rawText = \"\";\n let data: unknown;\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n\n try {\n rawText = await response.text();\n if (rawText) {\n data = contentType.includes(\"application/json\")\n ? JSON.parse(rawText)\n : rawText;\n } else {\n data = null;\n }\n } catch {\n data = rawText || null;\n }\n\n // ── Collect response headers ─────────────────────────────────────────────\n const responseHeaders: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n responseHeaders[key] = value;\n });\n\n // ── Success ──────────────────────────────────────────────────────────────\n if (response.ok) {\n return {\n data: data as T,\n status: response.status,\n headers: responseHeaders,\n };\n }\n\n // ── Error response ───────────────────────────────────────────────────────\n const isTransient = RETRYABLE_STATUSES.has(response.status);\n\n // Daraja error envelope shapes:\n // { requestId, errorCode, errorMessage }\n // { ResponseCode, ResponseDescription }\n const daraja =\n typeof data === \"object\" && data !== null\n ? (data as Record<string, unknown>)\n : {};\n\n const errorMessage =\n (daraja.errorMessage as string | undefined) ??\n (daraja.ResponseDescription as string | undefined) ??\n rawText ??\n `HTTP ${response.status}`;\n\n lastError = new PesafyError({\n code: isTransient ? \"REQUEST_FAILED\" : \"API_ERROR\",\n message: errorMessage,\n statusCode: response.status,\n response: data,\n requestId: daraja.requestId as string | undefined,\n });\n\n // Only retry transient errors. 4xx errors are client bugs — never retry.\n if (isTransient && attempt < maxRetries) {\n continue;\n }\n\n throw lastError;\n }\n\n // Unreachable — TypeScript requires this\n throw lastError!;\n}\n","/**\n * OAuth token manager for Daraja API.\n *\n * Daraja Authorization endpoint (GET, Basic Auth):\n * https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials\n * https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials\n *\n * Token validity: 3600 seconds (1 hour).\n * We refresh 60 s early to avoid edge-case expiry mid-request.\n *\n * Ref: Authorization By Safaricom docs\n */\n\nimport { PesafyError } from \"../../utils/errors\";\nimport { httpRequest } from \"../../utils/http\";\nimport type { TokenResponse } from \"./types\";\n\n/** Refresh the token this many seconds before it actually expires */\nconst TOKEN_BUFFER_SECONDS = 60;\n\nexport class TokenManager {\n private readonly consumerKey: string;\n private readonly consumerSecret: string;\n private readonly baseUrl: string;\n\n private cachedToken: string | null = null;\n private tokenExpiresAt = 0; // Unix seconds\n\n constructor(consumerKey: string, consumerSecret: string, baseUrl: string) {\n this.consumerKey = consumerKey;\n this.consumerSecret = consumerSecret;\n this.baseUrl = baseUrl;\n }\n\n private getBasicAuthHeader(): string {\n // Daraja spec: Base64(consumerKey:consumerSecret)\n const credentials = `${this.consumerKey}:${this.consumerSecret}`;\n const encoded = Buffer.from(credentials, \"utf-8\").toString(\"base64\");\n return `Basic ${encoded}`;\n }\n\n /**\n * Returns a valid access token, fetching a new one when the cached token\n * is absent or within TOKEN_BUFFER_SECONDS of expiry.\n */\n async getAccessToken(): Promise<string> {\n const now = Date.now() / 1000;\n\n if (this.cachedToken && this.tokenExpiresAt > now + TOKEN_BUFFER_SECONDS) {\n return this.cachedToken;\n }\n\n // Daraja Authorization API: GET with Basic Auth + grant_type query param\n const url = `${this.baseUrl}/oauth/v1/generate?grant_type=client_credentials`;\n\n const response = await httpRequest<TokenResponse>(url, {\n method: \"GET\",\n headers: {\n Authorization: this.getBasicAuthHeader(),\n },\n });\n\n const { access_token, expires_in } = response.data;\n\n if (!access_token) {\n throw new PesafyError({\n code: \"AUTH_FAILED\",\n message:\n \"Daraja did not return an access token. Check your consumer key and secret.\",\n response: response.data,\n });\n }\n\n this.cachedToken = access_token;\n // expires_in is 3599 per Daraja docs; default to 3600 if missing\n this.tokenExpiresAt = now + (expires_in ?? 3600);\n\n return this.cachedToken;\n }\n\n /** Force token refresh on the next call (e.g. after a 401 response) */\n clearCache(): void {\n this.cachedToken = null;\n this.tokenExpiresAt = 0;\n }\n}\n","/**\n * C2B Register URL\n *\n * Registers your Confirmation and Validation URLs with Safaricom.\n *\n * API:\n * v1: POST /mpesa/c2b/v1/registerurl\n * v2: POST /mpesa/c2b/v2/registerurl ← default (masked MSISDN in callbacks)\n *\n * Sandbox: https://sandbox.safaricom.co.ke/mpesa/c2b/v{1|2}/registerurl\n * Production: https://api.safaricom.co.ke/mpesa/c2b/v{1|2}/registerurl\n *\n * Notes from Daraja docs:\n * - Sandbox: you can register URLs multiple times / overwrite existing.\n * - Production: one-time call. To change, delete via Self Services > URL\n * Management on the Daraja portal, then re-register.\n * - ResponseType must be exactly \"Completed\" or \"Cancelled\" (sentence case).\n * - Production URLs must be HTTPS. Sandbox allows HTTP.\n * - URLs must not contain keywords: M-PESA, Safaricom, exe, exec, cmd, sql, query.\n * - Do not use public URL testers (ngrok, mockbin, requestbin) in production.\n */\n\nimport { createError } from \"../../utils/errors\";\nimport { httpRequest } from \"../../utils/http\";\nimport type {\n C2BApiVersion,\n C2BRegisterUrlRequest,\n C2BRegisterUrlResponse,\n} from \"./types\";\n\n/** Forbidden URL keywords per Daraja docs */\nconst FORBIDDEN_URL_KEYWORDS = [\n \"mpesa\",\n \"safaricom\",\n \".exe\",\n \".exec\",\n \"cmd\",\n \"sql\",\n \"query\",\n];\n\n/**\n * Validates a callback URL against Daraja's URL requirements.\n * Throws PesafyError if the URL violates any rule.\n */\nfunction validateCallbackUrl(url: string, fieldName: string): void {\n if (!url || !url.trim()) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: `${fieldName} is required`,\n });\n }\n\n const lower = url.toLowerCase();\n\n for (const keyword of FORBIDDEN_URL_KEYWORDS) {\n if (lower.includes(keyword)) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n `${fieldName} must not contain the keyword \"${keyword}\". ` +\n \"Daraja rejects URLs containing: M-PESA, Safaricom, exe, exec, cmd, sql, query.\",\n });\n }\n }\n}\n\n/**\n * Registers your C2B Confirmation and Validation URLs with Safaricom.\n *\n * @param baseUrl - Daraja base URL (sandbox or production)\n * @param accessToken - Valid OAuth bearer token\n * @param request - Registration parameters\n * @returns - Daraja registration response\n */\nexport async function registerC2BUrls(\n baseUrl: string,\n accessToken: string,\n request: C2BRegisterUrlRequest\n): Promise<C2BRegisterUrlResponse> {\n // ── Validation ──────────────────────────────────────────────────────────────\n\n if (!request.shortCode) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: \"shortCode is required\",\n });\n }\n\n if (!request.responseType) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n 'responseType is required: \"Completed\" or \"Cancelled\" (sentence case, exactly as spelled)',\n });\n }\n\n if (\n request.responseType !== \"Completed\" &&\n request.responseType !== \"Cancelled\"\n ) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n `responseType must be exactly \"Completed\" or \"Cancelled\" (sentence case). ` +\n `Got: \"${request.responseType}\"`,\n });\n }\n\n validateCallbackUrl(request.confirmationUrl, \"confirmationUrl\");\n validateCallbackUrl(request.validationUrl, \"validationUrl\");\n\n // ── Determine API version ───────────────────────────────────────────────────\n const version: C2BApiVersion = request.apiVersion ?? \"v2\";\n\n // ── Build payload matching Daraja spec exactly ──────────────────────────────\n const payload = {\n ShortCode: String(request.shortCode),\n ResponseType: request.responseType,\n ConfirmationURL: request.confirmationUrl,\n ValidationURL: request.validationUrl,\n };\n\n const { data } = await httpRequest<C2BRegisterUrlResponse>(\n `${baseUrl}/mpesa/c2b/${version}/registerurl`,\n {\n method: \"POST\",\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n }\n );\n\n return data;\n}\n","/**\n * C2B Simulate Transaction — SANDBOX ONLY\n *\n * Simulates a customer payment to your Paybill or Till number.\n *\n * API:\n * v1: POST /mpesa/c2b/v1/simulate\n * v2: POST /mpesa/c2b/v2/simulate ← default\n *\n * ─── CRITICAL: BillRefNumber rules (confirmed by Daraja docs + live testing) ─\n *\n * CustomerPayBillOnline (Paybill):\n * → INCLUDE BillRefNumber as the account/invoice reference.\n * Even an empty string \"\" is acceptable for Paybill.\n *\n * CustomerBuyGoodsOnline (Till):\n * → COMPLETELY OMIT BillRefNumber from the JSON body.\n * → DO NOT send it as null, undefined, or \"\" (empty string).\n * → Daraja docs say \"null for till number\" but in practice ANY presence\n * of the field — including null or \"\" — triggers:\n * HTTP 400: \"The element AccountReference is invalid\"\n * which Daraja sandbox may also surface as HTTP 503.\n * → The fix is to never include the key in the payload object at all.\n *\n * ─── CRITICAL: URL registration per shortcode ─────────────────────────────────\n *\n * Daraja docs: \"Register URLs before each simulation.\"\n * Registration is PER SHORTCODE — each shortcode is registered INDEPENDENTLY:\n * - Paybill shortcode (e.g. 600977): registerC2BUrls({ shortCode: \"600977\" })\n * - Till shortcode (e.g. 600000): registerC2BUrls({ shortCode: \"600000\" })\n * Simulating with a shortcode whose URLs are not registered will cause errors.\n *\n * ─── Sandbox shortcodes ───────────────────────────────────────────────────────\n *\n * CustomerPayBillOnline → your registered Paybill shortcode (e.g. 600977)\n * CustomerBuyGoodsOnline → Daraja test Till shortcode: 600000\n */\n\nimport { createError } from \"../../utils/errors\";\nimport { httpRequest } from \"../../utils/http\";\nimport type {\n C2BApiVersion,\n C2BSimulateRequest,\n C2BSimulateResponse,\n} from \"./types\";\n\n/**\n * Simulates a C2B customer payment. SANDBOX ONLY.\n *\n * @param baseUrl - Must be the sandbox base URL (contains \"sandbox\").\n * @param accessToken - Valid OAuth bearer token from the Authorization API.\n * @param request - Simulation parameters. Do NOT pass billRefNumber for Buy Goods.\n * @returns - Daraja simulate response (ResponseCode \"0\" = accepted).\n */\nexport async function simulateC2B(\n baseUrl: string,\n accessToken: string,\n request: C2BSimulateRequest\n): Promise<C2BSimulateResponse> {\n // ── Sandbox guard ───────────────────────────────────────────────────────────\n if (!baseUrl.includes(\"sandbox\")) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n \"C2B simulate is only available in the Sandbox environment. \" +\n \"In production, customers initiate payments via M-PESA App, USSD, or SIM Toolkit.\",\n });\n }\n\n // ── Input validation ────────────────────────────────────────────────────────\n\n if (!request.shortCode) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: \"shortCode is required.\",\n });\n }\n\n if (\n request.commandId !== \"CustomerPayBillOnline\" &&\n request.commandId !== \"CustomerBuyGoodsOnline\"\n ) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n `commandId must be \"CustomerPayBillOnline\" or \"CustomerBuyGoodsOnline\". ` +\n `Got: \"${request.commandId}\"`,\n });\n }\n\n const amount = Math.round(request.amount);\n if (!Number.isFinite(amount) || amount < 1) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: `amount must be a whole number ≥ 1 (got ${request.amount}).`,\n });\n }\n\n if (!request.msisdn) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: \"msisdn is required. Sandbox test MSISDN: 254708374149.\",\n });\n }\n\n // ── Determine transaction type ──────────────────────────────────────────────\n const isBuyGoods = request.commandId === \"CustomerBuyGoodsOnline\";\n const version: C2BApiVersion = request.apiVersion ?? \"v2\";\n\n // ── Build payload ───────────────────────────────────────────────────────────\n //\n // CRITICAL — BillRefNumber MUST be omitted for CustomerBuyGoodsOnline.\n //\n // We build the base object WITHOUT BillRefNumber and only conditionally\n // add it for Paybill. This is the only safe pattern because:\n //\n // • JSON.stringify({ BillRefNumber: null }) → includes \"BillRefNumber\":null ✗\n // • JSON.stringify({ BillRefNumber: \"\" }) → includes \"BillRefNumber\":\"\" ✗\n // • building without the key at all → field absent from JSON body ✓\n //\n // Daraja validates field presence, not just value — even null/empty triggers:\n // \"The element AccountReference is invalid\"\n //\n // Note: the caller (simulateC2BPayment in c2bActions.ts) must NOT pass\n // billRefNumber at all for Buy Goods — pass undefined or omit the key.\n // This function is defensive and will still correctly omit it regardless.\n\n const payload: Record<string, unknown> = {\n ShortCode: Number(request.shortCode),\n CommandID: request.commandId,\n Amount: amount,\n Msisdn: Number(request.msisdn),\n // BillRefNumber is NOT here — added conditionally below for Paybill only\n };\n\n if (!isBuyGoods) {\n // Paybill: include BillRefNumber (the account/invoice ref for this payment).\n // Empty string is acceptable when no specific account ref is needed.\n payload[\"BillRefNumber\"] = request.billRefNumber ?? \"\";\n }\n\n // ── Defensive check — should never trigger given the logic above ─────────────\n // Acts as a compile-time-visible safety net for future refactors.\n if (\n isBuyGoods &&\n Object.prototype.hasOwnProperty.call(payload, \"BillRefNumber\")\n ) {\n // This branch must never execute. If it does, remove the key to prevent\n // the \"AccountReference is invalid\" error from Daraja.\n delete payload[\"BillRefNumber\"];\n console.warn(\n \"[pesafy/simulateC2B] BillRefNumber leaked into Buy Goods payload — removed. \" +\n \"This is a library bug; please report it.\"\n );\n }\n\n // ── Call Daraja ─────────────────────────────────────────────────────────────\n const { data } = await httpRequest<C2BSimulateResponse>(\n `${baseUrl}/mpesa/c2b/${version}/simulate`,\n {\n method: \"POST\",\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n }\n );\n\n return data;\n}\n","/**\n * C2B Webhook Handlers\n *\n * Utilities for parsing and responding to Safaricom C2B callbacks:\n * - Validation callback → your ValidationURL\n * - Confirmation callback → your ConfirmationURL\n *\n * Safaricom IP whitelist applies (same as STK Push callbacks).\n * Always respond 200 to Safaricom — log errors internally.\n *\n * Validation response timing: must respond within ~8 seconds.\n *\n * Ref: C2B Daraja docs — Callback Payload section\n */\n\nimport type {\n C2BConfirmationAck,\n C2BConfirmationPayload,\n C2BValidationPayload,\n C2BValidationResponse,\n C2BValidationResultCode,\n} from \"./types\";\n\n// ── Type guards ───────────────────────────────────────────────────────────────\n\n/**\n * Checks if a body looks like a C2B Validation or Confirmation payload.\n * Both share the same shape; distinguish them by context (which URL received it).\n */\nexport function isC2BPayload(body: unknown): body is C2BValidationPayload {\n if (!body || typeof body !== \"object\") return false;\n const b = body as Record<string, unknown>;\n return (\n typeof b[\"TransID\"] === \"string\" &&\n typeof b[\"BusinessShortCode\"] === \"string\" &&\n typeof b[\"TransAmount\"] === \"string\"\n );\n}\n\n// ── Validation responses ──────────────────────────────────────────────────────\n\n/**\n * Builds an \"Accept\" validation response.\n * Send this to M-PESA to allow the transaction to proceed.\n *\n * @param thirdPartyTransID - Optional correlation ID echoed back in Confirmation.\n */\nexport function acceptC2BValidation(\n thirdPartyTransID?: string\n): C2BValidationResponse {\n return {\n ResultCode: \"0\",\n ResultDesc: \"Accepted\",\n ...(thirdPartyTransID ? { ThirdPartyTransID: thirdPartyTransID } : {}),\n };\n}\n\n/**\n * Builds a \"Reject\" validation response.\n * Send this to M-PESA to cancel the transaction.\n *\n * @param resultCode - C2B error code. Determines the SMS the customer gets.\n * @param resultDesc - Short description. Usually \"Rejected\".\n *\n * Result codes:\n * C2B00011 — Invalid MSISDN\n * C2B00012 — Invalid Account Number\n * C2B00013 — Invalid Amount\n * C2B00014 — Invalid KYC Details\n * C2B00015 — Invalid Short code\n * C2B00016 — Other Error\n */\nexport function rejectC2BValidation(\n resultCode: Exclude<C2BValidationResultCode, \"0\"> = \"C2B00016\",\n resultDesc = \"Rejected\"\n): C2BValidationResponse {\n return {\n ResultCode: resultCode,\n ResultDesc: resultDesc,\n };\n}\n\n/**\n * Builds the acknowledgement your ConfirmationURL must return to Safaricom.\n * Always return this with HTTP 200.\n */\nexport function acknowledgeC2BConfirmation(): C2BConfirmationAck {\n return { ResultCode: 0, ResultDesc: \"Success\" };\n}\n\n// ── Convenience extractors ────────────────────────────────────────────────────\n\n/** Extracts the transaction amount as a number from a C2B payload */\nexport function getC2BAmount(\n payload: C2BValidationPayload | C2BConfirmationPayload\n): number {\n return Number(payload.TransAmount);\n}\n\n/** Extracts the M-PESA receipt/transaction ID from a C2B payload */\nexport function getC2BTransactionId(\n payload: C2BValidationPayload | C2BConfirmationPayload\n): string {\n return payload.TransID;\n}\n\n/** Extracts the account reference (BillRefNumber) from a C2B payload */\nexport function getC2BAccountRef(\n payload: C2BValidationPayload | C2BConfirmationPayload\n): string {\n return payload.BillRefNumber;\n}\n\n/**\n * Returns the customer's full name from a C2B payload.\n * Note: data minimization per Safaricom data protection requirements;\n * some fields may be blank.\n */\nexport function getC2BCustomerName(\n payload: C2BValidationPayload | C2BConfirmationPayload\n): string {\n return [payload.FirstName, payload.MiddleName, payload.LastName]\n .filter(Boolean)\n .join(\" \")\n .trim();\n}\n\n/** Returns true if the C2B payload is a Paybill payment */\nexport function isPaybillPayment(\n payload: C2BValidationPayload | C2BConfirmationPayload\n): boolean {\n return (\n payload.TransactionType === \"Pay Bill\" ||\n payload.TransactionType === \"CustomerPayBillOnline\"\n );\n}\n\n/** Returns true if the C2B payload is a Buy Goods (Till) payment */\nexport function isBuyGoodsPayment(\n payload: C2BValidationPayload | C2BConfirmationPayload\n): boolean {\n return (\n payload.TransactionType === \"Buy Goods\" ||\n payload.TransactionType === \"CustomerBuyGoodsOnline\"\n );\n}\n","/**\n * Dynamic QR Code generation\n *\n * API: POST /mpesa/qrcode/v1/generate\n *\n * Generates a dynamic M-PESA QR code that customers can scan with\n * the My Safaricom App or M-PESA app to pay at LNM merchant outlets.\n *\n * Error codes (from Daraja docs):\n * 404.001.04 — Invalid Authentication Header (wrong HTTP method or misplaced headers)\n * 400.002.05 — Invalid Request Payload (malformed body)\n * 400.003.01 — Invalid Access Token (expired or incorrect)\n */\n\nimport { createError } from \"../../utils/errors\";\nimport { httpRequest } from \"../../utils/http\";\nimport type { DynamicQRRequest, DynamicQRResponse } from \"./types\";\n\n/**\n * Generates a Dynamic M-PESA QR Code.\n *\n * @param baseUrl - Daraja base URL (sandbox or production)\n * @param accessToken - Valid OAuth bearer token\n * @param request - QR generation parameters\n * @returns - Daraja response including base64 QRCode string\n */\nexport async function generateDynamicQR(\n baseUrl: string,\n accessToken: string,\n request: DynamicQRRequest\n): Promise<DynamicQRResponse> {\n // ── Validation ──────────────────────────────────────────────────────────────\n\n if (!request.merchantName?.trim()) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: \"merchantName is required\",\n });\n }\n\n if (!request.refNo?.trim()) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: \"refNo (transaction reference) is required\",\n });\n }\n\n const amount = Math.round(request.amount);\n if (!Number.isFinite(amount) || amount < 1) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: `Amount must be at least 1 (got ${request.amount} which rounds to ${amount}).`,\n });\n }\n\n if (!request.trxCode) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n 'trxCode is required. Supported values: \"BG\" | \"WA\" | \"PB\" | \"SM\" | \"SB\"',\n });\n }\n\n if (!request.cpi?.trim()) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n \"cpi (Credit Party Identifier) is required — e.g. till number, paybill, or MSISDN\",\n });\n }\n\n const size = request.size ?? 300;\n if (size < 1) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: \"size must be a positive number of pixels\",\n });\n }\n\n // ── Build payload matching Daraja spec exactly ──────────────────────────────\n\n const payload = {\n MerchantName: request.merchantName,\n RefNo: request.refNo,\n Amount: amount,\n TrxCode: request.trxCode,\n CPI: request.cpi,\n Size: String(size),\n };\n\n const { data } = await httpRequest<DynamicQRResponse>(\n `${baseUrl}/mpesa/qrcode/v1/generate`,\n {\n method: \"POST\",\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n }\n );\n\n return data;\n}\n","/**\n * Phone number utilities for Daraja API.\n *\n * Daraja spec: PartyA and PhoneNumber must be in the format 2547XXXXXXXX\n * (12-digit, starts with 254, no +, no spaces, no dashes).\n *\n * Accepted input formats:\n * 0712345678 → 254712345678\n * +254712345678 → 254712345678\n * 254712345678 → 254712345678 (already correct)\n * 712345678 → 254712345678\n */\n\nimport { PesafyError } from \"../errors\";\n\n/** Normalises any common Kenyan phone format to 254XXXXXXXXX (12 digits) */\nexport function formatSafaricomPhone(phone: string): string {\n const digits = phone.replace(/\\D/g, \"\");\n\n let normalised: string;\n\n if (digits.startsWith(\"254\") && digits.length === 12) {\n normalised = digits;\n } else if (digits.startsWith(\"0\") && digits.length === 10) {\n normalised = `254${digits.slice(1)}`;\n } else if (digits.length === 9) {\n // e.g. 712345678 → 254712345678\n normalised = `254${digits}`;\n } else if (digits.startsWith(\"254\") && digits.length !== 12) {\n throw new PesafyError({\n code: \"INVALID_PHONE\",\n message: `Invalid phone number \"${phone}\". Expected 254XXXXXXXXX (12 digits).`,\n });\n } else {\n throw new PesafyError({\n code: \"INVALID_PHONE\",\n message: `Cannot parse phone number \"${phone}\". Use 07XXXXXXXX, 2547XXXXXXXX, or +2547XXXXXXXX.`,\n });\n }\n\n // Final sanity check: must be exactly 12 digits\n if (normalised.length !== 12) {\n throw new PesafyError({\n code: \"INVALID_PHONE\",\n message: `Phone number \"${phone}\" normalised to \"${normalised}\" which is not 12 digits.`,\n });\n }\n\n return normalised;\n}\n","/**\n * STK Push utility functions\n *\n * Password spec (from Daraja docs):\n * Password = Base64( BusinessShortCode + Passkey + Timestamp )\n * Timestamp = YYYYMMDDHHmmss\n *\n * IMPORTANT: Generate the timestamp ONCE per request and pass the same\n * value to BOTH getStkPushPassword() and the request body's Timestamp field.\n * Safaricom validates that Base64(Shortcode+Passkey+Timestamp) matches the\n * Timestamp sent in the body — two separate calls to getTimestamp() will\n * produce different values and cause auth failures.\n */\n\nexport { formatSafaricomPhone as formatPhoneNumber } from \"../../utils/phone\";\n\n/**\n * Generates the STK Push password.\n * Formula: Base64( Shortcode + Passkey + Timestamp )\n *\n * Uses btoa() — works in Node.js ≥18, Bun, browsers, and edge runtimes.\n */\nexport function getStkPushPassword(\n shortCode: string,\n passKey: string,\n timestamp: string\n): string {\n return btoa(`${shortCode}${passKey}${timestamp}`);\n}\n\n/**\n * Returns a Daraja-compatible timestamp: YYYYMMDDHHmmss\n *\n * Call this ONCE per request and reuse the result.\n */\nexport function getTimestamp(): string {\n const now = new Date();\n const pad = (n: number): string => n.toString().padStart(2, \"0\");\n return [\n now.getFullYear(),\n pad(now.getMonth() + 1),\n pad(now.getDate()),\n pad(now.getHours()),\n pad(now.getMinutes()),\n pad(now.getSeconds()),\n ].join(\"\");\n}\n","// src/mpesa/stk-push/stk-push.ts\n\n/**\n * M-Pesa Express (STK Push) — initiates a payment prompt on the customer's phone.\n *\n * API: POST /mpesa/stkpush/v1/processrequest\n *\n * Daraja request body (from docs):\n * {\n * \"BusinessShortCode\": 174379,\n * \"Password\": \"base64(Shortcode+Passkey+Timestamp)\",\n * \"Timestamp\": \"20210628092408\",\n * \"TransactionType\": \"CustomerPayBillOnline\",\n * \"Amount\": \"1\",\n * \"PartyA\": \"254722000000\",\n * \"PartyB\": \"174379\",\n * \"PhoneNumber\": \"254722111111\",\n * \"CallBackURL\": \"https://mydomain.com/path\",\n * \"AccountReference\": \"accountref\", ← max 12 chars\n * \"TransactionDesc\": \"txndesc\" ← max 13 chars\n * }\n *\n * Notes from docs:\n * - All fields except TransactionDesc are mandatory.\n * - Amount must be a whole number ≥ 1 (KES).\n * - PartyA/PhoneNumber must be 254XXXXXXXXX format.\n * - AccountReference max 12 chars.\n * - TransactionDesc max 13 chars.\n */\n\nimport { PesafyError } from \"../../utils/errors\";\nimport { httpRequest } from \"../../utils/http\";\nimport type { StkPushRequest, StkPushResponse } from \"./types\";\nimport { formatPhoneNumber, getStkPushPassword, getTimestamp } from \"./utils\";\n\nexport async function processStkPush(\n baseUrl: string,\n accessToken: string,\n request: StkPushRequest\n): Promise<StkPushResponse> {\n // ── Amount validation ───────────────────────────────────────────────────────\n const amount = Math.round(request.amount);\n if (amount < 1) {\n throw new PesafyError({\n code: \"VALIDATION_ERROR\",\n message: `Amount must be at least KES 1 (got ${request.amount} which rounds to ${amount}).`,\n });\n }\n\n // ── Generate timestamp ONCE ─────────────────────────────────────────────────\n // Must be identical in Password (encoded) and Timestamp (body) fields.\n const timestamp = getTimestamp();\n\n // ── PartyB logic ────────────────────────────────────────────────────────────\n // Paybill → PartyB = shortCode\n // Buy Goods (Till) → PartyB = till number (passed as request.partyB)\n const partyB = request.partyB ?? request.shortCode;\n\n const body = {\n BusinessShortCode: request.shortCode,\n Password: getStkPushPassword(request.shortCode, request.passKey, timestamp),\n Timestamp: timestamp,\n TransactionType: request.transactionType ?? \"CustomerPayBillOnline\",\n Amount: amount,\n PartyA: formatPhoneNumber(request.phoneNumber),\n PartyB: partyB,\n PhoneNumber: formatPhoneNumber(request.phoneNumber),\n CallBackURL: request.callbackUrl,\n // Daraja docs: AccountReference max 12 chars, TransactionDesc max 13 chars\n AccountReference: request.accountReference.slice(0, 12),\n TransactionDesc: request.transactionDesc.slice(0, 13),\n };\n\n // httpRequest already retries 503/429/5xx with exponential backoff + jitter.\n // If all retries are exhausted it throws PesafyError with code \"REQUEST_FAILED\"\n // and statusCode 503 — callers should treat this as TRANSIENT, not a final\n // failure. Never mark a transaction \"failed\" on a 503.\n const { data } = await httpRequest<StkPushResponse>(\n `${baseUrl}/mpesa/stkpush/v1/processrequest`,\n {\n method: \"POST\",\n headers: { Authorization: `Bearer ${accessToken}` },\n body,\n // Daraja sandbox needs more retries and longer gaps due to instability\n retries: 5,\n retryDelay: 3000,\n }\n );\n\n return data;\n}\n","/**\n * STK Push Query — checks the status of a Lipa Na M-Pesa Online Payment.\n *\n * API: POST /mpesa/stkpushquery/v1/query\n *\n * Daraja request body (from Discover APIs M-Pesa Express Query docs):\n * {\n * \"BusinessShortCode\": \"174379\",\n * \"Password\": \"base64(Shortcode+Passkey+Timestamp)\",\n * \"Timestamp\": \"20160216165627\",\n * \"CheckoutRequestID\": \"ws_CO_260520211133524545\"\n * }\n *\n * Response ResultCode values (from docs):\n * 0 = The service request is processed successfully.\n * 1032 = Request cancelled by user\n * 1037 = DS timeout user cannot be reached\n * 2001 = Wrong PIN\n * (and more — see STK Push docs result code table)\n */\n\nimport { httpRequest } from \"../../utils/http\";\nimport type { StkQueryRequest, StkQueryResponse } from \"./types\";\nimport { getStkPushPassword, getTimestamp } from \"./utils\";\n\nexport async function queryStkPush(\n baseUrl: string,\n accessToken: string,\n request: StkQueryRequest\n): Promise<StkQueryResponse> {\n // Generate timestamp ONCE — Password and Timestamp field MUST match.\n const timestamp = getTimestamp();\n\n const body = {\n BusinessShortCode: request.shortCode,\n Password: getStkPushPassword(request.shortCode, request.passKey, timestamp),\n Timestamp: timestamp,\n CheckoutRequestID: request.checkoutRequestId,\n };\n\n const { data } = await httpRequest<StkQueryResponse>(\n `${baseUrl}/mpesa/stkpushquery/v1/query`,\n {\n method: \"POST\",\n headers: { Authorization: `Bearer ${accessToken}` },\n body,\n }\n );\n\n return data;\n}\n","/**\n * STK Push (M-Pesa Express) types\n *\n * API: POST /mpesa/stkpush/v1/processrequest\n * Query: POST /mpesa/stkpushquery/v1/query\n *\n * Ref: M-Pesa Express Simulate docs + Discover APIs M-Pesa Express Query docs\n */\n\n// ── Transaction type ─────────────────────────────────────────────────────────\n\n/**\n * CustomerPayBillOnline → Paybill numbers (PartyB = shortcode)\n * CustomerBuyGoodsOnline → Till numbers (PartyB = till number)\n */\nexport type TransactionType =\n | \"CustomerPayBillOnline\"\n | \"CustomerBuyGoodsOnline\";\n\n// ── STK Push request ─────────────────────────────────────────────────────────\n\nexport interface StkPushRequest {\n /** Transaction amount (minimum KES 1, must round to a whole number ≥ 1) */\n amount: number;\n\n /**\n * Phone number sending the money. Format: 2547XXXXXXXX.\n * Must be a valid Safaricom M-PESA number.\n * Daraja docs field name: PartyA / PhoneNumber\n */\n phoneNumber: string;\n\n /**\n * URL where Safaricom will POST the callback result.\n * Must be publicly accessible (use ngrok/localtunnel for local dev).\n * Daraja docs field name: CallBackURL\n */\n callbackUrl: string;\n\n /**\n * Alpha-numeric reference shown to customer in the USSD prompt.\n * Max 12 characters.\n * Daraja docs field name: AccountReference\n */\n accountReference: string;\n\n /**\n * Additional description for the transaction.\n * Max 13 characters.\n * Daraja docs field name: TransactionDesc\n */\n transactionDesc: string;\n\n /**\n * Business shortcode — Paybill number or HO/Store number for Till.\n * Daraja docs field name: BusinessShortCode\n */\n shortCode: string;\n\n /**\n * Passkey used to generate the Password.\n * Sandbox value: from Daraja simulator test data.\n * Production value: emailed after Go Live.\n */\n passKey: string;\n\n /**\n * \"CustomerPayBillOnline\" (default) for Paybill.\n * \"CustomerBuyGoodsOnline\" for Till Numbers.\n */\n transactionType?: TransactionType;\n\n /**\n * Credit party receiving funds.\n * - CustomerPayBillOnline: defaults to shortCode\n * - CustomerBuyGoodsOnline: set to the Till Number\n * Daraja docs field name: PartyB\n */\n partyB?: string;\n}\n\n// ── STK Push response ────────────────────────────────────────────────────────\n\nexport interface StkPushResponse {\n /** Global unique identifier for the submitted payment request */\n MerchantRequestID: string;\n /** Global unique identifier for the checkout transaction */\n CheckoutRequestID: string;\n /** \"0\" = successful submission */\n ResponseCode: string;\n ResponseDescription: string;\n CustomerMessage: string;\n}\n\n// ── STK Query request/response ───────────────────────────────────────────────\n\nexport interface StkQueryRequest {\n /** CheckoutRequestID from the STK Push response */\n checkoutRequestId: string;\n shortCode: string;\n passKey: string;\n}\n\nexport interface StkQueryResponse {\n ResponseCode: string;\n ResponseDescription: string;\n MerchantRequestID: string;\n CheckoutRequestID: string;\n /**\n * Daraja returns ResultCode as a NUMBER.\n * 0 = success\n * 1 = insufficient balance\n * 1032 = cancelled by user\n * 1037 = timeout / unreachable\n * 2001 = wrong PIN\n */\n ResultCode: number;\n ResultDesc: string;\n}\n\n// ── Callback payload types ────────────────────────────────────────────────────\n// Safaricom POSTs these to your CallBackURL after the customer responds.\n\n/** Single metadata item in a successful STK callback */\nexport interface StkCallbackMetadataItem {\n Name:\n | \"Amount\"\n | \"MpesaReceiptNumber\"\n | \"TransactionDate\"\n | \"PhoneNumber\"\n | \"Balance\";\n /** Present on successful transactions; absent on failure */\n Value?: number | string;\n}\n\n/** Inner callback for a SUCCESSFUL STK Push (ResultCode === 0) */\nexport interface StkCallbackSuccess {\n MerchantRequestID: string;\n CheckoutRequestID: string;\n ResultCode: 0;\n ResultDesc: string;\n CallbackMetadata: {\n Item: StkCallbackMetadataItem[];\n };\n}\n\n/** Inner callback for a FAILED / CANCELLED STK Push (ResultCode !== 0) */\nexport interface StkCallbackFailure {\n MerchantRequestID: string;\n CheckoutRequestID: string;\n /** e.g. 1032 = cancelled by user, 1037 = timeout */\n ResultCode: number;\n ResultDesc: string;\n CallbackMetadata?: never;\n}\n\nexport type StkCallbackInner = StkCallbackSuccess | StkCallbackFailure;\n\n/** Full wrapper Safaricom POSTs to your CallBackURL */\nexport interface StkPushCallback {\n Body: {\n stkCallback: StkCallbackInner;\n };\n}\n\n// ── Type guards & helpers ─────────────────────────────────────────────────────\n\n/**\n * Narrows StkCallbackInner to the success shape.\n *\n * @example\n * if (isStkCallbackSuccess(callback.Body.stkCallback)) {\n * const receipt = getCallbackValue(callback, \"MpesaReceiptNumber\");\n * }\n */\nexport function isStkCallbackSuccess(\n cb: StkCallbackInner\n): cb is StkCallbackSuccess {\n return cb.ResultCode === 0;\n}\n\n/**\n * Extracts a named value from a successful callback's metadata.\n * Returns undefined if the key is absent or the transaction failed.\n */\nexport function getCallbackValue(\n callback: StkPushCallback,\n name: StkCallbackMetadataItem[\"Name\"]\n): string | number | undefined {\n const inner = callback.Body.stkCallback;\n if (!isStkCallbackSuccess(inner)) return undefined;\n return inner.CallbackMetadata.Item.find((i) => i.Name === name)?.Value;\n}\n","/**\n * Transaction Status Query implementation\n *\n * API: POST /mpesa/transactionstatus/v1/query\n *\n * This is ASYNCHRONOUS. The synchronous response only acknowledges receipt.\n * Final results arrive via POST to your ResultURL.\n *\n * Required M-PESA org portal role: \"Transaction Status query ORG API\"\n */\n\nimport { createError } from \"../../utils/errors\";\nimport { httpRequest } from \"../../utils/http\"; // ← httpRequest, NOT httpClient\nimport type {\n TransactionStatusRequest,\n TransactionStatusResponse,\n} from \"./types\";\n\nexport async function queryTransactionStatus(\n baseUrl: string,\n token: string,\n securityCredential: string,\n initiator: string,\n request: TransactionStatusRequest\n): Promise<TransactionStatusResponse> {\n // ── Validation ──────────────────────────────────────────────────────────────\n\n if (!request.transactionId) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: \"transactionId is required\",\n });\n }\n\n if (!request.partyA) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n \"partyA is required (your business shortcode, till number, or MSISDN)\",\n });\n }\n\n if (!request.identifierType) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n 'identifierType is required: \"1\" (MSISDN) | \"2\" (Till) | \"4\" (ShortCode)',\n });\n }\n\n if (!request.resultUrl) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n \"resultUrl is required — Safaricom POSTs the transaction result here\",\n });\n }\n\n if (!request.queueTimeOutUrl) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: \"queueTimeOutUrl is required — Safaricom calls this on timeout\",\n });\n }\n\n // ── Build payload matching Daraja spec exactly ──────────────────────────────\n\n const payload = {\n Initiator: initiator,\n SecurityCredential: securityCredential,\n CommandID: request.commandId ?? \"TransactionStatusQuery\",\n TransactionID: request.transactionId,\n PartyA: request.partyA,\n IdentifierType: request.identifierType,\n ResultURL: request.resultUrl,\n QueueTimeOutURL: request.queueTimeOutUrl,\n Remarks: request.remarks ?? \"Transaction Status Query\",\n Occasion: request.occasion ?? \"\",\n };\n\n const { data } = await httpRequest<TransactionStatusResponse>(\n `${baseUrl}/mpesa/transactionstatus/v1/query`,\n {\n method: \"POST\",\n headers: { Authorization: `Bearer ${token}` },\n body: payload,\n }\n );\n\n return data;\n}\n","/**\n * Core M-Pesa / Daraja API types\n */\n\nexport type Environment = \"sandbox\" | \"production\";\n\n/** Base URLs per Daraja environment */\nexport const DARAJA_BASE_URLS: Record<Environment, string> = {\n sandbox: \"https://sandbox.safaricom.co.ke\",\n production: \"https://api.safaricom.co.ke\",\n} as const;\n\nexport interface MpesaConfig {\n // ── Required for all APIs ─────────────────────────────────────────────────\n consumerKey: string;\n consumerSecret: string;\n environment: Environment;\n\n // ── Required for STK Push (M-Pesa Express) ────────────────────────────────\n /** Paybill / HO shortcode (5–7 digits). Required for STK Push & STK Query. */\n lipaNaMpesaShortCode?: string;\n /**\n * Passkey from Daraja portal.\n * Sandbox: visible in the simulator test data section.\n * Production: emailed after Go Live.\n */\n lipaNaMpesaPassKey?: string;\n\n // ── Required for Transaction Status / B2C / Reversals ────────────────────\n /** M-PESA org portal API operator username */\n initiatorName?: string;\n /** Plain-text password for the API operator (will be RSA-encrypted) */\n initiatorPassword?: string;\n\n // ── Certificate options (choose one) ─────────────────────────────────────\n /**\n * Path to the .cer file on disk.\n * Bun: read via `Bun.file(path).text()`\n * Node: read via `fs.promises.readFile(path, \"utf-8\")`\n */\n certificatePath?: string;\n /** PEM string contents of the certificate (alternative to certificatePath) */\n certificatePem?: string;\n /**\n * Pre-computed base64 security credential.\n * Use this if you encrypt outside the library (e.g. at startup).\n * Skips the RSA encryption step entirely.\n */\n securityCredential?: string;\n}\n","/**\n * M-Pesa Daraja API client\n *\n * Supports:\n * - STK Push (M-Pesa Express) — stkPush()\n * - STK Query — stkQuery()\n * - Transaction Status Query — transactionStatus()\n * - Dynamic QR Code — generateDynamicQR()\n * - C2B Register URL — registerC2BUrls()\n * - C2B Simulate (sandbox only) — simulateC2B()\n *\n * @example\n * const mpesa = new Mpesa({\n * consumerKey: process.env.MPESA_CONSUMER_KEY!,\n * consumerSecret: process.env.MPESA_CONSUMER_SECRET!,\n * environment: \"sandbox\",\n * lipaNaMpesaShortCode: \"174379\",\n * lipaNaMpesaPassKey: \"bfb279...\",\n * initiatorName: \"testapi\",\n * initiatorPassword: \"Safaricom123!\",\n * certificatePath: \"./SandboxCertificate.cer\",\n * });\n */\n\nimport { TokenManager } from \"../core/auth\";\nimport { encryptSecurityCredential } from \"../core/encryption\";\nimport { PesafyError } from \"../utils/errors\";\nimport {\n registerC2BUrls as _registerC2BUrls,\n simulateC2B as _simulateC2B,\n type C2BRegisterUrlRequest,\n type C2BRegisterUrlResponse,\n type C2BSimulateRequest,\n type C2BSimulateResponse,\n} from \"./c2b\";\nimport {\n generateDynamicQR as _generateDynamicQR,\n type DynamicQRRequest,\n type DynamicQRResponse,\n} from \"./dynamic-qr\";\nimport {\n processStkPush,\n queryStkPush,\n type StkPushRequest,\n type StkQueryRequest,\n} from \"./stk-push\";\nimport {\n queryTransactionStatus,\n type TransactionStatusRequest,\n} from \"./transaction-status\";\nimport { DARAJA_BASE_URLS, type MpesaConfig } from \"./types\";\n\nexport class Mpesa {\n private readonly config: MpesaConfig;\n private readonly tokenManager: TokenManager;\n private readonly baseUrl: string;\n\n constructor(config: MpesaConfig) {\n if (!config.consumerKey || !config.consumerSecret) {\n throw new PesafyError({\n code: \"INVALID_CREDENTIALS\",\n message: \"consumerKey and consumerSecret are required\",\n });\n }\n\n this.config = config;\n this.baseUrl = DARAJA_BASE_URLS[config.environment];\n this.tokenManager = new TokenManager(\n config.consumerKey,\n config.consumerSecret,\n this.baseUrl\n );\n }\n\n // ── Internal helpers ────────────────────────────────────────────────────────\n\n private getToken(): Promise<string> {\n return this.tokenManager.getAccessToken();\n }\n\n private async buildSecurityCredential(): Promise<string> {\n if (this.config.securityCredential) return this.config.securityCredential;\n\n if (!this.config.initiatorPassword) {\n throw new PesafyError({\n code: \"INVALID_CREDENTIALS\",\n message:\n \"Provide securityCredential (pre-encrypted) \" +\n \"OR (initiatorPassword + certificatePath/certificatePem)\",\n });\n }\n\n let cert: string;\n if (this.config.certificatePem) {\n cert = this.config.certificatePem;\n } else if (this.config.certificatePath) {\n if (typeof Bun !== \"undefined\") {\n cert = await Bun.file(this.config.certificatePath).text();\n } else {\n const { readFile } = await import(\"node:fs/promises\");\n cert = await readFile(this.config.certificatePath, \"utf-8\");\n }\n } else {\n throw new PesafyError({\n code: \"INVALID_CREDENTIALS\",\n message:\n \"certificatePath or certificatePem required to encrypt the initiator password\",\n });\n }\n\n return encryptSecurityCredential(this.config.initiatorPassword, cert);\n }\n\n // ── STK Push ──────────────────────────────────────────────────────────────\n\n /**\n * M-Pesa Express — sends a payment prompt to the customer's phone.\n *\n * Requires: lipaNaMpesaShortCode + lipaNaMpesaPassKey in config.\n *\n * @example\n * const res = await mpesa.stkPush({\n * amount: 100,\n * phoneNumber: \"0712345678\",\n * callbackUrl: \"https://yourdomain.com/mpesa/callback\",\n * accountReference: \"INV-001\",\n * transactionDesc: \"Payment\",\n * });\n * console.log(res.CheckoutRequestID);\n */\n async stkPush(request: Omit<StkPushRequest, \"shortCode\" | \"passKey\">) {\n const shortCode = this.config.lipaNaMpesaShortCode ?? \"\";\n const passKey = this.config.lipaNaMpesaPassKey ?? \"\";\n\n if (!shortCode || !passKey) {\n throw new PesafyError({\n code: \"VALIDATION_ERROR\",\n message:\n \"lipaNaMpesaShortCode and lipaNaMpesaPassKey are required for STK Push\",\n });\n }\n\n const token = await this.getToken();\n return processStkPush(this.baseUrl, token, {\n ...request,\n shortCode,\n passKey,\n });\n }\n\n /**\n * STK Query — checks the status of a previous STK Push.\n *\n * @example\n * const status = await mpesa.stkQuery({\n * checkoutRequestId: \"ws_CO_1007202409152617172396192\",\n * });\n * if (status.ResultCode === 0) // payment confirmed\n */\n async stkQuery(request: Omit<StkQueryRequest, \"shortCode\" | \"passKey\">) {\n const shortCode = this.config.lipaNaMpesaShortCode ?? \"\";\n const passKey = this.config.lipaNaMpesaPassKey ?? \"\";\n\n if (!shortCode || !passKey) {\n throw new PesafyError({\n code: \"VALIDATION_ERROR\",\n message:\n \"lipaNaMpesaShortCode and lipaNaMpesaPassKey are required for STK Query\",\n });\n }\n\n const token = await this.getToken();\n return queryStkPush(this.baseUrl, token, {\n ...request,\n shortCode,\n passKey,\n });\n }\n\n /**\n * Transaction Status — queries the result of a completed M-Pesa transaction.\n *\n * Requires: initiatorName + (initiatorPassword + certificate) OR securityCredential.\n *\n * This is ASYNCHRONOUS. The synchronous response only confirms receipt.\n * Final details are POSTed to your resultUrl.\n *\n * @example\n * await mpesa.transactionStatus({\n * transactionId: \"OEI2AK4XXXX\",\n * partyA: \"174379\",\n * identifierType: \"4\",\n * resultUrl: \"https://yourdomain.com/mpesa/result\",\n * queueTimeOutUrl: \"https://yourdomain.com/mpesa/timeout\",\n * remarks: \"Check payment status\",\n * });\n */\n async transactionStatus(request: TransactionStatusRequest) {\n const initiator = this.config.initiatorName ?? \"\";\n if (!initiator) {\n throw new PesafyError({\n code: \"VALIDATION_ERROR\",\n message: \"initiatorName is required for Transaction Status\",\n });\n }\n\n const [token, securityCred] = await Promise.all([\n this.getToken(),\n this.buildSecurityCredential(),\n ]);\n\n return queryTransactionStatus(\n this.baseUrl,\n token,\n securityCred,\n initiator,\n request\n );\n }\n\n // ── Dynamic QR Code ───────────────────────────────────────────────────────\n\n /**\n * Dynamic QR — generates an M-PESA QR code for LNM merchant payments.\n *\n * Customers scan the code with My Safaricom App or M-PESA app to\n * capture the till/paybill number and amount, then authorize payment.\n *\n * @example\n * const res = await mpesa.generateDynamicQR({\n * merchantName: \"My Shop\",\n * refNo: \"INV-001\",\n * amount: 500,\n * trxCode: \"BG\", // Buy Goods (till number)\n * cpi: \"373132\",\n * size: 300,\n * });\n *\n * // res.QRCode is a base64-encoded PNG — render in an <img> tag:\n * // <img src={`data:image/png;base64,${res.QRCode}`} />\n */\n async generateDynamicQR(\n request: DynamicQRRequest\n ): Promise<DynamicQRResponse> {\n const token = await this.getToken();\n return _generateDynamicQR(this.baseUrl, token, request);\n }\n\n // ── C2B Register URL ──────────────────────────────────────────────────────\n\n /**\n * Registers your Confirmation and Validation URLs with M-PESA.\n *\n * Use v2 (default) for new integrations — callbacks include a masked MSISDN.\n * Use v1 only if you need SHA256-hashed MSISDN in callbacks.\n *\n * Sandbox: URLs can be re-registered freely (overwriting existing ones).\n * Production: One-time call. To change URLs, delete them via Daraja Self\n * Services → URL Management, then call this again.\n *\n * URL rules (Daraja docs — enforced by this library):\n * ✓ Must be publicly accessible\n * ✓ Production: HTTPS required\n * ✗ Must NOT contain: M-PESA, Safaricom, exe, exec, cmd, sql, query\n * ✗ Do NOT use ngrok, mockbin, requestbin in production\n * ✓ responseType must be exactly \"Completed\" or \"Cancelled\" (sentence case)\n *\n * External Validation (optional):\n * By default it is disabled. To enable, email apisupport@safaricom.co.ke.\n * When enabled, Safaricom calls your validationUrl before processing payment.\n * You must respond within ~8 seconds.\n *\n * @example\n * await mpesa.registerC2BUrls({\n * shortCode: \"600984\",\n * responseType: \"Completed\",\n * confirmationUrl: \"https://yourdomain.com/mpesa/c2b/confirmation\",\n * validationUrl: \"https://yourdomain.com/mpesa/c2b/validation\",\n * apiVersion: \"v2\", // default — recommended\n * });\n */\n async registerC2BUrls(\n request: C2BRegisterUrlRequest\n ): Promise<C2BRegisterUrlResponse> {\n const token = await this.getToken();\n return _registerC2BUrls(this.baseUrl, token, request);\n }\n\n // ── C2B Simulate (Sandbox ONLY) ───────────────────────────────────────────\n\n /**\n * Simulates a C2B customer payment. SANDBOX ONLY.\n *\n * In production, real customers initiate payments via M-PESA App, USSD,\n * or SIM Toolkit — simulation is not available.\n *\n * The API version used here should match the version used when registering URLs.\n *\n * @example\n * await mpesa.simulateC2B({\n * shortCode: 600984,\n * commandId: \"CustomerPayBillOnline\",\n * amount: 10,\n * msisdn: 254708374149, // Daraja test MSISDN\n * billRefNumber: \"INV-001\", // account ref for Paybill; null for Till\n * apiVersion: \"v2\", // must match registered URL version\n * });\n */\n async simulateC2B(request: C2BSimulateRequest): Promise<C2BSimulateResponse> {\n const token = await this.getToken();\n return _simulateC2B(this.baseUrl, token, request);\n }\n\n /** Force the cached OAuth token to be refreshed on the next API call */\n clearTokenCache(): void {\n this.tokenManager.clearCache();\n }\n}\n","/**\n * Exponential backoff retry for webhook at-least-once delivery.\n *\n * Daraja is asynchronous — if your callback endpoint is down, the API\n * Gateway logs a 503 and discards the result. Use this utility to\n * retry your own internal processing after receiving a webhook.\n */\n\nexport interface RetryOptions {\n /** Maximum number of attempts (default: Infinity) */\n maxRetries?: number;\n /** Initial delay in ms (default: 1000 = 1 second) */\n initialDelay?: number;\n /** Maximum delay cap in ms (default: 3_600_000 = 1 hour) */\n maxDelay?: number;\n /** Multiplier per retry (default: 2 — doubles each time) */\n backoffMultiplier?: number;\n /** Maximum total duration in ms (default: 30 days) */\n maxRetryDuration?: number;\n}\n\nconst DEFAULT_OPTIONS: Required<RetryOptions> = {\n maxRetries: Infinity,\n initialDelay: 1_000,\n maxDelay: 3_600_000,\n backoffMultiplier: 2,\n maxRetryDuration: 30 * 24 * 60 * 60 * 1_000, // 30 days\n};\n\nexport interface RetryResult<T> {\n success: boolean;\n data?: T;\n attempts: number;\n error?: Error;\n}\n\n/**\n * Retries `fn` with exponential backoff until it resolves, or limits are hit.\n *\n * @example\n * const result = await retryWithBackoff(\n * () => sendToDatabase(webhookData),\n * { maxRetries: 5, initialDelay: 500 }\n * );\n */\nexport async function retryWithBackoff<T>(\n fn: () => Promise<T>,\n options: RetryOptions = {}\n): Promise<RetryResult<T>> {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n let delay = opts.initialDelay;\n let attempts = 0;\n const startTime = Date.now();\n\n while (attempts < opts.maxRetries) {\n attempts++;\n\n if (Date.now() - startTime > opts.maxRetryDuration) {\n return {\n success: false,\n attempts,\n error: new Error(\"Max retry duration exceeded\"),\n };\n }\n\n try {\n const data = await fn();\n return { success: true, data, attempts };\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n\n // Don't retry client errors (4xx) — they won't self-heal\n if (err.message.includes(\"4\")) {\n return { success: false, attempts, error: err };\n }\n\n if (attempts < opts.maxRetries) {\n await new Promise((resolve) => setTimeout(resolve, delay));\n delay = Math.min(delay * opts.backoffMultiplier, opts.maxDelay);\n }\n }\n }\n\n return {\n success: false,\n attempts,\n error: new Error(\"Max retries exceeded\"),\n };\n}\n","/**\n * Webhook verification utilities\n *\n * Daraja does NOT use HMAC webhook signatures like Stripe.\n * Instead, verify that callbacks come from whitelisted Safaricom IPs.\n *\n * Official Safaricom IP whitelist (from Getting Started docs):\n * 196.201.214.200\n * 196.201.214.206\n * 196.201.213.114\n * 196.201.214.207\n * 196.201.214.208\n * 196.201.213.44\n * 196.201.212.127\n * 196.201.212.138\n * 196.201.212.129\n * 196.201.212.136\n * 196.201.212.74\n * 196.201.212.69\n */\n\nimport type { StkPushWebhook } from \"./types\";\n\n/** Official Safaricom API Gateway IP addresses */\nexport const SAFARICOM_IPS: readonly string[] = [\n \"196.201.214.200\",\n \"196.201.214.206\",\n \"196.201.213.114\",\n \"196.201.214.207\",\n \"196.201.214.208\",\n \"196.201.213.44\",\n \"196.201.212.127\",\n \"196.201.212.138\",\n \"196.201.212.129\",\n \"196.201.212.136\",\n \"196.201.212.74\",\n \"196.201.212.69\",\n] as const;\n\n/**\n * Returns true if requestIP is in the allowed list.\n * Defaults to the official Safaricom IP whitelist.\n */\nexport function verifyWebhookIP(\n requestIP: string,\n allowedIPs: readonly string[] = SAFARICOM_IPS\n): boolean {\n return allowedIPs.includes(requestIP);\n}\n\n/**\n * Parses and validates an STK Push webhook body.\n * Returns the typed payload or null if it doesn't match the expected shape.\n */\nexport function parseStkPushWebhook(body: unknown): StkPushWebhook | null {\n try {\n const parsed = body as StkPushWebhook;\n if (parsed?.Body?.stkCallback) return parsed;\n return null;\n } catch {\n return null;\n }\n}\n","/**\n * High-level webhook event handler\n */\n\nimport { parseStkPushWebhook, verifyWebhookIP } from \"./signature-verifier\";\nimport type { StkPushWebhook, WebhookEventType } from \"./types\";\n\nexport interface WebhookHandlerOptions {\n /** IP address of the incoming request (from req.ip or x-forwarded-for) */\n requestIP?: string;\n /** Override the default Safaricom IP whitelist */\n allowedIPs?: string[];\n /** Skip IP verification — ONLY for local development/testing */\n skipIPCheck?: boolean;\n}\n\nexport interface WebhookHandlerResult<T = unknown> {\n success: boolean;\n eventType: WebhookEventType | null;\n data: T | null;\n error?: string;\n}\n\n/**\n * Parses and validates an inbound Daraja webhook payload.\n *\n * @example\n * // Express route\n * app.post(\"/mpesa/callback\", (req, res) => {\n * const result = handleWebhook(req.body, { requestIP: req.ip });\n * if (!result.success) return res.status(400).json({ error: result.error });\n * // process result.data (StkPushWebhook)\n * res.json({ ResultCode: 0, ResultDesc: \"Accepted\" });\n * });\n */\nexport function handleWebhook(\n body: unknown,\n options: WebhookHandlerOptions = {}\n): WebhookHandlerResult {\n // ── IP verification ─────────────────────────────────────────────────────────\n if (!options.skipIPCheck && options.requestIP) {\n if (!verifyWebhookIP(options.requestIP, options.allowedIPs)) {\n return {\n success: false,\n eventType: null,\n data: null,\n error: `IP address ${options.requestIP} is not in the Safaricom whitelist`,\n };\n }\n }\n\n // ── Parse STK Push callback ─────────────────────────────────────────────────\n const stkPush = parseStkPushWebhook(body);\n if (stkPush) {\n return {\n success: true,\n eventType: \"stk_push\",\n data: stkPush,\n };\n }\n\n return {\n success: false,\n eventType: null,\n data: null,\n error: \"Unknown or malformed webhook payload\",\n };\n}\n\n// ── Convenience extractors ────────────────────────────────────────────────────\n\n/** Extracts the M-Pesa receipt number from a successful STK Push callback */\nexport function extractTransactionId(webhook: StkPushWebhook): string | null {\n const items = webhook.Body?.stkCallback?.CallbackMetadata?.Item;\n const item = items?.find((i) => i.Name === \"MpesaReceiptNumber\");\n return item ? String(item.Value) : null;\n}\n\n/** Extracts the transaction amount from a successful STK Push callback */\nexport function extractAmount(webhook: StkPushWebhook): number | null {\n const items = webhook.Body?.stkCallback?.CallbackMetadata?.Item;\n const item = items?.find((i) => i.Name === \"Amount\");\n return item ? Number(item.Value) : null;\n}\n\n/** Extracts the phone number from a successful STK Push callback */\nexport function extractPhoneNumber(webhook: StkPushWebhook): string | null {\n const items = webhook.Body?.stkCallback?.CallbackMetadata?.Item;\n const item = items?.find((i) => i.Name === \"PhoneNumber\");\n return item ? String(item.Value) : null;\n}\n\n/** Returns true if the STK Push callback represents a successful transaction */\nexport function isSuccessfulCallback(webhook: StkPushWebhook): boolean {\n return webhook.Body?.stkCallback?.ResultCode === 0;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/errors/index.ts","../src/core/encryption/security-credentials.ts","../src/utils/http/index.ts","../src/core/auth/token-manager.ts","../src/mpesa/c2b/register-url.ts","../src/mpesa/c2b/simulate.ts","../src/mpesa/c2b/webhooks.ts","../src/mpesa/dynamic-qr/generate.ts","../src/utils/phone/index.ts","../src/mpesa/stk-push/utils.ts","../src/mpesa/stk-push/stk-push.ts","../src/mpesa/stk-push/stk-query.ts","../src/mpesa/stk-push/types.ts","../src/mpesa/tax-remittance/remit-tax.ts","../src/mpesa/transaction-status/query.ts","../src/mpesa/types.ts","../src/mpesa/index.ts","../src/mpesa/webhooks/retry.ts","../src/mpesa/webhooks/signature-verifier.ts","../src/mpesa/webhooks/webhook-handler.ts"],"names":["publicEncrypt","constants"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Pesafy error types and utilities.\n *\n * Single source of truth — no duplicate definitions.\n * Previously this file had two conflicting ErrorCode aliases and two\n * PesafyError class definitions which caused TypeScript to use an\n * unpredictable one at runtime.\n */\n\n// ── Error code union ──────────────────────────────────────────────────────────\n\nexport type ErrorCode =\n | \"AUTH_FAILED\"\n | \"INVALID_CREDENTIALS\"\n | \"INVALID_PHONE\"\n | \"ENCRYPTION_FAILED\"\n | \"VALIDATION_ERROR\"\n | \"API_ERROR\"\n | \"HTTP_ERROR\"\n | \"NETWORK_ERROR\"\n | \"REQUEST_FAILED\"\n | \"INVALID_RESPONSE\"\n | \"TIMEOUT\";\n\n// ── Error options ─────────────────────────────────────────────────────────────\n\nexport interface PesafyErrorOptions {\n code: ErrorCode;\n message: string;\n /** HTTP status code from Daraja (if applicable) */\n statusCode?: number;\n /** Raw Daraja response body (for debugging) */\n response?: unknown;\n /** Underlying caught error (network, crypto, etc.) */\n cause?: unknown;\n /** Daraja requestId from the error envelope */\n requestId?: string;\n}\n\n// ── PesafyError class ─────────────────────────────────────────────────────────\n\nexport class PesafyError extends Error {\n readonly code: ErrorCode;\n readonly statusCode: number | undefined;\n readonly response: unknown;\n readonly requestId: string | undefined;\n override readonly cause: unknown;\n\n constructor(options: PesafyErrorOptions) {\n super(options.message);\n // Ensure instanceof checks work correctly\n Object.defineProperty(this, \"name\", { value: \"PesafyError\" });\n this.code = options.code;\n this.statusCode = options.statusCode;\n this.response = options.response;\n this.requestId = options.requestId;\n this.cause = options.cause;\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, PesafyError);\n }\n }\n\n toJSON() {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n statusCode: this.statusCode,\n requestId: this.requestId,\n };\n }\n}\n\n// ── Factory ───────────────────────────────────────────────────────────────────\n\n/** Convenience factory — identical API to `new PesafyError(...)` */\nexport function createError(options: PesafyErrorOptions): PesafyError {\n return new PesafyError(options);\n}\n","/**\n * Security credential encryption for Daraja APIs that require it:\n * B2C, B2B, Transaction Status Query, Reversals, Tax Remittance.\n *\n * Algorithm (from Safaricom \"Getting Started\" docs):\n * 1. Write the unencrypted initiator password into a byte array.\n * 2. Encrypt using the M-Pesa public key certificate:\n * - RSA algorithm\n * - PKCS #1 v1.5 padding (NOT OAEP)\n * 3. Base64-encode the encrypted byte array.\n *\n * Certificate source:\n * Sandbox: https://developer.safaricom.co.ke (SandboxCertificate.cer)\n * Production: https://developer.safaricom.co.ke (ProductionCertificate.cer)\n *\n * NOTE: Use the correct certificate for each environment or credentials\n * will be rejected.\n */\n\nimport { constants, publicEncrypt } from \"node:crypto\";\nimport { PesafyError } from \"../../utils/errors\";\n\n/**\n * Encrypts `initiatorPassword` with the given PEM certificate and returns\n * the base64-encoded security credential ready to send to Daraja.\n *\n * @param initiatorPassword - Plain-text password set on the M-PESA org portal\n * @param certificatePem - Full PEM string (the .cer file contents)\n */\nexport function encryptSecurityCredential(\n initiatorPassword: string,\n certificatePem: string\n): string {\n try {\n const passwordBuffer = Buffer.from(initiatorPassword, \"utf-8\");\n\n const encrypted = publicEncrypt(\n {\n key: certificatePem,\n // RSA_PKCS1_PADDING = 1 (NOT RSA_PKCS1_OAEP_PADDING = 4)\n padding: constants.RSA_PKCS1_PADDING,\n },\n passwordBuffer\n );\n\n return encrypted.toString(\"base64\");\n } catch (error) {\n throw new PesafyError({\n code: \"ENCRYPTION_FAILED\",\n message:\n \"Failed to encrypt security credential. \" +\n \"Ensure the certificate PEM is valid and matches the environment (sandbox/production).\",\n cause: error,\n });\n }\n}\n","/**\n * HTTP client for Daraja API calls.\n *\n * Single source of truth — previously this file contained TWO conflicting\n * httpRequest implementations:\n * 1. A retry-capable version (retries, retryDelay options)\n * 2. An older timeout-based version (timeout option, no retries)\n *\n * Having both caused TypeScript to pick an unpredictable implementation.\n * The older version did NOT retry on 503 — meaning Daraja sandbox failures\n * surfaced immediately rather than being absorbed by backoff.\n *\n * This file is the SINGLE canonical export. Only export: httpRequest.\n * Never export \"httpClient\" — consumers must call httpRequest directly.\n */\n\nimport { PesafyError } from \"../errors\";\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface HttpRequestOptions {\n method: \"GET\" | \"POST\";\n headers?: Record<string, string>;\n /**\n * Request body. Will be JSON.stringify'd and sent as application/json.\n * Pass undefined (not null) to omit the body entirely.\n */\n body?: unknown;\n /**\n * Number of retry attempts on transient errors (503, 429, 502, 504, network).\n * Default: 4. Set to 0 to disable retries.\n *\n * Daraja sandbox is notoriously unstable — 503s are common under load.\n * 4 retries with exponential backoff covers the typical sandbox blip.\n */\n retries?: number;\n /**\n * Base delay in ms before the first retry. Doubles each attempt + ±25% jitter.\n * Default: 2000 (2 s).\n */\n retryDelay?: number;\n /**\n * Per-request timeout in ms. Default: 30000 (30 s).\n * The timeout applies to each individual attempt, not the total retry duration.\n */\n timeout?: number;\n}\n\nexport interface HttpResponse<T> {\n data: T;\n status: number;\n headers: Record<string, string>;\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\n/** HTTP status codes that are transient and safe to retry */\nconst RETRYABLE_STATUSES = new Set([429, 500, 502, 503, 504]);\n\n/** Promise-based sleep */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Adds ±25% jitter to avoid thundering-herd retry storms.\n * Daraja sandbox 503s are caused by sandbox overload — spreading retries\n * across a time window prevents making the overload worse.\n */\nfunction withJitter(baseMs: number): number {\n const spread = baseMs * 0.25;\n return baseMs + (Math.random() * spread * 2 - spread);\n}\n\n// ── httpRequest ───────────────────────────────────────────────────────────────\n\n/**\n * Sends an HTTP request and returns parsed JSON.\n *\n * Automatically retries on transient errors with exponential backoff + jitter.\n * Never retries on 4xx client errors — those indicate a logic bug.\n *\n * @throws PesafyError on non-retryable errors or exhausted retries\n */\nexport async function httpRequest<T = unknown>(\n url: string,\n options: HttpRequestOptions\n): Promise<HttpResponse<T>> {\n const maxRetries = options.retries ?? 4;\n const baseDelay = options.retryDelay ?? 2000;\n const timeout = options.timeout ?? 30_000;\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...options.headers,\n };\n\n // Build the fetch init once — body is immutable across retries\n const init: RequestInit = {\n method: options.method,\n headers,\n ...(options.body !== undefined\n ? { body: JSON.stringify(options.body) }\n : {}),\n };\n\n let lastError: PesafyError | null = null;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n // ── Backoff before retry (not before first attempt) ─────────────────────\n if (attempt > 0) {\n const delay = withJitter(baseDelay * Math.pow(2, attempt - 1));\n console.warn(\n `[pesafy/http] Retry ${attempt}/${maxRetries} for ${options.method} ${url} ` +\n `in ${Math.round(delay)}ms (last error: ${lastError?.message ?? \"unknown\"})`\n );\n await sleep(delay);\n }\n\n // ── Per-attempt timeout via AbortController ──────────────────────────────\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n let response: Response;\n\n try {\n response = await fetch(url, { ...init, signal: controller.signal });\n } catch (err) {\n clearTimeout(timeoutId);\n\n // AbortError = timeout\n if (err instanceof Error && err.name === \"AbortError\") {\n lastError = new PesafyError({\n code: \"TIMEOUT\",\n message: `Request to ${url} timed out after ${timeout}ms`,\n cause: err,\n });\n if (attempt < maxRetries) continue;\n throw lastError;\n }\n\n // Network-level failure: DNS, ECONNRESET, ECONNREFUSED, etc.\n lastError = new PesafyError({\n code: \"NETWORK_ERROR\",\n message: `Network error calling ${url}: ${err instanceof Error ? err.message : String(err)}`,\n cause: err,\n });\n if (attempt < maxRetries) continue;\n throw lastError;\n } finally {\n clearTimeout(timeoutId);\n }\n\n // ── Parse body regardless of status ─────────────────────────────────────\n // We always read the body so we can include Daraja's error message in\n // thrown errors instead of just \"HTTP 503\".\n let rawText = \"\";\n let data: unknown;\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n\n try {\n rawText = await response.text();\n if (rawText) {\n data = contentType.includes(\"application/json\")\n ? JSON.parse(rawText)\n : rawText;\n } else {\n data = null;\n }\n } catch {\n data = rawText || null;\n }\n\n // ── Collect response headers ─────────────────────────────────────────────\n const responseHeaders: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n responseHeaders[key] = value;\n });\n\n // ── Success ──────────────────────────────────────────────────────────────\n if (response.ok) {\n return {\n data: data as T,\n status: response.status,\n headers: responseHeaders,\n };\n }\n\n // ── Error response ───────────────────────────────────────────────────────\n const isTransient = RETRYABLE_STATUSES.has(response.status);\n\n // Daraja error envelope shapes:\n // { requestId, errorCode, errorMessage }\n // { ResponseCode, ResponseDescription }\n const daraja =\n typeof data === \"object\" && data !== null\n ? (data as Record<string, unknown>)\n : {};\n\n const errorMessage =\n (daraja.errorMessage as string | undefined) ??\n (daraja.ResponseDescription as string | undefined) ??\n rawText ??\n `HTTP ${response.status}`;\n\n lastError = new PesafyError({\n code: isTransient ? \"REQUEST_FAILED\" : \"API_ERROR\",\n message: errorMessage,\n statusCode: response.status,\n response: data,\n requestId: daraja.requestId as string | undefined,\n });\n\n // Only retry transient errors. 4xx errors are client bugs — never retry.\n if (isTransient && attempt < maxRetries) {\n continue;\n }\n\n throw lastError;\n }\n\n // Unreachable — TypeScript requires this\n throw lastError!;\n}\n","/**\n * OAuth token manager for Daraja API.\n *\n * Daraja Authorization endpoint (GET, Basic Auth):\n * https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials\n * https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials\n *\n * Token validity: 3600 seconds (1 hour).\n * We refresh 60 s early to avoid edge-case expiry mid-request.\n *\n * Ref: Authorization By Safaricom docs\n */\n\nimport { PesafyError } from \"../../utils/errors\";\nimport { httpRequest } from \"../../utils/http\";\nimport type { TokenResponse } from \"./types\";\n\n/** Refresh the token this many seconds before it actually expires */\nconst TOKEN_BUFFER_SECONDS = 60;\n\nexport class TokenManager {\n private readonly consumerKey: string;\n private readonly consumerSecret: string;\n private readonly baseUrl: string;\n\n private cachedToken: string | null = null;\n private tokenExpiresAt = 0; // Unix seconds\n\n constructor(consumerKey: string, consumerSecret: string, baseUrl: string) {\n this.consumerKey = consumerKey;\n this.consumerSecret = consumerSecret;\n this.baseUrl = baseUrl;\n }\n\n private getBasicAuthHeader(): string {\n // Daraja spec: Base64(consumerKey:consumerSecret)\n const credentials = `${this.consumerKey}:${this.consumerSecret}`;\n const encoded = Buffer.from(credentials, \"utf-8\").toString(\"base64\");\n return `Basic ${encoded}`;\n }\n\n /**\n * Returns a valid access token, fetching a new one when the cached token\n * is absent or within TOKEN_BUFFER_SECONDS of expiry.\n */\n async getAccessToken(): Promise<string> {\n const now = Date.now() / 1000;\n\n if (this.cachedToken && this.tokenExpiresAt > now + TOKEN_BUFFER_SECONDS) {\n return this.cachedToken;\n }\n\n // Daraja Authorization API: GET with Basic Auth + grant_type query param\n const url = `${this.baseUrl}/oauth/v1/generate?grant_type=client_credentials`;\n\n const response = await httpRequest<TokenResponse>(url, {\n method: \"GET\",\n headers: {\n Authorization: this.getBasicAuthHeader(),\n },\n });\n\n const { access_token, expires_in } = response.data;\n\n if (!access_token) {\n throw new PesafyError({\n code: \"AUTH_FAILED\",\n message:\n \"Daraja did not return an access token. Check your consumer key and secret.\",\n response: response.data,\n });\n }\n\n this.cachedToken = access_token;\n // expires_in is 3599 per Daraja docs; default to 3600 if missing\n this.tokenExpiresAt = now + (expires_in ?? 3600);\n\n return this.cachedToken;\n }\n\n /** Force token refresh on the next call (e.g. after a 401 response) */\n clearCache(): void {\n this.cachedToken = null;\n this.tokenExpiresAt = 0;\n }\n}\n","/**\n * C2B Register URL\n *\n * Registers your Confirmation and Validation URLs with Safaricom.\n *\n * API:\n * v1: POST /mpesa/c2b/v1/registerurl\n * v2: POST /mpesa/c2b/v2/registerurl ← default (masked MSISDN in callbacks)\n *\n * Sandbox: https://sandbox.safaricom.co.ke/mpesa/c2b/v{1|2}/registerurl\n * Production: https://api.safaricom.co.ke/mpesa/c2b/v{1|2}/registerurl\n *\n * Notes from Daraja docs:\n * - Sandbox: you can register URLs multiple times / overwrite existing.\n * - Production: one-time call. To change, delete via Self Services > URL\n * Management on the Daraja portal, then re-register.\n * - ResponseType must be exactly \"Completed\" or \"Cancelled\" (sentence case).\n * - Production URLs must be HTTPS. Sandbox allows HTTP.\n * - URLs must not contain keywords: M-PESA, Safaricom, exe, exec, cmd, sql, query.\n * - Do not use public URL testers (ngrok, mockbin, requestbin) in production.\n */\n\nimport { createError } from \"../../utils/errors\";\nimport { httpRequest } from \"../../utils/http\";\nimport type {\n C2BApiVersion,\n C2BRegisterUrlRequest,\n C2BRegisterUrlResponse,\n} from \"./types\";\n\n/** Forbidden URL keywords per Daraja docs */\nconst FORBIDDEN_URL_KEYWORDS = [\n \"mpesa\",\n \"safaricom\",\n \".exe\",\n \".exec\",\n \"cmd\",\n \"sql\",\n \"query\",\n];\n\n/**\n * Validates a callback URL against Daraja's URL requirements.\n * Throws PesafyError if the URL violates any rule.\n */\nfunction validateCallbackUrl(url: string, fieldName: string): void {\n if (!url || !url.trim()) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: `${fieldName} is required`,\n });\n }\n\n const lower = url.toLowerCase();\n\n for (const keyword of FORBIDDEN_URL_KEYWORDS) {\n if (lower.includes(keyword)) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n `${fieldName} must not contain the keyword \"${keyword}\". ` +\n \"Daraja rejects URLs containing: M-PESA, Safaricom, exe, exec, cmd, sql, query.\",\n });\n }\n }\n}\n\n/**\n * Registers your C2B Confirmation and Validation URLs with Safaricom.\n *\n * @param baseUrl - Daraja base URL (sandbox or production)\n * @param accessToken - Valid OAuth bearer token\n * @param request - Registration parameters\n * @returns - Daraja registration response\n */\nexport async function registerC2BUrls(\n baseUrl: string,\n accessToken: string,\n request: C2BRegisterUrlRequest\n): Promise<C2BRegisterUrlResponse> {\n // ── Validation ──────────────────────────────────────────────────────────────\n\n if (!request.shortCode) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: \"shortCode is required\",\n });\n }\n\n if (!request.responseType) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n 'responseType is required: \"Completed\" or \"Cancelled\" (sentence case, exactly as spelled)',\n });\n }\n\n if (\n request.responseType !== \"Completed\" &&\n request.responseType !== \"Cancelled\"\n ) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n `responseType must be exactly \"Completed\" or \"Cancelled\" (sentence case). ` +\n `Got: \"${request.responseType}\"`,\n });\n }\n\n validateCallbackUrl(request.confirmationUrl, \"confirmationUrl\");\n validateCallbackUrl(request.validationUrl, \"validationUrl\");\n\n // ── Determine API version ───────────────────────────────────────────────────\n const version: C2BApiVersion = request.apiVersion ?? \"v2\";\n\n // ── Build payload matching Daraja spec exactly ──────────────────────────────\n const payload = {\n ShortCode: String(request.shortCode),\n ResponseType: request.responseType,\n ConfirmationURL: request.confirmationUrl,\n ValidationURL: request.validationUrl,\n };\n\n const { data } = await httpRequest<C2BRegisterUrlResponse>(\n `${baseUrl}/mpesa/c2b/${version}/registerurl`,\n {\n method: \"POST\",\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n }\n );\n\n return data;\n}\n","/**\n * C2B Simulate Transaction — SANDBOX ONLY\n *\n * Simulates a customer payment to your Paybill or Till number.\n *\n * API:\n * v1: POST /mpesa/c2b/v1/simulate\n * v2: POST /mpesa/c2b/v2/simulate ← default\n *\n * ─── CRITICAL: BillRefNumber rules (confirmed by Daraja docs + live testing) ─\n *\n * CustomerPayBillOnline (Paybill):\n * → INCLUDE BillRefNumber as the account/invoice reference.\n * Even an empty string \"\" is acceptable for Paybill.\n *\n * CustomerBuyGoodsOnline (Till):\n * → COMPLETELY OMIT BillRefNumber from the JSON body.\n * → DO NOT send it as null, undefined, or \"\" (empty string).\n * → Daraja docs say \"null for till number\" but in practice ANY presence\n * of the field — including null or \"\" — triggers:\n * HTTP 400: \"The element AccountReference is invalid\"\n * which Daraja sandbox may also surface as HTTP 503.\n * → The fix is to never include the key in the payload object at all.\n *\n * ─── CRITICAL: URL registration per shortcode ─────────────────────────────────\n *\n * Daraja docs: \"Register URLs before each simulation.\"\n * Registration is PER SHORTCODE — each shortcode is registered INDEPENDENTLY:\n * - Paybill shortcode (e.g. 600977): registerC2BUrls({ shortCode: \"600977\" })\n * - Till shortcode (e.g. 600000): registerC2BUrls({ shortCode: \"600000\" })\n * Simulating with a shortcode whose URLs are not registered will cause errors.\n *\n * ─── Sandbox shortcodes ───────────────────────────────────────────────────────\n *\n * CustomerPayBillOnline → your registered Paybill shortcode (e.g. 600977)\n * CustomerBuyGoodsOnline → Daraja test Till shortcode: 600000\n */\n\nimport { createError } from \"../../utils/errors\";\nimport { httpRequest } from \"../../utils/http\";\nimport type {\n C2BApiVersion,\n C2BSimulateRequest,\n C2BSimulateResponse,\n} from \"./types\";\n\n/**\n * Simulates a C2B customer payment. SANDBOX ONLY.\n *\n * @param baseUrl - Must be the sandbox base URL (contains \"sandbox\").\n * @param accessToken - Valid OAuth bearer token from the Authorization API.\n * @param request - Simulation parameters. Do NOT pass billRefNumber for Buy Goods.\n * @returns - Daraja simulate response (ResponseCode \"0\" = accepted).\n */\nexport async function simulateC2B(\n baseUrl: string,\n accessToken: string,\n request: C2BSimulateRequest\n): Promise<C2BSimulateResponse> {\n // ── Sandbox guard ───────────────────────────────────────────────────────────\n if (!baseUrl.includes(\"sandbox\")) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n \"C2B simulate is only available in the Sandbox environment. \" +\n \"In production, customers initiate payments via M-PESA App, USSD, or SIM Toolkit.\",\n });\n }\n\n // ── Input validation ────────────────────────────────────────────────────────\n\n if (!request.shortCode) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: \"shortCode is required.\",\n });\n }\n\n if (\n request.commandId !== \"CustomerPayBillOnline\" &&\n request.commandId !== \"CustomerBuyGoodsOnline\"\n ) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n `commandId must be \"CustomerPayBillOnline\" or \"CustomerBuyGoodsOnline\". ` +\n `Got: \"${request.commandId}\"`,\n });\n }\n\n const amount = Math.round(request.amount);\n if (!Number.isFinite(amount) || amount < 1) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: `amount must be a whole number ≥ 1 (got ${request.amount}).`,\n });\n }\n\n if (!request.msisdn) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: \"msisdn is required. Sandbox test MSISDN: 254708374149.\",\n });\n }\n\n // ── Determine transaction type ──────────────────────────────────────────────\n const isBuyGoods = request.commandId === \"CustomerBuyGoodsOnline\";\n const version: C2BApiVersion = request.apiVersion ?? \"v2\";\n\n // ── Build payload ───────────────────────────────────────────────────────────\n //\n // CRITICAL — BillRefNumber MUST be omitted for CustomerBuyGoodsOnline.\n //\n // We build the base object WITHOUT BillRefNumber and only conditionally\n // add it for Paybill. This is the only safe pattern because:\n //\n // • JSON.stringify({ BillRefNumber: null }) → includes \"BillRefNumber\":null ✗\n // • JSON.stringify({ BillRefNumber: \"\" }) → includes \"BillRefNumber\":\"\" ✗\n // • building without the key at all → field absent from JSON body ✓\n //\n // Daraja validates field presence, not just value — even null/empty triggers:\n // \"The element AccountReference is invalid\"\n //\n // Note: the caller (simulateC2BPayment in c2bActions.ts) must NOT pass\n // billRefNumber at all for Buy Goods — pass undefined or omit the key.\n // This function is defensive and will still correctly omit it regardless.\n\n const payload: Record<string, unknown> = {\n ShortCode: Number(request.shortCode),\n CommandID: request.commandId,\n Amount: amount,\n Msisdn: Number(request.msisdn),\n // BillRefNumber is NOT here — added conditionally below for Paybill only\n };\n\n if (!isBuyGoods) {\n // Paybill: include BillRefNumber (the account/invoice ref for this payment).\n // Empty string is acceptable when no specific account ref is needed.\n payload[\"BillRefNumber\"] = request.billRefNumber ?? \"\";\n }\n\n // ── Defensive check — should never trigger given the logic above ─────────────\n // Acts as a compile-time-visible safety net for future refactors.\n if (\n isBuyGoods &&\n Object.prototype.hasOwnProperty.call(payload, \"BillRefNumber\")\n ) {\n // This branch must never execute. If it does, remove the key to prevent\n // the \"AccountReference is invalid\" error from Daraja.\n delete payload[\"BillRefNumber\"];\n console.warn(\n \"[pesafy/simulateC2B] BillRefNumber leaked into Buy Goods payload — removed. \" +\n \"This is a library bug; please report it.\"\n );\n }\n\n // ── Call Daraja ─────────────────────────────────────────────────────────────\n const { data } = await httpRequest<C2BSimulateResponse>(\n `${baseUrl}/mpesa/c2b/${version}/simulate`,\n {\n method: \"POST\",\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n }\n );\n\n return data;\n}\n","/**\n * C2B Webhook Handlers\n *\n * Utilities for parsing and responding to Safaricom C2B callbacks:\n * - Validation callback → your ValidationURL\n * - Confirmation callback → your ConfirmationURL\n *\n * Safaricom IP whitelist applies (same as STK Push callbacks).\n * Always respond 200 to Safaricom — log errors internally.\n *\n * Validation response timing: must respond within ~8 seconds.\n *\n * Ref: C2B Daraja docs — Callback Payload section\n */\n\nimport type {\n C2BConfirmationAck,\n C2BConfirmationPayload,\n C2BValidationPayload,\n C2BValidationResponse,\n C2BValidationResultCode,\n} from \"./types\";\n\n// ── Type guards ───────────────────────────────────────────────────────────────\n\n/**\n * Checks if a body looks like a C2B Validation or Confirmation payload.\n * Both share the same shape; distinguish them by context (which URL received it).\n */\nexport function isC2BPayload(body: unknown): body is C2BValidationPayload {\n if (!body || typeof body !== \"object\") return false;\n const b = body as Record<string, unknown>;\n return (\n typeof b[\"TransID\"] === \"string\" &&\n typeof b[\"BusinessShortCode\"] === \"string\" &&\n typeof b[\"TransAmount\"] === \"string\"\n );\n}\n\n// ── Validation responses ──────────────────────────────────────────────────────\n\n/**\n * Builds an \"Accept\" validation response.\n * Send this to M-PESA to allow the transaction to proceed.\n *\n * @param thirdPartyTransID - Optional correlation ID echoed back in Confirmation.\n */\nexport function acceptC2BValidation(\n thirdPartyTransID?: string\n): C2BValidationResponse {\n return {\n ResultCode: \"0\",\n ResultDesc: \"Accepted\",\n ...(thirdPartyTransID ? { ThirdPartyTransID: thirdPartyTransID } : {}),\n };\n}\n\n/**\n * Builds a \"Reject\" validation response.\n * Send this to M-PESA to cancel the transaction.\n *\n * @param resultCode - C2B error code. Determines the SMS the customer gets.\n * @param resultDesc - Short description. Usually \"Rejected\".\n *\n * Result codes:\n * C2B00011 — Invalid MSISDN\n * C2B00012 — Invalid Account Number\n * C2B00013 — Invalid Amount\n * C2B00014 — Invalid KYC Details\n * C2B00015 — Invalid Short code\n * C2B00016 — Other Error\n */\nexport function rejectC2BValidation(\n resultCode: Exclude<C2BValidationResultCode, \"0\"> = \"C2B00016\",\n resultDesc = \"Rejected\"\n): C2BValidationResponse {\n return {\n ResultCode: resultCode,\n ResultDesc: resultDesc,\n };\n}\n\n/**\n * Builds the acknowledgement your ConfirmationURL must return to Safaricom.\n * Always return this with HTTP 200.\n */\nexport function acknowledgeC2BConfirmation(): C2BConfirmationAck {\n return { ResultCode: 0, ResultDesc: \"Success\" };\n}\n\n// ── Convenience extractors ────────────────────────────────────────────────────\n\n/** Extracts the transaction amount as a number from a C2B payload */\nexport function getC2BAmount(\n payload: C2BValidationPayload | C2BConfirmationPayload\n): number {\n return Number(payload.TransAmount);\n}\n\n/** Extracts the M-PESA receipt/transaction ID from a C2B payload */\nexport function getC2BTransactionId(\n payload: C2BValidationPayload | C2BConfirmationPayload\n): string {\n return payload.TransID;\n}\n\n/** Extracts the account reference (BillRefNumber) from a C2B payload */\nexport function getC2BAccountRef(\n payload: C2BValidationPayload | C2BConfirmationPayload\n): string {\n return payload.BillRefNumber;\n}\n\n/**\n * Returns the customer's full name from a C2B payload.\n * Note: data minimization per Safaricom data protection requirements;\n * some fields may be blank.\n */\nexport function getC2BCustomerName(\n payload: C2BValidationPayload | C2BConfirmationPayload\n): string {\n return [payload.FirstName, payload.MiddleName, payload.LastName]\n .filter(Boolean)\n .join(\" \")\n .trim();\n}\n\n/** Returns true if the C2B payload is a Paybill payment */\nexport function isPaybillPayment(\n payload: C2BValidationPayload | C2BConfirmationPayload\n): boolean {\n return (\n payload.TransactionType === \"Pay Bill\" ||\n payload.TransactionType === \"CustomerPayBillOnline\"\n );\n}\n\n/** Returns true if the C2B payload is a Buy Goods (Till) payment */\nexport function isBuyGoodsPayment(\n payload: C2BValidationPayload | C2BConfirmationPayload\n): boolean {\n return (\n payload.TransactionType === \"Buy Goods\" ||\n payload.TransactionType === \"CustomerBuyGoodsOnline\"\n );\n}\n","/**\n * Dynamic QR Code generation\n *\n * API: POST /mpesa/qrcode/v1/generate\n *\n * Generates a dynamic M-PESA QR code that customers can scan with\n * the My Safaricom App or M-PESA app to pay at LNM merchant outlets.\n *\n * Error codes (from Daraja docs):\n * 404.001.04 — Invalid Authentication Header (wrong HTTP method or misplaced headers)\n * 400.002.05 — Invalid Request Payload (malformed body)\n * 400.003.01 — Invalid Access Token (expired or incorrect)\n */\n\nimport { createError } from \"../../utils/errors\";\nimport { httpRequest } from \"../../utils/http\";\nimport type { DynamicQRRequest, DynamicQRResponse } from \"./types\";\n\n/**\n * Generates a Dynamic M-PESA QR Code.\n *\n * @param baseUrl - Daraja base URL (sandbox or production)\n * @param accessToken - Valid OAuth bearer token\n * @param request - QR generation parameters\n * @returns - Daraja response including base64 QRCode string\n */\nexport async function generateDynamicQR(\n baseUrl: string,\n accessToken: string,\n request: DynamicQRRequest\n): Promise<DynamicQRResponse> {\n // ── Validation ──────────────────────────────────────────────────────────────\n\n if (!request.merchantName?.trim()) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: \"merchantName is required\",\n });\n }\n\n if (!request.refNo?.trim()) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: \"refNo (transaction reference) is required\",\n });\n }\n\n const amount = Math.round(request.amount);\n if (!Number.isFinite(amount) || amount < 1) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: `Amount must be at least 1 (got ${request.amount} which rounds to ${amount}).`,\n });\n }\n\n if (!request.trxCode) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n 'trxCode is required. Supported values: \"BG\" | \"WA\" | \"PB\" | \"SM\" | \"SB\"',\n });\n }\n\n if (!request.cpi?.trim()) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n \"cpi (Credit Party Identifier) is required — e.g. till number, paybill, or MSISDN\",\n });\n }\n\n const size = request.size ?? 300;\n if (size < 1) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: \"size must be a positive number of pixels\",\n });\n }\n\n // ── Build payload matching Daraja spec exactly ──────────────────────────────\n\n const payload = {\n MerchantName: request.merchantName,\n RefNo: request.refNo,\n Amount: amount,\n TrxCode: request.trxCode,\n CPI: request.cpi,\n Size: String(size),\n };\n\n const { data } = await httpRequest<DynamicQRResponse>(\n `${baseUrl}/mpesa/qrcode/v1/generate`,\n {\n method: \"POST\",\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n }\n );\n\n return data;\n}\n","/**\n * Phone number utilities for Daraja API.\n *\n * Daraja spec: PartyA and PhoneNumber must be in the format 2547XXXXXXXX\n * (12-digit, starts with 254, no +, no spaces, no dashes).\n *\n * Accepted input formats:\n * 0712345678 → 254712345678\n * +254712345678 → 254712345678\n * 254712345678 → 254712345678 (already correct)\n * 712345678 → 254712345678\n */\n\nimport { PesafyError } from \"../errors\";\n\n/** Normalises any common Kenyan phone format to 254XXXXXXXXX (12 digits) */\nexport function formatSafaricomPhone(phone: string): string {\n const digits = phone.replace(/\\D/g, \"\");\n\n let normalised: string;\n\n if (digits.startsWith(\"254\") && digits.length === 12) {\n normalised = digits;\n } else if (digits.startsWith(\"0\") && digits.length === 10) {\n normalised = `254${digits.slice(1)}`;\n } else if (digits.length === 9) {\n // e.g. 712345678 → 254712345678\n normalised = `254${digits}`;\n } else if (digits.startsWith(\"254\") && digits.length !== 12) {\n throw new PesafyError({\n code: \"INVALID_PHONE\",\n message: `Invalid phone number \"${phone}\". Expected 254XXXXXXXXX (12 digits).`,\n });\n } else {\n throw new PesafyError({\n code: \"INVALID_PHONE\",\n message: `Cannot parse phone number \"${phone}\". Use 07XXXXXXXX, 2547XXXXXXXX, or +2547XXXXXXXX.`,\n });\n }\n\n // Final sanity check: must be exactly 12 digits\n if (normalised.length !== 12) {\n throw new PesafyError({\n code: \"INVALID_PHONE\",\n message: `Phone number \"${phone}\" normalised to \"${normalised}\" which is not 12 digits.`,\n });\n }\n\n return normalised;\n}\n","/**\n * STK Push utility functions\n *\n * Password spec (from Daraja docs):\n * Password = Base64( BusinessShortCode + Passkey + Timestamp )\n * Timestamp = YYYYMMDDHHmmss\n *\n * IMPORTANT: Generate the timestamp ONCE per request and pass the same\n * value to BOTH getStkPushPassword() and the request body's Timestamp field.\n * Safaricom validates that Base64(Shortcode+Passkey+Timestamp) matches the\n * Timestamp sent in the body — two separate calls to getTimestamp() will\n * produce different values and cause auth failures.\n */\n\nexport { formatSafaricomPhone as formatPhoneNumber } from \"../../utils/phone\";\n\n/**\n * Generates the STK Push password.\n * Formula: Base64( Shortcode + Passkey + Timestamp )\n *\n * Uses btoa() — works in Node.js ≥18, Bun, browsers, and edge runtimes.\n */\nexport function getStkPushPassword(\n shortCode: string,\n passKey: string,\n timestamp: string\n): string {\n return btoa(`${shortCode}${passKey}${timestamp}`);\n}\n\n/**\n * Returns a Daraja-compatible timestamp: YYYYMMDDHHmmss\n *\n * Call this ONCE per request and reuse the result.\n */\nexport function getTimestamp(): string {\n const now = new Date();\n const pad = (n: number): string => n.toString().padStart(2, \"0\");\n return [\n now.getFullYear(),\n pad(now.getMonth() + 1),\n pad(now.getDate()),\n pad(now.getHours()),\n pad(now.getMinutes()),\n pad(now.getSeconds()),\n ].join(\"\");\n}\n","// src/mpesa/stk-push/stk-push.ts\n\n/**\n * M-Pesa Express (STK Push) — initiates a payment prompt on the customer's phone.\n *\n * API: POST /mpesa/stkpush/v1/processrequest\n *\n * Daraja request body (from docs):\n * {\n * \"BusinessShortCode\": 174379,\n * \"Password\": \"base64(Shortcode+Passkey+Timestamp)\",\n * \"Timestamp\": \"20210628092408\",\n * \"TransactionType\": \"CustomerPayBillOnline\",\n * \"Amount\": \"1\",\n * \"PartyA\": \"254722000000\",\n * \"PartyB\": \"174379\",\n * \"PhoneNumber\": \"254722111111\",\n * \"CallBackURL\": \"https://mydomain.com/path\",\n * \"AccountReference\": \"accountref\", ← max 12 chars\n * \"TransactionDesc\": \"txndesc\" ← max 13 chars\n * }\n *\n * Notes from docs:\n * - All fields except TransactionDesc are mandatory.\n * - Amount must be a whole number ≥ 1 (KES).\n * - PartyA/PhoneNumber must be 254XXXXXXXXX format.\n * - AccountReference max 12 chars.\n * - TransactionDesc max 13 chars.\n */\n\nimport { PesafyError } from \"../../utils/errors\";\nimport { httpRequest } from \"../../utils/http\";\nimport type { StkPushRequest, StkPushResponse } from \"./types\";\nimport { formatPhoneNumber, getStkPushPassword, getTimestamp } from \"./utils\";\n\nexport async function processStkPush(\n baseUrl: string,\n accessToken: string,\n request: StkPushRequest\n): Promise<StkPushResponse> {\n // ── Amount validation ───────────────────────────────────────────────────────\n const amount = Math.round(request.amount);\n if (amount < 1) {\n throw new PesafyError({\n code: \"VALIDATION_ERROR\",\n message: `Amount must be at least KES 1 (got ${request.amount} which rounds to ${amount}).`,\n });\n }\n\n // ── Generate timestamp ONCE ─────────────────────────────────────────────────\n // Must be identical in Password (encoded) and Timestamp (body) fields.\n const timestamp = getTimestamp();\n\n // ── PartyB logic ────────────────────────────────────────────────────────────\n // Paybill → PartyB = shortCode\n // Buy Goods (Till) → PartyB = till number (passed as request.partyB)\n const partyB = request.partyB ?? request.shortCode;\n\n const body = {\n BusinessShortCode: request.shortCode,\n Password: getStkPushPassword(request.shortCode, request.passKey, timestamp),\n Timestamp: timestamp,\n TransactionType: request.transactionType ?? \"CustomerPayBillOnline\",\n Amount: amount,\n PartyA: formatPhoneNumber(request.phoneNumber),\n PartyB: partyB,\n PhoneNumber: formatPhoneNumber(request.phoneNumber),\n CallBackURL: request.callbackUrl,\n // Daraja docs: AccountReference max 12 chars, TransactionDesc max 13 chars\n AccountReference: request.accountReference.slice(0, 12),\n TransactionDesc: request.transactionDesc.slice(0, 13),\n };\n\n // httpRequest already retries 503/429/5xx with exponential backoff + jitter.\n // If all retries are exhausted it throws PesafyError with code \"REQUEST_FAILED\"\n // and statusCode 503 — callers should treat this as TRANSIENT, not a final\n // failure. Never mark a transaction \"failed\" on a 503.\n const { data } = await httpRequest<StkPushResponse>(\n `${baseUrl}/mpesa/stkpush/v1/processrequest`,\n {\n method: \"POST\",\n headers: { Authorization: `Bearer ${accessToken}` },\n body,\n // Daraja sandbox needs more retries and longer gaps due to instability\n retries: 5,\n retryDelay: 3000,\n }\n );\n\n return data;\n}\n","/**\n * STK Push Query — checks the status of a Lipa Na M-Pesa Online Payment.\n *\n * API: POST /mpesa/stkpushquery/v1/query\n *\n * Daraja request body (from Discover APIs M-Pesa Express Query docs):\n * {\n * \"BusinessShortCode\": \"174379\",\n * \"Password\": \"base64(Shortcode+Passkey+Timestamp)\",\n * \"Timestamp\": \"20160216165627\",\n * \"CheckoutRequestID\": \"ws_CO_260520211133524545\"\n * }\n *\n * Response ResultCode values (from docs):\n * 0 = The service request is processed successfully.\n * 1032 = Request cancelled by user\n * 1037 = DS timeout user cannot be reached\n * 2001 = Wrong PIN\n * (and more — see STK Push docs result code table)\n */\n\nimport { httpRequest } from \"../../utils/http\";\nimport type { StkQueryRequest, StkQueryResponse } from \"./types\";\nimport { getStkPushPassword, getTimestamp } from \"./utils\";\n\nexport async function queryStkPush(\n baseUrl: string,\n accessToken: string,\n request: StkQueryRequest\n): Promise<StkQueryResponse> {\n // Generate timestamp ONCE — Password and Timestamp field MUST match.\n const timestamp = getTimestamp();\n\n const body = {\n BusinessShortCode: request.shortCode,\n Password: getStkPushPassword(request.shortCode, request.passKey, timestamp),\n Timestamp: timestamp,\n CheckoutRequestID: request.checkoutRequestId,\n };\n\n const { data } = await httpRequest<StkQueryResponse>(\n `${baseUrl}/mpesa/stkpushquery/v1/query`,\n {\n method: \"POST\",\n headers: { Authorization: `Bearer ${accessToken}` },\n body,\n }\n );\n\n return data;\n}\n","/**\n * STK Push (M-Pesa Express) types\n *\n * API: POST /mpesa/stkpush/v1/processrequest\n * Query: POST /mpesa/stkpushquery/v1/query\n *\n * Ref: M-Pesa Express Simulate docs + Discover APIs M-Pesa Express Query docs\n */\n\n// ── Transaction type ─────────────────────────────────────────────────────────\n\n/**\n * CustomerPayBillOnline → Paybill numbers (PartyB = shortcode)\n * CustomerBuyGoodsOnline → Till numbers (PartyB = till number)\n */\nexport type TransactionType =\n | \"CustomerPayBillOnline\"\n | \"CustomerBuyGoodsOnline\";\n\n// ── STK Push request ─────────────────────────────────────────────────────────\n\nexport interface StkPushRequest {\n /** Transaction amount (minimum KES 1, must round to a whole number ≥ 1) */\n amount: number;\n\n /**\n * Phone number sending the money. Format: 2547XXXXXXXX.\n * Must be a valid Safaricom M-PESA number.\n * Daraja docs field name: PartyA / PhoneNumber\n */\n phoneNumber: string;\n\n /**\n * URL where Safaricom will POST the callback result.\n * Must be publicly accessible (use ngrok/localtunnel for local dev).\n * Daraja docs field name: CallBackURL\n */\n callbackUrl: string;\n\n /**\n * Alpha-numeric reference shown to customer in the USSD prompt.\n * Max 12 characters.\n * Daraja docs field name: AccountReference\n */\n accountReference: string;\n\n /**\n * Additional description for the transaction.\n * Max 13 characters.\n * Daraja docs field name: TransactionDesc\n */\n transactionDesc: string;\n\n /**\n * Business shortcode — Paybill number or HO/Store number for Till.\n * Daraja docs field name: BusinessShortCode\n */\n shortCode: string;\n\n /**\n * Passkey used to generate the Password.\n * Sandbox value: from Daraja simulator test data.\n * Production value: emailed after Go Live.\n */\n passKey: string;\n\n /**\n * \"CustomerPayBillOnline\" (default) for Paybill.\n * \"CustomerBuyGoodsOnline\" for Till Numbers.\n */\n transactionType?: TransactionType;\n\n /**\n * Credit party receiving funds.\n * - CustomerPayBillOnline: defaults to shortCode\n * - CustomerBuyGoodsOnline: set to the Till Number\n * Daraja docs field name: PartyB\n */\n partyB?: string;\n}\n\n// ── STK Push response ────────────────────────────────────────────────────────\n\nexport interface StkPushResponse {\n /** Global unique identifier for the submitted payment request */\n MerchantRequestID: string;\n /** Global unique identifier for the checkout transaction */\n CheckoutRequestID: string;\n /** \"0\" = successful submission */\n ResponseCode: string;\n ResponseDescription: string;\n CustomerMessage: string;\n}\n\n// ── STK Query request/response ───────────────────────────────────────────────\n\nexport interface StkQueryRequest {\n /** CheckoutRequestID from the STK Push response */\n checkoutRequestId: string;\n shortCode: string;\n passKey: string;\n}\n\nexport interface StkQueryResponse {\n ResponseCode: string;\n ResponseDescription: string;\n MerchantRequestID: string;\n CheckoutRequestID: string;\n /**\n * Daraja returns ResultCode as a NUMBER.\n * 0 = success\n * 1 = insufficient balance\n * 1032 = cancelled by user\n * 1037 = timeout / unreachable\n * 2001 = wrong PIN\n */\n ResultCode: number;\n ResultDesc: string;\n}\n\n// ── Callback payload types ────────────────────────────────────────────────────\n// Safaricom POSTs these to your CallBackURL after the customer responds.\n\n/** Single metadata item in a successful STK callback */\nexport interface StkCallbackMetadataItem {\n Name:\n | \"Amount\"\n | \"MpesaReceiptNumber\"\n | \"TransactionDate\"\n | \"PhoneNumber\"\n | \"Balance\";\n /** Present on successful transactions; absent on failure */\n Value?: number | string;\n}\n\n/** Inner callback for a SUCCESSFUL STK Push (ResultCode === 0) */\nexport interface StkCallbackSuccess {\n MerchantRequestID: string;\n CheckoutRequestID: string;\n ResultCode: 0;\n ResultDesc: string;\n CallbackMetadata: {\n Item: StkCallbackMetadataItem[];\n };\n}\n\n/** Inner callback for a FAILED / CANCELLED STK Push (ResultCode !== 0) */\nexport interface StkCallbackFailure {\n MerchantRequestID: string;\n CheckoutRequestID: string;\n /** e.g. 1032 = cancelled by user, 1037 = timeout */\n ResultCode: number;\n ResultDesc: string;\n CallbackMetadata?: never;\n}\n\nexport type StkCallbackInner = StkCallbackSuccess | StkCallbackFailure;\n\n/** Full wrapper Safaricom POSTs to your CallBackURL */\nexport interface StkPushCallback {\n Body: {\n stkCallback: StkCallbackInner;\n };\n}\n\n// ── Type guards & helpers ─────────────────────────────────────────────────────\n\n/**\n * Narrows StkCallbackInner to the success shape.\n *\n * @example\n * if (isStkCallbackSuccess(callback.Body.stkCallback)) {\n * const receipt = getCallbackValue(callback, \"MpesaReceiptNumber\");\n * }\n */\nexport function isStkCallbackSuccess(\n cb: StkCallbackInner\n): cb is StkCallbackSuccess {\n return cb.ResultCode === 0;\n}\n\n/**\n * Extracts a named value from a successful callback's metadata.\n * Returns undefined if the key is absent or the transaction failed.\n */\nexport function getCallbackValue(\n callback: StkPushCallback,\n name: StkCallbackMetadataItem[\"Name\"]\n): string | number | undefined {\n const inner = callback.Body.stkCallback;\n if (!isStkCallbackSuccess(inner)) return undefined;\n return inner.CallbackMetadata.Item.find((i) => i.Name === name)?.Value;\n}\n","/**\n * Tax Remittance — remits tax to Kenya Revenue Authority (KRA) via M-PESA.\n *\n * API: POST /mpesa/b2b/v1/remittax\n *\n * This is ASYNCHRONOUS. The synchronous response only acknowledges receipt.\n * Final results arrive via POST to your ResultURL.\n *\n * Prerequisites (from Daraja docs):\n * - Prior integration with KRA for tax declaration.\n * - A valid Payment Registration Number (PRN) from KRA.\n * - Initiator with the \"Tax Remittance ORG API\" role on the M-PESA org portal.\n * - SecurityCredential encrypted with the correct environment certificate.\n *\n * Fixed Daraja field values for this API:\n * CommandID: \"PayTaxToKRA\" (always)\n * SenderIdentifierType: \"4\" (always — Organisation ShortCode)\n * RecieverIdentifierType: \"4\" (always — Organisation ShortCode)\n * PartyB: \"572572\" (always — KRA's M-PESA shortcode)\n */\n\nimport { createError } from \"../../utils/errors\";\nimport { httpRequest } from \"../../utils/http\";\nimport type { TaxRemittanceRequest, TaxRemittanceResponse } from \"./types\";\n\n/** KRA's M-PESA shortcode — the only allowed PartyB for tax remittance */\nexport const KRA_SHORTCODE = \"572572\";\n\n/** The only CommandID accepted by the Tax Remittance API */\nexport const TAX_COMMAND_ID = \"PayTaxToKRA\";\n\n/**\n * Remits tax to Kenya Revenue Authority (KRA) via M-PESA.\n *\n * @param baseUrl - Daraja base URL (sandbox or production)\n * @param accessToken - Valid OAuth bearer token\n * @param securityCredential - RSA-encrypted initiator password (base64)\n * @param initiatorName - M-PESA org portal API operator username\n * @param request - Tax remittance parameters\n * @returns - Daraja acknowledgement response\n */\nexport async function remitTax(\n baseUrl: string,\n accessToken: string,\n securityCredential: string,\n initiatorName: string,\n request: TaxRemittanceRequest\n): Promise<TaxRemittanceResponse> {\n // ── Validation ──────────────────────────────────────────────────────────────\n\n const amount = Math.round(request.amount);\n if (!Number.isFinite(amount) || amount < 1) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: `amount must be a whole number ≥ 1 (got ${request.amount} which rounds to ${amount}).`,\n });\n }\n\n if (!request.partyA) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n \"partyA is required — your M-PESA business shortcode from which tax is deducted.\",\n });\n }\n\n if (!request.accountReference?.trim()) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n \"accountReference is required — the Payment Registration Number (PRN) issued by KRA.\",\n });\n }\n\n if (!request.resultUrl?.trim()) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n \"resultUrl is required — Safaricom POSTs the tax remittance result here.\",\n });\n }\n\n if (!request.queueTimeOutUrl?.trim()) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n \"queueTimeOutUrl is required — Safaricom calls this on request timeout.\",\n });\n }\n\n // ── Build payload matching Daraja spec exactly ──────────────────────────────\n //\n // Fixed values per Daraja Tax Remittance docs:\n // CommandID: \"PayTaxToKRA\" — only valid value\n // SenderIdentifierType: \"4\" — Organisation ShortCode (only allowed)\n // RecieverIdentifierType: \"4\" — Organisation ShortCode (only allowed)\n // PartyB: \"572572\" — KRA shortcode (only allowed)\n\n const payload = {\n Initiator: initiatorName,\n SecurityCredential: securityCredential,\n CommandID: TAX_COMMAND_ID,\n SenderIdentifierType: \"4\",\n RecieverIdentifierType: \"4\",\n Amount: String(amount),\n PartyA: String(request.partyA),\n PartyB: request.partyB ?? KRA_SHORTCODE,\n AccountReference: request.accountReference,\n Remarks: request.remarks ?? \"Tax Remittance\",\n QueueTimeOutURL: request.queueTimeOutUrl,\n ResultURL: request.resultUrl,\n };\n\n const { data } = await httpRequest<TaxRemittanceResponse>(\n `${baseUrl}/mpesa/b2b/v1/remittax`,\n {\n method: \"POST\",\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n }\n );\n\n return data;\n}\n","/**\n * Transaction Status Query implementation\n *\n * API: POST /mpesa/transactionstatus/v1/query\n *\n * This is ASYNCHRONOUS. The synchronous response only acknowledges receipt.\n * Final results arrive via POST to your ResultURL.\n *\n * Required M-PESA org portal role: \"Transaction Status query ORG API\"\n */\n\nimport { createError } from \"../../utils/errors\";\nimport { httpRequest } from \"../../utils/http\"; // ← httpRequest, NOT httpClient\nimport type {\n TransactionStatusRequest,\n TransactionStatusResponse,\n} from \"./types\";\n\nexport async function queryTransactionStatus(\n baseUrl: string,\n token: string,\n securityCredential: string,\n initiator: string,\n request: TransactionStatusRequest\n): Promise<TransactionStatusResponse> {\n // ── Validation ──────────────────────────────────────────────────────────────\n\n if (!request.transactionId) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: \"transactionId is required\",\n });\n }\n\n if (!request.partyA) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n \"partyA is required (your business shortcode, till number, or MSISDN)\",\n });\n }\n\n if (!request.identifierType) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n 'identifierType is required: \"1\" (MSISDN) | \"2\" (Till) | \"4\" (ShortCode)',\n });\n }\n\n if (!request.resultUrl) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message:\n \"resultUrl is required — Safaricom POSTs the transaction result here\",\n });\n }\n\n if (!request.queueTimeOutUrl) {\n throw createError({\n code: \"VALIDATION_ERROR\",\n message: \"queueTimeOutUrl is required — Safaricom calls this on timeout\",\n });\n }\n\n // ── Build payload matching Daraja spec exactly ──────────────────────────────\n\n const payload = {\n Initiator: initiator,\n SecurityCredential: securityCredential,\n CommandID: request.commandId ?? \"TransactionStatusQuery\",\n TransactionID: request.transactionId,\n PartyA: request.partyA,\n IdentifierType: request.identifierType,\n ResultURL: request.resultUrl,\n QueueTimeOutURL: request.queueTimeOutUrl,\n Remarks: request.remarks ?? \"Transaction Status Query\",\n Occasion: request.occasion ?? \"\",\n };\n\n const { data } = await httpRequest<TransactionStatusResponse>(\n `${baseUrl}/mpesa/transactionstatus/v1/query`,\n {\n method: \"POST\",\n headers: { Authorization: `Bearer ${token}` },\n body: payload,\n }\n );\n\n return data;\n}\n","/**\n * Core M-Pesa / Daraja API types\n */\n\nexport type Environment = \"sandbox\" | \"production\";\n\n/** Base URLs per Daraja environment */\nexport const DARAJA_BASE_URLS: Record<Environment, string> = {\n sandbox: \"https://sandbox.safaricom.co.ke\",\n production: \"https://api.safaricom.co.ke\",\n} as const;\n\nexport interface MpesaConfig {\n // ── Required for all APIs ─────────────────────────────────────────────────\n consumerKey: string;\n consumerSecret: string;\n environment: Environment;\n\n // ── Required for STK Push (M-Pesa Express) ────────────────────────────────\n /** Paybill / HO shortcode (5–7 digits). Required for STK Push & STK Query. */\n lipaNaMpesaShortCode?: string;\n /**\n * Passkey from Daraja portal.\n * Sandbox: visible in the simulator test data section.\n * Production: emailed after Go Live.\n */\n lipaNaMpesaPassKey?: string;\n\n // ── Required for Transaction Status / B2C / Reversals ────────────────────\n /** M-PESA org portal API operator username */\n initiatorName?: string;\n /** Plain-text password for the API operator (will be RSA-encrypted) */\n initiatorPassword?: string;\n\n // ── Certificate options (choose one) ─────────────────────────────────────\n /**\n * Path to the .cer file on disk.\n * Bun: read via `Bun.file(path).text()`\n * Node: read via `fs.promises.readFile(path, \"utf-8\")`\n */\n certificatePath?: string;\n /** PEM string contents of the certificate (alternative to certificatePath) */\n certificatePem?: string;\n /**\n * Pre-computed base64 security credential.\n * Use this if you encrypt outside the library (e.g. at startup).\n * Skips the RSA encryption step entirely.\n */\n securityCredential?: string;\n}\n","/**\n * M-Pesa Daraja API client\n *\n * Supports:\n * - STK Push (M-Pesa Express) — stkPush()\n * - STK Query — stkQuery()\n * - Transaction Status Query — transactionStatus()\n * - Dynamic QR Code — generateDynamicQR()\n * - C2B Register URL — registerC2BUrls()\n * - C2B Simulate (sandbox only) — simulateC2B()\n * - Tax Remittance (KRA) — remitTax()\n *\n * @example\n * const mpesa = new Mpesa({\n * consumerKey: process.env.MPESA_CONSUMER_KEY!,\n * consumerSecret: process.env.MPESA_CONSUMER_SECRET!,\n * environment: \"sandbox\",\n * lipaNaMpesaShortCode: \"174379\",\n * lipaNaMpesaPassKey: \"bfb279...\",\n * initiatorName: \"testapi\",\n * initiatorPassword: \"Safaricom123!\",\n * certificatePath: \"./SandboxCertificate.cer\",\n * });\n */\n\nimport { TokenManager } from \"../core/auth\";\nimport { encryptSecurityCredential } from \"../core/encryption\";\nimport { PesafyError } from \"../utils/errors\";\nimport {\n registerC2BUrls as _registerC2BUrls,\n simulateC2B as _simulateC2B,\n type C2BRegisterUrlRequest,\n type C2BRegisterUrlResponse,\n type C2BSimulateRequest,\n type C2BSimulateResponse,\n} from \"./c2b\";\nimport {\n generateDynamicQR as _generateDynamicQR,\n type DynamicQRRequest,\n type DynamicQRResponse,\n} from \"./dynamic-qr\";\nimport {\n processStkPush,\n queryStkPush,\n type StkPushRequest,\n type StkQueryRequest,\n} from \"./stk-push\";\nimport {\n remitTax as _remitTax,\n type TaxRemittanceRequest,\n type TaxRemittanceResponse,\n} from \"./tax-remittance\";\nimport {\n queryTransactionStatus,\n type TransactionStatusRequest,\n} from \"./transaction-status\";\nimport { DARAJA_BASE_URLS, type MpesaConfig } from \"./types\";\n\nexport class Mpesa {\n private readonly config: MpesaConfig;\n private readonly tokenManager: TokenManager;\n private readonly baseUrl: string;\n\n constructor(config: MpesaConfig) {\n if (!config.consumerKey || !config.consumerSecret) {\n throw new PesafyError({\n code: \"INVALID_CREDENTIALS\",\n message: \"consumerKey and consumerSecret are required\",\n });\n }\n\n this.config = config;\n this.baseUrl = DARAJA_BASE_URLS[config.environment];\n this.tokenManager = new TokenManager(\n config.consumerKey,\n config.consumerSecret,\n this.baseUrl\n );\n }\n\n // ── Internal helpers ────────────────────────────────────────────────────────\n\n private getToken(): Promise<string> {\n return this.tokenManager.getAccessToken();\n }\n\n private async buildSecurityCredential(): Promise<string> {\n if (this.config.securityCredential) return this.config.securityCredential;\n\n if (!this.config.initiatorPassword) {\n throw new PesafyError({\n code: \"INVALID_CREDENTIALS\",\n message:\n \"Provide securityCredential (pre-encrypted) \" +\n \"OR (initiatorPassword + certificatePath/certificatePem)\",\n });\n }\n\n let cert: string;\n if (this.config.certificatePem) {\n cert = this.config.certificatePem;\n } else if (this.config.certificatePath) {\n if (typeof Bun !== \"undefined\") {\n cert = await Bun.file(this.config.certificatePath).text();\n } else {\n const { readFile } = await import(\"node:fs/promises\");\n cert = await readFile(this.config.certificatePath, \"utf-8\");\n }\n } else {\n throw new PesafyError({\n code: \"INVALID_CREDENTIALS\",\n message:\n \"certificatePath or certificatePem required to encrypt the initiator password\",\n });\n }\n\n return encryptSecurityCredential(this.config.initiatorPassword, cert);\n }\n\n // ── STK Push ──────────────────────────────────────────────────────────────\n\n /**\n * M-Pesa Express — sends a payment prompt to the customer's phone.\n *\n * Requires: lipaNaMpesaShortCode + lipaNaMpesaPassKey in config.\n *\n * @example\n * const res = await mpesa.stkPush({\n * amount: 100,\n * phoneNumber: \"0712345678\",\n * callbackUrl: \"https://yourdomain.com/mpesa/callback\",\n * accountReference: \"INV-001\",\n * transactionDesc: \"Payment\",\n * });\n * console.log(res.CheckoutRequestID);\n */\n async stkPush(request: Omit<StkPushRequest, \"shortCode\" | \"passKey\">) {\n const shortCode = this.config.lipaNaMpesaShortCode ?? \"\";\n const passKey = this.config.lipaNaMpesaPassKey ?? \"\";\n\n if (!shortCode || !passKey) {\n throw new PesafyError({\n code: \"VALIDATION_ERROR\",\n message:\n \"lipaNaMpesaShortCode and lipaNaMpesaPassKey are required for STK Push\",\n });\n }\n\n const token = await this.getToken();\n return processStkPush(this.baseUrl, token, {\n ...request,\n shortCode,\n passKey,\n });\n }\n\n /**\n * STK Query — checks the status of a previous STK Push.\n *\n * @example\n * const status = await mpesa.stkQuery({\n * checkoutRequestId: \"ws_CO_1007202409152617172396192\",\n * });\n * if (status.ResultCode === 0) // payment confirmed\n */\n async stkQuery(request: Omit<StkQueryRequest, \"shortCode\" | \"passKey\">) {\n const shortCode = this.config.lipaNaMpesaShortCode ?? \"\";\n const passKey = this.config.lipaNaMpesaPassKey ?? \"\";\n\n if (!shortCode || !passKey) {\n throw new PesafyError({\n code: \"VALIDATION_ERROR\",\n message:\n \"lipaNaMpesaShortCode and lipaNaMpesaPassKey are required for STK Query\",\n });\n }\n\n const token = await this.getToken();\n return queryStkPush(this.baseUrl, token, {\n ...request,\n shortCode,\n passKey,\n });\n }\n\n /**\n * Transaction Status — queries the result of a completed M-Pesa transaction.\n *\n * Requires: initiatorName + (initiatorPassword + certificate) OR securityCredential.\n *\n * This is ASYNCHRONOUS. The synchronous response only confirms receipt.\n * Final details are POSTed to your resultUrl.\n *\n * @example\n * await mpesa.transactionStatus({\n * transactionId: \"OEI2AK4XXXX\",\n * partyA: \"174379\",\n * identifierType: \"4\",\n * resultUrl: \"https://yourdomain.com/mpesa/result\",\n * queueTimeOutUrl: \"https://yourdomain.com/mpesa/timeout\",\n * remarks: \"Check payment status\",\n * });\n */\n async transactionStatus(request: TransactionStatusRequest) {\n const initiator = this.config.initiatorName ?? \"\";\n if (!initiator) {\n throw new PesafyError({\n code: \"VALIDATION_ERROR\",\n message: \"initiatorName is required for Transaction Status\",\n });\n }\n\n const [token, securityCred] = await Promise.all([\n this.getToken(),\n this.buildSecurityCredential(),\n ]);\n\n return queryTransactionStatus(\n this.baseUrl,\n token,\n securityCred,\n initiator,\n request\n );\n }\n\n // ── Dynamic QR Code ───────────────────────────────────────────────────────\n\n /**\n * Dynamic QR — generates an M-PESA QR code for LNM merchant payments.\n *\n * Customers scan the code with My Safaricom App or M-PESA app to\n * capture the till/paybill number and amount, then authorize payment.\n *\n * @example\n * const res = await mpesa.generateDynamicQR({\n * merchantName: \"My Shop\",\n * refNo: \"INV-001\",\n * amount: 500,\n * trxCode: \"BG\", // Buy Goods (till number)\n * cpi: \"373132\",\n * size: 300,\n * });\n *\n * // res.QRCode is a base64-encoded PNG — render in an <img> tag:\n * // <img src={`data:image/png;base64,${res.QRCode}`} />\n */\n async generateDynamicQR(\n request: DynamicQRRequest\n ): Promise<DynamicQRResponse> {\n const token = await this.getToken();\n return _generateDynamicQR(this.baseUrl, token, request);\n }\n\n // ── C2B Register URL ──────────────────────────────────────────────────────\n\n /**\n * Registers your Confirmation and Validation URLs with M-PESA.\n *\n * Use v2 (default) for new integrations — callbacks include a masked MSISDN.\n * Use v1 only if you need SHA256-hashed MSISDN in callbacks.\n *\n * Sandbox: URLs can be re-registered freely (overwriting existing ones).\n * Production: One-time call. To change URLs, delete them via Daraja Self\n * Services → URL Management, then call this again.\n *\n * URL rules (Daraja docs — enforced by this library):\n * ✓ Must be publicly accessible\n * ✓ Production: HTTPS required\n * ✗ Must NOT contain: M-PESA, Safaricom, exe, exec, cmd, sql, query\n * ✗ Do NOT use ngrok, mockbin, requestbin in production\n * ✓ responseType must be exactly \"Completed\" or \"Cancelled\" (sentence case)\n *\n * External Validation (optional):\n * By default it is disabled. To enable, email apisupport@safaricom.co.ke.\n * When enabled, Safaricom calls your validationUrl before processing payment.\n * You must respond within ~8 seconds.\n *\n * @example\n * await mpesa.registerC2BUrls({\n * shortCode: \"600984\",\n * responseType: \"Completed\",\n * confirmationUrl: \"https://yourdomain.com/mpesa/c2b/confirmation\",\n * validationUrl: \"https://yourdomain.com/mpesa/c2b/validation\",\n * apiVersion: \"v2\", // default — recommended\n * });\n */\n async registerC2BUrls(\n request: C2BRegisterUrlRequest\n ): Promise<C2BRegisterUrlResponse> {\n const token = await this.getToken();\n return _registerC2BUrls(this.baseUrl, token, request);\n }\n\n // ── C2B Simulate (Sandbox ONLY) ───────────────────────────────────────────\n\n /**\n * Simulates a C2B customer payment. SANDBOX ONLY.\n *\n * In production, real customers initiate payments via M-PESA App, USSD,\n * or SIM Toolkit — simulation is not available.\n *\n * The API version used here should match the version used when registering URLs.\n *\n * @example\n * await mpesa.simulateC2B({\n * shortCode: 600984,\n * commandId: \"CustomerPayBillOnline\",\n * amount: 10,\n * msisdn: 254708374149, // Daraja test MSISDN\n * billRefNumber: \"INV-001\", // account ref for Paybill; null for Till\n * apiVersion: \"v2\", // must match registered URL version\n * });\n */\n async simulateC2B(request: C2BSimulateRequest): Promise<C2BSimulateResponse> {\n const token = await this.getToken();\n return _simulateC2B(this.baseUrl, token, request);\n }\n\n // ── Tax Remittance ────────────────────────────────────────────────────────\n\n /**\n * Tax Remittance — remits tax to Kenya Revenue Authority (KRA) via M-PESA.\n *\n * Requires:\n * - initiatorName in config\n * - initiatorPassword + certificate (or pre-computed securityCredential)\n *\n * This is ASYNCHRONOUS. The synchronous response only confirms receipt.\n * Final details are POSTed to your resultUrl.\n *\n * Prerequisites (from Daraja docs):\n * - Prior integration with KRA for tax declaration.\n * - A Payment Registration Number (PRN) from KRA.\n * - Initiator with \"Tax Remittance ORG API\" role on M-PESA org portal.\n *\n * Fixed values (set automatically — do NOT override unless Safaricom changes them):\n * CommandID: \"PayTaxToKRA\"\n * SenderIdentifierType: \"4\"\n * RecieverIdentifierType: \"4\"\n * PartyB: \"572572\" (KRA shortcode)\n *\n * @example\n * await mpesa.remitTax({\n * amount: 5000,\n * partyA: \"888880\",\n * accountReference: \"PRN1234XN\", // PRN from KRA\n * resultUrl: \"https://yourdomain.com/mpesa/tax/result\",\n * queueTimeOutUrl: \"https://yourdomain.com/mpesa/tax/timeout\",\n * remarks: \"Monthly PAYE remittance\",\n * });\n */\n async remitTax(\n request: TaxRemittanceRequest\n ): Promise<TaxRemittanceResponse> {\n const initiator = this.config.initiatorName ?? \"\";\n if (!initiator) {\n throw new PesafyError({\n code: \"VALIDATION_ERROR\",\n message: \"initiatorName is required for Tax Remittance\",\n });\n }\n\n const [token, securityCred] = await Promise.all([\n this.getToken(),\n this.buildSecurityCredential(),\n ]);\n\n return _remitTax(this.baseUrl, token, securityCred, initiator, request);\n }\n\n /** Force the cached OAuth token to be refreshed on the next API call */\n clearTokenCache(): void {\n this.tokenManager.clearCache();\n }\n}\n","/**\n * Exponential backoff retry for webhook at-least-once delivery.\n *\n * Daraja is asynchronous — if your callback endpoint is down, the API\n * Gateway logs a 503 and discards the result. Use this utility to\n * retry your own internal processing after receiving a webhook.\n */\n\nexport interface RetryOptions {\n /** Maximum number of attempts (default: Infinity) */\n maxRetries?: number;\n /** Initial delay in ms (default: 1000 = 1 second) */\n initialDelay?: number;\n /** Maximum delay cap in ms (default: 3_600_000 = 1 hour) */\n maxDelay?: number;\n /** Multiplier per retry (default: 2 — doubles each time) */\n backoffMultiplier?: number;\n /** Maximum total duration in ms (default: 30 days) */\n maxRetryDuration?: number;\n}\n\nconst DEFAULT_OPTIONS: Required<RetryOptions> = {\n maxRetries: Infinity,\n initialDelay: 1_000,\n maxDelay: 3_600_000,\n backoffMultiplier: 2,\n maxRetryDuration: 30 * 24 * 60 * 60 * 1_000, // 30 days\n};\n\nexport interface RetryResult<T> {\n success: boolean;\n data?: T;\n attempts: number;\n error?: Error;\n}\n\n/**\n * Retries `fn` with exponential backoff until it resolves, or limits are hit.\n *\n * @example\n * const result = await retryWithBackoff(\n * () => sendToDatabase(webhookData),\n * { maxRetries: 5, initialDelay: 500 }\n * );\n */\nexport async function retryWithBackoff<T>(\n fn: () => Promise<T>,\n options: RetryOptions = {}\n): Promise<RetryResult<T>> {\n const opts = { ...DEFAULT_OPTIONS, ...options };\n let delay = opts.initialDelay;\n let attempts = 0;\n const startTime = Date.now();\n\n while (attempts < opts.maxRetries) {\n attempts++;\n\n if (Date.now() - startTime > opts.maxRetryDuration) {\n return {\n success: false,\n attempts,\n error: new Error(\"Max retry duration exceeded\"),\n };\n }\n\n try {\n const data = await fn();\n return { success: true, data, attempts };\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n\n // Don't retry client errors (4xx) — they won't self-heal\n if (err.message.includes(\"4\")) {\n return { success: false, attempts, error: err };\n }\n\n if (attempts < opts.maxRetries) {\n await new Promise((resolve) => setTimeout(resolve, delay));\n delay = Math.min(delay * opts.backoffMultiplier, opts.maxDelay);\n }\n }\n }\n\n return {\n success: false,\n attempts,\n error: new Error(\"Max retries exceeded\"),\n };\n}\n","/**\n * Webhook verification utilities\n *\n * Daraja does NOT use HMAC webhook signatures like Stripe.\n * Instead, verify that callbacks come from whitelisted Safaricom IPs.\n *\n * Official Safaricom IP whitelist (from Getting Started docs):\n * 196.201.214.200\n * 196.201.214.206\n * 196.201.213.114\n * 196.201.214.207\n * 196.201.214.208\n * 196.201.213.44\n * 196.201.212.127\n * 196.201.212.138\n * 196.201.212.129\n * 196.201.212.136\n * 196.201.212.74\n * 196.201.212.69\n */\n\nimport type { StkPushWebhook } from \"./types\";\n\n/** Official Safaricom API Gateway IP addresses */\nexport const SAFARICOM_IPS: readonly string[] = [\n \"196.201.214.200\",\n \"196.201.214.206\",\n \"196.201.213.114\",\n \"196.201.214.207\",\n \"196.201.214.208\",\n \"196.201.213.44\",\n \"196.201.212.127\",\n \"196.201.212.138\",\n \"196.201.212.129\",\n \"196.201.212.136\",\n \"196.201.212.74\",\n \"196.201.212.69\",\n] as const;\n\n/**\n * Returns true if requestIP is in the allowed list.\n * Defaults to the official Safaricom IP whitelist.\n */\nexport function verifyWebhookIP(\n requestIP: string,\n allowedIPs: readonly string[] = SAFARICOM_IPS\n): boolean {\n return allowedIPs.includes(requestIP);\n}\n\n/**\n * Parses and validates an STK Push webhook body.\n * Returns the typed payload or null if it doesn't match the expected shape.\n */\nexport function parseStkPushWebhook(body: unknown): StkPushWebhook | null {\n try {\n const parsed = body as StkPushWebhook;\n if (parsed?.Body?.stkCallback) return parsed;\n return null;\n } catch {\n return null;\n }\n}\n","/**\n * High-level webhook event handler\n */\n\nimport { parseStkPushWebhook, verifyWebhookIP } from \"./signature-verifier\";\nimport type { StkPushWebhook, WebhookEventType } from \"./types\";\n\nexport interface WebhookHandlerOptions {\n /** IP address of the incoming request (from req.ip or x-forwarded-for) */\n requestIP?: string;\n /** Override the default Safaricom IP whitelist */\n allowedIPs?: string[];\n /** Skip IP verification — ONLY for local development/testing */\n skipIPCheck?: boolean;\n}\n\nexport interface WebhookHandlerResult<T = unknown> {\n success: boolean;\n eventType: WebhookEventType | null;\n data: T | null;\n error?: string;\n}\n\n/**\n * Parses and validates an inbound Daraja webhook payload.\n *\n * @example\n * // Express route\n * app.post(\"/mpesa/callback\", (req, res) => {\n * const result = handleWebhook(req.body, { requestIP: req.ip });\n * if (!result.success) return res.status(400).json({ error: result.error });\n * // process result.data (StkPushWebhook)\n * res.json({ ResultCode: 0, ResultDesc: \"Accepted\" });\n * });\n */\nexport function handleWebhook(\n body: unknown,\n options: WebhookHandlerOptions = {}\n): WebhookHandlerResult {\n // ── IP verification ─────────────────────────────────────────────────────────\n if (!options.skipIPCheck && options.requestIP) {\n if (!verifyWebhookIP(options.requestIP, options.allowedIPs)) {\n return {\n success: false,\n eventType: null,\n data: null,\n error: `IP address ${options.requestIP} is not in the Safaricom whitelist`,\n };\n }\n }\n\n // ── Parse STK Push callback ─────────────────────────────────────────────────\n const stkPush = parseStkPushWebhook(body);\n if (stkPush) {\n return {\n success: true,\n eventType: \"stk_push\",\n data: stkPush,\n };\n }\n\n return {\n success: false,\n eventType: null,\n data: null,\n error: \"Unknown or malformed webhook payload\",\n };\n}\n\n// ── Convenience extractors ────────────────────────────────────────────────────\n\n/** Extracts the M-Pesa receipt number from a successful STK Push callback */\nexport function extractTransactionId(webhook: StkPushWebhook): string | null {\n const items = webhook.Body?.stkCallback?.CallbackMetadata?.Item;\n const item = items?.find((i) => i.Name === \"MpesaReceiptNumber\");\n return item ? String(item.Value) : null;\n}\n\n/** Extracts the transaction amount from a successful STK Push callback */\nexport function extractAmount(webhook: StkPushWebhook): number | null {\n const items = webhook.Body?.stkCallback?.CallbackMetadata?.Item;\n const item = items?.find((i) => i.Name === \"Amount\");\n return item ? Number(item.Value) : null;\n}\n\n/** Extracts the phone number from a successful STK Push callback */\nexport function extractPhoneNumber(webhook: StkPushWebhook): string | null {\n const items = webhook.Body?.stkCallback?.CallbackMetadata?.Item;\n const item = items?.find((i) => i.Name === \"PhoneNumber\");\n return item ? String(item.Value) : null;\n}\n\n/** Returns true if the STK Push callback represents a successful transaction */\nexport function isSuccessfulCallback(webhook: StkPushWebhook): boolean {\n return webhook.Body?.stkCallback?.ResultCode === 0;\n}\n"]}
|