pesafy 0.0.2 → 0.2.0
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/CHANGELOG.md +39 -0
- package/README.md +62 -40
- package/dist/adapters/express.d.ts +5 -81
- package/dist/adapters/express.js +127 -1
- package/dist/adapters/express.js.map +1 -0
- package/dist/adapters/fastify.d.ts +10 -74
- package/dist/adapters/fastify.js +85 -1
- package/dist/adapters/fastify.js.map +1 -0
- package/dist/adapters/hono.d.ts +4 -88
- package/dist/adapters/hono.js +105 -1
- package/dist/adapters/hono.js.map +1 -0
- package/dist/adapters/nextjs.d.ts +28 -176
- package/dist/adapters/nextjs.js +166 -1
- package/dist/adapters/nextjs.js.map +1 -0
- package/dist/cli.mjs +2387 -112
- package/dist/cli.mjs.map +1 -0
- package/dist/errors.mjs +6 -1
- package/dist/errors.mjs.map +1 -0
- package/dist/index.d.ts +4864 -136
- package/dist/index.js +4197 -1
- package/dist/index.js.map +1 -0
- package/dist/phone.mjs +5 -1
- package/dist/phone.mjs.map +1 -0
- package/dist/rolldown-runtime.mjs +18 -0
- package/dist/route-definitions.js +3292 -0
- package/dist/route-definitions.js.map +1 -0
- package/dist/types.d.ts +924 -5
- package/package.json +6 -1
- package/dist/chunk.js +0 -1
- package/dist/encryption.mjs +0 -22
- package/dist/utils.mjs +0 -32
- package/dist/webhook-guard.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-definitions.js","names":["KNOWN_RESULT_CODES","IDENTIFIER_TYPE","IDENTIFIER_TYPE","formatPhoneNumber","_generateDynamicQR","_registerC2BUrls","_simulateC2B","_remitTax","_initiateB2BExpressCheckout","_initiateB2BBuyGoods","_initiateB2BPayBill","_initiateB2CPayment","_initiateB2CDisbursement","_billManagerOptIn","_updateOptIn","_sendSingleInvoice","_sendBulkInvoices","_cancelInvoice","_cancelBulkInvoices","_reconcilePayment"],"sources":["../src/utils/errors/index.ts","../src/utils/http/index.ts","../src/core/auth/types.ts","../src/core/auth/token-manager.ts","../src/core/encryption/security-credentials.ts","../src/core/idempotency/generate-key.ts","../src/core/idempotency/store.ts","../src/core/idempotency/manager.ts","../src/types/branded.ts","../src/core/validation/zod-error.ts","../src/schemas/common.ts","../src/schemas/async-apis.ts","../src/mpesa/account-balance/query.ts","../src/mpesa/account-balance/types.ts","../src/schemas/b2b.ts","../src/mpesa/b2b-express-checkout/initiate.ts","../src/mpesa/b2b-express-checkout/types.ts","../src/mpesa/b2b-express-checkout/webhooks.ts","../src/mpesa/b2b-buy-goods/payment.ts","../src/mpesa/b2b-pay-bill/payment.ts","../src/schemas/b2c.ts","../src/mpesa/b2c/payment.ts","../src/mpesa/b2c/webhooks.ts","../src/mpesa/b2c-disbursement/payment.ts","../src/mpesa/b2c-disbursement/webhooks.ts","../src/schemas/bill-manager.ts","../src/mpesa/bill-manager/invoice.ts","../src/schemas/c2b.ts","../src/mpesa/c2b/register-url.ts","../src/mpesa/c2b/simulate.ts","../src/mpesa/c2b/webhooks.ts","../src/mpesa/dynamic-qr/generate.ts","../src/mpesa/reversal/types.ts","../src/mpesa/reversal/request.ts","../src/schemas/stk-push.ts","../src/mpesa/stk-push/types.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/tax-remittance/remit-tax.ts","../src/mpesa/tax-remittance/webhooks.ts","../src/mpesa/transaction-status/query.ts","../src/mpesa/transaction-status/types.ts","../src/mpesa/transaction-status/webhooks.ts","../src/mpesa/types.ts","../src/mpesa/webhooks/hmac-verifier.ts","../src/mpesa/webhooks/signature-verifier.ts","../src/mpesa/webhooks/webhook-handler.ts","../src/mpesa/index.ts","../src/adapters/webhook-guard.ts","../src/adapters/shared/helpers.ts","../src/adapters/shared/handlers.ts","../src/adapters/shared/route-definitions.ts"],"sourcesContent":["// 📁 PATH: src/utils/errors/index.ts\n\n/**\n * Pesafy error types — single source of truth.\n *\n * All codes map to a specific failure category so callers can handle\n * them without string-matching on the message.\n */\n\n// ── Error codes ───────────────────────────────────────────────────────────────\n\nexport const ERROR_CODES = [\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 'RATE_LIMITED',\n 'INSUFFICIENT_FUNDS',\n 'TRANSACTION_FAILED',\n 'DUPLICATE_REQUEST',\n 'IDEMPOTENCY_ERROR',\n] as const\n\nexport type ErrorCode = (typeof ERROR_CODES)[number]\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 /** Whether this error is retryable */\n retryable?: boolean\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 readonly retryable: boolean\n\n constructor(options: PesafyErrorOptions) {\n super(options.message)\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 // 429 / 503 / network errors are retryable\n this.retryable =\n options.retryable ??\n (options.code === 'NETWORK_ERROR' ||\n options.code === 'TIMEOUT' ||\n options.code === 'RATE_LIMITED' ||\n options.code === 'REQUEST_FAILED')\n\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, PesafyError)\n }\n }\n\n /** Returns true if this is a validation error (user bug — do not retry) */\n get isValidation(): boolean {\n return this.code === 'VALIDATION_ERROR'\n }\n\n /** Returns true if this is an auth error */\n get isAuth(): boolean {\n return this.code === 'AUTH_FAILED' || this.code === 'INVALID_CREDENTIALS'\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 retryable: this.retryable,\n }\n }\n}\n\n/** Convenience factory */\nexport function createError(options: PesafyErrorOptions): PesafyError {\n return new PesafyError(options)\n}\n\n/** Type guard */\nexport function isPesafyError(err: unknown): err is PesafyError {\n return err instanceof PesafyError\n}\n","// 📁 PATH: src/utils/http/index.ts\n\n/**\n * HTTP client for Daraja API calls.\n *\n * Single, canonical implementation — retries transient errors with\n * exponential back-off + ±25 % jitter. Never retries 4xx errors.\n */\n\nimport type { IdempotencyManager } from '../../core/idempotency'\nimport { PesafyError } from '../errors'\n\nexport type { DarajaHttpOptions } from './types'\nimport type { DarajaHttpOptions } from './types'\n\n/** Merge explicit Daraja HTTP options into an httpRequest options object. */\nexport function withDarajaHttp(\n options: HttpRequestOptions,\n http?: DarajaHttpOptions,\n): HttpRequestOptions {\n if (!http?.idempotency) return options\n return { ...options, idempotency: http.idempotency }\n}\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface HttpRequestOptions {\n method: 'GET' | 'POST'\n headers?: Record<string, string>\n /**\n * Request body — JSON-serialised and sent as application/json.\n * Omit or pass `undefined` to send no body.\n */\n body?: unknown\n /**\n * Retry attempts on transient errors (503, 429, 502, 504, network).\n * Default: 4. Set 0 to disable.\n */\n retries?: number\n /**\n * Base delay in ms before first retry. Doubles each attempt + ±25 % jitter.\n * Default: 2000 ms.\n */\n retryDelay?: number\n /**\n * Per-attempt timeout in ms. Does NOT cover total retry duration.\n * Default: 30 000 ms.\n */\n timeout?: number\n /**\n * Optional idempotency key — passed as Idempotency-Key header.\n * Daraja ignores it but useful for your own gateway layer.\n */\n idempotencyKey?: string\n /**\n * Idempotency manager — auto-injects keys and detects duplicates on POST.\n * Pass from `Mpesa` via `DarajaHttpOptions`; do not use module-global state.\n */\n idempotency?: IdempotencyManager\n}\n\nexport interface HttpResponse<T> {\n data: T\n status: number\n headers: Record<string, string>\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nconst RETRYABLE_STATUSES = new Set([429, 500, 502, 503, 504])\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms))\n}\n\nfunction withJitter(base: number): number {\n const spread = base * 0.25\n return base + (Math.random() * spread * 2 - spread)\n}\n\n/** Origin + path only (no query) for safe retry logs. */\nfunction logSafeUrl(url: string): string {\n try {\n const u = new URL(url)\n return `${u.origin}${u.pathname}`\n } catch {\n const noQuery = url.split('?')[0]\n return noQuery ?? url\n }\n}\n\n// ── httpRequest ───────────────────────────────────────────────────────────────\n\n/**\n * Sends an HTTP request to Daraja and returns parsed JSON.\n * Automatically retries transient failures with exponential back-off.\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 ?? 2_000\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 const manager = options.idempotency\n let idempotencyKey = options.idempotencyKey\n\n if (options.method === 'POST' && manager?.enabled) {\n idempotencyKey = manager.reserve(idempotencyKey)\n const headerName = manager.headerName\n headers[headerName] = idempotencyKey\n } else if (idempotencyKey) {\n headers['Idempotency-Key'] = idempotencyKey\n }\n\n const init: RequestInit = {\n method: options.method,\n headers,\n ...(options.body !== undefined ? { body: JSON.stringify(options.body) } : {}),\n }\n\n let lastError: PesafyError | null = null\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n if (attempt > 0) {\n const delay = withJitter(baseDelay * Math.pow(2, attempt - 1))\n console.warn(\n `[pesafy] Retry ${attempt}/${maxRetries} → ${options.method} ${logSafeUrl(url)} in ${Math.round(delay)} ms`,\n )\n await sleep(delay)\n }\n\n const controller = new AbortController()\n const tid = setTimeout(() => controller.abort(), timeout)\n let response: Response\n\n try {\n response = await fetch(url, { ...init, signal: controller.signal })\n } catch (err) {\n clearTimeout(tid)\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 retryable: true,\n })\n } else {\n lastError = new PesafyError({\n code: 'NETWORK_ERROR',\n message: `Network error: ${err instanceof Error ? err.message : String(err)}`,\n cause: err,\n retryable: true,\n })\n }\n if (attempt < maxRetries) continue\n if (idempotencyKey && manager?.enabled) manager.release(idempotencyKey)\n throw lastError\n } finally {\n clearTimeout(tid)\n }\n\n // ── Parse body ───────────────────────────────────────────────────────────\n let rawText = ''\n let data: unknown = null\n const ct = response.headers.get('content-type') ?? ''\n\n try {\n rawText = await response.text()\n if (rawText) {\n data = ct.includes('application/json') ? JSON.parse(rawText) : rawText\n }\n } catch {\n data = rawText || null\n }\n\n const responseHeaders: Record<string, string> = {}\n response.headers.forEach((v, k) => {\n responseHeaders[k] = v\n })\n\n if (response.ok) {\n if (idempotencyKey && manager?.enabled) {\n manager.complete(idempotencyKey)\n }\n return { data: data as T, status: response.status, headers: responseHeaders }\n }\n\n // ── Error path ───────────────────────────────────────────────────────────\n const isTransient = RETRYABLE_STATUSES.has(response.status)\n const daraja =\n typeof data === 'object' && data !== null ? (data as Record<string, unknown>) : {}\n\n const message =\n (daraja['errorMessage'] as string | undefined) ??\n (daraja['ResponseDescription'] as string | undefined) ??\n (daraja['resultDesc'] as string | undefined) ??\n rawText ??\n `HTTP ${response.status}`\n\n // exactOptionalPropertyTypes: only include requestId when it is actually\n // a string — never pass `undefined` for an optional property at a call-site.\n lastError = new PesafyError({\n code: isTransient ? 'REQUEST_FAILED' : 'API_ERROR',\n message,\n statusCode: response.status,\n response: data,\n retryable: isTransient,\n ...(typeof daraja['requestId'] === 'string' ? { requestId: daraja['requestId'] } : {}),\n })\n\n if (isTransient && attempt < maxRetries) continue\n if (idempotencyKey && manager?.enabled) {\n manager.release(idempotencyKey)\n }\n throw lastError\n }\n\n if (idempotencyKey && manager?.enabled) {\n manager.release(idempotencyKey)\n }\n throw lastError!\n}\n","// src/core/auth/types.ts\n\nexport interface TokenResponse {\n /** The OAuth bearer token */\n access_token: string\n /** Seconds until expiry — Daraja returns 3599 */\n expires_in: number\n}\n\nexport interface TokenCacheEntry {\n token: string\n /** Unix timestamp (seconds) after which token is considered expired */\n expiresAt: number\n}\n\n/**\n * Daraja Authorization API error codes\n * Documented at: https://developer.safaricom.co.ke/APIs/Authorization\n *\n * These are returned in the `errorCode` field of a 400 error response body\n * when the OAuth token request is malformed.\n */\nexport const AUTH_ERROR_CODES = {\n /**\n * Invalid authentication type passed.\n * Mitigation: Use Basic authentication (Authorization: Basic <base64>).\n */\n INVALID_AUTH_TYPE: '400.008.01',\n /**\n * Invalid grant type passed.\n * Mitigation: Set grant_type=client_credentials in the request params.\n */\n INVALID_GRANT_TYPE: '400.008.02',\n} as const\n\nexport type AuthErrorCode = (typeof AUTH_ERROR_CODES)[keyof typeof AUTH_ERROR_CODES]\n\n/** Error response body returned by the Daraja Authorization API on failure */\nexport interface AuthErrorResponse {\n /** Daraja-specific error code, e.g. \"400.008.01\" */\n errorCode: string\n /** Human-readable error description */\n errorMessage: string\n}\n","// src/core/auth/token-manager.ts\n\nimport { PesafyError } from '../../utils/errors'\nimport { httpRequest } from '../../utils/http'\nimport { AUTH_ERROR_CODES } from './types'\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 * Maps Daraja-specific auth error codes (400.008.01 / 400.008.02) to\n * descriptive PesafyError messages so callers get actionable feedback.\n *\n * Always throws — the `never` return type signals this to TypeScript.\n */\n private mapAuthError(error: unknown): never {\n if (error instanceof PesafyError) {\n // If we already enriched this error, re-throw it as-is.\n if (error.code === 'AUTH_FAILED') throw error\n\n const raw = error.response as Record<string, unknown> | null | undefined\n if (raw && typeof raw === 'object') {\n // Daraja sends the code in `errorCode`; guard against both spellings.\n const errorCode = (raw['errorCode'] ?? raw['error_code']) as string | undefined\n\n if (errorCode === AUTH_ERROR_CODES.INVALID_AUTH_TYPE) {\n throw new PesafyError({\n code: 'AUTH_FAILED',\n message:\n 'Invalid authentication type (400.008.01). ' +\n 'Use Basic authentication: Authorization: Basic <Base64(consumerKey:consumerSecret)>.',\n ...(error.statusCode !== undefined && { statusCode: error.statusCode }),\n response: error.response,\n })\n }\n\n if (errorCode === AUTH_ERROR_CODES.INVALID_GRANT_TYPE) {\n throw new PesafyError({\n code: 'AUTH_FAILED',\n message:\n 'Invalid grant type (400.008.02). ' +\n 'Set grant_type=client_credentials in the request query parameters.',\n ...(error.statusCode !== undefined && { statusCode: error.statusCode }),\n response: error.response,\n })\n }\n }\n\n throw error\n }\n\n // Non-PesafyError (e.g. raw network exception) — re-throw unchanged.\n throw error\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 * Daraja endpoint: GET /oauth/v1/generate?grant_type=client_credentials\n * Auth: Basic Base64(consumerKey:consumerSecret)\n * Token lifetime: 3599 seconds (Daraja docs)\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 try {\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. ' +\n 'Verify your consumer key and consumer secret.',\n response: response.data,\n })\n }\n\n this.cachedToken = access_token\n // expires_in is 3599 per Daraja docs; fall back to 3600 if absent.\n this.tokenExpiresAt = now + (expires_in ?? 3600)\n\n return this.cachedToken\n } catch (error) {\n // mapAuthError always throws — satisfies TypeScript's control-flow analysis.\n return this.mapAuthError(error)\n }\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","// src/core/encryption/security-credentials.ts\n\nimport { constants, publicEncrypt } from 'node:crypto'\nimport { PesafyError } from '../../utils/errors'\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 * Generate idempotency keys for Daraja mutating requests.\n */\n\n/** UUID v4 idempotency key, optionally prefixed for debugging. */\nexport function generateIdempotencyKey(prefix?: string): string {\n const id = crypto.randomUUID()\n return prefix ? `${prefix}-${id}` : id\n}\n\n/** Daraja OriginatorConversationID — unique per async API request. */\nexport function generateOriginatorConversationId(): string {\n return generateIdempotencyKey('pesafy')\n}\n\n/** B2B Express RequestRefID — unique per checkout request. */\nexport function generateRequestRefId(): string {\n return crypto.randomUUID()\n}\n","/**\n * In-memory idempotency store (per-process).\n * For multi-instance deployments, supply a custom IdempotencyStore (e.g. Redis).\n */\n\nexport interface IdempotencyEntry {\n key: string\n createdAt: number\n completedAt?: number\n}\n\nexport interface IdempotencyStore {\n get(key: string): IdempotencyEntry | undefined\n set(key: string, entry: IdempotencyEntry): void\n delete(key: string): void\n}\n\nexport class InMemoryIdempotencyStore implements IdempotencyStore {\n private readonly entries = new Map<string, IdempotencyEntry>()\n\n get(key: string): IdempotencyEntry | undefined {\n return this.entries.get(key)\n }\n\n set(key: string, entry: IdempotencyEntry): void {\n this.entries.set(key, entry)\n }\n\n delete(key: string): void {\n this.entries.delete(key)\n }\n\n /** Remove entries older than ttlMs. */\n prune(ttlMs: number): void {\n const cutoff = Date.now() - ttlMs\n for (const [key, entry] of this.entries) {\n if (entry.createdAt < cutoff) this.entries.delete(key)\n }\n }\n}\n","import { PesafyError } from '../../utils/errors'\nimport { generateIdempotencyKey } from './generate-key'\nimport { InMemoryIdempotencyStore, type IdempotencyStore } from './store'\n\nexport interface IdempotencyConfig {\n /** Default true for mutating POSTs */\n enabled?: boolean\n /** HTTP header name. Default: Idempotency-Key */\n headerName?: string\n /** Pluggable store; default in-memory */\n store?: IdempotencyStore\n /** Dedup window in ms. Default: 24h */\n ttlMs?: number\n /** Custom key generator */\n generateKey?: () => string\n}\n\nconst DEFAULT_TTL_MS = 24 * 60 * 60 * 1000\n\nexport class IdempotencyManager {\n readonly enabled: boolean\n readonly headerName: string\n readonly ttlMs: number\n private readonly store: IdempotencyStore\n private readonly generateKey: () => string\n\n constructor(config: IdempotencyConfig = {}) {\n this.enabled = config.enabled !== false\n this.headerName = config.headerName ?? 'Idempotency-Key'\n this.ttlMs = config.ttlMs ?? DEFAULT_TTL_MS\n this.store = config.store ?? new InMemoryIdempotencyStore()\n this.generateKey = config.generateKey ?? generateIdempotencyKey\n }\n\n /**\n * Reserve an idempotency key before the HTTP call.\n * @throws PesafyError with IDEMPOTENCY_ERROR if duplicate in-flight/completed within TTL.\n */\n reserve(key?: string): string {\n if (!this.enabled) return key ?? this.generateKey()\n\n this.pruneExpired()\n\n const resolved = key ?? this.generateKey()\n const existing = this.store.get(resolved)\n\n if (existing) {\n const age = Date.now() - existing.createdAt\n if (age < this.ttlMs) {\n throw new PesafyError({\n code: 'IDEMPOTENCY_ERROR',\n message: `Duplicate request detected for idempotency key \"${resolved}\".`,\n })\n }\n this.store.delete(resolved)\n }\n\n this.store.set(resolved, { key: resolved, createdAt: Date.now() })\n return resolved\n }\n\n /** Mark key as successfully completed. */\n complete(key: string): void {\n if (!this.enabled) return\n const entry = this.store.get(key)\n if (entry) {\n this.store.set(key, { ...entry, completedAt: Date.now() })\n }\n }\n\n /** Release reservation on failure so callers can retry with same key. */\n release(key: string): void {\n if (!this.enabled) return\n this.store.delete(key)\n }\n\n private pruneExpired(): void {\n if (this.store instanceof InMemoryIdempotencyStore) {\n this.store.prune(this.ttlMs)\n }\n }\n}\n","// 📁 PATH: src/types/branded.ts\n\ndeclare const __brand: unique symbol\ntype Brand<T, B extends string> = T & { readonly [__brand]: B }\n\n// ── Money ─────────────────────────────────────────────────────────────────────\n\n/**\n * A whole-number KES amount (Kenyan Shillings).\n * M-PESA only supports whole numbers — fractional shillings are rejected.\n */\nexport type KesAmount = Brand<number, 'KesAmount'>\n\n/**\n * Creates a validated KesAmount.\n * @throws {TypeError} if amount is not a whole number ≥ 1\n */\nexport function toKesAmount(value: number): KesAmount {\n const rounded = Math.round(value)\n if (!Number.isFinite(rounded) || rounded < 1) {\n throw new TypeError(`KesAmount must be a whole number ≥ 1, got ${value}`)\n }\n return rounded as KesAmount\n}\n\n// ── Phone numbers ─────────────────────────────────────────────────────────────\n\n/**\n * A validated Kenyan MSISDN in Daraja format: 254XXXXXXXXX (12 digits).\n */\nexport type MsisdnKE = Brand<string, 'MsisdnKE'>\n\n/**\n * Creates a validated MsisdnKE from any common Kenyan phone format.\n */\nexport function toMsisdn(phone: string): MsisdnKE {\n const digits = phone.replace(/\\D/g, '')\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 normalised = `254${digits}`\n } else {\n throw new TypeError(\n `Cannot normalise \"${phone}\" to 254XXXXXXXXX. Use 07XX…, 2547XX…, or +2547XX….`,\n )\n }\n\n if (normalised.length !== 12) {\n throw new TypeError(`Phone \"${phone}\" normalised to \"${normalised}\" — expected 12 digits.`)\n }\n return normalised as MsisdnKE\n}\n\n// ── Shortcodes ────────────────────────────────────────────────────────────────\n\n/** M-PESA Paybill shortcode */\nexport type PaybillCode = Brand<string, 'PaybillCode'>\n\n/** M-PESA Till/Buy-Goods shortcode */\nexport type TillCode = Brand<string, 'TillCode'>\n\n/** Any M-PESA shortcode (paybill, till, or B2C) */\nexport type ShortCode = PaybillCode | TillCode | Brand<string, 'ShortCode'>\n\nexport function toPaybill(code: string | number): PaybillCode {\n return String(code) as PaybillCode\n}\nexport function toTill(code: string | number): TillCode {\n return String(code) as TillCode\n}\nexport function toShortCode(code: string | number): ShortCode {\n return String(code) as ShortCode\n}\n\n// ── Transaction IDs ───────────────────────────────────────────────────────────\n\n/** M-PESA receipt number, e.g. \"OEI2AK4XXXX\" */\nexport type MpesaReceiptNumber = Brand<string, 'MpesaReceiptNumber'>\n\n/** Daraja ConversationID */\nexport type ConversationID = Brand<string, 'ConversationID'>\n\n/** Daraja OriginatorConversationID */\nexport type OriginatorConversationID = Brand<string, 'OriginatorConversationID'>\n\n/** Daraja CheckoutRequestID (STK Push) */\nexport type CheckoutRequestID = Brand<string, 'CheckoutRequestID'>\n\n// ── Result type ───────────────────────────────────────────────────────────────\n\n/**\n * A discriminated union result — either Ok<T> or Err<E>.\n * Prefer this over throwing in application-level code.\n *\n * @example\n * const result = await mpesa.stkPushSafe({ ... });\n * if (result.ok) {\n * console.log(result.data.CheckoutRequestID);\n * } else {\n * console.error(result.error.code, result.error.message);\n * }\n */\nexport type Result<T, E = import('../utils/errors').PesafyError> =\n | { readonly ok: true; readonly data: T }\n | { readonly ok: false; readonly error: E }\n\nexport function ok<T>(data: T): Result<T, never> {\n return { ok: true, data }\n}\n\nexport function err<E>(error: E): Result<never, E> {\n return { ok: false, error }\n}\n\n// ── Utility types ─────────────────────────────────────────────────────────────\n\n/** Makes all properties deeply readonly */\nexport type DeepReadonly<T> = {\n readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]\n}\n\n/** Strict pick — only allows known keys */\nexport type StrictPick<T, K extends keyof T> = Pick<T, K>\n\n/** Non-empty string */\nexport type NonEmptyString = Brand<string, 'NonEmptyString'>\nexport function toNonEmpty(s: string): NonEmptyString {\n if (!s.trim()) throw new TypeError('String must not be empty')\n return s as NonEmptyString\n}\n","import type { ZodError, ZodSchema } from 'zod'\nimport { PesafyError } from '../../utils/errors'\n\n/** Map Zod validation failures to PesafyError. */\nexport function zodToPesafyError(error: ZodError, label = 'Request'): PesafyError {\n const issues = error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ')\n return new PesafyError({\n code: 'VALIDATION_ERROR',\n message: `${label} validation failed: ${issues}`,\n cause: error,\n })\n}\n\n/** Parse with Zod; throw PesafyError on failure. */\nexport function parseWithSchema<T>(schema: ZodSchema<T>, data: unknown, label?: string): T {\n const result = schema.safeParse(data)\n if (!result.success) throw zodToPesafyError(result.error, label)\n return result.data\n}\n\n/** Safe parse — returns null on failure. */\nexport function safeParseWithSchema<T>(\n schema: ZodSchema<T>,\n data: unknown,\n): { success: true; data: T } | { success: false; error: PesafyError } {\n const result = schema.safeParse(data)\n if (result.success) return { success: true, data: result.data }\n return { success: false, error: zodToPesafyError(result.error) }\n}\n","import { z } from 'zod'\n\nexport const EnvironmentSchema = z.enum(['sandbox', 'production'])\n\nexport const MsisdnSchema = z\n .string()\n .min(10)\n .regex(/^254\\d{9}$/, 'Must be Safaricom format 2547XXXXXXXX')\n\nexport const KesAmountSchema = z\n .number()\n .finite()\n .positive()\n .refine((n) => Math.round(n) >= 1, {\n message: 'amount must round to at least 1 KES',\n })\n\nexport const NonEmptyStringSchema = z.string().trim().min(1)\n\nexport const UrlSchema = z.string().url()\n\nexport const DarajaErrorResponseSchema = z\n .object({\n errorMessage: z.string().optional(),\n ResponseDescription: z.string().optional(),\n resultDesc: z.string().optional(),\n requestId: z.string().optional(),\n })\n .passthrough()\n","import { z } from 'zod'\nimport { KesAmountSchema, NonEmptyStringSchema, UrlSchema } from './common'\n\nconst IdentifierTypeSchema = z.enum(['1', '2', '4'])\n\nexport const AsyncApiResponseSchema = z\n .object({\n ConversationID: z.string().optional(),\n OriginatorConversationID: z.string().optional(),\n ResponseCode: z.string(),\n ResponseDescription: z.string(),\n })\n .passthrough()\n\nexport const TransactionStatusRequestSchema = z\n .object({\n transactionId: z.string().optional(),\n originalConversationId: z.string().optional(),\n partyA: NonEmptyStringSchema,\n identifierType: IdentifierTypeSchema,\n resultUrl: UrlSchema,\n queueTimeOutUrl: UrlSchema,\n commandId: z.literal('TransactionStatusQuery').optional(),\n remarks: z.string().optional(),\n occasion: z.string().optional(),\n })\n .superRefine((data, ctx) => {\n if (!data.transactionId?.trim() && !data.originalConversationId?.trim()) {\n ctx.addIssue({\n code: 'custom',\n message:\n 'Either transactionId (M-Pesa Receipt Number) or originalConversationId is required',\n path: ['transactionId'],\n })\n }\n })\n\nexport const TransactionStatusResponseSchema = AsyncApiResponseSchema\n\nexport const AccountBalanceRequestSchema = z.object({\n partyA: NonEmptyStringSchema,\n identifierType: IdentifierTypeSchema,\n resultUrl: UrlSchema,\n queueTimeOutUrl: UrlSchema,\n remarks: z.string().optional(),\n})\n\nexport const AccountBalanceResponseSchema = AsyncApiResponseSchema\n\nexport const ReversalRequestSchema = z\n .object({\n transactionId: NonEmptyStringSchema,\n receiverParty: NonEmptyStringSchema,\n receiverIdentifierType: z.literal('11').optional(),\n amount: KesAmountSchema,\n resultUrl: UrlSchema,\n queueTimeOutUrl: UrlSchema,\n remarks: z.string().optional(),\n occasion: z.string().optional(),\n })\n .superRefine((data, ctx) => {\n if (data.receiverIdentifierType !== undefined && data.receiverIdentifierType !== '11') {\n ctx.addIssue({\n code: 'custom',\n message: 'receiverIdentifierType must be \"11\" for the Reversals API',\n path: ['receiverIdentifierType'],\n })\n }\n const remarks = data.remarks ?? 'Transaction Reversal'\n if (remarks.length < 2 || remarks.length > 100) {\n ctx.addIssue({\n code: 'custom',\n message: 'remarks must be between 2 and 100 characters',\n path: ['remarks'],\n })\n }\n })\n\nexport const ReversalResponseSchema = AsyncApiResponseSchema\n\nexport const TaxRemittanceRequestSchema = z.object({\n amount: KesAmountSchema,\n partyA: NonEmptyStringSchema,\n partyB: z.string().optional(),\n accountReference: NonEmptyStringSchema,\n resultUrl: UrlSchema,\n queueTimeOutUrl: UrlSchema,\n remarks: z.string().optional(),\n})\n\nexport const TaxRemittanceResponseSchema = AsyncApiResponseSchema\n\nexport const DynamicQRRequestSchema = z.object({\n merchantName: NonEmptyStringSchema,\n refNo: NonEmptyStringSchema,\n amount: KesAmountSchema,\n trxCode: z.enum(['BG', 'WA', 'PB', 'SM', 'SB']),\n cpi: NonEmptyStringSchema,\n size: z.number().int().min(1).max(1000).optional(),\n})\n\nexport const DynamicQRResponseSchema = z\n .object({\n ResponseCode: z.string(),\n RequestID: z.string().optional(),\n ResponseDescription: z.string(),\n QRCode: z.string().optional(),\n })\n .passthrough()\n","// 📁 PATH: src/mpesa/account-balance/query.ts\n\n/**\n * Account Balance Query — checks the balance of an M-PESA shortcode.\n *\n * API: POST /mpesa/accountbalance/v1/query\n *\n * This is ASYNCHRONOUS. The sync response only confirms receipt.\n * Balance data arrives via POST to your ResultURL.\n *\n * Required org portal role: \"Balance Query ORG API\" (Account Balance ORG API initiator)\n *\n * Ref: https://sandbox.safaricom.co.ke/mpesa/accountbalance/v1/query\n */\n\nimport { parseWithSchema } from '../../core/validation/zod-error'\nimport { AccountBalanceRequestSchema, AccountBalanceResponseSchema } from '../../schemas/async-apis'\nimport { httpRequest, type DarajaHttpOptions, withDarajaHttp } from '../../utils/http'\nimport type { AccountBalanceRequest, AccountBalanceResponse } from './types'\n\nexport async function queryAccountBalance(\n baseUrl: string,\n accessToken: string,\n securityCredential: string,\n initiatorName: string,\n request: AccountBalanceRequest,\n http?: DarajaHttpOptions,\n): Promise<AccountBalanceResponse> {\n const validated = parseWithSchema(AccountBalanceRequestSchema, request, 'Account Balance request')\n\n const payload = {\n Initiator: initiatorName,\n SecurityCredential: securityCredential,\n CommandID: 'AccountBalance',\n PartyA: String(validated.partyA.trim()),\n IdentifierType: validated.identifierType,\n ResultURL: validated.resultUrl,\n QueueTimeOutURL: validated.queueTimeOutUrl,\n Remarks: validated.remarks ?? 'Account Balance Query',\n }\n\n const { data } = await httpRequest<AccountBalanceResponse>(\n `${baseUrl}/mpesa/accountbalance/v1/query`,\n withDarajaHttp(\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n },\n http,\n ),\n )\n\n return parseWithSchema(\n AccountBalanceResponseSchema,\n data,\n 'Account Balance response',\n ) as AccountBalanceResponse\n}\n","// 📁 PATH: src/mpesa/account-balance/types.ts\n\n/**\n * Account Balance Query types\n *\n * API: POST /mpesa/accountbalance/v1/query\n *\n * Queries the current balance of an M-PESA shortcode.\n * This is ASYNCHRONOUS — the sync response is only acknowledgement.\n * Balance data is POSTed to your ResultURL callback.\n *\n * Required org portal role: \"Balance Query ORG API\" (Account Balance ORG API initiator)\n *\n * Ref: Account Balance — Daraja Developer Portal\n */\n\n// ── Error codes (documented by Daraja) ───────────────────────────────────────\n\n/**\n * Documented Account Balance API error/result codes.\n * These appear in the async callback ResultCode field.\n *\n * Ref: Account Balance — Error Codes section\n */\nexport const ACCOUNT_BALANCE_ERROR_CODES = {\n /** 15 — Duplicate Detected: OriginatorConversationID has been seen before */\n DUPLICATE_DETECTED: 15,\n /** 17 — Internal Failure: catch-all for unidentified failures */\n INTERNAL_FAILURE: 17,\n /** 18 — Initiator Credential Check Failure: wrong password or encryption issue */\n INITIATOR_CREDENTIAL_CHECK_FAILURE: 18,\n /** 19 — Message Sequencing Failure */\n MESSAGE_SEQUENCING_FAILURE: 19,\n /** 20 — Unresolved Initiator: initiator username not found */\n UNRESOLVED_INITIATOR: 20,\n /** 21 — Initiator to Primary Party Permission Failure */\n INITIATOR_TO_PRIMARY_PARTY_PERMISSION_FAILURE: 21,\n /** 22 — Initiator to Receiver Party Permission Failure: initiator not active */\n INITIATOR_TO_RECEIVER_PARTY_PERMISSION_FAILURE: 22,\n /** 24 — Missing Mandatory Fields */\n MISSING_MANDATORY_FIELDS: 24,\n /** 100000001 — System Overload */\n SYSTEM_OVERLOAD: 100000001,\n /** 100000002 — Throttling Error */\n THROTTLING_ERROR: 100000002,\n /** 100000004 — Internal Server Error */\n INTERNAL_SERVER_ERROR: 100000004,\n /** 100000005 — Invalid Input Value: %1 indicates the parameter's name */\n INVALID_INPUT_VALUE: 100000005,\n /** 100000007 — Service's status is abnormal */\n SERVICE_ABNORMAL: 100000007,\n /** 100000009 — API's status is abnormal */\n API_STATUS_ABNORMAL: 100000009,\n /** 100000010 — Insufficient permissions */\n INSUFFICIENT_PERMISSIONS: 100000010,\n /** 100000011 — Exceed the limitation of request rate */\n REQUEST_RATE_EXCEEDED: 100000011,\n} as const\n\nexport type AccountBalanceErrorCode =\n (typeof ACCOUNT_BALANCE_ERROR_CODES)[keyof typeof ACCOUNT_BALANCE_ERROR_CODES]\n\n// ── Request ───────────────────────────────────────────────────────────────────\n\nexport interface AccountBalanceRequest {\n /**\n * Your business shortcode from which balance is queried.\n * Daraja field: PartyA (Numeric)\n * Example: \"600000\"\n */\n partyA: string\n\n /**\n * Type of the PartyA identifier.\n * \"1\" = MSISDN\n * \"2\" = Till Number\n * \"4\" = Organisation ShortCode (most common — Paybill/B2C/Buy Goods)\n * Daraja field: IdentifierType (Numeric)\n * Sample: 4\n */\n identifierType: '1' | '2' | '4'\n\n /**\n * URL where Safaricom POSTs the balance result (async callback).\n * Must be publicly accessible. HTTPS required in production.\n * Daraja field: ResultURL (URL)\n * Sample: https://ip:port/path or domain:port/path\n */\n resultUrl: string\n\n /**\n * URL Safaricom calls when the request times out.\n * Daraja field: QueueTimeOutURL (URL)\n * Sample: https://ip:port/path or domain:port/path\n */\n queueTimeOutUrl: string\n\n /**\n * Comments sent along with the transaction (up to 100 characters).\n * Daraja field: Remarks (String)\n * Sample: ok\n */\n remarks?: string\n}\n\n// ── Synchronous acknowledgement ───────────────────────────────────────────────\n\n/**\n * Synchronous response from Daraja confirming the request was received.\n * This is NOT the balance data — balance arrives via async callback to ResultURL.\n *\n * ResponseCode \"0\" = request accepted for processing.\n */\nexport interface AccountBalanceResponse {\n /** Unique identifier of the request. Auto-generated by M-PESA. */\n OriginatorConversationID: string\n /** Unique identifier generated by M-PESA for this request */\n ConversationID: string\n /** \"0\" = request accepted by Mobile Money */\n ResponseCode: string\n /** Description of the ResponseCode */\n ResponseDescription: string\n}\n\n// ── Async result (POSTed to ResultURL) ───────────────────────────────────────\n\n/**\n * AccountBalance result parameter key names as documented by Daraja.\n */\nexport type AccountBalanceResultParameterKey = 'AccountBalance' | 'BOCompletedTime'\n\n/**\n * A single result parameter entry from the Daraja callback.\n */\nexport interface AccountBalanceResultParameter {\n Key: AccountBalanceResultParameterKey | string\n Value: string | number\n}\n\n/**\n * The ReferenceItem from the async callback.\n * Key is typically \"QueueTimeoutURL\" as documented.\n */\nexport interface AccountBalanceReferenceItem {\n Key: string\n Value: string\n}\n\n/**\n * Full async callback payload POSTed to your ResultURL after processing.\n *\n * ResultCode 0 = success; non-zero = failure.\n *\n * ResultParameters.ResultParameter contains:\n * - AccountBalance: pipe-delimited balance string for all accounts\n * - BOCompletedTime: completion timestamp (format: YYYYMMDDHHmmss)\n *\n * Ref: Callback Result Payload — Daraja Account Balance docs\n */\nexport interface AccountBalanceResult {\n Result: {\n /**\n * ResultType:\n * \"0\" = completed\n * \"1\" = waiting for further messages\n */\n ResultType: string | number\n /**\n * Result code indicating success or failure.\n * 0 = success; see ACCOUNT_BALANCE_ERROR_CODES for failure codes.\n * Note: Daraja may return this as string \"0\" or number 0 on success.\n */\n ResultCode: number | string\n /** Human-readable description of the result */\n ResultDesc: string\n /** Unique identifier from the original request */\n OriginatorConversationID: string\n /** Unique identifier generated by M-PESA */\n ConversationID: string\n /** M-PESA transaction identifier */\n TransactionID: string\n /** Balance data — only present on success (ResultCode 0) */\n ResultParameters?: {\n /**\n * Can be an array (multiple params) or a single object (Daraja inconsistency).\n * Use getAccountBalanceParam() to handle both forms safely.\n */\n ResultParameter: AccountBalanceResultParameter[] | AccountBalanceResultParameter\n }\n /** Reference data Daraja records in transaction logs */\n ReferenceData?: {\n ReferenceItem: AccountBalanceReferenceItem | AccountBalanceReferenceItem[]\n }\n }\n}\n\n// ── Parsed account types ──────────────────────────────────────────────────────\n\n/**\n * Balance data for a single M-PESA account type.\n *\n * Daraja returns balance for all account types as a single pipe-delimited string.\n * Each group is: AccountName|Currency|Amount\n * Multiple groups are concatenated with pipe separators.\n *\n * Documented account types:\n * - Working Account (MMF): transition account for bank settlement\n * - Utility Account: receives customer payments (C2B/Buy Goods)\n * - Charges Paid Account: deducts charges per business tariff\n * - Organization Settlement: after charges deducted, money passes to working\n *\n * Example raw string:\n * \"Working Account|KES|700000.00|KES|0.00|KES|0.00|Utility Account|KES|228037.00|...\"\n */\nexport interface ParsedAccount {\n /** Account name e.g. \"Working Account\", \"Utility Account\", \"Charges Paid Account\" */\n name: string\n /** Currency code e.g. \"KES\" */\n currency: string\n /** Amount as string (preserves Daraja format, may be negative for Charges Paid) */\n amount: string\n}\n\nexport interface AccountBalanceData {\n /** Raw pipe-delimited string from Daraja AccountBalance result parameter */\n rawBalance: string\n /** Parsed accounts — one entry per account type in the shortcode */\n accounts: ParsedAccount[]\n}\n\n// ── Parsers ───────────────────────────────────────────────────────────────────\n\n/**\n * Parses the raw Daraja AccountBalance string into structured account objects.\n *\n * Daraja returns each account as 3 consecutive pipe-separated fields:\n * AccountName | Currency | Amount\n * Multiple accounts are concatenated, with additional balance sub-fields interleaved.\n *\n * The parser extracts triplets where the first element is a non-numeric account name.\n *\n * Example input:\n * \"Working Account|KES|700000.00|KES|0.00|KES|0.00|Utility Account|KES|228037.00|\"\n *\n * Example output:\n * [\n * { name: \"Working Account\", currency: \"KES\", amount: \"700000.00\" },\n * { name: \"Utility Account\", currency: \"KES\", amount: \"228037.00\" },\n * ]\n */\nexport function parseAccountBalance(raw: string): ParsedAccount[] {\n if (!raw.trim()) return []\n\n const parts = raw.split('|')\n const accounts: ParsedAccount[] = []\n\n for (let i = 0; i + 2 < parts.length; i++) {\n const candidate = parts[i]?.trim()\n const currency = parts[i + 1]?.trim()\n const amount = parts[i + 2]?.trim()\n\n // An account name starts a new group — it is non-numeric and non-empty\n // Numeric-only parts are sub-fields (uncleared/reserved) — skip them\n if (\n candidate &&\n currency &&\n amount !== undefined &&\n isNaN(Number(candidate)) &&\n candidate.length > 0\n ) {\n accounts.push({ name: candidate, currency, amount })\n }\n }\n\n return accounts\n}\n\n// ── Result parameter extractor ────────────────────────────────────────────────\n\n/**\n * Extracts a result parameter value from an AccountBalanceResult by key.\n * Handles both array and single-object forms of ResultParameter (Daraja inconsistency).\n *\n * Documented keys: \"AccountBalance\", \"BOCompletedTime\"\n */\nexport function getAccountBalanceParam(\n result: AccountBalanceResult,\n key: string,\n): string | number | undefined {\n const params = result.Result.ResultParameters?.ResultParameter\n if (!params) return undefined\n const arr = Array.isArray(params) ? params : [params]\n return arr.find((p) => p.Key === key)?.Value\n}\n\n// ── Field extractor helpers ───────────────────────────────────────────────────\n\n/**\n * Returns the M-PESA TransactionID from the result.\n */\nexport function getAccountBalanceTransactionId(result: AccountBalanceResult): string {\n return result.Result.TransactionID\n}\n\n/**\n * Returns the ConversationID from the result.\n */\nexport function getAccountBalanceConversationId(result: AccountBalanceResult): string {\n return result.Result.ConversationID\n}\n\n/**\n * Returns the OriginatorConversationID from the result.\n * This correlates the callback to the original request.\n */\nexport function getAccountBalanceOriginatorConversationId(result: AccountBalanceResult): string {\n return result.Result.OriginatorConversationID\n}\n\n/**\n * Returns the BOCompletedTime from the result parameters, or null if not present.\n * Format: YYYYMMDDHHmmss (e.g. \"20200109125710\")\n */\nexport function getAccountBalanceCompletedTime(result: AccountBalanceResult): string | null {\n const value = getAccountBalanceParam(result, 'BOCompletedTime')\n if (value === undefined || value === null) return null\n return String(value)\n}\n\n/**\n * Returns the raw AccountBalance pipe-delimited string from the result, or null if absent.\n * Use parseAccountBalance() to parse this into structured account objects.\n */\nexport function getAccountBalanceRawBalance(result: AccountBalanceResult): string | null {\n const value = getAccountBalanceParam(result, 'AccountBalance')\n if (value === undefined || value === null) return null\n return String(value)\n}\n\n/**\n * Returns the ReferenceItem from the async callback, or null if not present.\n * Key is typically \"QueueTimeoutURL\" as documented by Daraja.\n */\nexport function getAccountBalanceReferenceItem(\n result: AccountBalanceResult,\n): AccountBalanceReferenceItem | null {\n const refData = result.Result.ReferenceData\n if (!refData) return null\n const item = refData.ReferenceItem\n if (!item) return null\n return Array.isArray(item) ? (item[0] ?? null) : item\n}\n\n// ── Result type guard ─────────────────────────────────────────────────────────\n\n/**\n * Returns true if the Account Balance result indicates success (ResultCode 0).\n * Handles both numeric 0 and string \"0\" (Daraja inconsistency).\n */\nexport function isAccountBalanceSuccess(result: AccountBalanceResult): boolean {\n const code = result.Result.ResultCode\n return code === 0 || code === '0'\n}\n","import { z } from 'zod'\nimport { KesAmountSchema, NonEmptyStringSchema, UrlSchema } from './common'\n\nconst B2BAsyncResponseSchema = z\n .object({\n ConversationID: z.string(),\n OriginatorConversationID: z.string(),\n ResponseCode: z.string(),\n ResponseDescription: z.string(),\n })\n .passthrough()\n\nconst B2BPaymentBaseSchema = z.object({\n amount: KesAmountSchema,\n partyA: NonEmptyStringSchema,\n partyB: NonEmptyStringSchema,\n accountReference: NonEmptyStringSchema,\n requester: z.string().optional(),\n remarks: z.string().optional(),\n resultUrl: UrlSchema,\n queueTimeOutUrl: UrlSchema,\n occasion: z.string().optional(),\n})\n\nexport const B2BBuyGoodsRequestSchema = B2BPaymentBaseSchema.extend({\n commandId: z.literal('BusinessBuyGoods'),\n})\n\nexport const B2BPayBillRequestSchema = B2BPaymentBaseSchema.extend({\n commandId: z.literal('BusinessPayBill'),\n})\n\nexport const B2BBuyGoodsResponseSchema = B2BAsyncResponseSchema\nexport const B2BPayBillResponseSchema = B2BAsyncResponseSchema\n\nexport const B2BExpressCheckoutRequestSchema = z.object({\n primaryShortCode: NonEmptyStringSchema,\n receiverShortCode: NonEmptyStringSchema,\n amount: KesAmountSchema,\n paymentRef: NonEmptyStringSchema,\n callbackUrl: UrlSchema,\n partnerName: NonEmptyStringSchema,\n requestRefId: NonEmptyStringSchema.optional(),\n})\n\nexport const B2BExpressCheckoutResponseSchema = z\n .object({\n code: z.string(),\n status: z.string(),\n })\n .passthrough()\n\nexport const B2BResponseSchema = z\n .object({\n ConversationID: z.string().optional(),\n OriginatorConversationID: z.string().optional(),\n ResponseCode: z.string(),\n ResponseDescription: z.string(),\n })\n .passthrough()\n","/**\n * src/mpesa/b2b-express-checkout/initiate.ts\n *\n * B2B Express Checkout USSD Push to Till implementation.\n */\n\nimport { generateRequestRefId } from '../../core/idempotency'\nimport { parseWithSchema } from '../../core/validation/zod-error'\nimport {\n B2BExpressCheckoutRequestSchema,\n B2BExpressCheckoutResponseSchema,\n} from '../../schemas/b2b'\nimport { httpRequest, type DarajaHttpOptions, withDarajaHttp } from '../../utils/http'\nimport type { B2BExpressCheckoutRequest, B2BExpressCheckoutResponse } from './types'\n\nexport async function initiateB2BExpressCheckout(\n baseUrl: string,\n accessToken: string,\n request: B2BExpressCheckoutRequest,\n http?: DarajaHttpOptions,\n): Promise<B2BExpressCheckoutResponse> {\n const validated = parseWithSchema(\n B2BExpressCheckoutRequestSchema,\n request,\n 'B2B Express Checkout request',\n )\n\n const amount = Math.round(validated.amount)\n\n const payload = {\n primaryShortCode: String(validated.primaryShortCode),\n receiverShortCode: String(validated.receiverShortCode),\n amount: String(amount),\n paymentRef: validated.paymentRef,\n callbackUrl: validated.callbackUrl,\n partnerName: validated.partnerName,\n RequestRefID: validated.requestRefId ?? generateRequestRefId(),\n }\n\n const { data } = await httpRequest<B2BExpressCheckoutResponse>(\n `${baseUrl}/v1/ussdpush/get-msisdn`,\n withDarajaHttp(\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n },\n http,\n ),\n )\n\n return parseWithSchema(B2BExpressCheckoutResponseSchema, data, 'B2B Express Checkout response')\n}\n","/**\n * src/mpesa/b2b-express-checkout/types.ts\n *\n * B2B Express Checkout (USSD Push to Till) type definitions.\n * Strictly aligned with Safaricom Daraja B2B Express Checkout API documentation.\n *\n * Endpoint: POST https://sandbox.safaricom.co.ke/v1/ussdpush/get-msisdn\n *\n * Flow:\n * 1. Vendor initiates USSD push via Daraja\n * 2. Merchant receives USSD prompt on their till\n * 3. Merchant enters Operator ID + M-PESA PIN\n * 4. M-PESA debits merchant, credits vendor\n * 5. Daraja POSTs callback to your callbackUrl\n */\n\n// ── Request ───────────────────────────────────────────────────────────────────\n\n/**\n * Request payload for B2B Express Checkout USSD Push.\n *\n * Daraja payload shape (all field names are camelCase except RequestRefID):\n * {\n * \"primaryShortCode\": \"000001\",\n * \"receiverShortCode\": \"000002\",\n * \"amount\": \"100\", ← sent as string per Daraja spec\n * \"paymentRef\": \"paymentRef\",\n * \"callbackUrl\": \"https://...\",\n * \"partnerName\": \"Vendor\",\n * \"RequestRefID\": \"uuid\" ← PascalCase per Daraja spec\n * }\n */\nexport interface B2BExpressCheckoutRequest {\n /**\n * Debit party — the merchant's till number that will be charged.\n * Daraja field: primaryShortCode\n */\n primaryShortCode: string\n\n /**\n * Credit party — the vendor's Paybill account that will receive funds.\n * Daraja field: receiverShortCode\n */\n receiverShortCode: string\n\n /**\n * Amount to send. Must be a whole number ≥ 1.\n * Sent as a string in the Daraja payload (e.g. \"100\").\n * Daraja field: amount\n */\n amount: number\n\n /**\n * Payment reference shown in the merchant's USSD prompt.\n * Daraja field: paymentRef\n */\n paymentRef: string\n\n /**\n * Publicly accessible URL where Daraja POSTs the transaction result.\n * Daraja field: callbackUrl\n */\n callbackUrl: string\n\n /**\n * Vendor's friendly name shown in the merchant's USSD prompt:\n * \"You are about to send Ksh {{amount}} to {{partnerName}}...\"\n * Daraja field: partnerName\n */\n partnerName: string\n\n /**\n * Unique identifier for this request — used for idempotency.\n * Auto-generated (UUID v4) if not provided.\n * Daraja field: RequestRefID (PascalCase per Daraja spec)\n */\n requestRefId?: string\n}\n\n// ── Synchronous acknowledgement (returned immediately by Daraja) ─────────────\n\n/**\n * Synchronous acknowledgement returned immediately by Daraja.\n *\n * Daraja response shape:\n * {\n * \"code\": \"0\",\n * \"status\": \"USSD Initiated Successfully\"\n * }\n *\n * code \"0\" means the USSD push was initiated. The actual transaction result\n * comes later via the callbackUrl.\n */\nexport interface B2BExpressCheckoutResponse {\n /** \"0\" = USSD push initiated successfully */\n code: string\n /** Human-readable status, e.g. \"USSD Initiated Successfully\" */\n status: string\n}\n\n// ── Async callback payloads (POSTed to your callbackUrl) ─────────────────────\n\n/**\n * Callback when the merchant CANCELLED the USSD prompt.\n *\n * Daraja callback shape:\n * {\n * \"resultCode\": \"4001\",\n * \"resultDesc\": \"User cancelled transaction\",\n * \"requestId\": \"c2a9ba32-9e11-4b90-892c-7bc54944609a\",\n * \"amount\": \"71.0\",\n * \"paymentReference\": \"MAndbubry3hi\"\n * }\n */\nexport interface B2BExpressCheckoutCallbackCancelled {\n /** \"4001\" for user cancellation */\n resultCode: string\n resultDesc: string\n requestId: string\n /** Amount as a string, e.g. \"71.0\" */\n amount: string\n /** The payment reference from the original request */\n paymentReference: string\n}\n\n/**\n * Callback when the M-PESA transaction SUCCEEDED.\n *\n * Daraja callback shape:\n * {\n * \"resultCode\": \"0\",\n * \"resultDesc\": \"The service request is processed successfully.\",\n * \"amount\": \"71.0\",\n * \"requestId\": \"404e1aec-19e0-4ce3-973d-bd92e94c8021\",\n * \"resultType\": \"0\",\n * \"conversationID\":\"AG_20230426_2010434680d9f5a73766\",\n * \"transactionId\": \"RDQ01NFT1Q\",\n * \"status\": \"SUCCESS\"\n * }\n */\nexport interface B2BExpressCheckoutCallbackSuccess {\n /** \"0\" */\n resultCode: string\n resultDesc: string\n /** Amount as a string, e.g. \"71.0\" */\n amount: string\n requestId: string\n /** Usually \"0\" */\n resultType: string\n /** M-PESA conversation ID, e.g. \"AG_20230426_...\" */\n conversationID: string\n /** M-PESA receipt number, e.g. \"RDQ01NFT1Q\" */\n transactionId: string\n /** \"SUCCESS\" */\n status: string\n}\n\n/**\n * Callback for any non-zero, non-cancelled failure\n * (e.g. KYC failure, USSD network error, missing nominated number).\n *\n * Error codes documented by Daraja:\n * 4102 — Merchant KYC failure\n * 4104 — Missing nominated number\n * 4201 — USSD network error\n * 4203 — USSD exception error\n */\nexport interface B2BExpressCheckoutCallbackFailed {\n resultCode: string\n resultDesc: string\n requestId: string\n amount: string\n}\n\n/**\n * Union of all possible callback payload shapes.\n * Discriminate on `resultCode`:\n * \"0\" → B2BExpressCheckoutCallbackSuccess\n * \"4001\" → B2BExpressCheckoutCallbackCancelled\n * other → B2BExpressCheckoutCallbackFailed\n */\nexport type B2BExpressCheckoutCallback =\n | B2BExpressCheckoutCallbackSuccess\n | B2BExpressCheckoutCallbackCancelled\n | B2BExpressCheckoutCallbackFailed\n\n// ── Error codes (documented by Daraja) ───────────────────────────────────────\n\n/**\n * Known B2B Express Checkout result codes.\n *\n * SUCCESS (0) — Transaction completed successfully\n * CANCELLED(4001) — Merchant cancelled the USSD prompt\n * KYC_FAIL (4102) — Merchant KYC failure; provide valid KYC\n * NO_NUMBER(4104) — Missing nominated number; configure in M-PESA portal\n * NET_ERROR(4201) — USSD network error; retry on stable network\n * USSD_ERR (4203) — USSD exception error; retry on stable network\n */\nexport const B2B_RESULT_CODES = {\n SUCCESS: '0',\n CANCELLED: '4001',\n KYC_FAIL: '4102',\n NO_NOMINATED_NUMBER: '4104',\n USSD_NETWORK_ERROR: '4201',\n USSD_EXCEPTION_ERROR: '4203',\n} as const\n\nexport type B2BResultCode = (typeof B2B_RESULT_CODES)[keyof typeof B2B_RESULT_CODES]\n\n/**\n * B2B Express Checkout error codes.\n * `(string & {})` keeps these literals in IntelliSense while still accepting\n * unknown codes without triggering no-redundant-type-constituents.\n */\nexport type B2BExpressCheckoutErrorCode = B2BResultCode | (string & {})\n\n// ── Synchronous error response ────────────────────────────────────────────────\n\n/**\n * Synchronous error response from Daraja when the request itself fails\n * (before the USSD is initiated).\n */\nexport interface B2BExpressCheckoutErrorResponse {\n requestId: string\n errorCode: B2BExpressCheckoutErrorCode\n errorMessage: string\n}\n","/**\n * src/mpesa/b2b-express-checkout/webhooks.ts\n *\n * B2B Express Checkout callback (webhook) helpers.\n * Strictly aligned with Safaricom Daraja B2B Express Checkout documentation.\n *\n * All callbacks are discriminated on `resultCode`:\n * \"0\" → success (B2BExpressCheckoutCallbackSuccess)\n * \"4001\" → cancelled (B2BExpressCheckoutCallbackCancelled)\n * other → failed (B2BExpressCheckoutCallbackFailed)\n */\n\nimport {\n B2B_RESULT_CODES,\n type B2BExpressCheckoutCallback,\n type B2BExpressCheckoutCallbackCancelled,\n type B2BExpressCheckoutCallbackFailed,\n type B2BExpressCheckoutCallbackSuccess,\n type B2BResultCode,\n} from './types'\n\n// ── Known result codes set (for O(1) lookup) ──────────────────────────────────\n\nconst KNOWN_RESULT_CODES = new Set<string>(Object.values(B2B_RESULT_CODES))\n\n// ── Runtime type guard ─────────────────────────────────────────────────────────\n\n/**\n * Runtime type guard — returns true if `body` looks like a valid B2B\n * Express Checkout callback payload.\n *\n * Use this in your callback route before casting the body:\n * @example\n * app.post('/mpesa/b2b/callback', (req, res) => {\n * if (!isB2BCheckoutCallback(req.body)) {\n * return res.status(400).json({ error: 'unrecognised payload' })\n * }\n * // safe to use as B2BExpressCheckoutCallback\n * })\n */\nexport function isB2BCheckoutCallback(body: unknown): body is B2BExpressCheckoutCallback {\n if (!body || typeof body !== 'object') return false\n const b = body as Record<string, unknown>\n return (\n typeof b['resultCode'] === 'string' &&\n typeof b['requestId'] === 'string' &&\n typeof b['amount'] === 'string'\n )\n}\n\n// ── Success type guard ────────────────────────────────────────────────────────\n\n/**\n * Returns true when the B2B callback represents a SUCCESSFUL transaction.\n * A successful callback has resultCode \"0\" and includes transactionId.\n *\n * Per Daraja docs:\n * {\n * \"resultCode\": \"0\",\n * \"resultDesc\": \"The service request is processed successfully.\",\n * \"transactionId\": \"RDQ01NFT1Q\",\n * \"status\": \"SUCCESS\",\n * ...\n * }\n */\nexport function isB2BCheckoutSuccess(\n callback: B2BExpressCheckoutCallback,\n): callback is B2BExpressCheckoutCallbackSuccess {\n return callback.resultCode === B2B_RESULT_CODES.SUCCESS\n}\n\n// ── Cancelled type guard ──────────────────────────────────────────────────────\n\n/**\n * Returns true when the merchant CANCELLED the USSD prompt.\n * resultCode \"4001\" = \"User cancelled transaction\".\n *\n * Per Daraja docs:\n * {\n * \"resultCode\": \"4001\",\n * \"resultDesc\": \"User cancelled transaction\",\n * \"paymentReference\": \"MAndbubry3hi\",\n * ...\n * }\n */\nexport function isB2BCheckoutCancelled(\n callback: B2BExpressCheckoutCallback,\n): callback is B2BExpressCheckoutCallbackCancelled {\n return callback.resultCode === B2B_RESULT_CODES.CANCELLED\n}\n\n// ── Failed type guard ─────────────────────────────────────────────────────────\n\n/**\n * Returns true when the callback represents any non-success outcome.\n * This covers cancelled (4001) AND all other error codes\n * (4102, 4104, 4201, 4203, etc.).\n */\nexport function isB2BCheckoutFailed(callback: B2BExpressCheckoutCallback): boolean {\n return callback.resultCode !== B2B_RESULT_CODES.SUCCESS\n}\n\n// ── Error code helper ─────────────────────────────────────────────────────────\n\n/**\n * Returns true if the given result code is one of the documented B2B codes.\n *\n * Documented codes:\n * \"0\" — SUCCESS\n * \"4001\" — CANCELLED (user cancelled USSD)\n * \"4102\" — KYC_FAIL (merchant KYC failure)\n * \"4104\" — NO_NOMINATED_NUMBER (configure in M-PESA portal)\n * \"4201\" — USSD_NETWORK_ERROR (retry on stable network)\n * \"4203\" — USSD_EXCEPTION_ERROR (retry on stable network)\n */\nexport function isKnownB2BResultCode(code: string): code is B2BResultCode {\n return KNOWN_RESULT_CODES.has(code)\n}\n\n// ── Payload extractors ────────────────────────────────────────────────────────\n\n/**\n * Returns the result code from any B2B callback.\n */\nexport function getB2BResultCode(callback: B2BExpressCheckoutCallback): string {\n return callback.resultCode\n}\n\n/**\n * Returns the result description from any B2B callback.\n */\nexport function getB2BResultDesc(callback: B2BExpressCheckoutCallback): string {\n return callback.resultDesc\n}\n\n/**\n * Returns the requestId from any B2B callback.\n * Use this to correlate the callback with your original request.\n */\nexport function getB2BRequestId(callback: B2BExpressCheckoutCallback): string {\n return callback.requestId\n}\n\n/**\n * Returns the transaction amount as a number from any B2B callback.\n * Daraja sends amount as a string (e.g. \"71.0\"); this converts it to number.\n */\nexport function getB2BAmount(callback: B2BExpressCheckoutCallback): number {\n return Number(callback.amount)\n}\n\n/**\n * Returns the M-PESA receipt number from a SUCCESSFUL callback.\n * Returns null if the callback is not a success.\n */\nexport function getB2BTransactionId(callback: B2BExpressCheckoutCallback): string | null {\n if (!isB2BCheckoutSuccess(callback)) return null\n return (callback as B2BExpressCheckoutCallbackSuccess).transactionId ?? null\n}\n\n/**\n * Returns the M-PESA conversationID from a SUCCESSFUL callback.\n * Returns null if the callback is not a success.\n */\nexport function getB2BConversationId(callback: B2BExpressCheckoutCallback): string | null {\n if (!isB2BCheckoutSuccess(callback)) return null\n return (callback as B2BExpressCheckoutCallbackSuccess).conversationID ?? null\n}\n\n/**\n * Returns the paymentReference from a CANCELLED callback.\n * Returns null if the callback is not a cancellation.\n *\n * Per Daraja docs: \"paymentReference\" is only present on cancelled callbacks.\n */\nexport function getB2BPaymentReference(callback: B2BExpressCheckoutCallback): string | null {\n if (!isB2BCheckoutCancelled(callback)) return null\n return (callback as B2BExpressCheckoutCallbackCancelled).paymentReference ?? null\n}\n\n/**\n * Returns true if the transaction status is \"SUCCESS\".\n * Only meaningful for success callbacks — always false otherwise.\n */\nexport function isB2BStatusSuccess(callback: B2BExpressCheckoutCallback): boolean {\n if (!isB2BCheckoutSuccess(callback)) return false\n return (callback as B2BExpressCheckoutCallbackSuccess).status === 'SUCCESS'\n}\n\nexport function isB2BCheckoutFailedCode(\n callback: B2BExpressCheckoutCallback,\n): callback is B2BExpressCheckoutCallbackFailed {\n return !isB2BCheckoutSuccess(callback) && !isB2BCheckoutCancelled(callback)\n}\n","/**\n * src/mpesa/b2b-buy-goods/payment.ts\n *\n * Initiates a Business Buy Goods payment via Safaricom Daraja.\n * Endpoint: POST /mpesa/b2b/v1/paymentrequest\n *\n * Strictly follows the Safaricom Daraja Business Buy Goods API documentation:\n * - CommandID must be \"BusinessBuyGoods\"\n * - SenderIdentifierType is always \"4\" (hardcoded per docs)\n * - RecieverIdentifierType is always \"4\" (hardcoded per docs)\n * - Amount is sent as a string per the JSON spec\n * - AccountReference is truncated to max 13 characters per docs\n * - Requester and Occassion are optional\n */\n\nimport { parseWithSchema } from '../../core/validation/zod-error'\nimport { B2BBuyGoodsRequestSchema, B2BBuyGoodsResponseSchema } from '../../schemas/b2b'\nimport { httpRequest, type DarajaHttpOptions, withDarajaHttp } from '../../utils/http'\nimport type { B2BBuyGoodsRequest, B2BBuyGoodsResponse } from './types'\n\n/** Daraja Business Buy Goods endpoint — same as Pay Bill per docs */\nconst B2B_BUY_GOODS_ENDPOINT = '/mpesa/b2b/v1/paymentrequest'\n\n/**\n * Per documentation: SenderIdentifierType and RecieverIdentifierType\n * must always be \"4\" (Organisation ShortCode). Not configurable.\n */\nconst IDENTIFIER_TYPE = '4' as const\n\n/**\n * Initiates a Business Buy Goods payment request.\n *\n * Moves money from your MMF/Working account to the recipient's merchant account\n * (till number, merchant store number, or Merchant HO).\n * The sync response is acknowledgement only — the result arrives via resultUrl.\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 API operator username with B2B role\n * @param request - Business Buy Goods request parameters\n * @returns Synchronous acknowledgement response from Daraja\n * @throws {PesafyError} VALIDATION_ERROR for invalid input before HTTP call\n * @throws {PesafyError} From httpRequest on network / API errors\n */\nexport async function initiateB2BBuyGoods(\n baseUrl: string,\n accessToken: string,\n securityCredential: string,\n initiatorName: string,\n request: B2BBuyGoodsRequest,\n http?: DarajaHttpOptions,\n): Promise<B2BBuyGoodsResponse> {\n const validated = parseWithSchema(B2BBuyGoodsRequestSchema, request, 'B2B Buy Goods request')\n const amount = Math.round(validated.amount)\n\n // ── Build payload matching Daraja spec exactly ──────────────────────────────\n //\n // Field mapping (camelCase → Daraja PascalCase):\n // commandId → CommandID (\"BusinessBuyGoods\")\n // partyA → PartyA (string)\n // partyB → PartyB (string)\n // accountReference → AccountReference (string, max 13 chars per docs)\n // requester → Requester (optional)\n // remarks → Remarks (string)\n // resultUrl → ResultURL (string)\n // queueTimeOutUrl → QueueTimeOutURL (string)\n // occasion → Occassion (optional, Daraja typo preserved)\n // amount → Amount (string per JSON sample in docs)\n //\n // Hardcoded per docs:\n // SenderIdentifierType → \"4\"\n // RecieverIdentifierType → \"4\"\n //\n const payload: Record<string, unknown> = {\n Initiator: initiatorName,\n SecurityCredential: securityCredential,\n CommandID: validated.commandId,\n SenderIdentifierType: IDENTIFIER_TYPE,\n RecieverIdentifierType: IDENTIFIER_TYPE,\n Amount: String(amount),\n PartyA: String(validated.partyA),\n PartyB: String(validated.partyB),\n AccountReference: validated.accountReference.slice(0, 13),\n Remarks: validated.remarks ?? 'Business Buy Goods',\n QueueTimeOutURL: validated.queueTimeOutUrl,\n ResultURL: validated.resultUrl,\n }\n\n if (validated.requester?.trim()) {\n payload['Requester'] = String(validated.requester)\n }\n\n if (validated.occasion?.trim()) {\n payload['Occassion'] = validated.occasion\n }\n\n const { data } = await httpRequest<B2BBuyGoodsResponse>(\n `${baseUrl}${B2B_BUY_GOODS_ENDPOINT}`,\n withDarajaHttp(\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n },\n http,\n ),\n )\n\n return parseWithSchema(\n B2BBuyGoodsResponseSchema,\n data,\n 'B2B Buy Goods response',\n ) as B2BBuyGoodsResponse\n}\n","/**\n * src/mpesa/b2b-pay-bill/payment.ts\n *\n * Initiates a Business Pay Bill payment via Safaricom Daraja.\n * Endpoint: POST /mpesa/b2b/v1/paymentrequest\n *\n * Strictly follows the Safaricom Daraja Business Pay Bill API documentation:\n * - CommandID must be \"BusinessPayBill\"\n * - SenderIdentifierType is always \"4\" (hardcoded per docs)\n * - RecieverIdentifierType is always \"4\" (hardcoded per docs)\n * - Amount is sent as a string per the JSON spec\n * - AccountReference is truncated to max 13 characters per docs\n * - Requester and Occassion are optional\n */\n\nimport { parseWithSchema } from '../../core/validation/zod-error'\nimport { B2BPayBillRequestSchema, B2BPayBillResponseSchema } from '../../schemas/b2b'\nimport { httpRequest, type DarajaHttpOptions, withDarajaHttp } from '../../utils/http'\nimport type { B2BPayBillRequest, B2BPayBillResponse } from './types'\n\n/** Daraja Business Pay Bill endpoint */\nconst B2B_PAY_BILL_ENDPOINT = '/mpesa/b2b/v1/paymentrequest'\n\n/**\n * Per documentation: SenderIdentifierType and RecieverIdentifierType\n * must always be \"4\" (Organisation ShortCode). Not configurable.\n */\nconst IDENTIFIER_TYPE = '4' as const\n\n/**\n * Initiates a Business Pay Bill payment request.\n *\n * Moves money from your MMF/Working account to the recipient's utility account.\n * The sync response is acknowledgement only — the result arrives via resultUrl.\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 API operator username with B2B role\n * @param request - Business Pay Bill request parameters\n * @returns Synchronous acknowledgement response from Daraja\n * @throws {PesafyError} VALIDATION_ERROR for invalid input before HTTP call\n * @throws {PesafyError} From httpRequest on network / API errors\n */\nexport async function initiateB2BPayBill(\n baseUrl: string,\n accessToken: string,\n securityCredential: string,\n initiatorName: string,\n request: B2BPayBillRequest,\n http?: DarajaHttpOptions,\n): Promise<B2BPayBillResponse> {\n const validated = parseWithSchema(B2BPayBillRequestSchema, request, 'B2B Pay Bill request')\n const amount = Math.round(validated.amount)\n\n // ── Build payload matching Daraja spec exactly ──────────────────────────────\n //\n // Field mapping (camelCase → Daraja PascalCase):\n // commandId → CommandID (\"BusinessPayBill\")\n // partyA → PartyA (string)\n // partyB → PartyB (string)\n // accountReference → AccountReference (string, max 13 chars per docs)\n // requester → Requester (optional)\n // remarks → Remarks (string)\n // resultUrl → ResultURL (string)\n // queueTimeOutUrl → QueueTimeOutURL (string)\n // occasion → Occassion (optional, Daraja typo preserved)\n // amount → Amount (string per JSON sample in docs)\n //\n // Hardcoded per docs:\n // SenderIdentifierType → \"4\"\n // RecieverIdentifierType → \"4\"\n //\n const payload: Record<string, unknown> = {\n Initiator: initiatorName,\n SecurityCredential: securityCredential,\n CommandID: validated.commandId,\n SenderIdentifierType: IDENTIFIER_TYPE,\n RecieverIdentifierType: IDENTIFIER_TYPE,\n Amount: String(amount),\n PartyA: String(validated.partyA),\n PartyB: String(validated.partyB),\n AccountReference: validated.accountReference.slice(0, 13),\n Remarks: validated.remarks ?? 'Business Pay Bill',\n QueueTimeOutURL: validated.queueTimeOutUrl,\n ResultURL: validated.resultUrl,\n }\n\n if (validated.requester?.trim()) {\n payload['Requester'] = String(validated.requester)\n }\n\n if (validated.occasion?.trim()) {\n payload['Occassion'] = validated.occasion\n }\n\n const { data } = await httpRequest<B2BPayBillResponse>(\n `${baseUrl}${B2B_PAY_BILL_ENDPOINT}`,\n withDarajaHttp(\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n },\n http,\n ),\n )\n\n return parseWithSchema(\n B2BPayBillResponseSchema,\n data,\n 'B2B Pay Bill response',\n ) as B2BPayBillResponse\n}\n","import { z } from 'zod'\nimport { KesAmountSchema, NonEmptyStringSchema, UrlSchema } from './common'\n\nconst B2CAsyncResponseSchema = z\n .object({\n ConversationID: z.string(),\n OriginatorConversationID: z.string(),\n ResponseCode: z.string(),\n ResponseDescription: z.string(),\n })\n .passthrough()\n\n/** B2C Account Top Up (BusinessPayToBulk) */\nexport const B2CRequestSchema = z.object({\n commandId: z.literal('BusinessPayToBulk'),\n amount: KesAmountSchema,\n partyA: NonEmptyStringSchema,\n partyB: NonEmptyStringSchema,\n accountReference: NonEmptyStringSchema,\n requester: z.string().optional(),\n remarks: z.string().optional(),\n resultUrl: UrlSchema,\n queueTimeOutUrl: UrlSchema,\n})\n\nexport const B2CResponseSchema = B2CAsyncResponseSchema\n\nexport const B2CDisbursementRequestSchema = z.object({\n commandId: z.enum(['BusinessPayment', 'SalaryPayment', 'PromotionPayment']),\n amount: z.number().finite().positive(),\n partyA: NonEmptyStringSchema,\n partyB: NonEmptyStringSchema,\n remarks: NonEmptyStringSchema,\n queueTimeOutUrl: UrlSchema,\n resultUrl: UrlSchema,\n originatorConversationId: NonEmptyStringSchema.optional(),\n occasion: z.string().optional(),\n})\n\nexport const B2CDisbursementResponseSchema = B2CAsyncResponseSchema\n","/**\n * src/mpesa/b2c/payment.ts\n *\n * Initiates a B2C Account Top Up via Safaricom Daraja.\n * Endpoint: POST /mpesa/b2b/v1/paymentrequest\n *\n * Strictly follows the Safaricom Daraja B2C Account Top Up API documentation:\n * - CommandID must be \"BusinessPayToBulk\"\n * - SenderIdentifierType is always \"4\" (hardcoded per docs)\n * - RecieverIdentifierType is always \"4\" (hardcoded per docs)\n * - Amount is sent as a string per the JSON spec\n * - Requester is optional\n */\n\nimport { parseWithSchema } from '../../core/validation/zod-error'\nimport { B2CRequestSchema, B2CResponseSchema } from '../../schemas/b2c'\nimport { httpRequest, type DarajaHttpOptions, withDarajaHttp } from '../../utils/http'\nimport type { B2CRequest, B2CResponse } from './types'\n\n/** The only endpoint documented for this API */\nconst B2C_ENDPOINT = '/mpesa/b2b/v1/paymentrequest'\n\n/**\n * Per documentation: SenderIdentifierType and RecieverIdentifierType\n * must always be \"4\" (Organisation ShortCode). Not configurable.\n */\nconst IDENTIFIER_TYPE = '4' as const\n\n/**\n * Initiates a B2C Account Top Up payment request.\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 API operator username with B2B role\n * @param request - B2C top-up request parameters\n * @returns Synchronous acknowledgement response from Daraja\n * @throws {PesafyError} VALIDATION_ERROR for invalid input before HTTP call\n * @throws {PesafyError} From httpRequest on network / API errors\n */\nexport async function initiateB2CPayment(\n baseUrl: string,\n accessToken: string,\n securityCredential: string,\n initiatorName: string,\n request: B2CRequest,\n http?: DarajaHttpOptions,\n): Promise<B2CResponse> {\n const validated = parseWithSchema(B2CRequestSchema, request, 'B2C request')\n const amount = Math.round(validated.amount)\n\n // ── Build payload matching Daraja spec exactly ──────────────────────────────\n //\n // Field mapping (camelCase → Daraja PascalCase):\n // commandId → CommandID (\"BusinessPayToBulk\")\n // partyA → PartyA (string)\n // partyB → PartyB (string)\n // accountReference → AccountReference (string)\n // requester → Requester (string, omitted if not provided)\n // remarks → Remarks (string)\n // resultUrl → ResultURL (string)\n // queueTimeOutUrl → QueueTimeOutURL (string)\n // amount → Amount (string per JSON sample in docs)\n //\n // Hardcoded per docs:\n // SenderIdentifierType → \"4\"\n // RecieverIdentifierType → \"4\"\n\n const payload: Record<string, unknown> = {\n Initiator: initiatorName,\n SecurityCredential: securityCredential,\n CommandID: validated.commandId,\n SenderIdentifierType: IDENTIFIER_TYPE,\n RecieverIdentifierType: IDENTIFIER_TYPE,\n Amount: String(amount),\n PartyA: String(validated.partyA),\n PartyB: String(validated.partyB),\n AccountReference: validated.accountReference,\n Remarks: validated.remarks ?? 'B2C Account Top Up',\n QueueTimeOutURL: validated.queueTimeOutUrl,\n ResultURL: validated.resultUrl,\n }\n\n if (validated.requester?.trim()) {\n payload['Requester'] = String(validated.requester)\n }\n\n const { data } = await httpRequest<B2CResponse>(\n `${baseUrl}${B2C_ENDPOINT}`,\n withDarajaHttp(\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n },\n http,\n ),\n )\n\n return parseWithSchema(B2CResponseSchema, data, 'B2C response') as B2CResponse\n}\n","/**\n * src/mpesa/b2c/webhooks.ts\n *\n * Type guards and payload extractors for B2C Account Top Up result callbacks.\n *\n * Result parameters are aligned strictly to Safaricom Daraja documentation:\n * - DebitAccountBalance\n * - Amount\n * - Currency\n * - ReceiverPartyPublicName\n * - TransactionCompletedTime\n * - DebitPartyCharges\n *\n * Docs note: ResultCode is \"0\" (string) on success but 2001 (number) on\n * failure — both forms are handled by the type guard.\n */\n\nimport { B2C_RESULT_CODES } from './types'\nimport type { B2CResult, B2CResultParameter } from './types'\n\n// ── Type guards ───────────────────────────────────────────────────────────────\n\n/**\n * Runtime type guard — checks if a body looks like a B2C result callback.\n * Validates the minimum documented structure.\n */\nexport function isB2CResult(body: unknown): body is B2CResult {\n if (!body || typeof body !== 'object') return false\n const b = body as Record<string, unknown>\n if (!b['Result'] || typeof b['Result'] !== 'object') return false\n const result = b['Result'] as Record<string, unknown>\n return (\n (typeof result['ResultCode'] === 'number' || typeof result['ResultCode'] === 'string') &&\n typeof result['ConversationID'] === 'string' &&\n typeof result['OriginatorConversationID'] === 'string'\n )\n}\n\n/**\n * Returns true if the B2C result represents a successful transaction.\n * Handles both string \"0\" (documented in success sample) and number 0.\n */\nexport function isB2CSuccess(result: B2CResult): boolean {\n const code = result.Result.ResultCode\n return code === 0 || code === '0'\n}\n\n/**\n * Returns true if the B2C result represents a failure.\n * Handles both string \"0\" (documented in success sample) and number 0.\n */\nexport function isB2CFailure(result: B2CResult): boolean {\n return !isB2CSuccess(result)\n}\n\n/**\n * Returns true if the result code matches a known documented code.\n * Empty strings are explicitly rejected — Number('') coerces to 0 which\n * would otherwise incorrectly match B2C_RESULT_CODES.SUCCESS.\n */\nexport function isKnownB2CResultCode(code: unknown): boolean {\n if (typeof code !== 'number' && typeof code !== 'string') return false\n if (typeof code === 'string' && code.trim() === '') return false\n const numeric = Number(code)\n return Object.values(B2C_RESULT_CODES).includes(numeric as 0 | 2001)\n}\n\n// ── Core field extractors ─────────────────────────────────────────────────────\n\n/**\n * Extracts the M-PESA transaction ID from a B2C result.\n * Present on both success and failure (generic ID on failure).\n */\nexport function getB2CTransactionId(result: B2CResult): string | null {\n return result.Result.TransactionID ?? null\n}\n\n/**\n * Extracts the ConversationID from a B2C result.\n */\nexport function getB2CConversationId(result: B2CResult): string {\n return result.Result.ConversationID\n}\n\n/**\n * Extracts the OriginatorConversationID from a B2C result.\n * Use this to correlate with the original API call response.\n */\nexport function getB2COriginatorConversationId(result: B2CResult): string {\n return result.Result.OriginatorConversationID\n}\n\n/**\n * Extracts the result description (human-readable status).\n */\nexport function getB2CResultDesc(result: B2CResult): string {\n return result.Result.ResultDesc\n}\n\n// ── Result parameter extractors (documented fields only) ──────────────────────\n\n/**\n * Extracts the transaction amount from B2C result parameters.\n * Documented field: \"Amount\"\n * Returns null if not present (e.g. on failure).\n */\nexport function getB2CAmount(result: B2CResult): number | null {\n const value = getB2CResultParam(result, 'Amount')\n if (value === undefined) return null\n const num = Number(value)\n return Number.isFinite(num) ? num : null\n}\n\n/**\n * Extracts the transaction currency from B2C result parameters.\n * Documented field: \"Currency\"\n * Returns \"KES\" as default when not present.\n */\nexport function getB2CCurrency(result: B2CResult): string {\n const value = getB2CResultParam(result, 'Currency')\n if (value === undefined || value === '') return 'KES'\n return String(value)\n}\n\n/**\n * Extracts the receiver's public name from B2C result parameters.\n * Documented field: \"ReceiverPartyPublicName\"\n * Returns null if not present.\n */\nexport function getB2CReceiverPublicName(result: B2CResult): string | null {\n const value = getB2CResultParam(result, 'ReceiverPartyPublicName')\n if (value === undefined) return null\n return String(value)\n}\n\n/**\n * Extracts the transaction completion timestamp from B2C result parameters.\n * Documented field: \"TransactionCompletedTime\" — format: YYYYMMDDHHmmss\n * Returns null if not present.\n */\nexport function getB2CTransactionCompletedTime(result: B2CResult): string | null {\n const value = getB2CResultParam(result, 'TransactionCompletedTime')\n if (value === undefined) return null\n return String(value)\n}\n\n/**\n * Extracts the debit account balance from B2C result parameters.\n * Documented field: \"DebitAccountBalance\" (e.g. \"{CurrencyCode=KES}\")\n * Returns null if not present.\n */\nexport function getB2CDebitAccountBalance(result: B2CResult): string | null {\n const value = getB2CResultParam(result, 'DebitAccountBalance')\n if (value === undefined) return null\n return String(value)\n}\n\n/**\n * Extracts the debit party charges from B2C result parameters.\n * Documented field: \"DebitPartyCharges\"\n * Returns null if not present or empty.\n */\nexport function getB2CDebitPartyCharges(result: B2CResult): string | null {\n const value = getB2CResultParam(result, 'DebitPartyCharges')\n if (value === undefined || value === '') return null\n return String(value)\n}\n\n// ── Internal helper ───────────────────────────────────────────────────────────\n\n/**\n * Extracts a named value from B2C result parameters.\n * Handles both single-object and array forms of ResultParameter\n * (Daraja returns either depending on how many parameters are present).\n * Returns undefined if key is absent or no ResultParameters exist.\n */\nexport function getB2CResultParam(result: B2CResult, key: string): string | number | undefined {\n const params = result.Result.ResultParameters?.ResultParameter\n if (!params) return undefined\n\n // ResultParameter may be a single object or an array\n const paramArray = Array.isArray(params)\n ? (params as B2CResultParameter[])\n : [params as B2CResultParameter]\n\n const item = paramArray.find((p) => p.Key === key)\n return item?.Value\n}\n","/**\n * src/mpesa/b2c-disbursement/payment.ts\n *\n * Initiates a B2C Disbursement payment (Salary / Cashback / Promotion).\n * Endpoint: POST /mpesa/b2c/v3/paymentrequest\n */\n\nimport { generateOriginatorConversationId } from '../../core/idempotency'\nimport { parseWithSchema } from '../../core/validation/zod-error'\nimport { B2CDisbursementRequestSchema, B2CDisbursementResponseSchema } from '../../schemas/b2c'\nimport { createError } from '../../utils/errors'\nimport { httpRequest, type DarajaHttpOptions, withDarajaHttp } from '../../utils/http'\nimport type { B2CDisbursementRequest, B2CDisbursementResponse } from './types'\n\nconst B2C_DISBURSEMENT_ENDPOINT = '/mpesa/b2c/v3/paymentrequest'\n\nconst VALID_COMMAND_IDS = new Set(['BusinessPayment', 'SalaryPayment', 'PromotionPayment'])\n\n/**\n * Initiates a B2C disbursement payment request.\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 API operator username\n * @param request - B2C disbursement request parameters\n */\nexport async function initiateB2CDisbursement(\n baseUrl: string,\n accessToken: string,\n securityCredential: string,\n initiatorName: string,\n request: B2CDisbursementRequest,\n http?: DarajaHttpOptions,\n): Promise<B2CDisbursementResponse> {\n const originatorConversationId =\n request.originatorConversationId?.trim() || generateOriginatorConversationId()\n const validated = parseWithSchema(\n B2CDisbursementRequestSchema,\n { ...request, originatorConversationId },\n 'B2C Disbursement request',\n )\n\n // ── Validate CommandID ──────────────────────────────────────────────────────\n if (!validated.commandId || !VALID_COMMAND_IDS.has(validated.commandId)) {\n throw createError({\n code: 'VALIDATION_ERROR',\n message:\n `commandId must be one of: BusinessPayment, SalaryPayment, PromotionPayment. ` +\n `Got \"${validated.commandId}\".`,\n })\n }\n\n // ── Validate amount ─────────────────────────────────────────────────────────\n const amount = Math.round(validated.amount)\n if (!Number.isFinite(amount) || amount < 10) {\n throw createError({\n code: 'VALIDATION_ERROR',\n message: `amount must be ≥ 10 KES (got ${validated.amount} which rounds to ${amount}).`,\n })\n }\n\n // ── Validate required string fields ────────────────────────────────────────\n if (!validated.partyA?.trim()) {\n throw createError({\n code: 'VALIDATION_ERROR',\n message: 'partyA is required — the sending organisation shortcode.',\n })\n }\n\n if (!validated.partyB?.trim()) {\n throw createError({\n code: 'VALIDATION_ERROR',\n message: 'partyB is required — the receiving customer MSISDN (2547XXXXXXXX).',\n })\n }\n\n if (!validated.remarks?.trim()) {\n throw createError({\n code: 'VALIDATION_ERROR',\n message: 'remarks is required (2–100 characters).',\n })\n }\n\n if (!validated.resultUrl?.trim()) {\n throw createError({\n code: 'VALIDATION_ERROR',\n message: 'resultUrl is required — Safaricom POSTs the async result here.',\n })\n }\n\n if (!validated.queueTimeOutUrl?.trim()) {\n throw createError({\n code: 'VALIDATION_ERROR',\n message: 'queueTimeOutUrl is required — Safaricom calls this on request timeout.',\n })\n }\n\n // ── Build payload ───────────────────────────────────────────────────────────\n const payload: Record<string, unknown> = {\n OriginatorConversationID: originatorConversationId,\n InitiatorName: initiatorName,\n SecurityCredential: securityCredential,\n CommandID: validated.commandId,\n Amount: amount,\n PartyA: String(validated.partyA),\n PartyB: String(validated.partyB),\n Remarks: validated.remarks,\n QueueTimeOutURL: validated.queueTimeOutUrl,\n ResultURL: validated.resultUrl,\n }\n\n // Occassion is optional (sic — Daraja typo preserved)\n if (validated.occasion?.trim()) {\n payload['Occassion'] = validated.occasion\n }\n\n const { data } = await httpRequest<B2CDisbursementResponse>(\n `${baseUrl}${B2C_DISBURSEMENT_ENDPOINT}`,\n withDarajaHttp(\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n },\n http,\n ),\n )\n\n return parseWithSchema(B2CDisbursementResponseSchema, data, 'B2C Disbursement response')\n}\n","/**\n * src/mpesa/b2c-disbursement/webhooks.ts\n *\n * Type guards and payload extractors for B2C Disbursement result callbacks.\n */\n\nimport { B2C_DISBURSEMENT_RESULT_CODES } from './types'\nimport type { B2CDisbursementResult, B2CDisbursementResultParameter } from './types'\n\n// ── Type guards ───────────────────────────────────────────────────────────────\n\nexport function isB2CDisbursementResult(body: unknown): body is B2CDisbursementResult {\n if (!body || typeof body !== 'object') return false\n const b = body as Record<string, unknown>\n if (!b['Result'] || typeof b['Result'] !== 'object') return false\n const result = b['Result'] as Record<string, unknown>\n return (\n (typeof result['ResultCode'] === 'number' || typeof result['ResultCode'] === 'string') &&\n typeof result['ConversationID'] === 'string' &&\n typeof result['OriginatorConversationID'] === 'string'\n )\n}\n\nexport function isB2CDisbursementSuccess(result: B2CDisbursementResult): boolean {\n const code = result.Result.ResultCode\n return code === 0 || code === '0'\n}\n\nexport function isB2CDisbursementFailure(result: B2CDisbursementResult): boolean {\n return !isB2CDisbursementSuccess(result)\n}\n\n/**\n * Returns true if the result code is among the 14 documented codes.\n * Handles both numeric codes and the string code \"SFC_IC0003\".\n * Rejects empty strings (Number('') → 0 which would falsely match SUCCESS).\n */\nexport function isKnownB2CDisbursementResultCode(code: unknown): boolean {\n if (code === null || code === undefined) return false\n if (typeof code !== 'number' && typeof code !== 'string') return false\n\n // String code check first (SFC_IC0003 is the only non-numeric documented code)\n if (typeof code === 'string' && code === B2C_DISBURSEMENT_RESULT_CODES.OPERATOR_DOES_NOT_EXIST) {\n return true\n }\n\n // Reject blank strings before numeric coercion\n if (typeof code === 'string' && code.trim() === '') return false\n\n const numeric = Number(code)\n const numericCodes = Object.values(B2C_DISBURSEMENT_RESULT_CODES).filter(\n (v) => typeof v === 'number',\n ) as number[]\n\n return numericCodes.includes(numeric)\n}\n\n// ── Field extractors ──────────────────────────────────────────────────────────\n\nexport function getB2CDisbursementTransactionId(result: B2CDisbursementResult): string | null {\n return result.Result.TransactionID ?? null\n}\n\nexport function getB2CDisbursementConversationId(result: B2CDisbursementResult): string {\n return result.Result.ConversationID\n}\n\nexport function getB2CDisbursementOriginatorConversationId(result: B2CDisbursementResult): string {\n return result.Result.OriginatorConversationID\n}\n\nexport function getB2CDisbursementResultDesc(result: B2CDisbursementResult): string {\n return result.Result.ResultDesc\n}\n\nexport function getB2CDisbursementResultCode(result: B2CDisbursementResult): number | string {\n return result.Result.ResultCode\n}\n\n// ── Result parameter extractors ───────────────────────────────────────────────\n\nexport function getB2CDisbursementResultParam(\n result: B2CDisbursementResult,\n key: string,\n): string | number | undefined {\n const params = result.Result.ResultParameters?.ResultParameter\n if (!params) return undefined\n const arr = Array.isArray(params)\n ? (params as B2CDisbursementResultParameter[])\n : [params as B2CDisbursementResultParameter]\n return arr.find((p) => p.Key === key)?.Value\n}\n\nexport function getB2CDisbursementAmount(result: B2CDisbursementResult): number | null {\n const value = getB2CDisbursementResultParam(result, 'TransactionAmount')\n if (value === undefined) return null\n const num = Number(value)\n return Number.isFinite(num) ? num : null\n}\n\nexport function getB2CDisbursementReceiptNumber(result: B2CDisbursementResult): string | null {\n const value = getB2CDisbursementResultParam(result, 'TransactionReceipt')\n if (value === undefined) return null\n return String(value)\n}\n\nexport function getB2CDisbursementReceiverName(result: B2CDisbursementResult): string | null {\n const value = getB2CDisbursementResultParam(result, 'ReceiverPartyPublicName')\n if (value === undefined) return null\n return String(value)\n}\n\nexport function getB2CDisbursementCompletedTime(result: B2CDisbursementResult): string | null {\n const value = getB2CDisbursementResultParam(result, 'TransactionCompletedDateTime')\n if (value === undefined) return null\n return String(value)\n}\n\nexport function getB2CDisbursementUtilityBalance(result: B2CDisbursementResult): number | null {\n const value = getB2CDisbursementResultParam(result, 'B2CUtilityAccountAvailableFunds')\n if (value === undefined) return null\n const num = Number(value)\n return Number.isFinite(num) ? num : null\n}\n\nexport function getB2CDisbursementWorkingBalance(result: B2CDisbursementResult): number | null {\n const value = getB2CDisbursementResultParam(result, 'B2CWorkingAccountAvailableFunds')\n if (value === undefined) return null\n const num = Number(value)\n return Number.isFinite(num) ? num : null\n}\n\nexport function isB2CDisbursementRecipientRegistered(\n result: B2CDisbursementResult,\n): boolean | null {\n const value = getB2CDisbursementResultParam(result, 'B2CRecipientIsRegisteredCustomer')\n if (value === undefined) return null\n return String(value).toUpperCase() === 'Y'\n}\n","import { z } from 'zod'\nimport { KesAmountSchema, NonEmptyStringSchema, UrlSchema } from './common'\n\nconst SendRemindersSchema = z.enum(['0', '1'])\n\nexport const BillManagerOptInRequestSchema = z.object({\n shortcode: NonEmptyStringSchema,\n email: NonEmptyStringSchema,\n officialContact: NonEmptyStringSchema,\n sendReminders: SendRemindersSchema,\n logo: z.string().optional(),\n callbackUrl: UrlSchema,\n})\n\nexport const BillManagerOptInResponseSchema = z\n .object({\n app_key: z.string().optional(),\n resmsg: z.string(),\n rescode: z.string(),\n })\n .passthrough()\n\nexport const BillManagerUpdateOptInRequestSchema = BillManagerOptInRequestSchema\nexport const BillManagerUpdateOptInResponseSchema = z\n .object({\n resmsg: z.string(),\n rescode: z.string(),\n })\n .passthrough()\n\nexport const BillManagerInvoiceItemSchema = z.object({\n itemName: NonEmptyStringSchema,\n amount: KesAmountSchema,\n})\n\nexport const BillManagerSingleInvoiceRequestSchema = z.object({\n externalReference: NonEmptyStringSchema,\n billedFullName: NonEmptyStringSchema,\n billedPhoneNumber: NonEmptyStringSchema,\n billedPeriod: NonEmptyStringSchema,\n invoiceName: NonEmptyStringSchema,\n dueDate: NonEmptyStringSchema,\n accountReference: NonEmptyStringSchema,\n amount: KesAmountSchema,\n invoiceItems: z.array(BillManagerInvoiceItemSchema).optional(),\n})\n\nexport const BillManagerSingleInvoiceResponseSchema = z\n .object({\n Status_Message: z.string().optional(),\n resmsg: z.string(),\n rescode: z.string(),\n })\n .passthrough()\n\nexport const BillManagerBulkInvoiceRequestSchema = z.object({\n invoices: z.array(BillManagerSingleInvoiceRequestSchema).min(1).max(1000),\n})\n\nexport const BillManagerBulkInvoiceResponseSchema = z\n .object({\n Status_Message: z.string().optional(),\n resmsg: z.string(),\n rescode: z.string(),\n })\n .passthrough()\n\nexport const BillManagerCancelInvoiceRequestSchema = z.object({\n externalReference: NonEmptyStringSchema,\n})\n\nexport const BillManagerCancelInvoiceResponseSchema = z\n .object({\n Status_Message: z.string().optional(),\n resmsg: z.string(),\n rescode: z.string(),\n errors: z.array(z.unknown()).optional(),\n })\n .passthrough()\n\nexport const BillManagerCancelBulkInvoiceRequestSchema = z.object({\n externalReferences: z.array(NonEmptyStringSchema).min(1),\n})\n\nexport const BillManagerCancelBulkInvoiceResponseSchema = z\n .object({\n Status_Message: z.string().optional(),\n resmsg: z.string(),\n rescode: z.string(),\n errors: z.array(z.unknown()).optional(),\n })\n .passthrough()\n\nexport const BillManagerReconciliationRequestSchema = z.object({\n paymentDate: NonEmptyStringSchema,\n paidAmount: NonEmptyStringSchema,\n accountReference: NonEmptyStringSchema,\n transactionId: NonEmptyStringSchema,\n phoneNumber: NonEmptyStringSchema,\n fullName: NonEmptyStringSchema,\n invoiceName: NonEmptyStringSchema,\n externalReference: NonEmptyStringSchema,\n})\n\nexport const BillManagerReconciliationResponseSchema = z\n .object({\n resmsg: z.string(),\n rescode: z.string(),\n })\n .passthrough()\n","// src/mpesa/bill-manager/invoice.ts\n\n/**\n * src/mpesa/bill-manager/invoice.ts\n *\n * Bill Manager — opt-in, invoice creation/cancellation, and payment reconciliation.\n *\n * Strictly aligned with Safaricom Daraja Bill Manager API documentation.\n *\n * APIs:\n * POST /v1/billmanager-invoice/optin — Opt-in shortcode\n * POST /v1/billmanager-invoice/change-optin-details — Update opt-in details\n * POST /v1/billmanager-invoice/single-invoicing — Send a single invoice\n * POST /v1/billmanager-invoice/bulk-invoicing — Send bulk invoices (up to 1000)\n * POST /v1/billmanager-invoice/cancel-single-invoice — Cancel a single invoice\n * POST /v1/billmanager-invoice/cancel-bulk-invoices — Cancel multiple invoices\n * POST /v1/billmanager-invoice/reconciliation — Acknowledge a payment\n */\n\nimport { parseWithSchema } from '../../core/validation/zod-error'\nimport {\n BillManagerBulkInvoiceRequestSchema,\n BillManagerBulkInvoiceResponseSchema,\n BillManagerCancelBulkInvoiceRequestSchema,\n BillManagerCancelBulkInvoiceResponseSchema,\n BillManagerCancelInvoiceRequestSchema,\n BillManagerCancelInvoiceResponseSchema,\n BillManagerOptInRequestSchema,\n BillManagerOptInResponseSchema,\n BillManagerReconciliationRequestSchema,\n BillManagerReconciliationResponseSchema,\n BillManagerSingleInvoiceRequestSchema,\n BillManagerSingleInvoiceResponseSchema,\n BillManagerUpdateOptInRequestSchema,\n BillManagerUpdateOptInResponseSchema,\n} from '../../schemas/bill-manager'\nimport { httpRequest, type DarajaHttpOptions, withDarajaHttp } from '../../utils/http'\nimport type {\n BillManagerBulkInvoiceRequest,\n BillManagerBulkInvoiceResponse,\n BillManagerCancelBulkInvoiceRequest,\n BillManagerCancelBulkInvoiceResponse,\n BillManagerCancelInvoiceRequest,\n BillManagerCancelInvoiceResponse,\n BillManagerOptInRequest,\n BillManagerOptInResponse,\n BillManagerReconciliationRequest,\n BillManagerReconciliationResponse,\n BillManagerSingleInvoiceRequest,\n BillManagerSingleInvoiceResponse,\n BillManagerUpdateOptInRequest,\n BillManagerUpdateOptInResponse,\n} from './types'\n\nexport async function billManagerOptIn(\n baseUrl: string,\n accessToken: string,\n request: BillManagerOptInRequest,\n http?: DarajaHttpOptions,\n): Promise<BillManagerOptInResponse> {\n const validated = parseWithSchema(\n BillManagerOptInRequestSchema,\n request,\n 'Bill Manager opt-in request',\n )\n\n const payload: Record<string, unknown> = {\n shortcode: validated.shortcode,\n email: validated.email,\n officialContact: validated.officialContact,\n sendReminders: validated.sendReminders,\n logo: validated.logo ?? '',\n callbackurl: validated.callbackUrl,\n }\n\n const { data } = await httpRequest<BillManagerOptInResponse>(\n `${baseUrl}/v1/billmanager-invoice/optin`,\n withDarajaHttp(\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n },\n http,\n ),\n )\n return parseWithSchema(\n BillManagerOptInResponseSchema,\n data,\n 'Bill Manager opt-in response',\n ) as BillManagerOptInResponse\n}\n\nexport async function updateOptIn(\n baseUrl: string,\n accessToken: string,\n request: BillManagerUpdateOptInRequest,\n http?: DarajaHttpOptions,\n): Promise<BillManagerUpdateOptInResponse> {\n const validated = parseWithSchema(\n BillManagerUpdateOptInRequestSchema,\n request,\n 'Bill Manager update opt-in request',\n )\n\n const payload: Record<string, unknown> = {\n shortcode: validated.shortcode,\n email: validated.email,\n officialContact: validated.officialContact,\n sendReminders: validated.sendReminders,\n logo: validated.logo ?? '',\n callbackurl: validated.callbackUrl,\n }\n\n const { data } = await httpRequest<BillManagerUpdateOptInResponse>(\n `${baseUrl}/v1/billmanager-invoice/change-optin-details`,\n withDarajaHttp(\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n },\n http,\n ),\n )\n return parseWithSchema(\n BillManagerUpdateOptInResponseSchema,\n data,\n 'Bill Manager update opt-in response',\n ) as BillManagerUpdateOptInResponse\n}\n\nexport async function sendSingleInvoice(\n baseUrl: string,\n accessToken: string,\n request: BillManagerSingleInvoiceRequest,\n http?: DarajaHttpOptions,\n): Promise<BillManagerSingleInvoiceResponse> {\n const validated = parseWithSchema(\n BillManagerSingleInvoiceRequestSchema,\n request,\n 'Bill Manager single invoice request',\n )\n const amount = Math.round(validated.amount)\n\n const payload: Record<string, unknown> = {\n externalReference: validated.externalReference,\n billedFullName: validated.billedFullName,\n billedPhoneNumber: validated.billedPhoneNumber,\n billedPeriod: validated.billedPeriod,\n invoiceName: validated.invoiceName,\n dueDate: validated.dueDate,\n accountReference: validated.accountReference,\n amount: String(amount),\n invoiceItems:\n validated.invoiceItems?.map((i) => ({\n itemName: i.itemName,\n amount: String(Math.round(i.amount)),\n })) ?? [],\n }\n\n const { data } = await httpRequest<BillManagerSingleInvoiceResponse>(\n `${baseUrl}/v1/billmanager-invoice/single-invoicing`,\n withDarajaHttp(\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n },\n http,\n ),\n )\n return parseWithSchema(\n BillManagerSingleInvoiceResponseSchema,\n data,\n 'Bill Manager single invoice response',\n ) as BillManagerSingleInvoiceResponse\n}\n\nexport async function sendBulkInvoices(\n baseUrl: string,\n accessToken: string,\n request: BillManagerBulkInvoiceRequest,\n http?: DarajaHttpOptions,\n): Promise<BillManagerBulkInvoiceResponse> {\n const validated = parseWithSchema(\n BillManagerBulkInvoiceRequestSchema,\n request,\n 'Bill Manager bulk invoice request',\n )\n\n const payload = validated.invoices.map((inv) => ({\n externalReference: inv.externalReference,\n billedFullName: inv.billedFullName,\n billedPhoneNumber: inv.billedPhoneNumber,\n billedPeriod: inv.billedPeriod,\n invoiceName: inv.invoiceName,\n dueDate: inv.dueDate,\n accountReference: inv.accountReference,\n amount: String(Math.round(inv.amount)),\n invoiceItems:\n inv.invoiceItems?.map((item) => ({\n itemName: item.itemName,\n amount: String(Math.round(item.amount)),\n })) ?? [],\n }))\n\n const { data } = await httpRequest<BillManagerBulkInvoiceResponse>(\n `${baseUrl}/v1/billmanager-invoice/bulk-invoicing`,\n withDarajaHttp(\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n },\n http,\n ),\n )\n return parseWithSchema(\n BillManagerBulkInvoiceResponseSchema,\n data,\n 'Bill Manager bulk invoice response',\n ) as BillManagerBulkInvoiceResponse\n}\n\nexport async function cancelInvoice(\n baseUrl: string,\n accessToken: string,\n request: BillManagerCancelInvoiceRequest,\n http?: DarajaHttpOptions,\n): Promise<BillManagerCancelInvoiceResponse> {\n const validated = parseWithSchema(\n BillManagerCancelInvoiceRequestSchema,\n request,\n 'Bill Manager cancel invoice request',\n )\n\n const { data } = await httpRequest<BillManagerCancelInvoiceResponse>(\n `${baseUrl}/v1/billmanager-invoice/cancel-single-invoice`,\n withDarajaHttp(\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${accessToken}` },\n body: { externalReference: validated.externalReference },\n },\n http,\n ),\n )\n return parseWithSchema(\n BillManagerCancelInvoiceResponseSchema,\n data,\n 'Bill Manager cancel invoice response',\n ) as BillManagerCancelInvoiceResponse\n}\n\nexport async function cancelBulkInvoices(\n baseUrl: string,\n accessToken: string,\n request: BillManagerCancelBulkInvoiceRequest,\n http?: DarajaHttpOptions,\n): Promise<BillManagerCancelBulkInvoiceResponse> {\n const validated = parseWithSchema(\n BillManagerCancelBulkInvoiceRequestSchema,\n request,\n 'Bill Manager cancel bulk invoices request',\n )\n\n const payload = validated.externalReferences.map((ref) => ({ externalReference: ref }))\n\n const { data } = await httpRequest<BillManagerCancelBulkInvoiceResponse>(\n `${baseUrl}/v1/billmanager-invoice/cancel-bulk-invoices`,\n withDarajaHttp(\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n },\n http,\n ),\n )\n return parseWithSchema(\n BillManagerCancelBulkInvoiceResponseSchema,\n data,\n 'Bill Manager cancel bulk invoices response',\n ) as BillManagerCancelBulkInvoiceResponse\n}\n\nexport async function reconcilePayment(\n baseUrl: string,\n accessToken: string,\n request: BillManagerReconciliationRequest,\n http?: DarajaHttpOptions,\n): Promise<BillManagerReconciliationResponse> {\n const validated = parseWithSchema(\n BillManagerReconciliationRequestSchema,\n request,\n 'Bill Manager reconciliation request',\n )\n\n const payload: Record<string, unknown> = {\n paymentDate: validated.paymentDate,\n paidAmount: validated.paidAmount,\n accountReference: validated.accountReference,\n transactionId: validated.transactionId,\n phoneNumber: validated.phoneNumber,\n fullName: validated.fullName,\n invoiceName: validated.invoiceName,\n externalReference: validated.externalReference,\n }\n\n const { data } = await httpRequest<BillManagerReconciliationResponse>(\n `${baseUrl}/v1/billmanager-invoice/reconciliation`,\n withDarajaHttp(\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n },\n http,\n ),\n )\n return parseWithSchema(\n BillManagerReconciliationResponseSchema,\n data,\n 'Bill Manager reconciliation response',\n ) as BillManagerReconciliationResponse\n}\n","import { z } from 'zod'\nimport { KesAmountSchema, NonEmptyStringSchema, UrlSchema } from './common'\n\nexport const C2BRegisterUrlRequestSchema = z.object({\n shortCode: NonEmptyStringSchema,\n responseType: z.enum(['Completed', 'Cancelled']),\n confirmationUrl: UrlSchema,\n validationUrl: UrlSchema,\n apiVersion: z.enum(['v1', 'v2']).optional(),\n})\n\nexport const C2BBaseResponseSchema = z\n .object({\n OriginatorCoversationID: z.string(),\n ResponseCode: z.string(),\n ResponseDescription: z.string(),\n })\n .passthrough()\n\nexport const C2BRegisterUrlResponseSchema = C2BBaseResponseSchema\n\nexport const C2BSimulateResponseSchema = C2BBaseResponseSchema\n\nexport const C2BSimulateRequestSchema = z\n .object({\n shortCode: z.union([NonEmptyStringSchema, z.number()]),\n commandId: z.enum(['CustomerPayBillOnline', 'CustomerBuyGoodsOnline']),\n amount: KesAmountSchema,\n msisdn: z.union([NonEmptyStringSchema, z.number()]),\n billRefNumber: z.union([NonEmptyStringSchema, z.null()]).optional(),\n apiVersion: z.enum(['v1', 'v2']).optional(),\n })\n .superRefine((data, ctx) => {\n if (data.commandId === 'CustomerPayBillOnline' && !data.billRefNumber?.trim()) {\n ctx.addIssue({\n code: 'custom',\n message: 'billRefNumber is required for CustomerPayBillOnline',\n path: ['billRefNumber'],\n })\n }\n })\n\nexport const C2BValidationWebhookSchema = z\n .object({\n TransactionType: z.string(),\n TransID: z.string(),\n TransTime: z.string(),\n TransAmount: z.union([z.string(), z.number()]),\n BusinessShortCode: z.string(),\n BillRefNumber: z.string().optional(),\n MSISDN: z.string(),\n })\n .passthrough()\n","/**\n * src/mpesa/c2b/register-url.ts\n *\n * C2B Register URL implementation.\n * Strictly aligned with Safaricom Daraja C2B Register URL API documentation.\n *\n * Endpoint (v1 — documented primary):\n * Sandbox: POST https://sandbox.safaricom.co.ke/mpesa/c2b/v1/registerurl\n * Production: POST https://api.safaricom.co.ke/mpesa/c2b/v1/registerurl\n *\n * Also supports v2 via the apiVersion option.\n */\n\nimport { parseWithSchema } from '../../core/validation/zod-error'\nimport { C2BRegisterUrlRequestSchema, C2BRegisterUrlResponseSchema } from '../../schemas/c2b'\nimport { createError } from '../../utils/errors'\nimport { httpRequest, type DarajaHttpOptions, withDarajaHttp } from '../../utils/http'\nimport type { C2BApiVersion, C2BRegisterUrlRequest, C2BRegisterUrlResponse } from './types'\n\n/**\n * Forbidden URL keywords per Daraja documentation:\n * \"Avoid keywords such as M-PESA, M-Pesa, Safaricom, exe, exec, cme, or variants in your URLs.\"\n *\n * We lowercase-compare, so \"MPESA\", \"Mpesa\", \"mPeSa\" are all caught.\n *\n * Additional blocked keywords (documented variants): cmd, sql, query\n */\nconst FORBIDDEN_URL_KEYWORDS: readonly string[] = [\n 'mpesa',\n 'safaricom',\n 'exec',\n 'exe',\n 'cme', // explicitly documented by Daraja\n 'cmd', // documented variant of cme\n 'sql',\n 'query',\n] as const\n\n/**\n * Validates a callback URL against Daraja's documented URL requirements.\n * Throws PesafyError if the URL violates any documented 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: mpesa, safaricom, exe, exec, cme ` +\n `(and variants: cmd, sql, query).`,\n })\n }\n }\n}\n\n/**\n * Registers C2B Confirmation and Validation URLs with Safaricom.\n *\n * Per Daraja documentation:\n * - Sandbox: may be called multiple times (URLs can be overwritten).\n * - Production: one-time call. To change URLs, delete existing on the portal\n * or email apisupport@safaricom.co.ke, then re-register.\n * - ResponseType must be sentence-case: \"Completed\" or \"Cancelled\".\n * - Both URLs must be publicly accessible and internet-reachable.\n * - Production requires HTTPS; Sandbox allows HTTP.\n * - Do not use public URL testers (ngrok, mockbin, requestbin) — they are blocked.\n * - The Validation URL is only called when external validation is enabled.\n * To activate, email apisupport@safaricom.co.ke.\n * - If M-PESA cannot reach your Validation URL within ~8 seconds, it defaults\n * to the ResponseType action set during registration.\n *\n * @param baseUrl - Daraja environment base URL\n * @param accessToken - Valid OAuth bearer token from Authorization API\n * @param request - Registration parameters\n * @returns - Daraja registration response (ResponseCode \"0\" = success)\n */\nexport async function registerC2BUrls(\n baseUrl: string,\n accessToken: string,\n request: C2BRegisterUrlRequest,\n http?: DarajaHttpOptions,\n): Promise<C2BRegisterUrlResponse> {\n const validated = parseWithSchema(\n C2BRegisterUrlRequestSchema,\n request,\n 'C2B Register URL request',\n )\n\n validateCallbackUrl(validated.confirmationUrl, 'confirmationUrl')\n validateCallbackUrl(validated.validationUrl, 'validationUrl')\n\n const version: C2BApiVersion = validated.apiVersion ?? 'v2'\n\n const payload = {\n ShortCode: String(validated.shortCode),\n ResponseType: validated.responseType,\n ConfirmationURL: validated.confirmationUrl,\n ValidationURL: validated.validationUrl,\n }\n\n const { data } = await httpRequest<C2BRegisterUrlResponse>(\n `${baseUrl}/mpesa/c2b/${version}/registerurl`,\n withDarajaHttp(\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n },\n http,\n ),\n )\n\n return parseWithSchema(\n C2BRegisterUrlResponseSchema,\n data,\n 'C2B Register URL response',\n ) as C2BRegisterUrlResponse\n}\n","/**\n * src/mpesa/c2b/simulate.ts\n *\n * C2B Simulate implementation (Sandbox ONLY).\n * Strictly aligned with Safaricom Daraja C2B API documentation.\n *\n * Per docs: \"NB: Simulation is not supported on production.\"\n *\n * Endpoint (v2, sandbox only):\n * POST https://sandbox.safaricom.co.ke/mpesa/c2b/v2/simulate\n */\n\nimport { parseWithSchema } from '../../core/validation/zod-error'\nimport { C2BSimulateRequestSchema, C2BSimulateResponseSchema } from '../../schemas/c2b'\nimport { createError } from '../../utils/errors'\nimport { httpRequest, type DarajaHttpOptions, withDarajaHttp } from '../../utils/http'\nimport type { C2BApiVersion, C2BSimulateRequest, C2BSimulateResponse } from './types'\n\n/**\n * Simulates a C2B customer payment. SANDBOX ONLY.\n *\n * Daraja payload shape:\n * {\n * \"ShortCode\": 600984, ← numeric\n * \"CommandID\": \"CustomerPayBillOnline\",\n * \"Amount\": 1, ← numeric, whole number ≥ 1\n * \"Msisdn\": 254708374149, ← numeric\n * \"BillRefNumber\": \"AccountRef\" ← Paybill only; OMIT for BuyGoods\n * }\n *\n * CRITICAL — BillRefNumber handling (per docs):\n * \"Account reference for Customer paybills and null for customer buy goods\"\n * We omit the key entirely for BuyGoods (not null, not \"\") because Daraja\n * validates field presence and rejects even null/empty values for Buy Goods.\n *\n * @param baseUrl - Must be the sandbox base URL\n * @param accessToken - Valid OAuth bearer token from Authorization API\n * @param request - Simulation parameters\n * @returns - Daraja simulate response (ResponseCode \"0\" = accepted)\n */\nexport async function simulateC2B(\n baseUrl: string,\n accessToken: string,\n request: C2BSimulateRequest,\n http?: DarajaHttpOptions,\n): Promise<C2BSimulateResponse> {\n const validated = parseWithSchema(C2BSimulateRequestSchema, request, 'C2B Simulate request')\n\n if (!baseUrl.includes('sandbox')) {\n throw createError({\n code: 'VALIDATION_ERROR',\n message:\n 'C2B simulate is only available in the Sandbox environment (per Daraja docs). ' +\n 'In production, customers initiate payments directly via M-PESA App, USSD, or SIM Toolkit.',\n })\n }\n\n const amount = Math.round(validated.amount)\n const isBuyGoods = validated.commandId === 'CustomerBuyGoodsOnline'\n const version: C2BApiVersion = validated.apiVersion ?? 'v2'\n\n // ── Build payload ───────────────────────────────────────────────────────────\n //\n // ShortCode and Msisdn are sent as numbers per Daraja docs.\n // BillRefNumber is ONLY included for CustomerPayBillOnline.\n // For CustomerBuyGoodsOnline the key must be absent from the payload.\n //\n const payload: Record<string, unknown> = {\n ShortCode: Number(validated.shortCode),\n CommandID: validated.commandId,\n Amount: amount,\n Msisdn: Number(validated.msisdn),\n }\n\n if (!isBuyGoods) {\n payload['BillRefNumber'] = validated.billRefNumber!.trim()\n }\n\n const { data } = await httpRequest<C2BSimulateResponse>(\n `${baseUrl}/mpesa/c2b/${version}/simulate`,\n withDarajaHttp(\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n },\n http,\n ),\n )\n\n return parseWithSchema(\n C2BSimulateResponseSchema,\n data,\n 'C2B Simulate response',\n ) as C2BSimulateResponse\n}\n","/**\n * src/mpesa/c2b/webhooks.ts\n *\n * C2B webhook callback helpers.\n * Strictly aligned with Safaricom Daraja C2B API documentation.\n *\n * Process flow (per docs):\n * - External Validation DISABLED (default):\n * M-PESA automatically completes → sends ONE Confirmation to your ConfirmationURL.\n * - External Validation ENABLED (opt-in via apisupport@safaricom.co.ke):\n * M-PESA → Validation request → your response → M-PESA processes\n * → if accepted: Confirmation request (you receive TWO callbacks)\n * → if rejected or timeout: cancelled, NO Confirmation sent\n *\n * Callback TransactionType (per docs):\n * \"Pay Bill\" for Paybill payments\n * \"Buy Goods\" for Till payments\n * (NOT the request CommandID strings)\n */\n\nimport type {\n C2BConfirmationAck,\n C2BConfirmationPayload,\n C2BValidationPayload,\n C2BValidationResponse,\n C2BValidationResultCode,\n} from './types'\n\n// ── Runtime type guard ────────────────────────────────────────────────────────\n\n/**\n * Returns true if `body` looks like a valid C2B callback payload.\n * Works for both Validation and Confirmation payloads.\n *\n * Checks the minimum required fields from the Daraja docs payload sample:\n * - TransID\n * - BusinessShortCode\n * - TransAmount\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 response helpers ───────────────────────────────────────────────\n\n/**\n * Builds an \"accept\" validation response.\n *\n * Per Daraja docs (to accept the payment):\n * { \"ResultCode\": \"0\", \"ResultDesc\": \"Accepted\" }\n *\n * @param thirdPartyTransID - Optional: echo back the ThirdPartyTransID received\n * in the validation request. M-PESA includes it in the confirmation callback.\n */\nexport function acceptC2BValidation(thirdPartyTransID?: string): C2BValidationResponse {\n return {\n ResultCode: '0',\n ResultDesc: 'Accepted',\n ...(thirdPartyTransID ? { ThirdPartyTransID: thirdPartyTransID } : {}),\n }\n}\n\n/**\n * Builds a \"reject\" validation response.\n *\n * Per Daraja docs (to reject the payment):\n * { \"ResultCode\": \"C2B00011\", \"ResultDesc\": \"Rejected\" }\n *\n * Documented result codes and their meanings:\n * C2B00011 — Invalid MSISDN\n * C2B00012 — Invalid Account Number\n * C2B00013 — Invalid Amount\n * C2B00014 — Invalid KYC Details\n * C2B00015 — Invalid Shortcode\n * C2B00016 — Other Error (default)\n *\n * @param resultCode - Must NOT be \"0\". Defaults to \"C2B00016\" (Other Error).\n */\nexport function rejectC2BValidation(\n resultCode: Exclude<C2BValidationResultCode, '0'> = 'C2B00016',\n): C2BValidationResponse {\n return {\n ResultCode: resultCode,\n ResultDesc: 'Rejected',\n }\n}\n\n/**\n * Builds the confirmation acknowledgement your ConfirmationURL must return.\n * Always respond with ResultCode 0 to acknowledge receipt.\n *\n * Per docs process flow: M-PESA expects an acknowledgement response.\n * Failure to acknowledge may cause M-PESA to retry the confirmation.\n */\nexport function acknowledgeC2BConfirmation(): C2BConfirmationAck {\n return { ResultCode: 0, ResultDesc: 'Success' }\n}\n\n// ── Payload extractors ────────────────────────────────────────────────────────\n\n/**\n * Extracts the transaction amount as a number from a C2B callback payload.\n * TransAmount is a string in the payload (e.g. \"10\" per docs sample).\n */\nexport function getC2BAmount(payload: C2BValidationPayload | C2BConfirmationPayload): number {\n return Number(payload.TransAmount)\n}\n\n/**\n * Extracts the M-PESA transaction ID (receipt) from a C2B callback payload.\n * Example from docs: \"RKTQDM7W6S\"\n */\nexport function getC2BTransactionId(\n payload: C2BValidationPayload | C2BConfirmationPayload,\n): string {\n return payload.TransID\n}\n\n/**\n * Extracts the account reference (BillRefNumber) from a C2B callback payload.\n * Example from docs: \"invoice008\"\n * Empty string for Buy Goods payments (no account reference).\n */\nexport function getC2BAccountRef(payload: C2BValidationPayload | C2BConfirmationPayload): string {\n return payload.BillRefNumber\n}\n\n/**\n * Returns the customer's full name from a C2B callback payload.\n * Joins FirstName, MiddleName, LastName; skips empty parts.\n * Per docs: customer names \"can be empty\".\n */\nexport function getC2BCustomerName(payload: C2BValidationPayload | C2BConfirmationPayload): string {\n return [payload.FirstName, payload.MiddleName, payload.LastName].filter(Boolean).join(' ').trim()\n}\n\n// ── Transaction type helpers ──────────────────────────────────────────────────\n\n/**\n * Returns true if the payload is a Paybill payment.\n *\n * Per Daraja docs, the callback TransactionType for Paybill is \"Pay Bill\"\n * (NOT \"CustomerPayBillOnline\" which is the request CommandID).\n */\nexport function isPaybillPayment(payload: C2BValidationPayload | C2BConfirmationPayload): boolean {\n return payload.TransactionType === 'Pay Bill'\n}\n\n/**\n * Returns true if the payload is a Buy Goods (Till) payment.\n *\n * Per Daraja docs, the callback TransactionType for Buy Goods is \"Buy Goods\"\n * (NOT \"CustomerBuyGoodsOnline\" which is the request CommandID).\n */\nexport function isBuyGoodsPayment(payload: C2BValidationPayload | C2BConfirmationPayload): boolean {\n return payload.TransactionType === 'Buy Goods'\n}\n","/**\n * src/mpesa/dynamic-qr/generate.ts\n *\n * Core logic for the Safaricom Daraja Dynamic QR Code API.\n *\n * API: POST /mpesa/qrcode/v1/generate\n *\n * Error codes from Daraja docs:\n * 404.001.04 — Invalid Authentication Header\n * 400.002.05 — Invalid Request Payload\n * 400.003.01 — Invalid Access Token\n */\n\nimport { parseWithSchema } from '../../core/validation/zod-error'\nimport { DynamicQRRequestSchema, DynamicQRResponseSchema } from '../../schemas/async-apis'\nimport { PesafyError } from '../../utils/errors'\nimport { httpRequest, type DarajaHttpOptions, withDarajaHttp } from '../../utils/http'\nimport type {\n DynamicQRDarajaPayload,\n DynamicQRErrorResponse,\n DynamicQRRequest,\n DynamicQRResponse,\n} from './types'\nimport { DEFAULT_QR_SIZE } from './validators'\n\n// ── Daraja error code → PesafyError mapping ───────────────────────────────────\n\n/**\n * Maps Daraja-specific error codes to structured PesafyErrors with\n * actionable developer guidance.\n *\n * @internal\n */\nfunction mapDarajaError(errorCode: string, errorMessage: string): PesafyError {\n switch (errorCode) {\n case '404.001.04':\n return new PesafyError({\n code: 'AUTH_FAILED',\n message:\n 'Daraja rejected the request due to an invalid authentication header. ' +\n 'Ensure the Dynamic QR endpoint is called with POST and that the ' +\n `Authorization: Bearer <token> header is present. Daraja: \"${errorMessage}\"`,\n statusCode: 404,\n })\n\n case '400.003.01':\n return new PesafyError({\n code: 'AUTH_FAILED',\n message:\n 'The M-PESA access token is invalid or has expired. ' +\n 'Call clearTokenCache() on the Mpesa instance to force a token refresh ' +\n `and retry the request. Daraja: \"${errorMessage}\"`,\n statusCode: 401,\n })\n\n case '400.002.05':\n return new PesafyError({\n code: 'VALIDATION_ERROR',\n message:\n 'Daraja rejected the request payload as malformed. ' +\n 'Verify that all required fields (MerchantName, RefNo, Amount, TrxCode, CPI, Size) ' +\n `are present and have correct types. Daraja: \"${errorMessage}\"`,\n statusCode: 400,\n })\n\n default:\n return new PesafyError({\n code: 'REQUEST_FAILED',\n message: `Dynamic QR request failed (${errorCode}): ${errorMessage}`,\n statusCode: 400,\n })\n }\n}\n\n/**\n * Returns `true` when the raw response body looks like a Daraja error object.\n * @internal\n */\nfunction isDarajaError(body: unknown): body is DynamicQRErrorResponse {\n return (\n typeof body === 'object' &&\n body !== null &&\n 'errorCode' in body &&\n typeof (body as DynamicQRErrorResponse).errorCode === 'string'\n )\n}\n\n/**\n * Returns `true` when the response has the required QR success fields.\n * @internal\n */\nfunction isDarajaSuccess(body: unknown): body is DynamicQRResponse {\n return (\n typeof body === 'object' &&\n body !== null &&\n 'ResponseCode' in body &&\n 'QRCode' in body &&\n typeof (body as DynamicQRResponse).QRCode === 'string' &&\n (body as DynamicQRResponse).QRCode.length > 0\n )\n}\n\n// ── Public API ────────────────────────────────────────────────────────────────\n\n/**\n * Generates a Dynamic M-PESA QR Code via the Safaricom Daraja API.\n *\n * The QR code can be rendered directly in a browser as a base64 PNG:\n * ```html\n * <img src=\"data:image/png;base64,{response.QRCode}\" />\n * ```\n * Or written to disk:\n * ```ts\n * import { writeFileSync } from 'node:fs'\n * writeFileSync('qr.png', Buffer.from(response.QRCode, 'base64'))\n * ```\n *\n * @param baseUrl - Daraja base URL (`https://sandbox.safaricom.co.ke` or\n * `https://api.safaricom.co.ke`)\n * @param accessToken - Valid Daraja OAuth2 Bearer token\n * @param request - QR generation parameters (see {@link DynamicQRRequest})\n * @returns - Daraja response including the base64-encoded QR image\n *\n * @throws {PesafyError} `VALIDATION_ERROR` — payload failed pre-flight checks\n * @throws {PesafyError} `AUTH_FAILED` — bad/expired token or wrong headers\n * @throws {PesafyError} `REQUEST_FAILED` — unexpected Daraja error\n *\n * @example\n * ```ts\n * const response = await generateDynamicQR(\n * 'https://sandbox.safaricom.co.ke',\n * accessToken,\n * {\n * merchantName: 'Test Supermarket',\n * refNo: 'INV-001',\n * amount: 500,\n * trxCode: 'BG',\n * cpi: '373132',\n * size: 300,\n * },\n * )\n * console.log(response.QRCode) // base64 PNG\n * ```\n */\nexport async function generateDynamicQR(\n baseUrl: string,\n accessToken: string,\n request: DynamicQRRequest,\n http?: DarajaHttpOptions,\n): Promise<DynamicQRResponse> {\n const validated = parseWithSchema(DynamicQRRequestSchema, request, 'Dynamic QR request')\n\n // ── Guard: access token must be non-empty ───────────────────────────────\n\n if (!accessToken || typeof accessToken !== 'string' || accessToken.trim().length === 0) {\n throw new PesafyError({\n code: 'AUTH_FAILED',\n message:\n 'accessToken is required. Obtain one via the Daraja Authorization API ' +\n '(GET /oauth/v1/generate?grant_type=client_credentials).',\n })\n }\n\n // ── 3. Build Daraja payload (camelCase → PascalCase, size coerced to string) ─\n\n const size = validated.size ?? DEFAULT_QR_SIZE\n const amount = Math.round(validated.amount)\n\n const payload: DynamicQRDarajaPayload = {\n MerchantName: validated.merchantName.trim(),\n RefNo: validated.refNo.trim(),\n Amount: amount,\n TrxCode: validated.trxCode,\n CPI: validated.cpi.trim(),\n Size: String(size),\n }\n\n // ── 4. HTTP request ────────────────────────────────────────────────────────\n\n const url = `${baseUrl}/mpesa/qrcode/v1/generate`\n\n const { data } = await httpRequest<DynamicQRResponse | DynamicQRErrorResponse>(\n url,\n withDarajaHttp(\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${accessToken}`,\n },\n body: payload,\n },\n http,\n ),\n )\n\n // ── 5. Handle Daraja error responses ──────────────────────────────────────\n\n if (isDarajaError(data)) {\n throw mapDarajaError(data.errorCode, data.errorMessage)\n }\n\n // ── 6. Guard against malformed success responses ──────────────────────────\n\n if (!isDarajaSuccess(data)) {\n throw new PesafyError({\n code: 'REQUEST_FAILED',\n message:\n 'Daraja returned an unexpected response structure for the Dynamic QR request. ' +\n 'The response was missing required fields (ResponseCode, QRCode). ' +\n `Raw response: ${JSON.stringify(data).slice(0, 300)}`,\n })\n }\n\n return parseWithSchema(DynamicQRResponseSchema, data, 'Dynamic QR response') as DynamicQRResponse\n}\n","/**\n * src/mpesa/reversal/types.ts\n *\n * Transaction Reversal types, constants, and helpers.\n *\n * API: POST /mpesa/reversal/v1/request\n *\n * Reverses a completed M-PESA C2B transaction. The API is ASYNCHRONOUS —\n * the synchronous response is only an acknowledgement. The actual reversal\n * result is POSTed to your ResultURL after processing.\n *\n * Required org portal role: \"Org Reversals Initiator\"\n *\n * Per Daraja docs:\n * RecieverIdentifierType MUST always be \"11\" for reversals.\n * CommandID MUST always be \"TransactionReversal\".\n *\n * Ref: Reversals — Safaricom Daraja Developer Portal\n */\n\n// ── Constants ──────────────────────────────────────────────────────────────────\n\n/**\n * CommandID for the Reversals API.\n * Only \"TransactionReversal\" is allowed per Daraja docs.\n */\nexport const REVERSAL_COMMAND_ID = 'TransactionReversal' as const\n\n/**\n * RecieverIdentifierType for the Reversals API.\n * Per Daraja docs: \"Type of Organization (should be '11')\".\n * This value is fixed for all reversal requests.\n */\nexport const REVERSAL_RECEIVER_IDENTIFIER_TYPE = '11' as const\n\n/**\n * Result codes returned in the async callback POSTed to your ResultURL.\n *\n * Per Daraja Reversals documentation:\n *\n * | ResultCode | Meaning |\n * |------------|----------------------------------------------------|\n * | 0 | Success — transaction reversed |\n * | 1 | Insufficient balance |\n * | 11 | DebitParty in invalid state (shortcode not active) |\n * | 21 | Initiator not allowed to initiate reversals |\n * | 2001 | Initiator information invalid (bad credentials) |\n * | 2006 | Declined due to account rule (shortcode inactive) |\n * | 2028 | Not permitted (shortcode lacks reversal permission)|\n * | 8006 | Security credential locked |\n * | R000001 | Transaction already reversed |\n * | R000002 | OriginalTransactionID is invalid / does not exist |\n */\nexport const REVERSAL_RESULT_CODES = {\n /** Transaction reversed successfully */\n SUCCESS: 0,\n /** Organisation shortcode has insufficient balance */\n INSUFFICIENT_BALANCE: 1,\n /** DebitParty (shortcode) is in an invalid state */\n DEBIT_PARTY_INVALID_STATE: 11,\n /** Initiator does not have the Org Reversals Initiator role */\n INITIATOR_NOT_ALLOWED: 21,\n /** API user credentials are invalid */\n INITIATOR_INFORMATION_INVALID: 2001,\n /** Organisation/shortcode account is not active */\n DECLINED_ACCOUNT_RULE: 2006,\n /** Shortcode has no permission to perform this reversal */\n NOT_PERMITTED: 2028,\n /** API user password is locked — too many failed attempts */\n SECURITY_CREDENTIAL_LOCKED: 8006,\n /** The TransactionID has already been reversed */\n ALREADY_REVERSED: 'R000001',\n /** The TransactionID is invalid or does not exist on M-PESA */\n INVALID_TRANSACTION_ID: 'R000002',\n} as const satisfies Record<string, number | string>\n\nexport type ReversalResultCode = (typeof REVERSAL_RESULT_CODES)[keyof typeof REVERSAL_RESULT_CODES]\n\n/**\n * API-level error codes returned in the synchronous HTTP error response.\n *\n * Per Daraja Reversals error response documentation:\n *\n * | errorCode | Meaning | HTTP |\n * |-----------------|-------------------------------------------------|------|\n * | 404.001.03 | Invalid Access Token | 404 |\n * | 400.002.02 | Bad Request — Invalid payload field | 400 |\n * | 404.001.01 | Resource not found (wrong endpoint) | 404 |\n * | 500.001.1001 | Internal Server Error | 500 |\n * | 500.003.02 | Spike Arrest Violation (TPS limit exceeded) | 500 |\n * | 500.003.03 | Quota Violation (API request limit exceeded) | 500 |\n */\nexport const REVERSAL_ERROR_CODES = {\n /** Access token is invalid or expired. Regenerate and retry. */\n INVALID_ACCESS_TOKEN: '404.001.03',\n /** Request payload is malformed or missing required fields. */\n BAD_REQUEST: '400.002.02',\n /** Wrong endpoint URL — verify you are calling /mpesa/reversal/v1/request */\n RESOURCE_NOT_FOUND: '404.001.01',\n /** Internal server error on Daraja. Verify payload matches documentation. */\n INTERNAL_SERVER_ERROR: '500.001.1001',\n /** Too many requests per second — reduce TPS. */\n SPIKE_ARREST: '500.003.02',\n /** API request quota exceeded. */\n QUOTA_VIOLATION: '500.003.03',\n} as const\n\nexport type ReversalErrorCode = (typeof REVERSAL_ERROR_CODES)[keyof typeof REVERSAL_ERROR_CODES]\n\n// ── ResultParameter keys ──────────────────────────────────────────────────────\n\n/**\n * Keys present in the ResultParameters.ResultParameter array of a successful\n * reversal callback, as documented by Daraja.\n */\nexport type ReversalResultParameterKey =\n | 'DebitAccountBalance'\n | 'Amount'\n | 'TransCompletedTime'\n | 'OriginalTransactionID'\n | 'Charge'\n | 'CreditPartyPublicName'\n | 'DebitPartyPublicName'\n\n// ── Request ───────────────────────────────────────────────────────────────────\n\n/**\n * Parameters for the Reversals API request.\n *\n * Daraja request body shape:\n * ```json\n * {\n * \"Initiator\": \"apiop37\",\n * \"SecurityCredential\": \"RC6E9WDx9X2c6z3gp0oC5Th==\",\n * \"CommandID\": \"TransactionReversal\",\n * \"TransactionID\": \"PDU91HIVIT\",\n * \"Amount\": \"200\",\n * \"ReceiverParty\": \"603021\",\n * \"RecieverIdentifierType\":\"11\",\n * \"ResultURL\": \"https://example.org/reversal/result\",\n * \"QueueTimeOutURL\": \"https://example.org/reversal/queue\",\n * \"Remarks\": \"Payment reversal\"\n * }\n * ```\n *\n * Note: Initiator and SecurityCredential are supplied by the SDK layer;\n * they are not part of this user-facing request interface.\n */\nexport interface ReversalRequest {\n /**\n * The M-PESA receipt number of the transaction to reverse.\n * Example: \"PDU91HIVIT\"\n * Daraja field: TransactionID\n */\n transactionId: string\n\n /**\n * Your organisation shortcode (Paybill or Till number).\n * Daraja field: ReceiverParty\n */\n receiverParty: string\n\n /**\n * Identifier type for ReceiverParty.\n * Per Daraja docs: MUST be \"11\" (Organisation/ShortCode reversal type).\n * This value is validated and always sent as \"11\".\n * Daraja field: RecieverIdentifierType (note Daraja's spelling)\n *\n * @default \"11\"\n */\n receiverIdentifierType?: '11'\n\n /**\n * Amount to reverse. Must equal the original transaction amount.\n * Must be a whole number ≥ 1.\n * Daraja field: Amount (sent as string per Daraja docs sample)\n */\n amount: number\n\n /**\n * URL where Safaricom POSTs the reversal result asynchronously.\n * Must be publicly accessible over the internet.\n * Daraja field: ResultURL\n */\n resultUrl: string\n\n /**\n * URL called when the request times out in the Daraja queue.\n * Must be publicly accessible over the internet.\n * Daraja field: QueueTimeOutURL\n */\n queueTimeOutUrl: string\n\n /**\n * Short description of the reversal reason (2–100 characters).\n * Daraja field: Remarks\n * @default \"Transaction Reversal\"\n */\n remarks?: string\n\n /**\n * Optional occasion/reference string.\n * Not in the Daraja sample payload but accepted by the API.\n * Daraja field: Occasion\n */\n occasion?: string\n}\n\n// ── Synchronous acknowledgement ───────────────────────────────────────────────\n\n/**\n * Synchronous response from the Reversals API.\n *\n * This is only an acknowledgement that the request was received.\n * The actual reversal result arrives asynchronously at your ResultURL.\n *\n * Daraja response shape:\n * ```json\n * {\n * \"OriginatorConversationID\": \"f1e2-4b95-a71d-b30d3cdbb7a7735297\",\n * \"ConversationID\": \"AG_20210706_20106e9209f64bebd05b\",\n * \"ResponseCode\": \"0\",\n * \"ResponseDescription\": \"Accept the service request successfully.\"\n * }\n * ```\n */\nexport interface ReversalResponse {\n /** Unique identifier for this reversal request from M-PESA */\n OriginatorConversationID: string\n /** Unique global identifier for the transaction request */\n ConversationID: string\n /**\n * \"0\" = request accepted successfully.\n * Any other value = API-level error (not reversal failure).\n */\n ResponseCode: string\n /** Human-readable acknowledgement message */\n ResponseDescription: string\n}\n\n// ── Async result callback (POSTed to ResultURL) ───────────────────────────────\n\n/**\n * Result parameter item in a successful reversal callback.\n */\nexport interface ReversalResultParameter {\n Key: ReversalResultParameterKey\n Value: string | number\n}\n\n/**\n * Full async reversal result POSTed to your ResultURL after processing.\n *\n * Successful callback shape (per Daraja docs):\n * ```json\n * {\n * \"Result\": {\n * \"ResultType\": 0,\n * \"ResultCode\": 0,\n * \"ResultDesc\": \"The service request is processed successfully.\",\n * \"OriginatorConversationID\": \"...\",\n * \"ConversationID\": \"AG_...\",\n * \"TransactionID\": \"SKE52PAWR9\",\n * \"ResultParameters\": {\n * \"ResultParameter\": [\n * { \"Key\": \"DebitAccountBalance\", \"Value\": \"Utility Account|KES|...\" },\n * { \"Key\": \"Amount\", \"Value\": 1.00 },\n * { \"Key\": \"TransCompletedTime\", \"Value\": 20211114132711 },\n * { \"Key\": \"OriginalTransactionID\",\"Value\": \"SKC82PACB8\" },\n * { \"Key\": \"Charge\", \"Value\": 0.00 },\n * { \"Key\": \"CreditPartyPublicName\",\"Value\": \"254705912645 - NICHOLAS JOHN SONGOK\" },\n * { \"Key\": \"DebitPartyPublicName\", \"Value\": \"600992 - Safaricom Daraja 992\" }\n * ]\n * },\n * \"ReferenceData\": {\n * \"ReferenceItem\": {\n * \"Key\": \"QueueTimeoutURL\",\n * \"Value\": \"https://...\"\n * }\n * }\n * }\n * }\n * ```\n *\n * Failed callback shape (per Daraja docs):\n * ```json\n * {\n * \"Result\": {\n * \"ResultType\": 0,\n * \"ResultCode\": \"R000002\",\n * \"ResultDesc\": \"The OriginalTransactionID is invalid.\",\n * ...\n * }\n * }\n * ```\n *\n * Note: ResultCode is 0 (number) on success and a string like \"R000002\" on failure.\n */\nexport interface ReversalResult {\n Result: {\n /** Status type (usually 0) */\n ResultType: number\n /**\n * 0 (number) = success.\n * String codes like \"R000002\" = failure.\n * See REVERSAL_RESULT_CODES for all documented values.\n */\n ResultCode: number | string\n /** Human-readable result description */\n ResultDesc: string\n /** Unique identifier for the reversal request */\n OriginatorConversationID: string\n /** Unique identifier from M-PESA */\n ConversationID: string\n /** M-PESA receipt number for the reversal transaction */\n TransactionID: string\n /**\n * Present on success — contains transaction details.\n * Absent on failure.\n */\n ResultParameters?: {\n ResultParameter: ReversalResultParameter[]\n }\n ReferenceData?: {\n ReferenceItem: { Key: string; Value: string } | Array<{ Key: string; Value: string }>\n }\n }\n}\n\n// ── Type guards ───────────────────────────────────────────────────────────────\n\n/**\n * Returns true when the payload matches the shape of a ReversalResult.\n * Works for both success and failure callbacks.\n */\nexport function isReversalResult(body: unknown): body is ReversalResult {\n if (!body || typeof body !== 'object') return false\n const b = body as Record<string, unknown>\n if (!b['Result'] || typeof b['Result'] !== 'object') return false\n const r = b['Result'] as Record<string, unknown>\n return (\n typeof r['ResultCode'] !== 'undefined' &&\n typeof r['ResultDesc'] === 'string' &&\n typeof r['ConversationID'] === 'string'\n )\n}\n\n/**\n * Returns true when the reversal result indicates a successful reversal.\n * A successful reversal has ResultCode === 0 (number).\n */\nexport function isReversalSuccess(result: ReversalResult): boolean {\n return result.Result.ResultCode === REVERSAL_RESULT_CODES.SUCCESS\n}\n\n/**\n * Returns true when the reversal result indicates a failure.\n */\nexport function isReversalFailure(result: ReversalResult): boolean {\n return !isReversalSuccess(result)\n}\n\n/**\n * Returns true when the specified result code is a known documented code.\n */\nexport function isKnownReversalResultCode(code: number | string): code is ReversalResultCode {\n return Object.values(REVERSAL_RESULT_CODES).includes(code as ReversalResultCode)\n}\n\n// ── Result extractors ─────────────────────────────────────────────────────────\n\n/**\n * Extracts the M-PESA receipt number for the reversal transaction.\n * Returns null if the result does not contain a TransactionID.\n */\nexport function getReversalTransactionId(result: ReversalResult): string | null {\n return result.Result.TransactionID ?? null\n}\n\n/**\n * Extracts the ConversationID from the reversal result.\n */\nexport function getReversalConversationId(result: ReversalResult): string {\n return result.Result.ConversationID\n}\n\n/**\n * Extracts the OriginatorConversationID from the reversal result.\n */\nexport function getReversalOriginatorConversationId(result: ReversalResult): string {\n return result.Result.OriginatorConversationID\n}\n\n/**\n * Extracts the ResultCode from the reversal result.\n * Returns 0 for success, or a string/number error code for failure.\n */\nexport function getReversalResultCode(result: ReversalResult): number | string {\n return result.Result.ResultCode\n}\n\n/**\n * Extracts the ResultDesc from the reversal result.\n */\nexport function getReversalResultDesc(result: ReversalResult): string {\n return result.Result.ResultDesc\n}\n\n/**\n * Extracts a named parameter value from the ResultParameters of a successful\n * reversal callback. Returns undefined if absent or if the reversal failed.\n *\n * @example\n * const amount = getReversalResultParam(result, 'Amount')\n * const origTxId = getReversalResultParam(result, 'OriginalTransactionID')\n */\nexport function getReversalResultParam(\n result: ReversalResult,\n key: ReversalResultParameterKey,\n): string | number | undefined {\n const params = result.Result.ResultParameters?.ResultParameter\n if (!params) return undefined\n return params.find((p) => p.Key === key)?.Value\n}\n\n/**\n * Extracts the reversed transaction amount from a successful reversal callback.\n */\nexport function getReversalAmount(result: ReversalResult): number | undefined {\n const val = getReversalResultParam(result, 'Amount')\n return val !== undefined ? Number(val) : undefined\n}\n\n/**\n * Extracts the original TransactionID that was reversed.\n * This is the M-PESA receipt number of the original C2B transaction.\n */\nexport function getReversalOriginalTransactionId(result: ReversalResult): string | undefined {\n const val = getReversalResultParam(result, 'OriginalTransactionID')\n return val !== undefined ? String(val) : undefined\n}\n\n/**\n * Extracts the CreditPartyPublicName from a successful reversal callback.\n * Format: \"254705912645 - NICHOLAS JOHN SONGOK\"\n */\nexport function getReversalCreditPartyPublicName(result: ReversalResult): string | undefined {\n const val = getReversalResultParam(result, 'CreditPartyPublicName')\n return val !== undefined ? String(val) : undefined\n}\n\n/**\n * Extracts the DebitPartyPublicName from a successful reversal callback.\n * Format: \"600992 - Safaricom Daraja 992\"\n */\nexport function getReversalDebitPartyPublicName(result: ReversalResult): string | undefined {\n const val = getReversalResultParam(result, 'DebitPartyPublicName')\n return val !== undefined ? String(val) : undefined\n}\n\n/**\n * Extracts the DebitAccountBalance from a successful reversal callback.\n * Format: \"Utility Account|KES|7722179.62|7722179.62|0.00|0.00\"\n */\nexport function getReversalDebitAccountBalance(result: ReversalResult): string | undefined {\n const val = getReversalResultParam(result, 'DebitAccountBalance')\n return val !== undefined ? String(val) : undefined\n}\n\n/**\n * Extracts the reversal completion timestamp.\n * Format: YYYYMMDDHHmmss (e.g. 20211114132711)\n */\nexport function getReversalCompletedTime(result: ReversalResult): number | undefined {\n const val = getReversalResultParam(result, 'TransCompletedTime')\n return val !== undefined ? Number(val) : undefined\n}\n\n/**\n * Extracts the Charge from a successful reversal callback.\n * Per docs: usually 0.00 for reversals.\n */\nexport function getReversalCharge(result: ReversalResult): number | undefined {\n const val = getReversalResultParam(result, 'Charge')\n return val !== undefined ? Number(val) : undefined\n}\n","/**\n * src/mpesa/reversal/request.ts\n *\n * Transaction Reversal — reverses a completed M-PESA C2B transaction.\n *\n * API: POST /mpesa/reversal/v1/request\n *\n * ASYNCHRONOUS: The synchronous response is acknowledgement only.\n * The actual reversal result is POSTed to your ResultURL after processing.\n *\n * Per Daraja docs:\n * - CommandID is always \"TransactionReversal\"\n * - RecieverIdentifierType is always \"11\" (Organisation ShortCode for reversals)\n * - Amount is sent as a string per the Daraja sample payload\n * - Remarks must be 2–100 characters\n * - Cannot be used for B2C reversals (those are done on the M-PESA portal)\n *\n * Required org portal role: \"Org Reversals Initiator\"\n */\n\nimport { parseWithSchema } from '../../core/validation/zod-error'\nimport { ReversalRequestSchema, ReversalResponseSchema } from '../../schemas/async-apis'\nimport { httpRequest, type DarajaHttpOptions, withDarajaHttp } from '../../utils/http'\nimport {\n REVERSAL_COMMAND_ID,\n REVERSAL_RECEIVER_IDENTIFIER_TYPE,\n type ReversalRequest,\n type ReversalResponse,\n} from './types'\n\nexport async function requestReversal(\n baseUrl: string,\n accessToken: string,\n securityCredential: string,\n initiatorName: string,\n request: ReversalRequest,\n http?: DarajaHttpOptions,\n): Promise<ReversalResponse> {\n const validated = parseWithSchema(ReversalRequestSchema, request, 'Reversal request')\n const amount = Math.round(validated.amount)\n const remarks = validated.remarks ?? 'Transaction Reversal'\n\n const payload: Record<string, unknown> = {\n Initiator: initiatorName,\n SecurityCredential: securityCredential,\n CommandID: REVERSAL_COMMAND_ID,\n TransactionID: validated.transactionId,\n Amount: String(amount),\n ReceiverParty: String(validated.receiverParty),\n RecieverIdentifierType: REVERSAL_RECEIVER_IDENTIFIER_TYPE,\n ResultURL: validated.resultUrl,\n QueueTimeOutURL: validated.queueTimeOutUrl,\n Remarks: remarks,\n }\n\n if (validated.occasion !== undefined && validated.occasion !== null) {\n payload['Occasion'] = validated.occasion\n }\n\n const { data } = await httpRequest<ReversalResponse>(\n `${baseUrl}/mpesa/reversal/v1/request`,\n withDarajaHttp(\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n },\n http,\n ),\n )\n\n return parseWithSchema(ReversalResponseSchema, data, 'Reversal response') as ReversalResponse\n}\n","import { z } from 'zod'\nimport { NonEmptyStringSchema, UrlSchema } from './common'\n\nexport const TransactionTypeSchema = z.enum(['CustomerPayBillOnline', 'CustomerBuyGoodsOnline'])\n\nexport const StkPushRequestSchema = z.object({\n amount: z.number().finite({ message: 'amount must be a finite number (not NaN or Infinity)' }),\n phoneNumber: NonEmptyStringSchema,\n shortCode: NonEmptyStringSchema,\n passKey: NonEmptyStringSchema,\n callbackUrl: UrlSchema,\n accountReference: NonEmptyStringSchema,\n transactionDesc: NonEmptyStringSchema,\n transactionType: TransactionTypeSchema.optional(),\n partyB: z.string().optional(),\n})\n\nexport const StkPushResponseSchema = z\n .object({\n MerchantRequestID: z.string(),\n CheckoutRequestID: z.string(),\n ResponseCode: z.string(),\n ResponseDescription: z.string(),\n CustomerMessage: z.string().optional(),\n })\n .passthrough()\n\nexport const StkQueryRequestSchema = z.object({\n checkoutRequestId: NonEmptyStringSchema,\n shortCode: NonEmptyStringSchema,\n passKey: NonEmptyStringSchema,\n})\n\nexport const StkQueryResponseSchema = z\n .object({\n ResponseCode: z.string(),\n ResponseDescription: z.string(),\n MerchantRequestID: z.string().optional(),\n CheckoutRequestID: z.string().optional(),\n ResultCode: z.union([z.string(), z.number()]).optional(),\n ResultDesc: z.string().optional(),\n })\n .passthrough()\n\nexport const StkPushWebhookSchema = z.object({\n Body: z.object({\n stkCallback: z\n .object({\n MerchantRequestID: z.string(),\n CheckoutRequestID: z.string(),\n ResultCode: z.number(),\n ResultDesc: z.string(),\n CallbackMetadata: z\n .object({\n Item: z.array(\n z.object({\n Name: z.string(),\n Value: z.union([z.string(), z.number()]),\n }),\n ),\n })\n .optional(),\n })\n .passthrough(),\n }),\n})\n\nexport type StkPushRequestInput = z.infer<typeof StkPushRequestSchema>\nexport type StkPushResponseOutput = z.infer<typeof StkPushResponseSchema>\n","/**\n * src/mpesa/stk-push/types.ts\n *\n * Types, constants, and helpers for the M-PESA STK Push (M-PESA Express) API.\n *\n * Daraja docs:\n * STK Push → POST /mpesa/stkpush/v1/processrequest\n * STK Query → POST /mpesa/stkpushquery/v1/query\n */\n\n// ── Transaction type ──────────────────────────────────────────────────────────\n\nexport type TransactionType = 'CustomerPayBillOnline' | 'CustomerBuyGoodsOnline'\n\n// ── Transaction limits (from Daraja docs) ─────────────────────────────────────\n\n/**\n * M-PESA transaction limits as documented by Safaricom Daraja.\n *\n * | Limit | Value |\n * |------------------|-----------|\n * | Min per tx | KES 1 |\n * | Max per tx | KES 250 000|\n * | Max daily | KES 500 000|\n * | Max balance | KES 500 000|\n */\nexport const STK_PUSH_LIMITS = {\n MIN_AMOUNT: 1,\n MAX_AMOUNT: 250_000,\n} as const\n\n// ── Result codes (from Daraja docs) ──────────────────────────────────────────\n\n/**\n * All documented STK Push / Query result codes.\n *\n * These codes appear in:\n * - STK Query response → `ResultCode` field\n * - STK Callback body → `Body.stkCallback.ResultCode`\n *\n * | Code | Meaning |\n * |------|--------------------------|\n * | 0 | Transaction successful |\n * | 1 | Insufficient balance |\n * | 1032 | Cancelled by user |\n * | 1037 | Phone unreachable |\n * | 2001 | Invalid PIN |\n */\nexport const STK_RESULT_CODES = {\n SUCCESS: 0,\n INSUFFICIENT_BALANCE: 1,\n CANCELLED_BY_USER: 1032,\n PHONE_UNREACHABLE: 1037,\n INVALID_PIN: 2001,\n} as const satisfies Record<string, number>\n\n/** Union of all documented STK result code values */\nexport type StkResultCode = (typeof STK_RESULT_CODES)[keyof typeof STK_RESULT_CODES]\n\n/**\n * Returns true when `code` is one of the documented STK result codes.\n * Useful for narrowing unknown values from the Daraja response.\n */\nexport function isKnownStkResultCode(code: number): code is StkResultCode {\n return Object.values(STK_RESULT_CODES).includes(code as StkResultCode)\n}\n\n// ── STK Push request ─────────────────────────────────────────────────────────\n\nexport interface StkPushRequest {\n /**\n * Transaction amount in KES.\n * Min: {@link STK_PUSH_LIMITS.MIN_AMOUNT} (KES 1)\n * Max: {@link STK_PUSH_LIMITS.MAX_AMOUNT} (KES 250 000)\n * Must round to a whole number ≥ 1.\n * Daraja field: `Amount`\n */\n amount: number\n\n /**\n * Phone number sending the money. Format: 2547XXXXXXXX.\n * Must be a valid Safaricom M-PESA number.\n * Daraja fields: `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 field: `CallBackURL`\n */\n callbackUrl: string\n\n /**\n * Alpha-numeric reference shown to customer in the USSD prompt.\n * Truncated to max 12 characters before sending.\n * Daraja field: `AccountReference`\n */\n accountReference: string\n\n /**\n * Additional description for the transaction.\n * Truncated to max 13 characters before sending.\n * Daraja field: `TransactionDesc`\n */\n transactionDesc: string\n\n /**\n * Business shortcode — Paybill number or HO/Store number for Till.\n * Daraja field: `BusinessShortCode`\n */\n shortCode: string\n\n /**\n * Passkey used to generate the Password.\n * Sandbox: from Daraja simulator test data.\n * Production: emailed after Go Live.\n */\n passKey: string\n\n /**\n * \"CustomerPayBillOnline\" (default) for Paybill.\n * \"CustomerBuyGoodsOnline\" for Till Numbers.\n * Daraja field: `TransactionType`\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 field: `PartyB`\n */\n partyB?: string\n}\n\n// ── STK Push response ────────────────────────────────────────────────────────\n\n/**\n * Synchronous acknowledgement returned immediately after STK Push submission.\n * This is NOT the payment result — the result arrives via callback.\n *\n * Daraja endpoint: POST /mpesa/stkpush/v1/processrequest\n */\nexport interface StkPushResponse {\n /** Global unique identifier for the submitted payment request */\n MerchantRequestID: string\n /** Global unique identifier for the checkout transaction — use this for STK Query */\n CheckoutRequestID: string\n /**\n * \"0\" = request accepted successfully.\n * Any other value = submission error.\n */\n ResponseCode: string\n /** Human-readable submission status */\n ResponseDescription: string\n /** Message shown to customer on their device */\n CustomerMessage: string\n}\n\n// ── STK Query request / response ─────────────────────────────────────────────\n\n/**\n * Parameters for querying the status of a previously initiated STK Push.\n *\n * Daraja endpoint: POST /mpesa/stkpushquery/v1/query\n */\nexport interface StkQueryRequest {\n /** `CheckoutRequestID` from the STK Push response */\n checkoutRequestId: string\n shortCode: string\n passKey: string\n}\n\n/**\n * Response from the STK Query API.\n *\n * Note: `ResultCode` is documented as \"Numeric\" by Daraja.\n * The Daraja JSON example shows it as a string (`\"ResultCode\": \"0\"`),\n * but actual API responses return a number. Typed as `number` here.\n * Use {@link STK_RESULT_CODES} constants for comparisons.\n */\nexport interface StkQueryResponse {\n /** \"0\" = request accepted. Not the final payment status. */\n ResponseCode: string\n /** Submission status message */\n ResponseDescription: string\n /** Unique identifier for the merchant request */\n MerchantRequestID: string\n /** Unique identifier for the checkout transaction */\n CheckoutRequestID: string\n /**\n * Payment processing status code.\n * See {@link STK_RESULT_CODES} for all documented values:\n * 0 → success\n * 1 → insufficient balance\n * 1032 → cancelled by user\n * 1037 → phone unreachable\n * 2001 → wrong PIN\n */\n ResultCode: number\n /** Human-readable description of the result */\n ResultDesc: string\n}\n\n// ── Callback payload types ────────────────────────────────────────────────────\n\n/**\n * Single metadata item in a successful STK callback.\n * Daraja always sends these 4 items on success; `Balance` may appear on some accounts.\n */\nexport interface StkCallbackMetadataItem {\n Name: 'Amount' | 'MpesaReceiptNumber' | 'TransactionDate' | 'PhoneNumber' | 'Balance'\n /**\n * Present on successful transactions; absent on failure.\n * `TransactionDate` and `PhoneNumber` come back as numbers from Daraja.\n */\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: typeof STK_RESULT_CODES.SUCCESS\n ResultDesc: string\n CallbackMetadata: {\n Item: StkCallbackMetadataItem[]\n }\n}\n\n/**\n * Inner callback for a FAILED or CANCELLED STK Push.\n * `ResultCode` is one of 1, 1032, 1037, 2001 (or another undocumented code).\n */\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/**\n * Full wrapper Safaricom POSTs to your `CallBackURL`.\n *\n * Successful example:\n * ```json\n * {\n * \"Body\": {\n * \"stkCallback\": {\n * \"MerchantRequestID\": \"29115\",\n * \"CheckoutRequestID\": \"ws_CO_191220\",\n * \"ResultCode\": 0,\n * \"ResultDesc\": \"Success\",\n * \"CallbackMetadata\": {\n * \"Item\": [\n * { \"Name\": \"Amount\", \"Value\": 1.00 },\n * { \"Name\": \"MpesaReceiptNumber\", \"Value\": \"NLJ7RT61SV\" },\n * { \"Name\": \"TransactionDate\", \"Value\": 20191219102115 },\n * { \"Name\": \"PhoneNumber\", \"Value\": 254708374149 }\n * ]\n * }\n * }\n * }\n * }\n * ```\n */\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 * A callback is successful when `ResultCode === 0`.\n *\n * @example\n * if (isStkCallbackSuccess(callback.Body.stkCallback)) {\n * const receipt = getCallbackValue(callback, 'MpesaReceiptNumber')\n * }\n */\nexport function isStkCallbackSuccess(cb: StkCallbackInner): cb is StkCallbackSuccess {\n return cb.ResultCode === STK_RESULT_CODES.SUCCESS\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 *\n * @example\n * const receipt = getCallbackValue(callback, 'MpesaReceiptNumber') // \"NLJ7RT61SV\"\n * const amount = getCallbackValue(callback, 'Amount') // 1\n * const phone = getCallbackValue(callback, 'PhoneNumber') // 254708374149\n * const date = getCallbackValue(callback, 'TransactionDate') // 20191219102115\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","// 📁 PATH: src/utils/phone/index.ts\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 let n: string\n\n if (digits.startsWith('254') && digits.length === 12) n = digits\n else if (digits.startsWith('0') && digits.length === 10) n = `254${digits.slice(1)}`\n else if (digits.length === 9) n = `254${digits}`\n else {\n throw new PesafyError({\n code: 'INVALID_PHONE',\n message: `Cannot parse \"${phone}\". Use 07XXXXXXXX, 2547XXXXXXXX, 2541XXXXXXXX (Airtel), or +2547XXXXXXXX.`,\n })\n }\n\n /* v8 ignore next 6 -- unreachable: all branches above assign exactly 12 digits */\n if (n.length !== 12) {\n throw new PesafyError({\n code: 'INVALID_PHONE',\n message: `\"${phone}\" normalised to \"${n}\" — expected 12 digits.`,\n })\n }\n return n\n}\n","// src/mpesa/stk-push/utils.ts\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(shortCode: string, passKey: string, timestamp: string): 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","/**\n * src/mpesa/stk-push/stk-push.ts\n *\n * Initiates an STK Push (M-PESA Express) payment.\n *\n * Daraja endpoint: POST /mpesa/stkpush/v1/processrequest\n *\n * Transaction limits (Daraja docs):\n * Min per transaction: KES 1\n * Max per transaction: KES 250,000\n */\n\nimport { parseWithSchema } from '../../core/validation/zod-error'\nimport { StkPushRequestSchema, StkPushResponseSchema } from '../../schemas/stk-push'\nimport { PesafyError } from '../../utils/errors'\nimport { httpRequest, type DarajaHttpOptions, withDarajaHttp } from '../../utils/http'\nimport { STK_PUSH_LIMITS, type StkPushRequest, type StkPushResponse } from './types'\nimport { formatPhoneNumber, getStkPushPassword, getTimestamp } from './utils'\n\nexport async function processStkPush(\n baseUrl: string,\n accessToken: string,\n request: StkPushRequest,\n http?: DarajaHttpOptions,\n): Promise<StkPushResponse> {\n const validated = parseWithSchema(StkPushRequestSchema, request, 'STK Push request')\n\n // ── Amount validation (Daraja transaction limits) ──────────────────────────\n\n if (!Number.isFinite(validated.amount)) {\n throw new PesafyError({\n code: 'VALIDATION_ERROR',\n message: `amount must be a finite number (got ${validated.amount}).`,\n })\n }\n\n const amount = Math.round(validated.amount)\n\n if (amount < STK_PUSH_LIMITS.MIN_AMOUNT) {\n throw new PesafyError({\n code: 'VALIDATION_ERROR',\n message:\n `Amount must be at least KES ${STK_PUSH_LIMITS.MIN_AMOUNT} ` +\n `(got ${validated.amount} which rounds to ${amount}).`,\n })\n }\n\n if (amount > STK_PUSH_LIMITS.MAX_AMOUNT) {\n throw new PesafyError({\n code: 'VALIDATION_ERROR',\n message:\n `Amount must not exceed KES ${STK_PUSH_LIMITS.MAX_AMOUNT.toLocaleString()} per transaction ` +\n `as per Safaricom Daraja limits (got ${validated.amount} which rounds to ${amount}).`,\n })\n }\n\n // ── Generate timestamp ONCE ─────────────────────────────────────────────────\n // The Password and Timestamp fields MUST use the same timestamp value.\n // Daraja format: YYYYMMDDHHmmss\n const timestamp = getTimestamp()\n\n // ── PartyB logic ────────────────────────────────────────────────────────────\n // CustomerPayBillOnline → PartyB = shortCode (Paybill number)\n // CustomerBuyGoodsOnline → PartyB = till number (passed as request.partyB)\n const partyB = validated.partyB ?? validated.shortCode\n\n // ── Build Daraja request body ───────────────────────────────────────────────\n // All 11 fields documented by Safaricom Daraja are present.\n const body = {\n BusinessShortCode: validated.shortCode,\n Password: getStkPushPassword(validated.shortCode, validated.passKey, timestamp),\n Timestamp: timestamp,\n TransactionType: validated.transactionType ?? 'CustomerPayBillOnline',\n Amount: amount,\n PartyA: formatPhoneNumber(validated.phoneNumber),\n PartyB: partyB,\n PhoneNumber: formatPhoneNumber(validated.phoneNumber),\n CallBackURL: validated.callbackUrl,\n // Daraja docs: AccountReference max 12 chars, TransactionDesc max 13 chars\n AccountReference: validated.accountReference.slice(0, 12),\n TransactionDesc: validated.transactionDesc.slice(0, 13),\n }\n\n // ── HTTP request ────────────────────────────────────────────────────────────\n // httpRequest 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. Never mark a transaction \"failed\" on a 503 — the\n // transaction may have been processed even if the acknowledgement was lost.\n const { data } = await httpRequest<StkPushResponse>(\n `${baseUrl}/mpesa/stkpush/v1/processrequest`,\n withDarajaHttp(\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${accessToken}` },\n body,\n retries: 5,\n retryDelay: 3_000,\n },\n http,\n ),\n )\n\n return parseWithSchema(StkPushResponseSchema, data, 'STK Push response') as StkPushResponse\n}\n","// src/mpesa/stk-push/stk-query.ts\n\nimport { parseWithSchema } from '../../core/validation/zod-error'\nimport { StkQueryRequestSchema, StkQueryResponseSchema } from '../../schemas/stk-push'\nimport { httpRequest, type DarajaHttpOptions, withDarajaHttp } 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 http?: DarajaHttpOptions,\n): Promise<StkQueryResponse> {\n const validated = parseWithSchema(StkQueryRequestSchema, request, 'STK Query request')\n\n const timestamp = getTimestamp()\n\n const body = {\n BusinessShortCode: validated.shortCode,\n Password: getStkPushPassword(validated.shortCode, validated.passKey, timestamp),\n Timestamp: timestamp,\n CheckoutRequestID: validated.checkoutRequestId,\n }\n\n const { data } = await httpRequest<StkQueryResponse>(\n `${baseUrl}/mpesa/stkpushquery/v1/query`,\n withDarajaHttp(\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${accessToken}` },\n body,\n },\n http,\n ),\n )\n\n return parseWithSchema(StkQueryResponseSchema, data, 'STK Query response') as StkQueryResponse\n}\n","// src/mpesa/tax-remittance/remit-tax.ts\n\nimport { parseWithSchema } from '../../core/validation/zod-error'\nimport { TaxRemittanceRequestSchema, TaxRemittanceResponseSchema } from '../../schemas/async-apis'\nimport { httpRequest, type DarajaHttpOptions, withDarajaHttp } 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 * Endpoint: POST /mpesa/b2b/v1/remittax\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 synchronous acknowledgement response\n *\n * @example\n * const response = await remitTax(\n * 'https://sandbox.safaricom.co.ke',\n * accessToken,\n * securityCredential,\n * 'TaxPayer',\n * {\n * amount: 239,\n * partyA: '888880',\n * accountReference: '353353',\n * resultUrl: 'https://example.org/b2b/remittax/result/',\n * queueTimeOutUrl: 'https://example.org/b2b/remittax/queue/',\n * },\n * )\n */\nexport async function remitTax(\n baseUrl: string,\n accessToken: string,\n securityCredential: string,\n initiatorName: string,\n request: TaxRemittanceRequest,\n http?: DarajaHttpOptions,\n): Promise<TaxRemittanceResponse> {\n const validated = parseWithSchema(TaxRemittanceRequestSchema, request, 'Tax Remittance request')\n const amount = Math.round(validated.amount)\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(validated.partyA),\n PartyB: validated.partyB ?? KRA_SHORTCODE,\n AccountReference: validated.accountReference,\n Remarks: validated.remarks ?? 'Tax Remittance',\n QueueTimeOutURL: validated.queueTimeOutUrl,\n ResultURL: validated.resultUrl,\n }\n\n const { data } = await httpRequest<TaxRemittanceResponse>(\n `${baseUrl}/mpesa/b2b/v1/remittax`,\n withDarajaHttp(\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${accessToken}` },\n body: payload,\n },\n http,\n ),\n )\n\n return parseWithSchema(\n TaxRemittanceResponseSchema,\n data,\n 'Tax Remittance response',\n ) as TaxRemittanceResponse\n}\n","// src/mpesa/tax-remittance/webhooks.ts\n\nimport type {\n TaxRemittanceResult,\n TaxRemittanceResultParameter,\n TaxRemittanceResultParameterKey,\n} from './types'\n\n// ── Internal helpers ──────────────────────────────────────────────────────────\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction normaliseResultCode(code: string | number): number {\n return typeof code === 'string' ? Number(code) : code\n}\n\n// ── Runtime type guard ────────────────────────────────────────────────────────\n\n/**\n * Returns `true` when the value is a valid Tax Remittance async result payload\n * (the shape POSTed by Safaricom to your ResultURL).\n *\n * Checks the minimum required fields per the Daraja documentation:\n * - Result.ResultCode\n * - Result.ConversationID\n * - Result.OriginatorConversationID\n */\nexport function isTaxRemittanceResult(value: unknown): value is TaxRemittanceResult {\n if (!isObject(value)) return false\n\n const root = value as Record<string, unknown>\n if (!isObject(root['Result'])) return false\n\n const result = root['Result'] as Record<string, unknown>\n return (\n result['ResultCode'] !== undefined &&\n result['ResultCode'] !== null &&\n typeof result['ConversationID'] === 'string' &&\n typeof result['OriginatorConversationID'] === 'string'\n )\n}\n\n// ── Success / failure type guards ─────────────────────────────────────────────\n\n/**\n * Returns `true` when the Tax Remittance result indicates a successful\n * transaction — i.e. ResultCode is 0 or \"0\".\n */\nexport function isTaxRemittanceSuccess(result: TaxRemittanceResult): boolean {\n return normaliseResultCode(result.Result.ResultCode) === 0\n}\n\n/**\n * Returns `true` when the Tax Remittance result indicates a failure\n * — i.e. ResultCode is non-zero.\n *\n * `isTaxRemittanceSuccess` and `isTaxRemittanceFailure` are mutually exclusive.\n */\nexport function isTaxRemittanceFailure(result: TaxRemittanceResult): boolean {\n return !isTaxRemittanceSuccess(result)\n}\n\n// ── Result parameter extractor ────────────────────────────────────────────────\n\n/**\n * Extracts a single value from the Result's ResultParameter collection.\n *\n * Handles both the array form and the single-object form that Daraja may return.\n * Returns `undefined` when the key is not found or ResultParameters is absent.\n *\n * Documented result parameter keys:\n * - `Amount`\n * - `TransactionCompletedTime`\n * - `ReceiverPartyPublicName`\n */\nexport function getTaxResultParam(\n result: TaxRemittanceResult,\n key: TaxRemittanceResultParameterKey,\n): string | number | undefined {\n const params = result.Result.ResultParameters?.ResultParameter\n if (params === undefined || params === null) return undefined\n\n if (Array.isArray(params)) {\n return (params as TaxRemittanceResultParameter[]).find((p) => p.Key === key)?.Value\n }\n\n // Single-object form\n const single = params as TaxRemittanceResultParameter\n return single.Key === key ? single.Value : undefined\n}\n\n// ── Field extractors ──────────────────────────────────────────────────────────\n\n/**\n * Returns the ResultCode as-is (string or number) from the result payload.\n */\nexport function getTaxResultCode(result: TaxRemittanceResult): string | number {\n return result.Result.ResultCode\n}\n\n/**\n * Returns the ResultDesc from the result payload.\n */\nexport function getTaxResultDesc(result: TaxRemittanceResult): string {\n return result.Result.ResultDesc\n}\n\n/**\n * Returns the TransactionID (M-PESA receipt number) from the result payload.\n */\nexport function getTaxTransactionId(result: TaxRemittanceResult): string {\n return result.Result.TransactionID\n}\n\n/**\n * Returns the ConversationID from the result payload.\n */\nexport function getTaxConversationId(result: TaxRemittanceResult): string {\n return result.Result.ConversationID\n}\n\n/**\n * Returns the OriginatorConversationID from the result payload.\n */\nexport function getTaxOriginatorConversationId(result: TaxRemittanceResult): string {\n return result.Result.OriginatorConversationID\n}\n\n/**\n * Returns the remitted Amount as a number, or `null` when ResultParameters\n * is absent (e.g. on failure callbacks).\n *\n * Documented key: `Amount` — Daraja returns it as a string like \"190.00\".\n */\nexport function getTaxAmount(result: TaxRemittanceResult): number | null {\n const raw = getTaxResultParam(result, 'Amount')\n if (raw === undefined || raw === null) return null\n const parsed = typeof raw === 'number' ? raw : parseFloat(String(raw))\n return Number.isNaN(parsed) ? null : parsed\n}\n\n/**\n * Returns the TransactionCompletedTime string from ResultParameters,\n * or `null` when absent.\n *\n * Documented key: `TransactionCompletedTime` — format e.g. \"20221110110717\".\n */\nexport function getTaxCompletedTime(result: TaxRemittanceResult): string | null {\n const raw = getTaxResultParam(result, 'TransactionCompletedTime')\n return raw !== undefined ? String(raw) : null\n}\n\n/**\n * Returns the ReceiverPartyPublicName from ResultParameters, or `null` when absent.\n *\n * Documented key: `ReceiverPartyPublicName` — e.g. \"00000 Tax Collecting Company\".\n */\nexport function getTaxReceiverName(result: TaxRemittanceResult): string | null {\n const raw = getTaxResultParam(result, 'ReceiverPartyPublicName')\n return raw !== undefined ? String(raw) : null\n}\n","// src/mpesa/transaction-status/query.ts\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 * Reconciliation options (at least one required):\n * - transactionId — M-Pesa Receipt Number (e.g. \"NEF61H8J60\")\n * - originalConversationId — OriginatorConversationID from the original call\n */\n\nimport { parseWithSchema } from '../../core/validation/zod-error'\nimport {\n TransactionStatusRequestSchema,\n TransactionStatusResponseSchema,\n} from '../../schemas/async-apis'\nimport { httpRequest, type DarajaHttpOptions, withDarajaHttp } from '../../utils/http'\nimport type { TransactionStatusRequest, TransactionStatusResponse } from './types'\n\nexport async function queryTransactionStatus(\n baseUrl: string,\n token: string,\n securityCredential: string,\n initiator: string,\n request: TransactionStatusRequest,\n http?: DarajaHttpOptions,\n): Promise<TransactionStatusResponse> {\n const validated = parseWithSchema(\n TransactionStatusRequestSchema,\n request,\n 'Transaction Status request',\n )\n\n const payload: Record<string, string> = {\n Initiator: initiator,\n SecurityCredential: securityCredential,\n CommandID: validated.commandId ?? 'TransactionStatusQuery',\n TransactionID: validated.transactionId ?? '',\n OriginalConversationID: validated.originalConversationId ?? '',\n PartyA: validated.partyA,\n IdentifierType: validated.identifierType,\n ResultURL: validated.resultUrl,\n QueueTimeOutURL: validated.queueTimeOutUrl,\n Remarks: validated.remarks ?? 'Transaction Status Query',\n Occasion: validated.occasion ?? '',\n }\n\n const { data } = await httpRequest<TransactionStatusResponse>(\n `${baseUrl}/mpesa/transactionstatus/v1/query`,\n withDarajaHttp(\n {\n method: 'POST',\n headers: { Authorization: `Bearer ${token}` },\n body: payload,\n },\n http,\n ),\n )\n\n return parseWithSchema(\n TransactionStatusResponseSchema,\n data,\n 'Transaction Status response',\n ) as TransactionStatusResponse\n}\n","// src/mpesa/transaction-status/types.ts\n\n/**\n * Transaction Status Query types\n *\n * API: POST /mpesa/transactionstatus/v1/query\n *\n * Daraja request body (from official documentation):\n * {\n * \"Initiator\": \"\",\n * \"SecurityCredential\": \"\",\n * \"CommandID\": \"TransactionStatusQuery\",\n * \"TransactionID\": \"\",\n * \"OriginalConversationID\": \"\",\n * \"PartyA\": \"\",\n * \"IdentifierType\": \"\",\n * \"ResultURL\": \"\",\n * \"QueueTimeOutURL\": \"\",\n * \"Remarks\": \"\",\n * \"Occasion\": \"\"\n * }\n *\n * NOTE: This is an ASYNCHRONOUS API.\n * The synchronous response only confirms Safaricom received the request.\n * The actual transaction details arrive later via POST to your ResultURL.\n *\n * Reconciliation: Either transactionId OR originalConversationId is required.\n */\n\n// ── Request ───────────────────────────────────────────────────────────────────\n\nexport interface TransactionStatusRequest {\n /**\n * M-Pesa transaction ID to look up (M-Pesa Receipt Number).\n * Either transactionId OR originalConversationId is required.\n * Daraja field: TransactionID\n * Example: \"NEF61H8J60\"\n */\n transactionId?: string\n\n /**\n * The Originator Conversation ID of the transaction whose status is being\n * checked. Either transactionId OR originalConversationId is required.\n * Use this when you have a ConversationID from the original API response\n * but no M-Pesa receipt number.\n * Daraja field: OriginalConversationID\n * Example: \"7071-4170-a0e5-8345632bad4421\"\n */\n originalConversationId?: string\n\n /**\n * Organization/MSISDN receiving the transaction.\n * Usually your business shortcode.\n * Daraja field: PartyA\n */\n partyA: string\n\n /**\n * Type of the partyA identifier.\n * \"1\" = MSISDN\n * \"2\" = Till Number (Buy Goods)\n * \"4\" = Organisation ShortCode (Paybill / B2C) ← most common\n * Daraja field: IdentifierType\n */\n identifierType: '1' | '2' | '4'\n\n /**\n * URL where Safaricom POSTs the final result.\n * Must be publicly accessible.\n * Daraja field: ResultURL\n */\n resultUrl: string\n\n /**\n * URL Safaricom calls when the request times out in the queue.\n * Must be publicly accessible.\n * Daraja field: QueueTimeOutURL\n */\n queueTimeOutUrl: string\n\n /**\n * CommandID — always \"TransactionStatusQuery\".\n * Defaults to \"TransactionStatusQuery\" if omitted.\n */\n commandId?: 'TransactionStatusQuery'\n\n /** Optional remarks (up to 100 characters) */\n remarks?: string\n\n /** Optional occasion / reference (up to 100 characters) */\n occasion?: string\n}\n\n// ── Synchronous acknowledgement response ──────────────────────────────────────\n\nexport interface TransactionStatusResponse {\n /** Unique request ID for tracking */\n OriginatorConversationID: string\n /** Unique request ID returned by M-PESA */\n ConversationID: string\n /** \"0\" = request accepted */\n ResponseCode: string\n ResponseDescription: string\n}\n\n// ── Documented API error codes ────────────────────────────────────────────────\n\n/**\n * Daraja Transaction Status API error codes.\n * Returned in the errorCode field of error response bodies.\n *\n * @see https://developer.safaricom.co.ke/APIs/TransactionStatus\n */\nexport const TRANSACTION_STATUS_ERROR_CODES = {\n /** Wrong or expired access token */\n INVALID_ACCESS_TOKEN: '400.003.01',\n /** Server cannot process the request — something is missing */\n BAD_REQUEST: '400.003.02',\n /** Server failure */\n INTERNAL_SERVER_ERROR: '500.003.1001',\n /** Multiple requests violating M-PESA TPS limit */\n QUOTA_VIOLATION: '500.003.03',\n /** Spike arrest — endpoints generating constant errors */\n SPIKE_ARREST: '500.003.02',\n /** Requested resource could not be found */\n NOT_FOUND: '404.003.01',\n /** Invalid auth header / using GET instead of POST */\n INVALID_AUTH_HEADER: '404.001.04',\n /** Request body is not properly drafted */\n INVALID_PAYLOAD: '400.002.05',\n} as const\n\nexport type TransactionStatusErrorCode =\n (typeof TRANSACTION_STATUS_ERROR_CODES)[keyof typeof TRANSACTION_STATUS_ERROR_CODES]\n\n// ── Async result payload (POSTed to your ResultURL) ───────────────────────────\n\n/**\n * Documented result parameter keys for the Transaction Status callback.\n * @see Daraja Transaction Status result documentation\n */\nexport type TransactionStatusResultParameterKey =\n | 'DebitPartyName'\n | 'TransactionStatus'\n | 'Amount'\n | 'ReceiptNo'\n | 'DebitAccountBalance'\n | 'TransactionDate'\n | 'CreditPartyName'\n\nexport interface TransactionStatusResultParameter {\n Key: TransactionStatusResultParameterKey | string\n Value: string | number\n}\n\nexport interface TransactionStatusResult {\n Result: {\n /** 0 = completed, 1 = waiting for further messages */\n ResultType: number | string\n /** 0 = success */\n ResultCode: number | string\n /** Human-readable description of the result */\n ResultDesc: string\n OriginatorConversationID: string\n ConversationID: string\n TransactionID: string\n ResultParameters?: {\n ResultParameter: TransactionStatusResultParameter | TransactionStatusResultParameter[]\n }\n ReferenceData?: {\n ReferenceItem: { Key: string; Value?: string } | Array<{ Key: string; Value?: string }>\n }\n }\n}\n\n/**\n * Documented result codes for the Transaction Status async callback.\n * ResultCode 0 = success; anything else is a failure.\n */\nexport const TRANSACTION_STATUS_RESULT_CODES = {\n /** Transaction processed successfully */\n SUCCESS: 0,\n /** Invalid initiator information */\n INVALID_INITIATOR: 2001,\n} as const\n\nexport type TransactionStatusResultCode =\n (typeof TRANSACTION_STATUS_RESULT_CODES)[keyof typeof TRANSACTION_STATUS_RESULT_CODES]\n","// src/mpesa/transaction-status/webhooks.ts\n\n/**\n * Type guards and payload extractors for Transaction Status result callbacks.\n *\n * Documented result parameters (POSTed to your ResultURL):\n * - DebitPartyName\n * - TransactionStatus\n * - Amount\n * - ReceiptNo\n * - DebitAccountBalance\n * - TransactionDate\n * - CreditPartyName\n *\n * Docs note: ResultCode is 0 (number) on success. Daraja may return either\n * a number or a string depending on the transaction type — both forms handled.\n *\n * @see https://developer.safaricom.co.ke/APIs/TransactionStatus\n */\n\nimport { TRANSACTION_STATUS_RESULT_CODES } from './types'\nimport type { TransactionStatusResult, TransactionStatusResultParameter } from './types'\n\n// ── Known result codes set (O(1) lookup) ──────────────────────────────────────\n\nconst KNOWN_RESULT_CODES = new Set<number>(Object.values(TRANSACTION_STATUS_RESULT_CODES))\n\n// ── Type guards ───────────────────────────────────────────────────────────────\n\n/**\n * Runtime type guard — checks if a body looks like a Transaction Status\n * result callback. Validates the minimum documented structure.\n */\nexport function isTransactionStatusResult(body: unknown): body is TransactionStatusResult {\n if (!body || typeof body !== 'object') return false\n const b = body as Record<string, unknown>\n if (!b['Result'] || typeof b['Result'] !== 'object') return false\n const result = b['Result'] as Record<string, unknown>\n return (\n (typeof result['ResultCode'] === 'number' || typeof result['ResultCode'] === 'string') &&\n typeof result['ConversationID'] === 'string' &&\n typeof result['OriginatorConversationID'] === 'string'\n )\n}\n\n/**\n * Returns true if the Transaction Status result represents a successful query.\n * Handles both string \"0\" and number 0 (Daraja inconsistency).\n */\nexport function isTransactionStatusSuccess(result: TransactionStatusResult): boolean {\n const code = result.Result.ResultCode\n return code === 0 || code === '0'\n}\n\n/**\n * Returns true if the Transaction Status result represents a failure.\n */\nexport function isTransactionStatusFailure(result: TransactionStatusResult): boolean {\n return !isTransactionStatusSuccess(result)\n}\n\n/**\n * Returns true if the result code is among the documented codes.\n * Handles both numeric and string representations.\n */\nexport function isKnownTransactionStatusResultCode(code: unknown): boolean {\n if (typeof code !== 'number' && typeof code !== 'string') return false\n if (typeof code === 'string' && code.trim() === '') return false\n const numeric = Number(code)\n return Number.isFinite(numeric) && KNOWN_RESULT_CODES.has(numeric)\n}\n\n// ── Core field extractors ─────────────────────────────────────────────────────\n\n/**\n * Extracts the M-PESA transaction ID from a Transaction Status result.\n */\nexport function getTransactionStatusTransactionId(result: TransactionStatusResult): string {\n return result.Result.TransactionID\n}\n\n/**\n * Extracts the ConversationID from a Transaction Status result.\n */\nexport function getTransactionStatusConversationId(result: TransactionStatusResult): string {\n return result.Result.ConversationID\n}\n\n/**\n * Extracts the OriginatorConversationID from a Transaction Status result.\n * Use this to correlate with the original API call response.\n */\nexport function getTransactionStatusOriginatorConversationId(\n result: TransactionStatusResult,\n): string {\n return result.Result.OriginatorConversationID\n}\n\n/**\n * Extracts the human-readable result description.\n */\nexport function getTransactionStatusResultDesc(result: TransactionStatusResult): string {\n return result.Result.ResultDesc\n}\n\n/**\n * Extracts the result code from a Transaction Status result.\n */\nexport function getTransactionStatusResultCode(result: TransactionStatusResult): string | number {\n return result.Result.ResultCode\n}\n\n// ── Result parameter extractors (documented fields only) ──────────────────────\n\n/**\n * Extracts the transaction amount from result parameters.\n * Documented field: \"Amount\"\n * Returns null if not present (e.g. on failure).\n */\nexport function getTransactionStatusAmount(result: TransactionStatusResult): number | null {\n const value = getTransactionStatusResultParam(result, 'Amount')\n if (value === undefined) return null\n const num = Number(value)\n return Number.isFinite(num) ? num : null\n}\n\n/**\n * Extracts the M-Pesa receipt number from result parameters.\n * Documented field: \"ReceiptNo\"\n * Returns null if not present.\n */\nexport function getTransactionStatusReceiptNo(result: TransactionStatusResult): string | null {\n const value = getTransactionStatusResultParam(result, 'ReceiptNo')\n if (value === undefined) return null\n return String(value)\n}\n\n/**\n * Extracts the transaction status string from result parameters.\n * Documented field: \"TransactionStatus\" — e.g. \"Completed\"\n * Returns null if not present.\n */\nexport function getTransactionStatusStatus(result: TransactionStatusResult): string | null {\n const value = getTransactionStatusResultParam(result, 'TransactionStatus')\n if (value === undefined) return null\n return String(value)\n}\n\n/**\n * Extracts the debit party name from result parameters.\n * Documented field: \"DebitPartyName\" — e.g. \"600310 Safaricom333\"\n * Returns null if not present.\n */\nexport function getTransactionStatusDebitPartyName(result: TransactionStatusResult): string | null {\n const value = getTransactionStatusResultParam(result, 'DebitPartyName')\n if (value === undefined) return null\n return String(value)\n}\n\n/**\n * Extracts the credit party name from result parameters.\n * Documented field: \"CreditPartyName\"\n * Returns null if not present.\n */\nexport function getTransactionStatusCreditPartyName(\n result: TransactionStatusResult,\n): string | null {\n const value = getTransactionStatusResultParam(result, 'CreditPartyName')\n if (value === undefined) return null\n return String(value)\n}\n\n/**\n * Extracts the debit account balance from result parameters.\n * Documented field: \"DebitAccountBalance\"\n * Returns null if not present.\n */\nexport function getTransactionStatusDebitAccountBalance(\n result: TransactionStatusResult,\n): string | null {\n const value = getTransactionStatusResultParam(result, 'DebitAccountBalance')\n if (value === undefined) return null\n return String(value)\n}\n\n/**\n * Extracts the transaction date from result parameters.\n * Documented field: \"TransactionDate\"\n * Returns null if not present.\n */\nexport function getTransactionStatusTransactionDate(\n result: TransactionStatusResult,\n): string | null {\n const value = getTransactionStatusResultParam(result, 'TransactionDate')\n if (value === undefined) return null\n return String(value)\n}\n\n// ── Internal helper ───────────────────────────────────────────────────────────\n\n/**\n * Extracts a named value from Transaction Status result parameters.\n * Handles both single-object and array forms of ResultParameter\n * (Daraja returns either depending on how many parameters are present).\n * Returns undefined if key is absent or no ResultParameters exist.\n */\nexport function getTransactionStatusResultParam(\n result: TransactionStatusResult,\n key: string,\n): string | number | undefined {\n const params = result.Result.ResultParameters?.ResultParameter\n if (!params) return undefined\n\n const paramArray = Array.isArray(params)\n ? (params as TransactionStatusResultParameter[])\n : [params as TransactionStatusResultParameter]\n\n const item = paramArray.find((p) => p.Key === key)\n return item?.Value\n}\n","// 📁 PATH: src/mpesa/types.ts\n\nimport type { IdempotencyConfig } from '../core/idempotency'\n\nexport type Environment = 'sandbox' | 'production'\n\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 // ── STK Push (M-Pesa Express) ─────────────────────────────────────────────\n lipaNaMpesaShortCode?: string\n lipaNaMpesaPassKey?: string\n\n // ── Initiator (B2C / B2B / Reversal / Account Balance / Tax / TxStatus) ──\n initiatorName?: string\n initiatorPassword?: string\n\n // ── Certificate (one of) ──────────────────────────────────────────────────\n certificatePath?: string\n certificatePem?: string\n /**\n * Pre-computed base64 SecurityCredential — skips RSA encryption.\n * Use when you encrypt at startup outside the library.\n */\n securityCredential?: string\n\n // ── HTTP tuning ───────────────────────────────────────────────────────────\n /** Override default retry count (4) for all API calls */\n retries?: number\n /** Override default base retry delay in ms (2000) */\n retryDelay?: number\n /** Override default per-request timeout in ms (30000) */\n timeout?: number\n\n // ── Idempotency ───────────────────────────────────────────────────────────\n /** Automatic Idempotency-Key headers and duplicate detection */\n idempotency?: IdempotencyConfig\n\n // ── Webhooks ──────────────────────────────────────────────────────────────\n webhooks?: {\n allowedIPs?: readonly string[]\n /** HMAC secret when Safaricom ships signature support */\n secret?: string\n /** Signature header name (placeholder until official docs) */\n signatureHeader?: string\n /** Fail closed if HMAC required but missing. Default false */\n requireHMAC?: boolean\n }\n}\n","/** Default algorithm until Safaricom documents the official scheme. */\nexport const DEFAULT_WEBHOOK_HMAC_ALGORITHM = 'sha256' as const\n\nexport type WebhookHmacAlgorithm = 'sha256' | 'sha512'\n\nconst ALGO_MAP: Record<WebhookHmacAlgorithm, string> = {\n sha256: 'SHA-256',\n sha512: 'SHA-512',\n}\n\nfunction hexToBytes(hex: string): Uint8Array | null {\n const trimmed = hex.trim()\n if (!/^[0-9a-fA-F]+$/.test(trimmed) || trimmed.length % 2 !== 0) return null\n const bytes = new Uint8Array(trimmed.length / 2)\n for (let i = 0; i < bytes.length; i++) {\n bytes[i] = Number.parseInt(trimmed.slice(i * 2, i * 2 + 2), 16)\n }\n return bytes\n}\n\nfunction bytesToHex(bytes: ArrayBuffer): string {\n return [...new Uint8Array(bytes)].map((b) => b.toString(16).padStart(2, '0')).join('')\n}\n\n/** Constant-time comparison for equal-length byte arrays. */\nfunction timingSafeEqualBytes(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false\n let diff = 0\n for (let i = 0; i < a.length; i++) {\n diff |= a[i]! ^ b[i]!\n }\n return diff === 0\n}\n\n/**\n * Verify webhook HMAC signature (preview — opt-in until Safaricom publishes spec).\n * Uses Web Crypto (`globalThis.crypto.subtle`) for Node, Bun, Deno, and Workers.\n */\nexport async function verifyWebhookHMAC(\n payload: string | ArrayBuffer | Uint8Array,\n signature: string,\n secret: string,\n algorithm: WebhookHmacAlgorithm = DEFAULT_WEBHOOK_HMAC_ALGORITHM,\n): Promise<boolean> {\n if (!secret || !signature) return false\n\n const subtle = globalThis.crypto?.subtle\n if (!subtle) return false\n\n const body =\n typeof payload === 'string'\n ? new TextEncoder().encode(payload)\n : payload instanceof Uint8Array\n ? payload\n : new Uint8Array(payload)\n\n const sigBytes = hexToBytes(signature)\n if (!sigBytes) return false\n\n try {\n const key = await subtle.importKey(\n 'raw',\n new TextEncoder().encode(secret),\n { name: 'HMAC', hash: ALGO_MAP[algorithm] },\n false,\n ['sign'],\n )\n const mac = await subtle.sign('HMAC', key, body as BufferSource)\n const expected = hexToBytes(bytesToHex(mac))\n if (!expected) return false\n return timingSafeEqualBytes(sigBytes, expected)\n } catch {\n return false\n }\n}\n","// src/mpesa/webhooks/signature-verifier.ts\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 { StkPushWebhookSchema } from '../../schemas/stk-push'\nimport { verifyWebhookHMAC, type WebhookHmacAlgorithm } from './hmac-verifier'\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 const result = StkPushWebhookSchema.safeParse(body)\n if (!result.success) return null\n return body as StkPushWebhook\n}\n\nexport interface WebhookVerificationOptions {\n requestIP: string\n allowedIPs?: readonly string[]\n skipIPCheck?: boolean\n secret?: string\n signature?: string\n rawBody?: string | Buffer\n signatureHeader?: string\n requireHMAC?: boolean\n hmacAlgorithm?: WebhookHmacAlgorithm\n}\n\nexport interface WebhookVerificationResult {\n valid: boolean\n ipValid: boolean\n hmacValid: boolean\n errors: string[]\n}\n\n/**\n * Combined webhook verification: IP allowlist (default) + optional HMAC.\n */\nexport async function verifyWebhook(\n options: WebhookVerificationOptions,\n): Promise<WebhookVerificationResult> {\n const errors: string[] = []\n const allowed = options.allowedIPs ?? SAFARICOM_IPS\n\n const ipValid = options.skipIPCheck === true || verifyWebhookIP(options.requestIP, allowed)\n if (!ipValid) errors.push('Request IP is not in the Safaricom allowlist.')\n\n let hmacValid = true\n if (options.secret && options.signature && options.rawBody !== undefined) {\n hmacValid = await verifyWebhookHMAC(\n options.rawBody,\n options.signature,\n options.secret,\n options.hmacAlgorithm,\n )\n if (!hmacValid) errors.push('Webhook HMAC signature verification failed.')\n } else if (options.requireHMAC) {\n hmacValid = false\n errors.push('HMAC verification required but secret, signature, or rawBody missing.')\n }\n\n const valid = ipValid && hmacValid\n return { valid, ipValid, hmacValid, errors }\n}\n\nexport { verifyWebhookHMAC } from './hmac-verifier'\n","// src/mpesa/webhooks/webhook-handler.ts\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","/**\n * src/mpesa/index.ts\n *\n * Primary M-PESA module entry point.\n *\n * Exports:\n * 1. Mpesa class — the main SDK client\n * 2. All submodule APIs, types, constants, and helpers\n *\n * Submodules re-exported here:\n * - account-balance\n * - b2b-buy-goods\n * - b2b-express-checkout\n * - b2b-pay-bill\n * - b2c (Account Top Up)\n * - b2c-disbursement\n * - bill-manager\n * - c2b\n * - dynamic-qr\n * - reversal\n * - stk-push\n * - tax-remittance\n * - transaction-status\n * - webhooks\n */\n\nimport { readFile } from 'node:fs/promises'\nimport { TokenManager } from '../core/auth'\nimport { encryptSecurityCredential } from '../core/encryption'\nimport { generateOriginatorConversationId, IdempotencyManager } from '../core/idempotency'\nimport { PesafyError } from '../utils/errors'\nimport type { DarajaHttpOptions } from '../utils/http'\nimport { err, ok, type Result } from '../types/branded'\n\nimport {\n queryAccountBalance,\n type AccountBalanceRequest,\n type AccountBalanceResponse,\n} from './account-balance'\nimport {\n initiateB2BExpressCheckout as _initiateB2BExpressCheckout,\n type B2BExpressCheckoutRequest,\n type B2BExpressCheckoutResponse,\n} from './b2b-express-checkout'\nimport {\n initiateB2BBuyGoods as _initiateB2BBuyGoods,\n type B2BBuyGoodsRequest,\n type B2BBuyGoodsResponse,\n} from './b2b-buy-goods'\nimport {\n initiateB2BPayBill as _initiateB2BPayBill,\n type B2BPayBillRequest,\n type B2BPayBillResponse,\n} from './b2b-pay-bill'\nimport { initiateB2CPayment as _initiateB2CPayment, type B2CRequest, type B2CResponse } from './b2c'\nimport {\n initiateB2CDisbursement as _initiateB2CDisbursement,\n type B2CDisbursementRequest,\n type B2CDisbursementResponse,\n} from './b2c-disbursement'\nimport {\n billManagerOptIn as _billManagerOptIn,\n cancelBulkInvoices as _cancelBulkInvoices,\n cancelInvoice as _cancelInvoice,\n reconcilePayment as _reconcilePayment,\n sendBulkInvoices as _sendBulkInvoices,\n sendSingleInvoice as _sendSingleInvoice,\n updateOptIn as _updateOptIn,\n type BillManagerBulkInvoiceRequest,\n type BillManagerBulkInvoiceResponse,\n type BillManagerCancelBulkInvoiceRequest,\n type BillManagerCancelBulkInvoiceResponse,\n type BillManagerCancelInvoiceRequest,\n type BillManagerCancelInvoiceResponse,\n type BillManagerOptInRequest,\n type BillManagerOptInResponse,\n type BillManagerReconciliationRequest,\n type BillManagerReconciliationResponse,\n type BillManagerSingleInvoiceRequest,\n type BillManagerSingleInvoiceResponse,\n type BillManagerUpdateOptInRequest,\n type BillManagerUpdateOptInResponse,\n} from './bill-manager'\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 { requestReversal, type ReversalRequest, type ReversalResponse } from './reversal'\nimport { processStkPush, queryStkPush, type StkPushRequest, type StkQueryRequest } from './stk-push'\nimport {\n remitTax as _remitTax,\n type TaxRemittanceRequest,\n type TaxRemittanceResponse,\n} from './tax-remittance'\nimport { queryTransactionStatus, type TransactionStatusRequest } from './transaction-status'\n\nimport { DARAJA_BASE_URLS, type MpesaConfig } from './types'\n\n// ── Mpesa client ──────────────────────────────────────────────────────────────\n\nexport class Mpesa {\n private readonly config: MpesaConfig\n private readonly tokenManager: TokenManager\n private readonly baseUrl: string\n readonly idempotencyManager: IdempotencyManager\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 this.config = config\n this.baseUrl = DARAJA_BASE_URLS[config.environment]\n this.tokenManager = new TokenManager(config.consumerKey, config.consumerSecret, this.baseUrl)\n this.idempotencyManager = new IdempotencyManager(config.idempotency)\n }\n\n /** Idempotency options passed to all outbound Daraja HTTP calls. */\n private darajaHttp(): DarajaHttpOptions {\n return { idempotency: this.idempotencyManager }\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 cert = await readFile(this.config.certificatePath, 'utf-8')\n } else {\n throw new PesafyError({\n code: 'INVALID_CREDENTIALS',\n message: 'certificatePath or certificatePem is required to encrypt the initiator password.',\n })\n }\n return encryptSecurityCredential(this.config.initiatorPassword, cert)\n }\n\n private requireInitiator(forApi: string): string {\n const name = this.config.initiatorName ?? ''\n if (!name) {\n throw new PesafyError({\n code: 'VALIDATION_ERROR',\n message: `initiatorName is required for ${forApi}.`,\n })\n }\n return name\n }\n\n // ── Safe wrappers ──────────────────────────────────────────────────────────\n\n async stkPushSafe(\n request: Omit<StkPushRequest, 'shortCode' | 'passKey'>,\n ): Promise<Result<Awaited<ReturnType<typeof this.stkPush>>>> {\n try {\n return ok(await this.stkPush(request))\n } catch (e) {\n return err(e as PesafyError)\n }\n }\n\n async accountBalanceSafe(\n request: AccountBalanceRequest,\n ): Promise<Result<AccountBalanceResponse>> {\n try {\n return ok(await this.accountBalance(request))\n } catch (e) {\n return err(e as PesafyError)\n }\n }\n\n // ── STK Push ───────────────────────────────────────────────────────────────\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: 'lipaNaMpesaShortCode and lipaNaMpesaPassKey are required for STK Push.',\n })\n }\n\n const token = await this.getToken()\n return processStkPush(\n this.baseUrl,\n token,\n { ...request, shortCode, passKey },\n this.darajaHttp(),\n )\n }\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: 'lipaNaMpesaShortCode and lipaNaMpesaPassKey are required for STK Query.',\n })\n }\n\n const token = await this.getToken()\n return queryStkPush(this.baseUrl, token, { ...request, shortCode, passKey }, this.darajaHttp())\n }\n\n // ── Transaction Status ─────────────────────────────────────────────────────\n\n async transactionStatus(request: TransactionStatusRequest) {\n const initiator = this.requireInitiator('Transaction Status')\n const [token, cred] = await Promise.all([this.getToken(), this.buildSecurityCredential()])\n return queryTransactionStatus(this.baseUrl, token, cred, initiator, request, this.darajaHttp())\n }\n\n // ── Account Balance ────────────────────────────────────────────────────────\n\n async accountBalance(request: AccountBalanceRequest): Promise<AccountBalanceResponse> {\n const initiator = this.requireInitiator('Account Balance')\n const [token, cred] = await Promise.all([this.getToken(), this.buildSecurityCredential()])\n return queryAccountBalance(this.baseUrl, token, cred, initiator, request, this.darajaHttp())\n }\n\n // ── Reversal ───────────────────────────────────────────────────────────────\n\n async reverseTransaction(request: ReversalRequest): Promise<ReversalResponse> {\n const initiator = this.requireInitiator('Reversal')\n const [token, cred] = await Promise.all([this.getToken(), this.buildSecurityCredential()])\n return requestReversal(this.baseUrl, token, cred, initiator, request, this.darajaHttp())\n }\n\n // ── Dynamic QR ─────────────────────────────────────────────────────────────\n\n async generateDynamicQR(request: DynamicQRRequest): Promise<DynamicQRResponse> {\n const token = await this.getToken()\n return _generateDynamicQR(this.baseUrl, token, request, this.darajaHttp())\n }\n\n // ── C2B ────────────────────────────────────────────────────────────────────\n\n async registerC2BUrls(request: C2BRegisterUrlRequest): Promise<C2BRegisterUrlResponse> {\n const token = await this.getToken()\n return _registerC2BUrls(this.baseUrl, token, request, this.darajaHttp())\n }\n\n async simulateC2B(request: C2BSimulateRequest): Promise<C2BSimulateResponse> {\n const token = await this.getToken()\n return _simulateC2B(this.baseUrl, token, request, this.darajaHttp())\n }\n\n // ── Tax Remittance ─────────────────────────────────────────────────────────\n\n async remitTax(request: TaxRemittanceRequest): Promise<TaxRemittanceResponse> {\n const initiator = this.requireInitiator('Tax Remittance')\n const [token, cred] = await Promise.all([this.getToken(), this.buildSecurityCredential()])\n return _remitTax(this.baseUrl, token, cred, initiator, request, this.darajaHttp())\n }\n\n // ── B2B Express Checkout ───────────────────────────────────────────────────\n\n async b2bExpressCheckout(\n request: B2BExpressCheckoutRequest,\n ): Promise<B2BExpressCheckoutResponse> {\n const token = await this.getToken()\n return _initiateB2BExpressCheckout(this.baseUrl, token, request, this.darajaHttp())\n }\n\n // ── B2B Buy Goods ──────────────────────────────────────────────────────────\n\n async b2bBuyGoods(request: B2BBuyGoodsRequest): Promise<B2BBuyGoodsResponse> {\n const initiator = this.requireInitiator('B2B Buy Goods')\n const [token, cred] = await Promise.all([this.getToken(), this.buildSecurityCredential()])\n return _initiateB2BBuyGoods(this.baseUrl, token, cred, initiator, request, this.darajaHttp())\n }\n\n // ── B2B Pay Bill ───────────────────────────────────────────────────────────\n\n async b2bPayBill(request: B2BPayBillRequest): Promise<B2BPayBillResponse> {\n const initiator = this.requireInitiator('B2B Pay Bill')\n const [token, cred] = await Promise.all([this.getToken(), this.buildSecurityCredential()])\n return _initiateB2BPayBill(this.baseUrl, token, cred, initiator, request, this.darajaHttp())\n }\n\n // ── B2C Payment (Account Top Up) ──────────────────────────────────────────\n\n async b2cPayment(request: B2CRequest): Promise<B2CResponse> {\n const initiator = this.requireInitiator('B2C Payment')\n const [token, cred] = await Promise.all([this.getToken(), this.buildSecurityCredential()])\n return _initiateB2CPayment(this.baseUrl, token, cred, initiator, request, this.darajaHttp())\n }\n\n // ── B2C Disbursement ───────────────────────────────────────────────────────\n\n async b2cDisbursement(request: B2CDisbursementRequest): Promise<B2CDisbursementResponse> {\n const initiator = this.requireInitiator('B2C Disbursement')\n const req = {\n ...request,\n originatorConversationId:\n request.originatorConversationId ?? generateOriginatorConversationId(),\n }\n const [token, cred] = await Promise.all([this.getToken(), this.buildSecurityCredential()])\n return _initiateB2CDisbursement(this.baseUrl, token, cred, initiator, req, this.darajaHttp())\n }\n\n // ── Bill Manager ───────────────────────────────────────────────────────────\n\n async billManagerOptIn(request: BillManagerOptInRequest): Promise<BillManagerOptInResponse> {\n const token = await this.getToken()\n return _billManagerOptIn(this.baseUrl, token, request, this.darajaHttp())\n }\n\n async updateOptIn(\n request: BillManagerUpdateOptInRequest,\n ): Promise<BillManagerUpdateOptInResponse> {\n const token = await this.getToken()\n return _updateOptIn(this.baseUrl, token, request, this.darajaHttp())\n }\n\n async sendInvoice(\n request: BillManagerSingleInvoiceRequest,\n ): Promise<BillManagerSingleInvoiceResponse> {\n const token = await this.getToken()\n return _sendSingleInvoice(this.baseUrl, token, request, this.darajaHttp())\n }\n\n async sendBulkInvoices(\n request: BillManagerBulkInvoiceRequest,\n ): Promise<BillManagerBulkInvoiceResponse> {\n const token = await this.getToken()\n return _sendBulkInvoices(this.baseUrl, token, request, this.darajaHttp())\n }\n\n async cancelInvoice(\n request: BillManagerCancelInvoiceRequest,\n ): Promise<BillManagerCancelInvoiceResponse> {\n const token = await this.getToken()\n return _cancelInvoice(this.baseUrl, token, request, this.darajaHttp())\n }\n\n async cancelBulkInvoices(\n request: BillManagerCancelBulkInvoiceRequest,\n ): Promise<BillManagerCancelBulkInvoiceResponse> {\n const token = await this.getToken()\n return _cancelBulkInvoices(this.baseUrl, token, request, this.darajaHttp())\n }\n\n async reconcilePayment(\n request: BillManagerReconciliationRequest,\n ): Promise<BillManagerReconciliationResponse> {\n const token = await this.getToken()\n return _reconcilePayment(this.baseUrl, token, request, this.darajaHttp())\n }\n\n // ── Utilities ──────────────────────────────────────────────────────────────\n\n clearTokenCache(): void {\n this.tokenManager.clearCache()\n }\n\n get environment() {\n return this.config.environment\n }\n}\n\n// ══════════════════════════════════════════════════════════════════════════════\n// SUBMODULE RE-EXPORTS\n// All public APIs from every M-PESA submodule are re-exported here so that\n// consumers can import directly from 'pesafy/mpesa' (if that path is added to\n// package.json exports) or so that src/index.ts can barrel from one place.\n// ══════════════════════════════════════════════════════════════════════════════\n\n// ── Types ─────────────────────────────────────────────────────────────────────\nexport type { Environment, MpesaConfig } from './types'\nexport { DARAJA_BASE_URLS } from './types'\n\n// ── Account Balance ───────────────────────────────────────────────────────────\nexport { queryAccountBalance } from './account-balance'\nexport {\n ACCOUNT_BALANCE_ERROR_CODES,\n isAccountBalanceSuccess,\n parseAccountBalance,\n getAccountBalanceParam,\n getAccountBalanceTransactionId,\n getAccountBalanceConversationId,\n getAccountBalanceOriginatorConversationId,\n getAccountBalanceCompletedTime,\n getAccountBalanceRawBalance,\n getAccountBalanceReferenceItem,\n} from './account-balance'\nexport type {\n AccountBalanceData,\n AccountBalanceErrorCode,\n AccountBalanceRequest,\n AccountBalanceResponse,\n AccountBalanceResult,\n AccountBalanceReferenceItem,\n AccountBalanceResultParameter,\n AccountBalanceResultParameterKey,\n ParsedAccount,\n} from './account-balance'\n\n// ── B2B Buy Goods ─────────────────────────────────────────────────────────────\nexport { initiateB2BBuyGoods } from './b2b-buy-goods'\nexport {\n B2B_BUY_GOODS_ERROR_CODES,\n B2B_BUY_GOODS_RESULT_CODES,\n isB2BBuyGoodsResult,\n isB2BBuyGoodsSuccess,\n isB2BBuyGoodsFailure,\n isKnownB2BBuyGoodsResultCode,\n getB2BBuyGoodsAmount,\n getB2BBuyGoodsBillReferenceNumber,\n getB2BBuyGoodsCompletedTime,\n getB2BBuyGoodsConversationId,\n getB2BBuyGoodsCurrency,\n getB2BBuyGoodsDebitAccountBalance,\n getB2BBuyGoodsDebitPartyAffectedBalance,\n getB2BBuyGoodsDebitPartyCharges,\n getB2BBuyGoodsInitiatorBalance,\n getB2BBuyGoodsOriginatorConversationId,\n getB2BBuyGoodsQueueTimeoutUrl,\n getB2BBuyGoodsReceiverName,\n getB2BBuyGoodsResultCode,\n getB2BBuyGoodsResultDesc,\n getB2BBuyGoodsResultParam,\n getB2BBuyGoodsTransactionId,\n} from './b2b-buy-goods'\nexport type {\n B2BBuyGoodsCommandID,\n B2BBuyGoodsErrorCode,\n B2BBuyGoodsErrorResponse,\n B2BBuyGoodsReferenceItem,\n B2BBuyGoodsRequest,\n B2BBuyGoodsResponse,\n B2BBuyGoodsResult,\n B2BBuyGoodsResultCode,\n B2BBuyGoodsResultParameter,\n B2BBuyGoodsResultParameterKey,\n} from './b2b-buy-goods'\n\n// ── B2B Express Checkout ──────────────────────────────────────────────────────\nexport { initiateB2BExpressCheckout } from './b2b-express-checkout'\nexport {\n B2B_RESULT_CODES,\n getB2BAmount,\n getB2BConversationId,\n getB2BPaymentReference,\n getB2BRequestId,\n getB2BResultCode,\n getB2BResultDesc,\n getB2BTransactionId,\n isB2BCheckoutCallback,\n isB2BCheckoutCancelled,\n isB2BCheckoutFailed,\n isB2BCheckoutSuccess,\n isB2BStatusSuccess,\n isKnownB2BResultCode,\n} from './b2b-express-checkout'\nexport type {\n B2BExpressCheckoutCallback,\n B2BExpressCheckoutCallbackCancelled,\n B2BExpressCheckoutCallbackFailed,\n B2BExpressCheckoutCallbackSuccess,\n B2BExpressCheckoutErrorCode,\n B2BExpressCheckoutErrorResponse,\n B2BExpressCheckoutRequest,\n B2BExpressCheckoutResponse,\n B2BResultCode,\n} from './b2b-express-checkout'\n\n// ── B2B Pay Bill ──────────────────────────────────────────────────────────────\nexport { initiateB2BPayBill } from './b2b-pay-bill'\nexport {\n B2B_PAY_BILL_ERROR_CODES,\n B2B_PAY_BILL_RESULT_CODES,\n isB2BPayBillResult,\n isB2BPayBillSuccess,\n isB2BPayBillFailure,\n isKnownB2BPayBillResultCode,\n getB2BPayBillAmount,\n getB2BPayBillBillReferenceNumber,\n getB2BPayBillCompletedTime,\n getB2BPayBillConversationId,\n getB2BPayBillCurrency,\n getB2BPayBillDebitAccountBalance,\n getB2BPayBillDebitPartyAffectedBalance,\n getB2BPayBillDebitPartyCharges,\n getB2BPayBillInitiatorBalance,\n getB2BPayBillOriginatorConversationId,\n getB2BPayBillReceiverName,\n getB2BPayBillResultCode,\n getB2BPayBillResultDesc,\n getB2BPayBillResultParam,\n getB2BPayBillTransactionId,\n} from './b2b-pay-bill'\nexport type {\n B2BPayBillCommandID,\n B2BPayBillErrorCode,\n B2BPayBillErrorResponse,\n B2BPayBillReferenceItem,\n B2BPayBillRequest,\n B2BPayBillResponse,\n B2BPayBillResult,\n B2BPayBillResultCode,\n B2BPayBillResultParameter,\n B2BPayBillResultParameterKey,\n} from './b2b-pay-bill'\n\n// ── B2C Account Top Up ────────────────────────────────────────────────────────\nexport { initiateB2CPayment } from './b2c'\nexport {\n B2C_ERROR_CODES,\n B2C_RESULT_CODES,\n getB2CAmount,\n getB2CCurrency,\n getB2CDebitAccountBalance,\n getB2CDebitPartyCharges,\n getB2CConversationId,\n getB2COriginatorConversationId,\n getB2CReceiverPublicName,\n getB2CResultDesc,\n getB2CResultParam,\n getB2CTransactionCompletedTime,\n getB2CTransactionId,\n isB2CFailure,\n isB2CResult,\n isB2CSuccess,\n isKnownB2CResultCode,\n} from './b2c'\nexport type {\n B2CCommandID,\n B2CErrorCode,\n B2CErrorResponse,\n B2CRequest,\n B2CResponse,\n B2CResult,\n B2CResultCode,\n B2CResultParameter,\n B2CResultParameterKey,\n} from './b2c'\n\n// ── B2C Disbursement ──────────────────────────────────────────────────────────\nexport { initiateB2CDisbursement } from './b2c-disbursement'\nexport {\n B2C_DISBURSEMENT_RESULT_CODES,\n getB2CDisbursementAmount,\n getB2CDisbursementCompletedTime,\n getB2CDisbursementConversationId,\n getB2CDisbursementOriginatorConversationId,\n getB2CDisbursementReceiptNumber,\n getB2CDisbursementReceiverName,\n getB2CDisbursementResultCode,\n getB2CDisbursementResultDesc,\n getB2CDisbursementResultParam,\n getB2CDisbursementTransactionId,\n getB2CDisbursementUtilityBalance,\n getB2CDisbursementWorkingBalance,\n isB2CDisbursementFailure,\n isB2CDisbursementRecipientRegistered,\n isB2CDisbursementResult,\n isB2CDisbursementSuccess,\n isKnownB2CDisbursementResultCode,\n} from './b2c-disbursement'\nexport type {\n B2CDisbursementCommandID,\n B2CDisbursementErrorResponse,\n B2CDisbursementRequest,\n B2CDisbursementResponse,\n B2CDisbursementResult,\n B2CDisbursementResultCode,\n B2CDisbursementResultParameter,\n B2CDisbursementResultParameterKey,\n} from './b2c-disbursement'\n\n// ── Bill Manager ──────────────────────────────────────────────────────────────\nexport {\n billManagerOptIn,\n updateOptIn,\n sendSingleInvoice,\n sendBulkInvoices,\n cancelInvoice,\n cancelBulkInvoices,\n reconcilePayment,\n} from './bill-manager'\nexport type {\n BillManagerBulkInvoiceRequest,\n BillManagerBulkInvoiceResponse,\n BillManagerCancelBulkInvoiceRequest,\n BillManagerCancelBulkInvoiceResponse,\n BillManagerCancelInvoiceRequest,\n BillManagerCancelInvoiceResponse,\n BillManagerInvoiceItem,\n BillManagerOptInRequest,\n BillManagerOptInResponse,\n BillManagerPaymentCallbackResponse,\n BillManagerPaymentNotification,\n BillManagerReconciliationRequest,\n BillManagerReconciliationResponse,\n BillManagerSingleInvoiceRequest,\n BillManagerSingleInvoiceResponse,\n BillManagerUpdateOptInRequest,\n BillManagerUpdateOptInResponse,\n} from './bill-manager'\n\n// ── C2B ───────────────────────────────────────────────────────────────────────\nexport { registerC2BUrls, simulateC2B } from './c2b'\nexport {\n C2B_REGISTER_URL_ERROR_CODES,\n C2B_VALIDATION_RESULT_CODES,\n acceptC2BValidation,\n acknowledgeC2BConfirmation,\n getC2BAccountRef,\n getC2BAmount,\n getC2BCustomerName,\n getC2BTransactionId,\n isBuyGoodsPayment,\n isC2BPayload,\n isPaybillPayment,\n rejectC2BValidation,\n} from './c2b'\nexport type {\n C2BApiVersion,\n C2BCommandID,\n C2BConfirmationAck,\n C2BConfirmationPayload,\n C2BRegisterUrlErrorCode,\n C2BRegisterUrlRequest,\n C2BRegisterUrlResponse,\n C2BResponseType,\n C2BSimulateRequest,\n C2BSimulateResponse,\n C2BValidationPayload,\n C2BValidationResponse,\n C2BValidationResultCode,\n} from './c2b'\n\n// ── Dynamic QR ────────────────────────────────────────────────────────────────\nexport { generateDynamicQR } from './dynamic-qr'\nexport {\n QR_TRANSACTION_CODES,\n DEFAULT_QR_SIZE,\n MAX_QR_SIZE,\n MIN_AMOUNT,\n MIN_QR_SIZE,\n validateAmount,\n validateCpi,\n validateDynamicQRRequest,\n validateMerchantName,\n validateRefNo,\n validateSize,\n validateTrxCode,\n} from './dynamic-qr'\nexport type {\n DynamicQRDarajaPayload,\n DynamicQRErrorResponse,\n DynamicQRRequest,\n DynamicQRResponse,\n QRTransactionCode,\n ValidationFail,\n ValidationOk,\n ValidationResult,\n} from './dynamic-qr'\n\n// ── Reversal ──────────────────────────────────────────────────────────────────\nexport { requestReversal } from './reversal'\nexport {\n REVERSAL_COMMAND_ID,\n REVERSAL_ERROR_CODES,\n REVERSAL_RECEIVER_IDENTIFIER_TYPE,\n REVERSAL_RESULT_CODES,\n isKnownReversalResultCode,\n isReversalFailure,\n isReversalResult,\n isReversalSuccess,\n getReversalAmount,\n getReversalCharge,\n getReversalCompletedTime,\n getReversalConversationId,\n getReversalCreditPartyPublicName,\n getReversalDebitAccountBalance,\n getReversalDebitPartyPublicName,\n getReversalOriginalTransactionId,\n getReversalOriginatorConversationId,\n getReversalResultCode,\n getReversalResultDesc,\n getReversalResultParam,\n getReversalTransactionId,\n} from './reversal'\nexport type {\n ReversalErrorCode,\n ReversalRequest,\n ReversalResponse,\n ReversalResult,\n ReversalResultCode,\n ReversalResultParameter,\n ReversalResultParameterKey,\n} from './reversal'\n\n// ── STK Push ──────────────────────────────────────────────────────────────────\nexport { processStkPush } from './stk-push'\nexport { queryStkPush } from './stk-push'\nexport {\n STK_PUSH_LIMITS,\n STK_RESULT_CODES,\n isKnownStkResultCode,\n isStkCallbackSuccess,\n getCallbackValue,\n} from './stk-push'\nexport { formatPhoneNumber, getTimestamp } from './stk-push'\nexport type {\n StkCallbackFailure,\n StkCallbackInner,\n StkCallbackMetadataItem,\n StkCallbackSuccess,\n StkPushCallback,\n StkPushRequest,\n StkPushResponse,\n StkQueryRequest,\n StkQueryResponse,\n StkResultCode,\n TransactionType,\n} from './stk-push'\n\n// ── Tax Remittance ────────────────────────────────────────────────────────────\nexport { KRA_SHORTCODE, remitTax, TAX_COMMAND_ID } from './tax-remittance'\nexport {\n isTaxRemittanceFailure,\n isTaxRemittanceResult,\n isTaxRemittanceSuccess,\n getTaxAmount,\n getTaxCompletedTime,\n getTaxConversationId,\n getTaxOriginatorConversationId,\n getTaxReceiverName,\n getTaxResultCode,\n getTaxResultDesc,\n getTaxResultParam,\n getTaxTransactionId,\n} from './tax-remittance'\nexport type {\n TaxRemittanceErrorResponse,\n TaxRemittanceRequest,\n TaxRemittanceResponse,\n TaxRemittanceResult,\n TaxRemittanceResultParameter,\n TaxRemittanceResultParameterKey,\n} from './tax-remittance'\n\n// ── Transaction Status ────────────────────────────────────────────────────────\nexport { queryTransactionStatus } from './transaction-status'\nexport {\n TRANSACTION_STATUS_ERROR_CODES,\n TRANSACTION_STATUS_RESULT_CODES,\n isTransactionStatusFailure,\n isTransactionStatusResult,\n isTransactionStatusSuccess,\n isKnownTransactionStatusResultCode,\n getTransactionStatusConversationId,\n getTransactionStatusOriginatorConversationId,\n getTransactionStatusResultCode,\n getTransactionStatusResultDesc,\n getTransactionStatusTransactionId,\n getTransactionStatusAmount,\n getTransactionStatusCreditPartyName,\n getTransactionStatusDebitAccountBalance,\n getTransactionStatusDebitPartyName,\n getTransactionStatusReceiptNo,\n getTransactionStatusResultParam,\n getTransactionStatusStatus,\n getTransactionStatusTransactionDate,\n} from './transaction-status'\nexport type {\n TransactionStatusErrorCode,\n TransactionStatusRequest,\n TransactionStatusResponse,\n TransactionStatusResult,\n TransactionStatusResultCode,\n TransactionStatusResultParameter,\n TransactionStatusResultParameterKey,\n} from './transaction-status'\n\n// ── Webhooks ──────────────────────────────────────────────────────────────────\nexport {\n retryWithBackoff,\n parseStkPushWebhook,\n SAFARICOM_IPS,\n verifyWebhookIP,\n extractAmount,\n extractPhoneNumber,\n extractTransactionId,\n handleWebhook,\n isSuccessfulCallback,\n} from './webhooks'\nexport type {\n RetryOptions,\n RetryResult,\n StkPushWebhook,\n WebhookEvent,\n WebhookEventType,\n WebhookHandlerOptions,\n WebhookHandlerResult,\n} from './webhooks'\n","import { verifyWebhook } from '../mpesa/webhooks/signature-verifier'\n\n/** Optional webhook security settings shared by framework adapters. */\nexport interface AdapterWebhookSecurityConfig {\n /** Skip Safaricom IP allowlist (local dev only). */\n skipIPCheck?: boolean\n /** Shared secret for opt-in HMAC verification. */\n webhookSecret?: string\n /** Reject when HMAC is required but missing/invalid. */\n requireHMAC?: boolean\n /** Header carrying the HMAC signature (default: x-safaricom-signature). */\n signatureHeader?: string\n}\n\nconst DEFAULT_SIGNATURE_HEADER = 'x-safaricom-signature'\n\n/**\n * Verify webhook IP (+ optional HMAC). Logs warnings on failure; does not throw.\n * Returns whether verification passed (IP and HMAC when configured).\n */\nexport async function guardWebhookRequest(\n requestIP: string,\n rawBody: string | undefined,\n getHeader: (name: string) => string | undefined,\n config: AdapterWebhookSecurityConfig,\n): Promise<boolean> {\n const headerName = config.signatureHeader ?? DEFAULT_SIGNATURE_HEADER\n const signature =\n getHeader(headerName) ??\n getHeader(headerName.toLowerCase()) ??\n getHeader(headerName.toUpperCase())\n\n const result = await verifyWebhook({\n requestIP,\n ...(config.skipIPCheck !== undefined ? { skipIPCheck: config.skipIPCheck } : {}),\n ...(config.webhookSecret !== undefined ? { secret: config.webhookSecret } : {}),\n ...(signature !== undefined ? { signature } : {}),\n ...(rawBody !== undefined ? { rawBody } : {}),\n ...(config.requireHMAC !== undefined ? { requireHMAC: config.requireHMAC } : {}),\n })\n\n if (!result.valid) {\n console.warn('[pesafy] Webhook verification failed:', result.errors.join('; '))\n }\n\n return result.valid\n}\n","import { PesafyError } from '../../utils/errors'\n\nexport function resolveUrl(\n explicit: string | undefined,\n override: string | undefined,\n fallback: string | undefined,\n label: string,\n): string {\n // Use || (not ??) so that an empty-string explicit value falls through\n // to a non-empty override or fallback, matching the test contract:\n // resolveUrl('', 'https://override.co', undefined, 'url') → 'https://override.co'\n const resolved = explicit || override || fallback || ''\n if (!resolved) {\n throw new PesafyError({\n code: 'VALIDATION_ERROR',\n message: `${label} is required. Set it in config or include it in the request body.`,\n })\n }\n return resolved\n}\n\nexport function fireHook(fn: (() => Promise<void>) | undefined, label: string): void {\n if (!fn) return\n fn().catch((err) => console.error(`[pesafy] ${label} hook error:`, err))\n}\n","import { Mpesa } from '../../mpesa'\nimport {\n type AccountBalanceRequest,\n type AccountBalanceResult,\n getAccountBalanceRawBalance,\n isAccountBalanceSuccess,\n parseAccountBalance,\n} from '../../mpesa/account-balance'\nimport {\n type B2BExpressCheckoutCallback,\n getB2BAmount,\n getB2BConversationId,\n getB2BTransactionId,\n isB2BCheckoutCallback,\n isB2BCheckoutCancelled,\n isB2BCheckoutSuccess,\n} from '../../mpesa/b2b-express-checkout'\nimport {\n type B2CResult,\n getB2CAmount,\n getB2COriginatorConversationId,\n getB2CTransactionId,\n isB2CResult,\n isB2CSuccess,\n} from '../../mpesa/b2c'\nimport {\n type B2CDisbursementResult,\n isB2CDisbursementResult,\n isB2CDisbursementSuccess,\n} from '../../mpesa/b2c-disbursement'\nimport type {\n BillManagerBulkInvoiceRequest,\n BillManagerCancelBulkInvoiceRequest,\n BillManagerCancelInvoiceRequest,\n BillManagerOptInRequest,\n BillManagerReconciliationRequest,\n BillManagerSingleInvoiceRequest,\n} from '../../mpesa/bill-manager'\nimport {\n acceptC2BValidation,\n type C2BConfirmationPayload,\n type C2BValidationPayload,\n} from '../../mpesa/c2b'\nimport type { DynamicQRRequest } from '../../mpesa/dynamic-qr'\nimport {\n type ReversalRequest,\n type ReversalResult,\n isReversalResult,\n isReversalSuccess,\n getReversalTransactionId,\n} from '../../mpesa/reversal'\nimport {\n type TaxRemittanceRequest,\n isTaxRemittanceResult,\n isTaxRemittanceSuccess,\n} from '../../mpesa/tax-remittance'\nimport {\n isTransactionStatusResult,\n isTransactionStatusSuccess,\n} from '../../mpesa/transaction-status'\nimport {\n extractAmount,\n extractPhoneNumber,\n extractTransactionId,\n isSuccessfulCallback,\n} from '../../mpesa/webhooks'\nimport { PesafyError } from '../../utils/errors'\nimport { guardWebhookRequest } from '../webhook-guard'\nimport { fireHook, resolveUrl } from './helpers'\nimport type { RouteOperationId } from './route-definitions'\nimport type {\n HandlerContext,\n HandlerResult,\n MpesaAdapterConfig,\n StkFailurePayload,\n StkSuccessPayload,\n} from './types'\n\nasync function runWebhookGuard(ctx: HandlerContext, config: MpesaAdapterConfig): Promise<void> {\n const allowed = await guardWebhookRequest(ctx.requestIP, ctx.rawBody, ctx.getHeader, config)\n if (!allowed) {\n throw new PesafyError({\n code: 'AUTH_FAILED',\n message: 'Webhook verification failed: invalid source IP or HMAC signature',\n statusCode: 403,\n })\n }\n}\n\nexport function createRouteHandlers(\n mpesa: Mpesa,\n config: MpesaAdapterConfig,\n): Record<RouteOperationId, (ctx: HandlerContext) => Promise<HandlerResult>> {\n return {\n 'stk.push': async (ctx) => {\n const { amount, phoneNumber, accountReference, transactionDesc, transactionType, partyB } =\n ctx.body as {\n amount: number\n phoneNumber: string\n accountReference?: string\n transactionDesc?: string\n transactionType?: 'CustomerPayBillOnline' | 'CustomerBuyGoodsOnline'\n partyB?: string\n }\n\n if (!amount || amount <= 0)\n throw new PesafyError({ code: 'VALIDATION_ERROR', message: 'amount must be > 0' })\n if (!phoneNumber)\n throw new PesafyError({ code: 'VALIDATION_ERROR', message: 'phoneNumber is required' })\n\n const result = await mpesa.stkPush({\n amount,\n phoneNumber,\n callbackUrl: config.callbackUrl,\n accountReference: accountReference ?? `REF-${Date.now().toString(36).toUpperCase()}`,\n transactionDesc: transactionDesc ?? 'Payment',\n ...(transactionType !== undefined ? { transactionType } : {}),\n ...(partyB !== undefined ? { partyB } : {}),\n })\n\n return { type: 'api', body: result }\n },\n\n 'stk.query': async (ctx) => {\n const { checkoutRequestId } = ctx.body as { checkoutRequestId: string }\n if (!checkoutRequestId)\n throw new PesafyError({\n code: 'VALIDATION_ERROR',\n message: 'checkoutRequestId is required',\n })\n const result = await mpesa.stkQuery({ checkoutRequestId })\n return { type: 'api', body: result }\n },\n\n 'stk.callback': async (ctx) => {\n await runWebhookGuard(ctx, config)\n\n const webhook = ctx.body as import('../../mpesa/webhooks').StkPushWebhook\n const cb = webhook?.Body?.stkCallback\n if (!cb) return { type: 'daraja', body: { ResultCode: 0, ResultDesc: 'Accepted' } }\n\n if (isSuccessfulCallback(webhook)) {\n const payload: StkSuccessPayload = {\n receiptNumber: extractTransactionId(webhook),\n amount: extractAmount(webhook),\n phone: extractPhoneNumber(webhook),\n checkoutRequestId: cb.CheckoutRequestID,\n merchantRequestId: cb.MerchantRequestID,\n }\n console.info('[pesafy] STK success:', payload)\n fireHook(\n config.onStkSuccess ? () => Promise.resolve(config.onStkSuccess!(payload)) : undefined,\n 'onStkSuccess',\n )\n } else {\n const payload: StkFailurePayload = {\n resultCode: cb.ResultCode,\n resultDesc: cb.ResultDesc,\n checkoutRequestId: cb.CheckoutRequestID,\n merchantRequestId: cb.MerchantRequestID,\n }\n console.warn('[pesafy] STK failure:', payload)\n fireHook(\n config.onStkFailure ? () => Promise.resolve(config.onStkFailure!(payload)) : undefined,\n 'onStkFailure',\n )\n }\n\n return { type: 'daraja', body: { ResultCode: 0, ResultDesc: 'Accepted' } }\n },\n\n 'c2b.register': async (ctx) => {\n const {\n shortCode = config.c2b?.shortCode,\n confirmationUrl = config.c2b?.confirmationUrl,\n validationUrl = config.c2b?.validationUrl,\n responseType = config.c2b?.responseType ?? 'Completed',\n apiVersion = config.c2b?.apiVersion ?? 'v2',\n } = ctx.body as {\n shortCode?: string\n confirmationUrl?: string\n validationUrl?: string\n responseType?: 'Completed' | 'Cancelled'\n apiVersion?: 'v1' | 'v2'\n }\n\n if (!shortCode)\n throw new PesafyError({ code: 'VALIDATION_ERROR', message: 'shortCode is required' })\n if (!confirmationUrl)\n throw new PesafyError({ code: 'VALIDATION_ERROR', message: 'confirmationUrl is required' })\n if (!validationUrl)\n throw new PesafyError({ code: 'VALIDATION_ERROR', message: 'validationUrl is required' })\n\n const result = await mpesa.registerC2BUrls({\n shortCode,\n responseType,\n confirmationUrl,\n validationUrl,\n apiVersion,\n })\n return { type: 'api', body: result }\n },\n\n 'c2b.simulate': async (ctx) => {\n const { commandId, amount, msisdn, billRefNumber, shortCode, apiVersion } = ctx.body as {\n commandId: 'CustomerPayBillOnline' | 'CustomerBuyGoodsOnline'\n amount: number\n msisdn: string | number\n billRefNumber?: string\n shortCode?: string | number\n apiVersion?: 'v1' | 'v2'\n }\n\n if (!commandId)\n throw new PesafyError({ code: 'VALIDATION_ERROR', message: 'commandId is required' })\n if (!amount || amount <= 0)\n throw new PesafyError({ code: 'VALIDATION_ERROR', message: 'amount must be > 0' })\n if (!msisdn)\n throw new PesafyError({ code: 'VALIDATION_ERROR', message: 'msisdn is required' })\n\n const result = await mpesa.simulateC2B({\n shortCode: shortCode ?? config.c2b?.shortCode ?? '',\n commandId,\n amount,\n msisdn,\n apiVersion: apiVersion ?? config.c2b?.apiVersion ?? 'v2',\n ...(billRefNumber !== undefined ? { billRefNumber } : {}),\n })\n return { type: 'api', body: result }\n },\n\n 'c2b.validation': async (ctx) => {\n await runWebhookGuard(ctx, config)\n const payload = ctx.body as C2BValidationPayload\n const response = config.onC2BValidation\n ? await config.onC2BValidation(payload)\n : acceptC2BValidation()\n return { type: 'daraja', body: response }\n },\n\n // FIX (Issue 2): Added runWebhookGuard to all inbound webhook/callback/result handlers.\n 'c2b.confirmation': async (ctx) => {\n await runWebhookGuard(ctx, config)\n const payload = ctx.body as C2BConfirmationPayload\n console.info('[pesafy] C2B confirmation:', {\n transactionId: payload.TransID,\n amount: payload.TransAmount,\n billRef: payload.BillRefNumber,\n })\n fireHook(\n config.onC2BConfirmation\n ? () => Promise.resolve(config.onC2BConfirmation!(payload))\n : undefined,\n 'onC2BConfirmation',\n )\n return { type: 'daraja', body: { ResultCode: 0, ResultDesc: 'Success' } }\n },\n\n 'balance.query': async (ctx) => {\n const body = ctx.body as Partial<AccountBalanceRequest> & {\n resultUrl?: string\n queueTimeoutUrl?: string\n }\n const result = await mpesa.accountBalance({\n partyA: body.partyA ?? config.balance?.shortCode ?? '',\n identifierType: body.identifierType ?? '4',\n resultUrl: resolveUrl(\n body.resultUrl,\n config.balance?.resultUrl,\n config.resultUrl,\n 'resultUrl',\n ),\n queueTimeOutUrl: resolveUrl(\n body.queueTimeoutUrl,\n config.balance?.queueTimeoutUrl,\n config.queueTimeoutUrl,\n 'queueTimeoutUrl',\n ),\n ...(body.remarks !== undefined ? { remarks: body.remarks } : {}),\n })\n return { type: 'api', body: result }\n },\n\n // FIX (Issue 2): Added runWebhookGuard.\n 'balance.result': async (ctx) => {\n await runWebhookGuard(ctx, config)\n const body = ctx.body as unknown\n if (!isAccountBalanceSuccess(body as AccountBalanceResult)) {\n console.warn('[pesafy] Account balance failed:', body)\n } else {\n const raw = getAccountBalanceRawBalance(body as AccountBalanceResult)\n console.info('[pesafy] Account balance:', raw ? parseAccountBalance(raw) : body)\n }\n fireHook(\n config.onAccountBalanceResult\n ? () => Promise.resolve(config.onAccountBalanceResult!(body as AccountBalanceResult))\n : undefined,\n 'onAccountBalanceResult',\n )\n return { type: 'daraja', body: { ResultCode: 0, ResultDesc: 'Accepted' } }\n },\n\n 'qr.generate': async (ctx) => {\n const result = await mpesa.generateDynamicQR(ctx.body as DynamicQRRequest)\n return { type: 'api', body: result }\n },\n\n 'reversal.request': async (ctx) => {\n const body = ctx.body as Partial<ReversalRequest> & {\n resultUrl?: string\n queueTimeoutUrl?: string\n }\n if (!body.transactionId)\n throw new PesafyError({ code: 'VALIDATION_ERROR', message: 'transactionId is required' })\n if (!body.receiverParty)\n throw new PesafyError({ code: 'VALIDATION_ERROR', message: 'receiverParty is required' })\n if (!body.amount || body.amount <= 0)\n throw new PesafyError({ code: 'VALIDATION_ERROR', message: 'amount must be > 0' })\n\n const result = await mpesa.reverseTransaction({\n transactionId: body.transactionId,\n receiverParty: body.receiverParty,\n amount: body.amount,\n resultUrl: resolveUrl(\n body.resultUrl,\n config.reversal?.resultUrl,\n config.resultUrl,\n 'resultUrl',\n ),\n queueTimeOutUrl: resolveUrl(\n body.queueTimeoutUrl,\n config.reversal?.queueTimeoutUrl,\n config.queueTimeoutUrl,\n 'queueTimeoutUrl',\n ),\n ...(body.remarks !== undefined ? { remarks: body.remarks } : {}),\n ...(body.occasion !== undefined ? { occasion: body.occasion } : {}),\n })\n return { type: 'api', body: result }\n },\n\n 'reversal.result': async (ctx) => {\n await runWebhookGuard(ctx, config)\n const body = ctx.body as unknown\n if (isReversalResult(body)) {\n if (isReversalSuccess(body)) {\n console.info('[pesafy] Reversal success:', { txId: getReversalTransactionId(body) })\n } else {\n console.warn('[pesafy] Reversal failed:', body.Result.ResultDesc)\n }\n fireHook(\n config.onReversalResult\n ? () => Promise.resolve(config.onReversalResult!(body))\n : undefined,\n 'onReversalResult',\n )\n }\n return { type: 'daraja', body: { ResultCode: 0, ResultDesc: 'Accepted' } }\n },\n\n 'txStatus.query': async (ctx) => {\n const body = ctx.body as {\n transactionId?: string\n originalConversationId?: string\n partyA: string\n identifierType: '1' | '2' | '4'\n resultUrl?: string\n queueTimeoutUrl?: string\n remarks?: string\n occasion?: string\n }\n const result = await mpesa.transactionStatus({\n ...(body.transactionId !== undefined ? { transactionId: body.transactionId } : {}),\n ...(body.originalConversationId !== undefined\n ? { originalConversationId: body.originalConversationId }\n : {}),\n partyA: body.partyA,\n identifierType: body.identifierType,\n resultUrl: resolveUrl(\n body.resultUrl,\n config.txStatus?.resultUrl,\n config.resultUrl,\n 'resultUrl',\n ),\n queueTimeOutUrl: resolveUrl(\n body.queueTimeoutUrl,\n config.txStatus?.queueTimeoutUrl,\n config.queueTimeoutUrl,\n 'queueTimeoutUrl',\n ),\n ...(body.remarks !== undefined ? { remarks: body.remarks } : {}),\n ...(body.occasion !== undefined ? { occasion: body.occasion } : {}),\n })\n return { type: 'api', body: result }\n },\n\n 'txStatus.result': async (ctx) => {\n await runWebhookGuard(ctx, config)\n const body = ctx.body as unknown\n if (isTransactionStatusResult(body)) {\n if (isTransactionStatusSuccess(body)) {\n console.info('[pesafy] Transaction status success:', body.Result.TransactionID)\n } else {\n console.warn('[pesafy] Transaction status failed:', body.Result.ResultDesc)\n }\n fireHook(\n config.onTxStatusResult\n ? () => Promise.resolve(config.onTxStatusResult!(body))\n : undefined,\n 'onTxStatusResult',\n )\n }\n return { type: 'daraja', body: { ResultCode: 0, ResultDesc: 'Accepted' } }\n },\n\n 'tax.remit': async (ctx) => {\n const body = ctx.body as Partial<TaxRemittanceRequest> & {\n resultUrl?: string\n queueTimeoutUrl?: string\n }\n if (!body.amount || body.amount <= 0)\n throw new PesafyError({ code: 'VALIDATION_ERROR', message: 'amount must be > 0' })\n if (!body.accountReference)\n throw new PesafyError({\n code: 'VALIDATION_ERROR',\n message: 'accountReference (KRA PRN) is required',\n })\n\n const result = await mpesa.remitTax({\n amount: body.amount,\n partyA: body.partyA ?? config.tax?.partyA ?? '',\n accountReference: body.accountReference,\n resultUrl: resolveUrl(body.resultUrl, config.tax?.resultUrl, config.resultUrl, 'resultUrl'),\n queueTimeOutUrl: resolveUrl(\n body.queueTimeoutUrl,\n config.tax?.queueTimeoutUrl,\n config.queueTimeoutUrl,\n 'queueTimeoutUrl',\n ),\n ...(body.partyB !== undefined ? { partyB: body.partyB } : {}),\n ...(body.remarks !== undefined ? { remarks: body.remarks } : {}),\n })\n return { type: 'api', body: result }\n },\n\n 'tax.result': async (ctx) => {\n await runWebhookGuard(ctx, config)\n const body = ctx.body as unknown\n if (isTaxRemittanceResult(body)) {\n if (isTaxRemittanceSuccess(body)) {\n console.info('[pesafy] Tax remittance success:', body.Result.TransactionID)\n } else {\n console.warn('[pesafy] Tax remittance failed:', body.Result.ResultDesc)\n }\n fireHook(\n config.onTaxResult ? () => Promise.resolve(config.onTaxResult!(body)) : undefined,\n 'onTaxResult',\n )\n }\n return { type: 'daraja', body: { ResultCode: 0, ResultDesc: 'Accepted' } }\n },\n\n 'b2b.checkout': async (ctx) => {\n const body = ctx.body as {\n primaryShortCode: string\n receiverShortCode?: string\n amount: number\n paymentRef: string\n partnerName: string\n callbackUrl?: string\n requestRefId?: string\n }\n\n if (!body.primaryShortCode)\n throw new PesafyError({ code: 'VALIDATION_ERROR', message: 'primaryShortCode is required' })\n if (!body.amount || body.amount <= 0)\n throw new PesafyError({ code: 'VALIDATION_ERROR', message: 'amount must be > 0' })\n if (!body.paymentRef)\n throw new PesafyError({ code: 'VALIDATION_ERROR', message: 'paymentRef is required' })\n if (!body.partnerName)\n throw new PesafyError({ code: 'VALIDATION_ERROR', message: 'partnerName is required' })\n\n const receiverShortCode = body.receiverShortCode ?? config.b2b?.receiverShortCode ?? ''\n if (!receiverShortCode)\n throw new PesafyError({\n code: 'VALIDATION_ERROR',\n message: 'receiverShortCode is required',\n })\n\n const callbackUrl = body.callbackUrl ?? config.b2b?.callbackUrl ?? ''\n if (!callbackUrl)\n throw new PesafyError({ code: 'VALIDATION_ERROR', message: 'callbackUrl is required' })\n\n const result = await mpesa.b2bExpressCheckout({\n primaryShortCode: body.primaryShortCode,\n receiverShortCode,\n amount: body.amount,\n paymentRef: body.paymentRef,\n callbackUrl,\n partnerName: body.partnerName,\n ...(body.requestRefId !== undefined ? { requestRefId: body.requestRefId } : {}),\n })\n return { type: 'api', body: result }\n },\n\n 'b2b.callback': async (ctx) => {\n await runWebhookGuard(ctx, config)\n const body = ctx.body as unknown\n if (!isB2BCheckoutCallback(body)) {\n console.warn('[pesafy] Unknown B2B callback payload')\n return { type: 'daraja', body: { ResultCode: 0, ResultDesc: 'Accepted' } }\n }\n\n const callback = body as B2BExpressCheckoutCallback\n if (isB2BCheckoutSuccess(callback)) {\n console.info('[pesafy] B2B checkout success:', {\n txId: getB2BTransactionId(callback),\n conversationId: getB2BConversationId(callback),\n amount: getB2BAmount(callback),\n })\n } else if (isB2BCheckoutCancelled(callback)) {\n console.warn('[pesafy] B2B checkout cancelled by merchant')\n } else {\n console.warn('[pesafy] B2B checkout failed:', callback.resultDesc)\n }\n\n fireHook(\n config.onB2BCheckoutCallback\n ? () => Promise.resolve(config.onB2BCheckoutCallback!(callback))\n : undefined,\n 'onB2BCheckoutCallback',\n )\n return { type: 'daraja', body: { ResultCode: 0, ResultDesc: 'Accepted' } }\n },\n\n 'b2c.payment': async (ctx) => {\n const body = ctx.body as {\n commandId: 'BusinessPayToBulk'\n amount: number\n partyA?: string\n partyB: string\n accountReference: string\n requester?: string\n remarks?: string\n resultUrl?: string\n queueTimeoutUrl?: string\n }\n\n if (body.commandId !== 'BusinessPayToBulk')\n throw new PesafyError({\n code: 'VALIDATION_ERROR',\n message: 'commandId must be \"BusinessPayToBulk\"',\n })\n if (!body.amount || body.amount <= 0)\n throw new PesafyError({ code: 'VALIDATION_ERROR', message: 'amount must be > 0' })\n if (!body.partyB)\n throw new PesafyError({ code: 'VALIDATION_ERROR', message: 'partyB is required' })\n if (!body.accountReference)\n throw new PesafyError({ code: 'VALIDATION_ERROR', message: 'accountReference is required' })\n\n const result = await mpesa.b2cPayment({\n commandId: 'BusinessPayToBulk',\n amount: body.amount,\n partyA: body.partyA ?? config.b2c?.partyA ?? '',\n partyB: body.partyB,\n accountReference: body.accountReference,\n resultUrl: resolveUrl(body.resultUrl, config.b2c?.resultUrl, config.resultUrl, 'resultUrl'),\n queueTimeOutUrl: resolveUrl(\n body.queueTimeoutUrl,\n config.b2c?.queueTimeoutUrl,\n config.queueTimeoutUrl,\n 'queueTimeoutUrl',\n ),\n ...(body.requester !== undefined ? { requester: body.requester } : {}),\n ...(body.remarks !== undefined ? { remarks: body.remarks } : {}),\n })\n return { type: 'api', body: result }\n },\n\n 'b2c.result': async (ctx) => {\n await runWebhookGuard(ctx, config)\n const body = ctx.body as unknown\n if (isB2CResult(body)) {\n if (isB2CSuccess(body)) {\n console.info('[pesafy] B2C success:', {\n txId: getB2CTransactionId(body),\n amount: getB2CAmount(body),\n origConvId: getB2COriginatorConversationId(body),\n })\n } else {\n console.warn('[pesafy] B2C failed:', body.Result.ResultDesc)\n }\n fireHook(\n config.onB2CResult ? () => Promise.resolve(config.onB2CResult!(body)) : undefined,\n 'onB2CResult',\n )\n }\n return { type: 'daraja', body: { ResultCode: 0, ResultDesc: 'Accepted' } }\n },\n\n 'b2c.disburse': async (ctx) => {\n const body = ctx.body as {\n originatorConversationId: string\n commandId: 'BusinessPayment' | 'SalaryPayment' | 'PromotionPayment'\n amount: number\n partyA: string\n partyB: string\n remarks: string\n resultUrl?: string\n queueTimeoutUrl?: string\n occasion?: string\n }\n\n const result = await mpesa.b2cDisbursement({\n originatorConversationId: body.originatorConversationId,\n commandId: body.commandId,\n amount: body.amount,\n partyA: body.partyA,\n partyB: body.partyB,\n remarks: body.remarks,\n resultUrl: resolveUrl(body.resultUrl, config.b2c?.resultUrl, config.resultUrl, 'resultUrl'),\n queueTimeOutUrl: resolveUrl(\n body.queueTimeoutUrl,\n config.b2c?.queueTimeoutUrl,\n config.queueTimeoutUrl,\n 'queueTimeoutUrl',\n ),\n ...(body.occasion !== undefined ? { occasion: body.occasion } : {}),\n })\n return { type: 'api', body: result }\n },\n\n 'b2c.disburse.result': async (ctx) => {\n await runWebhookGuard(ctx, config)\n const body = ctx.body as unknown\n if (isB2CDisbursementResult(body)) {\n if (isB2CDisbursementSuccess(body)) {\n console.info('[pesafy] B2C disbursement success:', body.Result.TransactionID)\n } else {\n console.warn('[pesafy] B2C disbursement failed:', body.Result.ResultDesc)\n }\n fireHook(\n config.onB2CDisbursementResult\n ? () => Promise.resolve(config.onB2CDisbursementResult!(body))\n : undefined,\n 'onB2CDisbursementResult',\n )\n }\n return { type: 'daraja', body: { ResultCode: 0, ResultDesc: 'Accepted' } }\n },\n\n 'bills.optin': async (ctx) => {\n const result = await mpesa.billManagerOptIn(ctx.body as BillManagerOptInRequest)\n return { type: 'api', body: result }\n },\n\n 'bills.optin.patch': async (ctx) => {\n const result = await mpesa.updateOptIn(ctx.body as BillManagerOptInRequest)\n return { type: 'api', body: result }\n },\n\n 'bills.invoice': async (ctx) => {\n const result = await mpesa.sendInvoice(ctx.body as BillManagerSingleInvoiceRequest)\n return { type: 'api', body: result }\n },\n\n 'bills.invoice.delete': async (ctx) => {\n const result = await mpesa.cancelInvoice(ctx.body as BillManagerCancelInvoiceRequest)\n return { type: 'api', body: result }\n },\n\n 'bills.invoice.bulk': async (ctx) => {\n const result = await mpesa.sendBulkInvoices(ctx.body as BillManagerBulkInvoiceRequest)\n return { type: 'api', body: result }\n },\n\n 'bills.invoice.bulk.delete': async (ctx) => {\n const result = await mpesa.cancelBulkInvoices(ctx.body as BillManagerCancelBulkInvoiceRequest)\n return { type: 'api', body: result }\n },\n\n 'bills.reconcile': async (ctx) => {\n const result = await mpesa.reconcilePayment(ctx.body as BillManagerReconciliationRequest)\n return { type: 'api', body: result }\n },\n\n health: async () => ({\n type: 'api' as const,\n body: { ok: true, environment: mpesa.environment, ts: new Date().toISOString() },\n }),\n }\n}\n","export type RouteOperationId =\n | 'stk.push'\n | 'stk.query'\n | 'stk.callback'\n | 'c2b.register'\n | 'c2b.simulate'\n | 'c2b.validation'\n | 'c2b.confirmation'\n | 'balance.query'\n | 'balance.result'\n | 'qr.generate'\n | 'reversal.request'\n | 'reversal.result'\n | 'txStatus.query'\n | 'txStatus.result'\n | 'tax.remit'\n | 'tax.result'\n | 'b2b.checkout'\n | 'b2b.callback'\n | 'b2c.payment'\n | 'b2c.result'\n | 'b2c.disburse'\n | 'b2c.disburse.result'\n | 'bills.optin'\n | 'bills.optin.patch'\n | 'bills.invoice'\n | 'bills.invoice.delete'\n | 'bills.invoice.bulk'\n | 'bills.invoice.bulk.delete'\n | 'bills.reconcile'\n | 'health'\n\nexport type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE'\n\nexport interface RouteDefinition {\n id: RouteOperationId\n method: HttpMethod\n path: string\n /**\n * True for every inbound Daraja webhook/callback/result endpoint.\n * Used by adapters to:\n * 1. Run guardWebhookRequest before processing (handled in handlers.ts).\n * 2. Capture the raw request body for HMAC verification before JSON parsing.\n */\n webhook?: boolean\n}\n\nexport const ROUTE_DEFINITIONS: RouteDefinition[] = [\n { id: 'stk.push', method: 'POST', path: '/mpesa/stk/push' },\n { id: 'stk.query', method: 'POST', path: '/mpesa/stk/query' },\n { id: 'stk.callback', method: 'POST', path: '/mpesa/stk/callback', webhook: true },\n { id: 'c2b.register', method: 'POST', path: '/mpesa/c2b/register' },\n { id: 'c2b.simulate', method: 'POST', path: '/mpesa/c2b/simulate' },\n { id: 'c2b.validation', method: 'POST', path: '/mpesa/c2b/validation', webhook: true },\n { id: 'c2b.confirmation', method: 'POST', path: '/mpesa/c2b/confirmation', webhook: true },\n { id: 'balance.query', method: 'POST', path: '/mpesa/balance/query' },\n { id: 'balance.result', method: 'POST', path: '/mpesa/balance/result', webhook: true },\n { id: 'qr.generate', method: 'POST', path: '/mpesa/qr/generate' },\n { id: 'reversal.request', method: 'POST', path: '/mpesa/reversal/request' },\n { id: 'reversal.result', method: 'POST', path: '/mpesa/reversal/result', webhook: true },\n { id: 'txStatus.query', method: 'POST', path: '/mpesa/tx-status/query' },\n { id: 'txStatus.result', method: 'POST', path: '/mpesa/tx-status/result', webhook: true },\n { id: 'tax.remit', method: 'POST', path: '/mpesa/tax/remit' },\n { id: 'tax.result', method: 'POST', path: '/mpesa/tax/result', webhook: true },\n { id: 'b2b.checkout', method: 'POST', path: '/mpesa/b2b/checkout' },\n { id: 'b2b.callback', method: 'POST', path: '/mpesa/b2b/callback', webhook: true },\n { id: 'b2c.payment', method: 'POST', path: '/mpesa/b2c/payment' },\n { id: 'b2c.result', method: 'POST', path: '/mpesa/b2c/result', webhook: true },\n { id: 'b2c.disburse', method: 'POST', path: '/mpesa/b2c/disburse' },\n { id: 'b2c.disburse.result', method: 'POST', path: '/mpesa/b2c/disburse/result', webhook: true },\n { id: 'bills.optin', method: 'POST', path: '/mpesa/bills/optin' },\n { id: 'bills.optin.patch', method: 'PATCH', path: '/mpesa/bills/optin' },\n { id: 'bills.invoice', method: 'POST', path: '/mpesa/bills/invoice' },\n { id: 'bills.invoice.delete', method: 'DELETE', path: '/mpesa/bills/invoice' },\n { id: 'bills.invoice.bulk', method: 'POST', path: '/mpesa/bills/invoice/bulk' },\n { id: 'bills.invoice.bulk.delete', method: 'DELETE', path: '/mpesa/bills/invoice/bulk' },\n { id: 'bills.reconcile', method: 'POST', path: '/mpesa/bills/reconcile' },\n { id: 'health', method: 'GET', path: '/mpesa/health' },\n]\n\n/** Base paths (no routePrefix) for all adapter routes. */\nexport function getRoutePaths(prefix = ''): string[] {\n return ROUTE_DEFINITIONS.map((r) => `${prefix}${r.path}`)\n}\n"],"mappings":"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"}
|