pesafy 0.5.0 → 0.5.2

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.

Potentially problematic release.


This version of pesafy might be problematic. Click here for more details.

Files changed (57) hide show
  1. package/CHANGELOG.md +100 -16
  2. package/LICENSE +21 -0
  3. package/dist/{express/index.d.cts → adapters/express.d.ts} +23 -138
  4. package/dist/adapters/express.js +1 -0
  5. package/dist/adapters/fastify.d.ts +21 -0
  6. package/dist/adapters/fastify.js +1 -0
  7. package/dist/adapters/hono.d.ts +27 -0
  8. package/dist/adapters/hono.js +1 -0
  9. package/dist/adapters/{nextjs.d.cts → nextjs.d.ts} +3 -30
  10. package/dist/adapters/nextjs.js +1 -0
  11. package/dist/{cli/index.mjs → cli.mjs} +10 -9
  12. package/dist/encryption.mjs +22 -0
  13. package/dist/{cli/errors-DL4bkMZV.mjs → errors.mjs} +2 -1
  14. package/dist/{index.d.mts → index.d.ts} +37 -64
  15. package/dist/index.js +1 -0
  16. package/dist/{cli/phone-5wwAaQ_8.mjs → phone.mjs} +4 -2
  17. package/dist/signature-verifier.js +1 -0
  18. package/dist/{adapters/fastify.d.cts → types.d.ts} +2 -22
  19. package/dist/{cli/utils-BzEKV3nJ.mjs → utils.mjs} +4 -2
  20. package/dist/webhook-handler.js +1 -0
  21. package/package.json +119 -49
  22. package/dist/adapters/fastify.cjs +0 -1607
  23. package/dist/adapters/fastify.cjs.map +0 -1
  24. package/dist/adapters/fastify.d.mts +0 -49
  25. package/dist/adapters/fastify.mjs +0 -1606
  26. package/dist/adapters/fastify.mjs.map +0 -1
  27. package/dist/adapters/hono.cjs +0 -1651
  28. package/dist/adapters/hono.cjs.map +0 -1
  29. package/dist/adapters/hono.d.cts +0 -55
  30. package/dist/adapters/hono.d.mts +0 -55
  31. package/dist/adapters/hono.mjs +0 -1650
  32. package/dist/adapters/hono.mjs.map +0 -1
  33. package/dist/adapters/nextjs.cjs +0 -1655
  34. package/dist/adapters/nextjs.cjs.map +0 -1
  35. package/dist/adapters/nextjs.d.mts +0 -79
  36. package/dist/adapters/nextjs.mjs +0 -1651
  37. package/dist/adapters/nextjs.mjs.map +0 -1
  38. package/dist/cli/encryption-BA-_xrIW.mjs +0 -45
  39. package/dist/cli/encryption-CkSveeYj.cjs +0 -45
  40. package/dist/cli/errors-Bscvlb7X.cjs +0 -45
  41. package/dist/cli/index.cjs +0 -559
  42. package/dist/cli/phone-BD4QmEyl.cjs +0 -21
  43. package/dist/cli/utils-Dg9Gv_D3.cjs +0 -31
  44. package/dist/components/react/index.d.mts +0 -1
  45. package/dist/components/react/index.mjs +0 -1
  46. package/dist/express/index.cjs +0 -2201
  47. package/dist/express/index.cjs.map +0 -1
  48. package/dist/express/index.d.mts +0 -1322
  49. package/dist/express/index.mjs +0 -2199
  50. package/dist/express/index.mjs.map +0 -1
  51. package/dist/index.cjs +0 -2124
  52. package/dist/index.cjs.map +0 -1
  53. package/dist/index.d.cts +0 -1907
  54. package/dist/index.mjs +0 -2050
  55. package/dist/index.mjs.map +0 -1
  56. /package/dist/{components/react/index.d.cts → react/index.d.ts} +0 -0
  57. /package/dist/{components/react/index.cjs → react/index.js} +0 -0
@@ -1,31 +1,5 @@
1
- //#region src/mpesa/types.d.ts
2
- /**
3
- * Core M-Pesa / Daraja configuration types
4
- */
5
- type Environment = "sandbox" | "production";
6
- interface MpesaConfig {
7
- consumerKey: string;
8
- consumerSecret: string;
9
- environment: Environment;
10
- lipaNaMpesaShortCode?: string;
11
- lipaNaMpesaPassKey?: string;
12
- initiatorName?: string;
13
- initiatorPassword?: string;
14
- certificatePath?: string;
15
- certificatePem?: string;
16
- /**
17
- * Pre-computed base64 SecurityCredential — skips RSA encryption.
18
- * Use when you encrypt at startup outside the library.
19
- */
20
- securityCredential?: string;
21
- /** Override default retry count (4) for all API calls */
22
- retries?: number;
23
- /** Override default base retry delay in ms (2000) */
24
- retryDelay?: number;
25
- /** Override default per-request timeout in ms (30000) */
26
- timeout?: number;
27
- }
28
- //#endregion
1
+ import { n as MpesaConfig } from "../types.js";
2
+
29
3
  //#region src/adapters/nextjs.d.ts
30
4
  interface MpesaNextConfig extends MpesaConfig {
31
5
  callbackUrl: string;
@@ -75,5 +49,4 @@ declare function createMpesaNextHandlers(config: MpesaNextConfig): {
75
49
  POST(req: NextRequest): Promise<Response>;
76
50
  };
77
51
  //#endregion
78
- export { MpesaNextConfig, createMpesaNextHandlers, createStkCallbackHandler, createStkPushHandler, createStkQueryHandler };
79
- //# sourceMappingURL=nextjs.d.cts.map
52
+ export { MpesaNextConfig, createMpesaNextHandlers, createStkCallbackHandler, createStkPushHandler, createStkQueryHandler };
@@ -0,0 +1 @@
1
+ import{i as e,n as t,r as n}from"../signature-verifier.js";function r(e,t=200){return new Response(JSON.stringify(e),{status:t,headers:{"Content-Type":`application/json`}})}function i(e){return e.headers.get(`x-forwarded-for`)?.split(`,`)[0]?.trim()??e.headers.get(`x-real-ip`)??``}function a(t){let i=new n(t);return async function(n){try{let a=await n.json();if(!a.amount||a.amount<=0)throw new e({code:`VALIDATION_ERROR`,message:`amount must be > 0`});if(!a.phoneNumber)throw new e({code:`VALIDATION_ERROR`,message:`phoneNumber is required`});return r(await i.stkPush({amount:a.amount,phoneNumber:a.phoneNumber,callbackUrl:t.callbackUrl,accountReference:a.accountReference??`REF-${Date.now().toString(36).toUpperCase()}`,transactionDesc:a.transactionDesc??`Payment`,...a.partyB===void 0?{}:{partyB:a.partyB}}))}catch(t){return t instanceof e?r({error:t.code,message:t.message},t.statusCode??400):r({error:`INTERNAL_ERROR`,message:`Unexpected error`},500)}}}function o(t){let i=new n(t);return async function(t){try{let{checkoutRequestId:n}=await t.json();if(!n)throw new e({code:`VALIDATION_ERROR`,message:`checkoutRequestId is required`});return r(await i.stkQuery({checkoutRequestId:n}))}catch(t){return t instanceof e?r({error:t.code,message:t.message},t.statusCode??400):r({error:`INTERNAL_ERROR`},500)}}}function s(e){return async function(n){if(!e.skipIPCheck){let e=i(n);e&&!t(e)&&console.warn(`[pesafy/nextjs] Callback from unknown IP:`,e)}let a=(await n.json())?.Body?.stkCallback;if(!a)return r({ResultCode:0,ResultDesc:`Accepted`});let o=a.ResultCode===0,s=a.CallbackMetadata?.Item,c=e=>s?.find(t=>t.Name===e)?.Value;return o&&e.onSuccess?Promise.resolve(e.onSuccess({receiptNumber:c(`MpesaReceiptNumber`),amount:c(`Amount`),phone:c(`PhoneNumber`)})).catch(console.error):!o&&e.onFailure&&Promise.resolve(e.onFailure({resultCode:a.ResultCode,resultDesc:a.ResultDesc})).catch(console.error),r({ResultCode:0,ResultDesc:`Accepted`})}}function c(t){let i=a(t),c=o(t),l=s(t),u=new n(t);return{async POST(n){let a=n.nextUrl?.pathname??``;if(a.endsWith(`stk-push`))return i(n);if(a.endsWith(`stk-query`))return c(n);if(a.endsWith(`callback`))return l(n);if(a.endsWith(`balance`))try{let e=await n.json();return r(await u.accountBalance({...e,resultUrl:t.resultUrl??``,queueTimeOutUrl:t.queueTimeOutUrl??``}))}catch(t){return t instanceof e?r({error:t.code,message:t.message},400):r({error:`INTERNAL_ERROR`},500)}return r({error:`NOT_FOUND`,message:`No handler for ${a}`},404)}}}export{c as createMpesaNextHandlers,s as createStkCallbackHandler,a as createStkPushHandler,o as createStkQueryHandler};
@@ -2,9 +2,9 @@
2
2
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { createInterface } from "node:readline";
4
4
  import { resolve } from "node:path";
5
+
5
6
  //#region src/cli/index.ts
6
7
  /**
7
- * pesafy CLI
8
8
  *
9
9
  * Usage:
10
10
  * npx pesafy <command> [options]
@@ -283,7 +283,7 @@ async function cmdEncrypt(args) {
283
283
  process.exit(1);
284
284
  }
285
285
  try {
286
- const { encryptSecurityCredential } = await import("./encryption-BA-_xrIW.mjs");
286
+ const { encryptSecurityCredential } = await import("./encryption.mjs");
287
287
  const { readFile } = await import("node:fs/promises");
288
288
  const pem = await readFile(resolve(process.cwd(), certPath), "utf-8");
289
289
  const credential = encryptSecurityCredential(password, pem);
@@ -298,7 +298,7 @@ async function cmdValidatePhone(args) {
298
298
  let phone = args[0];
299
299
  if (!phone) phone = await prompt("Phone number to validate");
300
300
  try {
301
- const { formatSafaricomPhone } = await import("./phone-5wwAaQ_8.mjs");
301
+ const { formatSafaricomPhone } = await import("./phone.mjs");
302
302
  const normalised = formatSafaricomPhone(phone);
303
303
  console.log(`\n${g("✔")} ${b(phone)} → ${c(normalised)}\n`);
304
304
  } catch (e) {
@@ -323,9 +323,9 @@ async function cmdStkPush(args) {
323
323
  const baseUrl = env["MPESA_ENVIRONMENT"] === "production" ? "https://api.safaricom.co.ke" : "https://sandbox.safaricom.co.ke";
324
324
  console.log(dim("\nFetching token…"));
325
325
  const token = await getToken(env["MPESA_CONSUMER_KEY"], env["MPESA_CONSUMER_SECRET"], baseUrl);
326
- const { formatSafaricomPhone } = await import("./phone-5wwAaQ_8.mjs");
326
+ const { formatSafaricomPhone } = await import("./phone.mjs");
327
327
  const msisdn = formatSafaricomPhone(phone);
328
- const { getStkPushPassword, getTimestamp } = await import("./utils-BzEKV3nJ.mjs");
328
+ const { getStkPushPassword, getTimestamp } = await import("./utils.mjs");
329
329
  const timestamp = getTimestamp();
330
330
  const password = getStkPushPassword(env["MPESA_SHORTCODE"], env["MPESA_PASSKEY"], timestamp);
331
331
  console.log(dim("Sending STK Push…\n"));
@@ -365,7 +365,7 @@ async function cmdStkQuery(args) {
365
365
  if (!checkoutId) checkoutId = await prompt("CheckoutRequestID");
366
366
  const baseUrl = env["MPESA_ENVIRONMENT"] === "production" ? "https://api.safaricom.co.ke" : "https://sandbox.safaricom.co.ke";
367
367
  const token = await getToken(env["MPESA_CONSUMER_KEY"], env["MPESA_CONSUMER_SECRET"], baseUrl);
368
- const { getStkPushPassword, getTimestamp } = await import("./utils-BzEKV3nJ.mjs");
368
+ const { getStkPushPassword, getTimestamp } = await import("./utils.mjs");
369
369
  const timestamp = getTimestamp();
370
370
  const password = getStkPushPassword(env["MPESA_SHORTCODE"], env["MPESA_PASSKEY"], timestamp);
371
371
  try {
@@ -394,7 +394,7 @@ async function cmdBalance(args) {
394
394
  const baseUrl = env["MPESA_ENVIRONMENT"] === "production" ? "https://api.safaricom.co.ke" : "https://sandbox.safaricom.co.ke";
395
395
  console.log(dim("\nFetching token…"));
396
396
  const token = await getToken(env["MPESA_CONSUMER_KEY"], env["MPESA_CONSUMER_SECRET"], baseUrl);
397
- const { encryptSecurityCredential } = await import("./encryption-BA-_xrIW.mjs");
397
+ const { encryptSecurityCredential } = await import("./encryption.mjs");
398
398
  const { readFile } = await import("node:fs/promises");
399
399
  const certPath = env["MPESA_CERTIFICATE_PATH"] ?? "./SandboxCertificate.cer";
400
400
  const pem = await readFile(resolve(process.cwd(), certPath), "utf-8");
@@ -490,7 +490,7 @@ async function cmdReversal(args) {
490
490
  const queueTimeoutUrl = env["MPESA_QUEUE_TIMEOUT_URL"] ?? await prompt("Queue Timeout URL");
491
491
  const baseUrl = env["MPESA_ENVIRONMENT"] === "production" ? "https://api.safaricom.co.ke" : "https://sandbox.safaricom.co.ke";
492
492
  const token = await getToken(env["MPESA_CONSUMER_KEY"], env["MPESA_CONSUMER_SECRET"], baseUrl);
493
- const { encryptSecurityCredential } = await import("./encryption-BA-_xrIW.mjs");
493
+ const { encryptSecurityCredential } = await import("./encryption.mjs");
494
494
  const { readFile } = await import("node:fs/promises");
495
495
  const certPath = env["MPESA_CERTIFICATE_PATH"] ?? "./SandboxCertificate.cer";
496
496
  const pem = await readFile(resolve(process.cwd(), certPath), "utf-8");
@@ -556,5 +556,6 @@ async function main() {
556
556
  }
557
557
  }
558
558
  main();
559
+
559
560
  //#endregion
560
- export {};
561
+ export { };
@@ -0,0 +1,22 @@
1
+ import { t as PesafyError } from "./errors.mjs";
2
+ import { constants, publicEncrypt } from "node:crypto";
3
+
4
+ //#region src/core/encryption/security-credentials.ts
5
+ function encryptSecurityCredential(initiatorPassword, certificatePem) {
6
+ try {
7
+ const passwordBuffer = Buffer.from(initiatorPassword, "utf-8");
8
+ return publicEncrypt({
9
+ key: certificatePem,
10
+ padding: constants.RSA_PKCS1_PADDING
11
+ }, passwordBuffer).toString("base64");
12
+ } catch (error) {
13
+ throw new PesafyError({
14
+ code: "ENCRYPTION_FAILED",
15
+ message: "Failed to encrypt security credential. Ensure the certificate PEM is valid and matches the environment (sandbox/production).",
16
+ cause: error
17
+ });
18
+ }
19
+ }
20
+
21
+ //#endregion
22
+ export { encryptSecurityCredential };
@@ -36,5 +36,6 @@ var PesafyError = class PesafyError extends Error {
36
36
  };
37
37
  }
38
38
  };
39
+
39
40
  //#endregion
40
- export { PesafyError as t };
41
+ export { PesafyError as t };
@@ -1,4 +1,3 @@
1
-
2
1
  //#region src/mpesa/stk-push/types.d.ts
3
2
  /**
4
3
  * STK Push (M-Pesa Express) types
@@ -12,7 +11,7 @@
12
11
  * CustomerPayBillOnline → Paybill numbers (PartyB = shortcode)
13
12
  * CustomerBuyGoodsOnline → Till numbers (PartyB = till number)
14
13
  */
15
- type TransactionType = "CustomerPayBillOnline" | "CustomerBuyGoodsOnline";
14
+ type TransactionType = 'CustomerPayBillOnline' | 'CustomerBuyGoodsOnline';
16
15
  interface StkPushRequest {
17
16
  /** Transaction amount (minimum KES 1, must round to a whole number ≥ 1) */
18
17
  amount: number;
@@ -98,7 +97,7 @@ interface StkQueryResponse {
98
97
  }
99
98
  /** Single metadata item in a successful STK callback */
100
99
  interface StkCallbackMetadataItem {
101
- Name: "Amount" | "MpesaReceiptNumber" | "TransactionDate" | "PhoneNumber" | "Balance";
100
+ Name: 'Amount' | 'MpesaReceiptNumber' | 'TransactionDate' | 'PhoneNumber' | 'Balance';
102
101
  /** Present on successful transactions; absent on failure */
103
102
  Value?: number | string;
104
103
  }
@@ -141,7 +140,7 @@ declare function isStkCallbackSuccess(cb: StkCallbackInner): cb is StkCallbackSu
141
140
  * Extracts a named value from a successful callback's metadata.
142
141
  * Returns undefined if the key is absent or the transaction failed.
143
142
  */
144
- declare function getCallbackValue(callback: StkPushCallback, name: StkCallbackMetadataItem["Name"]): string | number | undefined;
143
+ declare function getCallbackValue(callback: StkPushCallback, name: StkCallbackMetadataItem['Name']): string | number | undefined;
145
144
  //#endregion
146
145
  //#region src/utils/phone/index.d.ts
147
146
  /** Normalises any common Kenyan phone format to 254XXXXXXXXX (12 digits) */
@@ -195,7 +194,7 @@ interface TransactionStatusRequest {
195
194
  * "4" = Organisation ShortCode (Paybill / B2C) ← most common
196
195
  * Daraja field: IdentifierType
197
196
  */
198
- identifierType: "1" | "2" | "4";
197
+ identifierType: '1' | '2' | '4';
199
198
  /**
200
199
  * URL where Safaricom POSTs the final result.
201
200
  * Must be publicly accessible.
@@ -255,7 +254,7 @@ interface TransactionStatusResult {
255
254
  /**
256
255
  * Core M-Pesa / Daraja configuration types
257
256
  */
258
- type Environment = "sandbox" | "production";
257
+ type Environment = 'sandbox' | 'production';
259
258
  declare const DARAJA_BASE_URLS: Record<Environment, string>;
260
259
  interface MpesaConfig {
261
260
  consumerKey: string;
@@ -352,7 +351,7 @@ type Brand<T, B extends string> = T & {
352
351
  * A whole-number KES amount (Kenyan Shillings).
353
352
  * M-PESA only supports whole numbers — fractional shillings are rejected.
354
353
  */
355
- type KesAmount = Brand<number, "KesAmount">;
354
+ type KesAmount = Brand<number, 'KesAmount'>;
356
355
  /**
357
356
  * Creates a validated KesAmount.
358
357
  * @throws {TypeError} if amount is not a whole number ≥ 1
@@ -361,28 +360,28 @@ declare function toKesAmount(value: number): KesAmount;
361
360
  /**
362
361
  * A validated Kenyan MSISDN in Daraja format: 254XXXXXXXXX (12 digits).
363
362
  */
364
- type MsisdnKE = Brand<string, "MsisdnKE">;
363
+ type MsisdnKE = Brand<string, 'MsisdnKE'>;
365
364
  /**
366
365
  * Creates a validated MsisdnKE from any common Kenyan phone format.
367
366
  */
368
367
  declare function toMsisdn(phone: string): MsisdnKE;
369
368
  /** M-PESA Paybill shortcode */
370
- type PaybillCode = Brand<string, "PaybillCode">;
369
+ type PaybillCode = Brand<string, 'PaybillCode'>;
371
370
  /** M-PESA Till/Buy-Goods shortcode */
372
- type TillCode = Brand<string, "TillCode">;
371
+ type TillCode = Brand<string, 'TillCode'>;
373
372
  /** Any M-PESA shortcode (paybill, till, or B2C) */
374
- type ShortCode = PaybillCode | TillCode | Brand<string, "ShortCode">;
373
+ type ShortCode = PaybillCode | TillCode | Brand<string, 'ShortCode'>;
375
374
  declare function toPaybill(code: string | number): PaybillCode;
376
375
  declare function toTill(code: string | number): TillCode;
377
376
  declare function toShortCode(code: string | number): ShortCode;
378
377
  /** M-PESA receipt number, e.g. "OEI2AK4XXXX" */
379
- type MpesaReceiptNumber = Brand<string, "MpesaReceiptNumber">;
378
+ type MpesaReceiptNumber = Brand<string, 'MpesaReceiptNumber'>;
380
379
  /** Daraja ConversationID */
381
- type ConversationID = Brand<string, "ConversationID">;
380
+ type ConversationID = Brand<string, 'ConversationID'>;
382
381
  /** Daraja OriginatorConversationID */
383
- type OriginatorConversationID = Brand<string, "OriginatorConversationID">;
382
+ type OriginatorConversationID = Brand<string, 'OriginatorConversationID'>;
384
383
  /** Daraja CheckoutRequestID (STK Push) */
385
- type CheckoutRequestID = Brand<string, "CheckoutRequestID">;
384
+ type CheckoutRequestID = Brand<string, 'CheckoutRequestID'>;
386
385
  /**
387
386
  * A discriminated union result — either Ok<T> or Err<E>.
388
387
  * Prefer this over throwing in application-level code.
@@ -409,7 +408,7 @@ type DeepReadonly<T> = { readonly [K in keyof T]: T[K] extends object ? DeepRead
409
408
  /** Strict pick — only allows known keys */
410
409
  type StrictPick<T, K extends keyof T> = Pick<T, K>;
411
410
  /** Non-empty string */
412
- type NonEmptyString = Brand<string, "NonEmptyString">;
411
+ type NonEmptyString = Brand<string, 'NonEmptyString'>;
413
412
  declare function toNonEmpty(s: string): NonEmptyString;
414
413
  //#endregion
415
414
  //#region src/mpesa/account-balance/types.d.ts
@@ -438,7 +437,7 @@ interface AccountBalanceRequest {
438
437
  * "4" = Organisation ShortCode (most common — Paybill/B2C)
439
438
  * Daraja field: IdentifierType
440
439
  */
441
- identifierType: "1" | "2" | "4";
440
+ identifierType: '1' | '2' | '4';
442
441
  /**
443
442
  * URL where Safaricom POSTs the balance result.
444
443
  * Must be publicly accessible. HTTPS required in production.
@@ -641,7 +640,7 @@ type B2BExpressCheckoutCallback = B2BExpressCheckoutCallbackSuccess | B2BExpress
641
640
  * `(string & {})` keeps these literals in IntelliSense while still
642
641
  * accepting unknown codes, without triggering no-redundant-type-constituents.
643
642
  */
644
- type B2BExpressCheckoutErrorCode = "4001" | "4102" | "4104" | "4201" | "4203" | (string & {});
643
+ type B2BExpressCheckoutErrorCode = '4001' | '4102' | '4104' | '4201' | '4203' | (string & {});
645
644
  interface B2BExpressCheckoutErrorResponse {
646
645
  requestId: string;
647
646
  errorCode: B2BExpressCheckoutErrorCode;
@@ -720,7 +719,7 @@ declare function getB2BConversationId(callback: B2BExpressCheckoutCallback): str
720
719
  * PromotionPayment — Payment of promotions/bonuses
721
720
  * BusinessPayToBulk — Load funds to a B2C shortcode for bulk disbursement
722
721
  */
723
- type B2CCommandID = "BusinessPayment" | "SalaryPayment" | "PromotionPayment" | "BusinessPayToBulk";
722
+ type B2CCommandID = 'BusinessPayment' | 'SalaryPayment' | 'PromotionPayment' | 'BusinessPayToBulk';
724
723
  interface B2CRequest {
725
724
  /**
726
725
  * The type of transaction. Use "BusinessPayToBulk" for account top-up.
@@ -758,14 +757,14 @@ interface B2CRequest {
758
757
  * Daraja field: SenderIdentifierType
759
758
  * Default: "4"
760
759
  */
761
- senderIdentifierType?: "4";
760
+ senderIdentifierType?: '4';
762
761
  /**
763
762
  * Type of the receiver (PartyB) identifier.
764
763
  * For this API, only "4" (Organisation ShortCode) is allowed.
765
764
  * Daraja field: RecieverIdentifierType
766
765
  * Default: "4"
767
766
  */
768
- receiverIdentifierType?: "4";
767
+ receiverIdentifierType?: '4';
769
768
  /**
770
769
  * A reference for this transaction (e.g. invoice number, batch reference).
771
770
  * Daraja field: AccountReference
@@ -813,7 +812,7 @@ interface B2CResponse {
813
812
  * - Any unknown future key Daraja may return is still accepted.
814
813
  * - The `no-redundant-type-constituents` ESLint rule is not triggered.
815
814
  */
816
- type B2CResultParameterKey = "DebitAccountBalance" | "Amount" | "DebitPartyAffectedAccountBalance" | "TransCompletedTime" | "DebitPartyCharges" | "ReceiverPartyPublicName" | "Currency" | "InitiatorAccountCurrentBalance" | "B2CRecipientIsRegisteredCustomer" | "B2CChargesPaidAccountAvailableFunds" | "B2CWorkingAccountAvailableFunds" | "B2CUtilityAccountAvailableFunds" | (string & {});
815
+ type B2CResultParameterKey = 'DebitAccountBalance' | 'Amount' | 'DebitPartyAffectedAccountBalance' | 'TransCompletedTime' | 'DebitPartyCharges' | 'ReceiverPartyPublicName' | 'Currency' | 'InitiatorAccountCurrentBalance' | 'B2CRecipientIsRegisteredCustomer' | 'B2CChargesPaidAccountAvailableFunds' | 'B2CWorkingAccountAvailableFunds' | 'B2CUtilityAccountAvailableFunds' | (string & {});
817
816
  interface B2CResultParameter {
818
817
  Key: B2CResultParameterKey;
819
818
  Value: string | number;
@@ -960,7 +959,7 @@ interface BillManagerOptInRequest {
960
959
  /** Your logo URL (public HTTPS link) */
961
960
  officialContact: string;
962
961
  /** Sender name shown on push notifications */
963
- sendReminders: "1" | "0";
962
+ sendReminders: '1' | '0';
964
963
  /** Logo URL */
965
964
  logo?: string;
966
965
  /** Callback URL for payment confirmations */
@@ -1042,7 +1041,7 @@ interface BillManagerPaymentNotification {
1042
1041
  * Ref: Customer To Business (C2B) — Daraja Developer Portal
1043
1042
  */
1044
1043
  /** C2B API version. v2 is recommended; v1 sends SHA256-hashed MSISDN. */
1045
- type C2BApiVersion = "v1" | "v2";
1044
+ type C2BApiVersion = 'v1' | 'v2';
1046
1045
  /**
1047
1046
  * What M-PESA should do if your Validation URL is unreachable or times out.
1048
1047
  * "Completed" — M-PESA automatically completes the transaction.
@@ -1051,7 +1050,7 @@ type C2BApiVersion = "v1" | "v2";
1051
1050
  * NOTE: Must be exactly "Completed" or "Cancelled" (sentence case, no typos).
1052
1051
  * Daraja docs: "the words Cancelled/Completed must be in sentence case and well-spelled."
1053
1052
  */
1054
- type C2BResponseType = "Completed" | "Cancelled";
1053
+ type C2BResponseType = 'Completed' | 'Cancelled';
1055
1054
  interface C2BRegisterUrlRequest {
1056
1055
  /**
1057
1056
  * Your M-PESA Paybill or Till shortcode.
@@ -1098,7 +1097,7 @@ interface C2BRegisterUrlResponse {
1098
1097
  *
1099
1098
  * NOTE: Simulation is ONLY supported in Sandbox, NOT in production.
1100
1099
  */
1101
- type C2BCommandID = "CustomerPayBillOnline" | "CustomerBuyGoodsOnline";
1100
+ type C2BCommandID = 'CustomerPayBillOnline' | 'CustomerBuyGoodsOnline';
1102
1101
  interface C2BSimulateRequest {
1103
1102
  /**
1104
1103
  * Your M-PESA Paybill or Till shortcode.
@@ -1190,7 +1189,7 @@ interface C2BValidationPayload {
1190
1189
  * "C2B00015" — Invalid Short code
1191
1190
  * "C2B00016" — Other Error
1192
1191
  */
1193
- type C2BValidationResultCode = "0" | "C2B00011" | "C2B00012" | "C2B00013" | "C2B00014" | "C2B00015" | "C2B00016";
1192
+ type C2BValidationResultCode = '0' | 'C2B00011' | 'C2B00012' | 'C2B00013' | 'C2B00014' | 'C2B00015' | 'C2B00016';
1194
1193
  interface C2BValidationResponse {
1195
1194
  /**
1196
1195
  * "0" = Accept the transaction.
@@ -1204,7 +1203,7 @@ interface C2BValidationResponse {
1204
1203
  * "Accepted" when ResultCode is "0".
1205
1204
  * "Rejected" when ResultCode is a non-zero error code.
1206
1205
  */
1207
- ResultDesc: "Accepted" | "Rejected";
1206
+ ResultDesc: 'Accepted' | 'Rejected';
1208
1207
  /**
1209
1208
  * Optional. If set, this value is echoed back in the Confirmation callback
1210
1209
  * as ThirdPartyTransID. Useful for correlating validation → confirmation.
@@ -1295,7 +1294,7 @@ declare function acceptC2BValidation(thirdPartyTransID?: string): C2BValidationR
1295
1294
  * C2B00015 — Invalid Short code
1296
1295
  * C2B00016 — Other Error
1297
1296
  */
1298
- declare function rejectC2BValidation(resultCode?: Exclude<C2BValidationResultCode, "0">, resultDesc?: "Rejected"): C2BValidationResponse;
1297
+ declare function rejectC2BValidation(resultCode?: Exclude<C2BValidationResultCode, '0'>, resultDesc?: 'Rejected'): C2BValidationResponse;
1299
1298
  /**
1300
1299
  * Builds the acknowledgement your ConfirmationURL must return to Safaricom.
1301
1300
  * Always return this with HTTP 200.
@@ -1345,7 +1344,7 @@ declare function isBuyGoodsPayment(payload: C2BValidationPayload | C2BConfirmati
1345
1344
  * SM: Send Money (Mobile number)
1346
1345
  * SB: Sent to Business (Business number CPI in MSISDN format)
1347
1346
  */
1348
- type QRTransactionCode = "BG" | "WA" | "PB" | "SM" | "SB";
1347
+ type QRTransactionCode = 'BG' | 'WA' | 'PB' | 'SM' | 'SB';
1349
1348
  interface DynamicQRRequest {
1350
1349
  /**
1351
1350
  * Name of the Company / M-Pesa Merchant Name.
@@ -1437,7 +1436,7 @@ interface ReversalRequest {
1437
1436
  * "4" = Organisation ShortCode
1438
1437
  * Daraja field: RecieverIdentifierType
1439
1438
  */
1440
- receiverIdentifierType: "1" | "2" | "4";
1439
+ receiverIdentifierType: '1' | '2' | '4';
1441
1440
  /**
1442
1441
  * URL where Safaricom POSTs the reversal result.
1443
1442
  * Daraja field: ResultURL
@@ -1567,7 +1566,7 @@ interface TaxRemittanceResponse {
1567
1566
  * - Any unknown future key Daraja may return is still accepted.
1568
1567
  * - The `no-redundant-type-constituents` ESLint rule is not triggered.
1569
1568
  */
1570
- type TaxRemittanceResultParameterKey = "DebitAccountBalance" | "Amount" | "DebitPartyAffectedAccountBalance" | "TransCompletedTime" | "DebitPartyCharges" | "ReceiverPartyPublicName" | "Currency" | "InitiatorAccountCurrentBalance" | (string & {});
1569
+ type TaxRemittanceResultParameterKey = 'DebitAccountBalance' | 'Amount' | 'DebitPartyAffectedAccountBalance' | 'TransCompletedTime' | 'DebitPartyCharges' | 'ReceiverPartyPublicName' | 'Currency' | 'InitiatorAccountCurrentBalance' | (string & {});
1571
1570
  interface TaxRemittanceResultParameter {
1572
1571
  Key: TaxRemittanceResultParameterKey;
1573
1572
  Value: string | number;
@@ -1633,9 +1632,9 @@ declare class Mpesa {
1633
1632
  * Like stkPush() but returns Result<T> instead of throwing.
1634
1633
  * Ideal for application-level code that prefers not to use try/catch.
1635
1634
  */
1636
- stkPushSafe(request: Omit<StkPushRequest, "shortCode" | "passKey">): Promise<Result<Awaited<ReturnType<typeof this.stkPush>>>>;
1637
- stkPush(request: Omit<StkPushRequest, "shortCode" | "passKey">): Promise<StkPushResponse>;
1638
- stkQuery(request: Omit<StkQueryRequest, "shortCode" | "passKey">): Promise<StkQueryResponse>;
1635
+ stkPushSafe(request: Omit<StkPushRequest, 'shortCode' | 'passKey'>): Promise<Result<Awaited<ReturnType<typeof this.stkPush>>>>;
1636
+ stkPush(request: Omit<StkPushRequest, 'shortCode' | 'passKey'>): Promise<StkPushResponse>;
1637
+ stkQuery(request: Omit<StkQueryRequest, 'shortCode' | 'passKey'>): Promise<StkQueryResponse>;
1639
1638
  transactionStatus(request: TransactionStatusRequest): Promise<TransactionStatusResponse>;
1640
1639
  /**
1641
1640
  * Queries the balance of your M-PESA shortcode.
@@ -1717,31 +1716,6 @@ declare class Mpesa {
1717
1716
  }
1718
1717
  //#endregion
1719
1718
  //#region src/core/encryption/security-credentials.d.ts
1720
- /**
1721
- * Security credential encryption for Daraja APIs that require it:
1722
- * B2C, B2B, Transaction Status Query, Reversals, Tax Remittance.
1723
- *
1724
- * Algorithm (from Safaricom "Getting Started" docs):
1725
- * 1. Write the unencrypted initiator password into a byte array.
1726
- * 2. Encrypt using the M-Pesa public key certificate:
1727
- * - RSA algorithm
1728
- * - PKCS #1 v1.5 padding (NOT OAEP)
1729
- * 3. Base64-encode the encrypted byte array.
1730
- *
1731
- * Certificate source:
1732
- * Sandbox: https://developer.safaricom.co.ke (SandboxCertificate.cer)
1733
- * Production: https://developer.safaricom.co.ke (ProductionCertificate.cer)
1734
- *
1735
- * NOTE: Use the correct certificate for each environment or credentials
1736
- * will be rejected.
1737
- */
1738
- /**
1739
- * Encrypts `initiatorPassword` with the given PEM certificate and returns
1740
- * the base64-encoded security credential ready to send to Daraja.
1741
- *
1742
- * @param initiatorPassword - Plain-text password set on the M-PESA org portal
1743
- * @param certificatePem - Full PEM string (the .cer file contents)
1744
- */
1745
1719
  declare function encryptSecurityCredential(initiatorPassword: string, certificatePem: string): string;
1746
1720
  //#endregion
1747
1721
  //#region src/mpesa/webhooks/retry.d.ts
@@ -1785,7 +1759,7 @@ declare function retryWithBackoff<T>(fn: () => Promise<T>, options?: RetryOption
1785
1759
  /**
1786
1760
  * Webhook event types for Daraja callbacks
1787
1761
  */
1788
- type WebhookEventType = "stk_push";
1762
+ type WebhookEventType = 'stk_push';
1789
1763
  interface WebhookEvent {
1790
1764
  eventType: WebhookEventType;
1791
1765
  timestamp: string;
@@ -1862,7 +1836,7 @@ declare function isSuccessfulCallback(webhook: StkPushWebhook): boolean;
1862
1836
  //#endregion
1863
1837
  //#region src/utils/http/index.d.ts
1864
1838
  interface HttpRequestOptions {
1865
- method: "GET" | "POST";
1839
+ method: 'GET' | 'POST';
1866
1840
  headers?: Record<string, string>;
1867
1841
  /**
1868
1842
  * Request body — JSON-serialised and sent as application/json.
@@ -1903,5 +1877,4 @@ interface HttpResponse<T> {
1903
1877
  */
1904
1878
  declare function httpRequest<T = unknown>(url: string, options: HttpRequestOptions): Promise<HttpResponse<T>>;
1905
1879
  //#endregion
1906
- export { type AccountBalanceData, type AccountBalanceRequest, type AccountBalanceResponse, type AccountBalanceResult, type B2BExpressCheckoutCallback, type B2BExpressCheckoutCallbackCancelled, type B2BExpressCheckoutCallbackSuccess, type B2BExpressCheckoutErrorCode, type B2BExpressCheckoutErrorResponse, type B2BExpressCheckoutRequest, type B2BExpressCheckoutResponse, type B2CCommandID, type B2CErrorResponse, type B2CRequest, type B2CResponse, type B2CResult, type B2CResultParameter, type BillManagerBulkInvoiceRequest, type BillManagerBulkInvoiceResponse, type BillManagerCancelInvoiceRequest, type BillManagerCancelInvoiceResponse, type BillManagerInvoiceItem, type BillManagerOptInRequest, type BillManagerOptInResponse, type BillManagerPaymentNotification, type BillManagerSingleInvoiceRequest, type BillManagerSingleInvoiceResponse, type C2BApiVersion, type C2BCommandID, type C2BConfirmationAck, type C2BConfirmationPayload, type C2BRegisterUrlRequest, type C2BRegisterUrlResponse, type C2BResponseType, type C2BSimulateRequest, type C2BSimulateResponse, type C2BValidationPayload, type C2BValidationResponse, type C2BValidationResultCode, type CheckoutRequestID, type ConversationID, DARAJA_BASE_URLS, type DeepReadonly, type DynamicQRRequest, type DynamicQRResponse, type Environment, type ErrorCode, type HttpRequestOptions, type HttpResponse, KRA_SHORTCODE, type KesAmount, Mpesa, type MpesaConfig, type MpesaReceiptNumber, type MsisdnKE, type NonEmptyString, type OriginatorConversationID, type ParsedAccount, type PaybillCode, PesafyError, type PesafyErrorOptions, type QRTransactionCode, type Result, type RetryOptions, type RetryResult, type ReversalRequest, type ReversalResponse, type ReversalResult, SAFARICOM_IPS, type ShortCode, type StkCallbackFailure, type StkCallbackInner, type StkCallbackMetadataItem, type StkCallbackSuccess, type StkPushCallback, type StkPushRequest, type StkPushResponse, type StkPushWebhook, type StkQueryRequest, type StkQueryResponse, type StrictPick, TAX_COMMAND_ID, type TaxRemittanceErrorResponse, type TaxRemittanceRequest, type TaxRemittanceResponse, type TaxRemittanceResult, type TaxRemittanceResultParameter, type TillCode, type TransactionStatusRequest, type TransactionStatusResponse, type TransactionStatusResult, type TransactionStatusResultParameter, type TransactionType, type WebhookEvent, type WebhookEventType, type WebhookHandlerOptions, type WebhookHandlerResult, acceptC2BValidation, acknowledgeC2BConfirmation, createError, encryptSecurityCredential, err, extractAmount, extractPhoneNumber, extractTransactionId, formatSafaricomPhone as formatPhoneNumber, formatSafaricomPhone, getAccountBalanceParam, getB2BAmount, getB2BConversationId, getB2BRequestId, getB2BTransactionId, getB2CAmount, getB2CConversationId, getB2CCurrency, getB2CDebitAccountBalance, getB2CDebitPartyCharges, getB2CInitiatorAccountBalance, getB2COriginatorConversationId, getB2CReceiverPublicName, getB2CResultDesc, getB2CResultParam, getB2CTransactionCompletedTime, getB2CTransactionId, getC2BAccountRef, getC2BAmount, getC2BCustomerName, getC2BTransactionId, getCallbackValue, getReversalConversationId, getReversalTransactionId, getTimestamp, handleWebhook, httpRequest, initiateB2BExpressCheckout, initiateB2CPayment, isAccountBalanceSuccess, isB2BCheckoutCallback, isB2BCheckoutCancelled, isB2BCheckoutSuccess, isB2CFailure, isB2CResult, isB2CSuccess, isBuyGoodsPayment, isC2BPayload, isPaybillPayment, isPesafyError, isReversalSuccess, isStkCallbackSuccess, isSuccessfulCallback, ok, parseAccountBalance, parseStkPushWebhook, registerC2BUrls, rejectC2BValidation, remitTax, retryWithBackoff, simulateC2B, toKesAmount, toMsisdn, toNonEmpty, toPaybill, toShortCode, toTill, verifyWebhookIP };
1907
- //# sourceMappingURL=index.d.mts.map
1880
+ export { type AccountBalanceData, type AccountBalanceRequest, type AccountBalanceResponse, type AccountBalanceResult, type B2BExpressCheckoutCallback, type B2BExpressCheckoutCallbackCancelled, type B2BExpressCheckoutCallbackSuccess, type B2BExpressCheckoutErrorCode, type B2BExpressCheckoutErrorResponse, type B2BExpressCheckoutRequest, type B2BExpressCheckoutResponse, type B2CCommandID, type B2CErrorResponse, type B2CRequest, type B2CResponse, type B2CResult, type B2CResultParameter, type BillManagerBulkInvoiceRequest, type BillManagerBulkInvoiceResponse, type BillManagerCancelInvoiceRequest, type BillManagerCancelInvoiceResponse, type BillManagerInvoiceItem, type BillManagerOptInRequest, type BillManagerOptInResponse, type BillManagerPaymentNotification, type BillManagerSingleInvoiceRequest, type BillManagerSingleInvoiceResponse, type C2BApiVersion, type C2BCommandID, type C2BConfirmationAck, type C2BConfirmationPayload, type C2BRegisterUrlRequest, type C2BRegisterUrlResponse, type C2BResponseType, type C2BSimulateRequest, type C2BSimulateResponse, type C2BValidationPayload, type C2BValidationResponse, type C2BValidationResultCode, type CheckoutRequestID, type ConversationID, DARAJA_BASE_URLS, type DeepReadonly, type DynamicQRRequest, type DynamicQRResponse, type Environment, type ErrorCode, type HttpRequestOptions, type HttpResponse, KRA_SHORTCODE, type KesAmount, Mpesa, type MpesaConfig, type MpesaReceiptNumber, type MsisdnKE, type NonEmptyString, type OriginatorConversationID, type ParsedAccount, type PaybillCode, PesafyError, type PesafyErrorOptions, type QRTransactionCode, type Result, type RetryOptions, type RetryResult, type ReversalRequest, type ReversalResponse, type ReversalResult, SAFARICOM_IPS, type ShortCode, type StkCallbackFailure, type StkCallbackInner, type StkCallbackMetadataItem, type StkCallbackSuccess, type StkPushCallback, type StkPushRequest, type StkPushResponse, type StkPushWebhook, type StkQueryRequest, type StkQueryResponse, type StrictPick, TAX_COMMAND_ID, type TaxRemittanceErrorResponse, type TaxRemittanceRequest, type TaxRemittanceResponse, type TaxRemittanceResult, type TaxRemittanceResultParameter, type TillCode, type TransactionStatusRequest, type TransactionStatusResponse, type TransactionStatusResult, type TransactionStatusResultParameter, type TransactionType, type WebhookEvent, type WebhookEventType, type WebhookHandlerOptions, type WebhookHandlerResult, acceptC2BValidation, acknowledgeC2BConfirmation, createError, encryptSecurityCredential, err, extractAmount, extractPhoneNumber, extractTransactionId, formatSafaricomPhone as formatPhoneNumber, formatSafaricomPhone, getAccountBalanceParam, getB2BAmount, getB2BConversationId, getB2BRequestId, getB2BTransactionId, getB2CAmount, getB2CConversationId, getB2CCurrency, getB2CDebitAccountBalance, getB2CDebitPartyCharges, getB2CInitiatorAccountBalance, getB2COriginatorConversationId, getB2CReceiverPublicName, getB2CResultDesc, getB2CResultParam, getB2CTransactionCompletedTime, getB2CTransactionId, getC2BAccountRef, getC2BAmount, getC2BCustomerName, getC2BTransactionId, getCallbackValue, getReversalConversationId, getReversalTransactionId, getTimestamp, handleWebhook, httpRequest, initiateB2BExpressCheckout, initiateB2CPayment, isAccountBalanceSuccess, isB2BCheckoutCallback, isB2BCheckoutCancelled, isB2BCheckoutSuccess, isB2CFailure, isB2CResult, isB2CSuccess, isBuyGoodsPayment, isC2BPayload, isPaybillPayment, isPesafyError, isReversalSuccess, isStkCallbackSuccess, isSuccessfulCallback, ok, parseAccountBalance, parseStkPushWebhook, registerC2BUrls, rejectC2BValidation, remitTax, retryWithBackoff, simulateC2B, toKesAmount, toMsisdn, toNonEmpty, toPaybill, toShortCode, toTill, verifyWebhookIP };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{readFile as e}from"node:fs/promises";import{constants as t,publicEncrypt as n}from"node:crypto";function r(e){"@babel/helpers - typeof";return r=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},r(e)}function i(e,t){if(r(e)!=`object`||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var i=n.call(e,t||`default`);if(r(i)!=`object`)return i;throw TypeError(`@@toPrimitive must return a primitive value.`)}return(t===`string`?String:Number)(e)}function a(e){var t=i(e,`string`);return r(t)==`symbol`?t:t+``}function o(e,t,n){return(t=a(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var s=class e extends Error{constructor(t){super(t.message),o(this,`code`,void 0),o(this,`statusCode`,void 0),o(this,`response`,void 0),o(this,`requestId`,void 0),o(this,`cause`,void 0),o(this,`retryable`,void 0),Object.defineProperty(this,`name`,{value:`PesafyError`}),this.code=t.code,this.statusCode=t.statusCode,this.response=t.response,this.requestId=t.requestId,this.cause=t.cause,this.retryable=t.retryable??(t.code===`NETWORK_ERROR`||t.code===`TIMEOUT`||t.code===`RATE_LIMITED`||t.code===`REQUEST_FAILED`),Error.captureStackTrace&&Error.captureStackTrace(this,e)}get isValidation(){return this.code===`VALIDATION_ERROR`}get isAuth(){return this.code===`AUTH_FAILED`||this.code===`INVALID_CREDENTIALS`}toJSON(){return{name:this.name,code:this.code,message:this.message,statusCode:this.statusCode,requestId:this.requestId,retryable:this.retryable}}};function c(e){return new s(e)}function l(e){return e instanceof s}const ee=new Set([429,500,502,503,504]);function te(e){return new Promise(t=>setTimeout(t,e))}function u(e){let t=e*.25;return e+(Math.random()*t*2-t)}async function d(e,t){let n=t.retries??4,r=t.retryDelay??2e3,i=t.timeout??3e4,a={"Content-Type":`application/json`,Accept:`application/json`,...t.headers};t.idempotencyKey&&(a[`Idempotency-Key`]=t.idempotencyKey);let o={method:t.method,headers:a,...t.body===void 0?{}:{body:JSON.stringify(t.body)}},c=null;for(let a=0;a<=n;a++){if(a>0){let i=u(r*2**(a-1));console.warn(`[pesafy] Retry ${a}/${n} → ${t.method} ${e} in ${Math.round(i)} ms`),await te(i)}let l=new AbortController,d=setTimeout(()=>l.abort(),i),f;try{f=await fetch(e,{...o,signal:l.signal})}catch(t){if(clearTimeout(d),c=t instanceof Error&&t.name===`AbortError`?new s({code:`TIMEOUT`,message:`Request to ${e} timed out after ${i} ms`,cause:t,retryable:!0}):new s({code:`NETWORK_ERROR`,message:`Network error: ${t instanceof Error?t.message:String(t)}`,cause:t,retryable:!0}),a<n)continue;throw c}finally{clearTimeout(d)}let p=``,m=null,h=f.headers.get(`content-type`)??``;try{p=await f.text(),p&&(m=h.includes(`application/json`)?JSON.parse(p):p)}catch{m=p||null}let g={};if(f.headers.forEach((e,t)=>{g[t]=e}),f.ok)return{data:m,status:f.status,headers:g};let _=ee.has(f.status),v=typeof m==`object`&&m?m:{},y=v.errorMessage??v.ResponseDescription??v.resultDesc??p??`HTTP ${f.status}`;if(c=new s({code:_?`REQUEST_FAILED`:`API_ERROR`,message:y,statusCode:f.status,response:m,retryable:_,...typeof v.requestId==`string`?{requestId:v.requestId}:{}}),!(_&&a<n))throw c}throw c}var f=class{constructor(e,t,n){o(this,`consumerKey`,void 0),o(this,`consumerSecret`,void 0),o(this,`baseUrl`,void 0),o(this,`cachedToken`,null),o(this,`tokenExpiresAt`,0),this.consumerKey=e,this.consumerSecret=t,this.baseUrl=n}getBasicAuthHeader(){let e=`${this.consumerKey}:${this.consumerSecret}`;return`Basic ${Buffer.from(e,`utf-8`).toString(`base64`)}`}async getAccessToken(){let e=Date.now()/1e3;if(this.cachedToken&&this.tokenExpiresAt>e+60)return this.cachedToken;let t=await d(`${this.baseUrl}/oauth/v1/generate?grant_type=client_credentials`,{method:`GET`,headers:{Authorization:this.getBasicAuthHeader()}}),{access_token:n,expires_in:r}=t.data;if(!n)throw new s({code:`AUTH_FAILED`,message:`Daraja did not return an access token. Check your consumer key and secret.`,response:t.data});return this.cachedToken=n,this.tokenExpiresAt=e+(r??3600),this.cachedToken}clearCache(){this.cachedToken=null,this.tokenExpiresAt=0}};function p(e,r){try{let i=Buffer.from(e,`utf-8`);return n({key:r,padding:t.RSA_PKCS1_PADDING},i).toString(`base64`)}catch(e){throw new s({code:`ENCRYPTION_FAILED`,message:`Failed to encrypt security credential. Ensure the certificate PEM is valid and matches the environment (sandbox/production).`,cause:e})}}function m(e){let t=Math.round(e);if(!Number.isFinite(t)||t<1)throw TypeError(`KesAmount must be a whole number ≥ 1, got ${e}`);return t}function h(e){let t=e.replace(/\D/g,``),n;if(t.startsWith(`254`)&&t.length===12)n=t;else if(t.startsWith(`0`)&&t.length===10)n=`254${t.slice(1)}`;else if(t.length===9)n=`254${t}`;else throw TypeError(`Cannot normalise "${e}" to 254XXXXXXXXX. Use 07XX…, 2547XX…, or +2547XX….`);if(n.length!==12)throw TypeError(`Phone "${e}" normalised to "${n}" — expected 12 digits.`);return n}function g(e){return String(e)}function _(e){return String(e)}function v(e){return String(e)}function y(e){return{ok:!0,data:e}}function b(e){return{ok:!1,error:e}}function ne(e){if(!e.trim())throw TypeError(`String must not be empty`);return e}async function re(e,t,n,r,i){if(!i.partyA?.trim())throw c({code:`VALIDATION_ERROR`,message:`partyA is required.`});if(![`1`,`2`,`4`].includes(i.identifierType))throw c({code:`VALIDATION_ERROR`,message:`identifierType must be "1" (MSISDN), "2" (Till), or "4" (ShortCode).`});if(!i.resultUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`resultUrl is required.`});if(!i.queueTimeOutUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`queueTimeOutUrl is required.`});let a={Initiator:r,SecurityCredential:n,CommandID:`AccountBalance`,PartyA:String(i.partyA),IdentifierType:i.identifierType,ResultURL:i.resultUrl,QueueTimeOutURL:i.queueTimeOutUrl,Remarks:i.remarks??`Account Balance Query`},{data:o}=await d(`${e}/mpesa/accountbalance/v1/query`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:a});return o}function ie(e){let t=e.split(`|`),n=[];for(let e=0;e+2<t.length;e+=3){let r=t[e]?.trim(),i=t[e+1]?.trim(),a=t[e+2]?.trim();r&&i&&a!==void 0&&n.push({name:r,currency:i,amount:a})}return n}function ae(e,t){let n=e.Result.ResultParameters?.ResultParameter;if(n)return(Array.isArray(n)?n:[n]).find(e=>e.Key===t)?.Value}function oe(e){return e.Result.ResultCode===0}function se(){try{if(typeof crypto<`u`&&crypto.randomUUID)return crypto.randomUUID()}catch{}return`${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`}async function x(e,t,n){if(!n.primaryShortCode?.trim())throw c({code:`VALIDATION_ERROR`,message:`primaryShortCode is required — the merchant's till number (debit party).`});if(!n.receiverShortCode?.trim())throw c({code:`VALIDATION_ERROR`,message:`receiverShortCode is required — the vendor's Paybill account (credit party).`});let r=Math.round(n.amount);if(!Number.isFinite(r)||r<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be a whole number ≥ 1 (got ${n.amount} which rounds to ${r}).`});if(!n.paymentRef?.trim())throw c({code:`VALIDATION_ERROR`,message:`paymentRef is required — shown in the merchant's USSD prompt as the payment reference.`});if(!n.callbackUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`callbackUrl is required — Safaricom POSTs the transaction result here.`});if(!n.partnerName?.trim())throw c({code:`VALIDATION_ERROR`,message:`partnerName is required — your friendly name shown in the merchant's USSD prompt.`});let i={primaryShortCode:String(n.primaryShortCode),receiverShortCode:String(n.receiverShortCode),amount:String(r),paymentRef:n.paymentRef,callbackUrl:n.callbackUrl,partnerName:n.partnerName,RequestRefID:n.requestRefId??se()},{data:a}=await d(`${e}/v1/ussdpush/get-msisdn`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:i});return a}function S(e){return e.resultCode===`0`}function ce(e){return e.resultCode===`4001`}function le(e){if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.resultCode==`string`&&typeof t.requestId==`string`&&typeof t.amount==`string`}function ue(e){return S(e)?e.transactionId??null:null}function de(e){return Number(e.amount)}function fe(e){return e.requestId}function pe(e){return S(e)?e.conversationID??null:null}async function C(e,t,n,r,i){if(!i.commandId)throw c({code:`VALIDATION_ERROR`,message:`commandId is required: "BusinessPayToBulk" | "BusinessPayment" | "SalaryPayment" | "PromotionPayment"`});let a=[`BusinessPayToBulk`,`BusinessPayment`,`SalaryPayment`,`PromotionPayment`];if(!a.includes(i.commandId))throw c({code:`VALIDATION_ERROR`,message:`commandId must be one of: ${a.join(`, `)}. Got: "${i.commandId}"`});let o=Math.round(i.amount);if(!Number.isFinite(o)||o<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be a whole number ≥ 1 (got ${i.amount} which rounds to ${o}).`});if(!i.partyA?.trim())throw c({code:`VALIDATION_ERROR`,message:`partyA is required — your business shortcode from which money is deducted.`});if(!i.partyB?.trim())throw c({code:`VALIDATION_ERROR`,message:`partyB is required — the recipient shortcode (BusinessPayToBulk) or customer MSISDN (other commands).`});if(!i.accountReference?.trim())throw c({code:`VALIDATION_ERROR`,message:`accountReference is required — a reference for this transaction.`});if(!i.resultUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`resultUrl is required — Safaricom POSTs the B2C result here.`});if(!i.queueTimeOutUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`queueTimeOutUrl is required — Safaricom calls this on request timeout.`});let s={Initiator:r,SecurityCredential:n,CommandID:i.commandId,SenderIdentifierType:i.senderIdentifierType??`4`,RecieverIdentifierType:i.receiverIdentifierType??`4`,Amount:String(o),PartyA:String(i.partyA),PartyB:String(i.partyB),AccountReference:i.accountReference,Remarks:i.remarks??`B2C Payment`,QueueTimeOutURL:i.queueTimeOutUrl,ResultURL:i.resultUrl};i.requester?.trim()&&(s.Requester=String(i.requester));let{data:l}=await d(`${e}/mpesa/b2b/v1/paymentrequest`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:s});return l}function me(e){if(!e||typeof e!=`object`)return!1;let t=e;if(!t.Result||typeof t.Result!=`object`)return!1;let n=t.Result;return typeof n.ResultCode==`number`&&typeof n.ConversationID==`string`}function w(e){return e.Result.ResultCode===0}function T(e){return e.Result.ResultCode!==0}function E(e){return e.Result.TransactionID??null}function D(e){return e.Result.ConversationID}function O(e){return e.Result.OriginatorConversationID}function k(e){return e.Result.ResultDesc}function A(e){let t=L(e,`Amount`);return t===void 0?null:Number(t)}function j(e){let t=L(e,`TransCompletedTime`);return t===void 0?null:String(t)}function M(e){let t=L(e,`DebitPartyCharges`);return t===void 0||t===``?null:String(t)}function N(e){let t=L(e,`ReceiverPartyPublicName`);return t===void 0?null:String(t)}function P(e){let t=L(e,`Currency`);return t===void 0?`KES`:String(t)}function F(e){let t=L(e,`DebitAccountBalance`);return t===void 0?null:String(t)}function I(e){let t=L(e,`InitiatorAccountCurrentBalance`);return t===void 0?null:String(t)}function L(e,t){let n=e.Result.ResultParameters?.ResultParameter;if(n)return(Array.isArray(n)?n:[n]).find(e=>e.Key===t)?.Value}async function R(e,t,n){if(!n.shortcode?.trim())throw c({code:`VALIDATION_ERROR`,message:`shortcode is required.`});if(!n.email?.trim())throw c({code:`VALIDATION_ERROR`,message:`email is required.`});if(!n.callbackUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`callbackUrl is required.`});let r={shortcode:n.shortcode,email:n.email,officialContact:n.officialContact,sendReminders:n.sendReminders,logo:n.logo??``,callbackUrl:n.callbackUrl},{data:i}=await d(`${e}/v1/billmanager-invoice/optin`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:r});return i}async function z(e,t,n){if(!n.externalReference?.trim())throw c({code:`VALIDATION_ERROR`,message:`externalReference is required.`});if(!n.partyA?.trim())throw c({code:`VALIDATION_ERROR`,message:`partyA (customer MSISDN) is required.`});let r=Math.round(n.amount);if(!Number.isFinite(r)||r<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be ≥ 1 (got ${n.amount}).`});let i={externalReference:n.externalReference,billingPeriod:n.billingPeriod,invoiceName:n.invoiceName,dueDate:n.dueDate,accountReference:n.accountReference,amount:String(r),partyA:n.partyA,invoiceItems:n.invoiceItems?.map(e=>({itemName:e.itemName,amount:String(Math.round(e.amount))}))??[]},{data:a}=await d(`${e}/v1/billmanager-invoice/single-invoicing`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:i});return a}async function he(e,t,n){if(!n.invoices?.length)throw c({code:`VALIDATION_ERROR`,message:`invoices array must not be empty.`});if(n.invoices.length>1e3)throw c({code:`VALIDATION_ERROR`,message:`Maximum 1 000 invoices per bulk request.`});let{data:r}=await d(`${e}/v1/billmanager-invoice/bulk-invoicing`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:n.invoices});return r}async function ge(e,t,n){if(!n.externalReference?.trim())throw c({code:`VALIDATION_ERROR`,message:`externalReference is required.`});let{data:r}=await d(`${e}/v1/billmanager-invoice/cancel-single-invoice`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:[{externalReference:n.externalReference}]});return r}const _e=[`mpesa`,`safaricom`,`.exe`,`.exec`,`cmd`,`sql`,`query`];function B(e,t){if(!e||!e.trim())throw c({code:`VALIDATION_ERROR`,message:`${t} is required`});let n=e.toLowerCase();for(let e of _e)if(n.includes(e))throw c({code:`VALIDATION_ERROR`,message:`${t} must not contain the keyword "${e}". Daraja rejects URLs containing: M-PESA, Safaricom, exe, exec, cmd, sql, query.`})}async function V(e,t,n){if(!n.shortCode)throw c({code:`VALIDATION_ERROR`,message:`shortCode is required`});if(!n.responseType)throw c({code:`VALIDATION_ERROR`,message:`responseType is required: "Completed" or "Cancelled" (sentence case, exactly as spelled)`});if(n.responseType!==`Completed`&&n.responseType!==`Cancelled`)throw c({code:`VALIDATION_ERROR`,message:`responseType must be exactly "Completed" or "Cancelled" (sentence case). Got: "${String(n.responseType)}"`});B(n.confirmationUrl,`confirmationUrl`),B(n.validationUrl,`validationUrl`);let r=n.apiVersion??`v2`,i={ShortCode:String(n.shortCode),ResponseType:n.responseType,ConfirmationURL:n.confirmationUrl,ValidationURL:n.validationUrl},{data:a}=await d(`${e}/mpesa/c2b/${r}/registerurl`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:i});return a}async function H(e,t,n){if(!e.includes(`sandbox`))throw c({code:`VALIDATION_ERROR`,message:`C2B simulate is only available in the Sandbox environment. In production, customers initiate payments via M-PESA App, USSD, or SIM Toolkit.`});if(!n.shortCode)throw c({code:`VALIDATION_ERROR`,message:`shortCode is required.`});if(n.commandId!==`CustomerPayBillOnline`&&n.commandId!==`CustomerBuyGoodsOnline`)throw c({code:`VALIDATION_ERROR`,message:`commandId must be "CustomerPayBillOnline" or "CustomerBuyGoodsOnline". Got: "${String(n.commandId)}"`});let r=Math.round(n.amount);if(!Number.isFinite(r)||r<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be a whole number ≥ 1 (got ${n.amount}).`});if(!n.msisdn)throw c({code:`VALIDATION_ERROR`,message:`msisdn is required. Sandbox test MSISDN: 254708374149.`});let i=n.commandId===`CustomerBuyGoodsOnline`,a=n.apiVersion??`v2`,o={ShortCode:Number(n.shortCode),CommandID:n.commandId,Amount:r,Msisdn:Number(n.msisdn)};i||(o.BillRefNumber=n.billRefNumber??``),i&&Object.prototype.hasOwnProperty.call(o,`BillRefNumber`)&&(delete o.BillRefNumber,console.warn(`[pesafy/simulateC2B] BillRefNumber leaked into Buy Goods payload — removed. This is a library bug; please report it.`));let{data:s}=await d(`${e}/mpesa/c2b/${a}/simulate`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:o});return s}function ve(e){if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.TransID==`string`&&typeof t.BusinessShortCode==`string`&&typeof t.TransAmount==`string`}function ye(e){return{ResultCode:`0`,ResultDesc:`Accepted`,...e?{ThirdPartyTransID:e}:{}}}function be(e=`C2B00016`,t=`Rejected`){return{ResultCode:e,ResultDesc:t}}function xe(){return{ResultCode:0,ResultDesc:`Success`}}function Se(e){return Number(e.TransAmount)}function U(e){return e.TransID}function Ce(e){return e.BillRefNumber}function we(e){return[e.FirstName,e.MiddleName,e.LastName].filter(Boolean).join(` `).trim()}function Te(e){return e.TransactionType===`Pay Bill`||e.TransactionType===`CustomerPayBillOnline`}function Ee(e){return e.TransactionType===`Buy Goods`||e.TransactionType===`CustomerBuyGoodsOnline`}async function De(e,t,n){if(!n.merchantName?.trim())throw c({code:`VALIDATION_ERROR`,message:`merchantName is required`});if(!n.refNo?.trim())throw c({code:`VALIDATION_ERROR`,message:`refNo (transaction reference) is required`});let r=Math.round(n.amount);if(!Number.isFinite(r)||r<1)throw c({code:`VALIDATION_ERROR`,message:`Amount must be at least 1 (got ${n.amount} which rounds to ${r}).`});if(!n.trxCode)throw c({code:`VALIDATION_ERROR`,message:`trxCode is required. Supported values: "BG" | "WA" | "PB" | "SM" | "SB"`});if(!n.cpi?.trim())throw c({code:`VALIDATION_ERROR`,message:`cpi (Credit Party Identifier) is required — e.g. till number, paybill, or MSISDN`});let i=n.size??300;if(i<1)throw c({code:`VALIDATION_ERROR`,message:`size must be a positive number of pixels`});let a={MerchantName:n.merchantName,RefNo:n.refNo,Amount:r,TrxCode:n.trxCode,CPI:n.cpi,Size:String(i)},{data:o}=await d(`${e}/mpesa/qrcode/v1/generate`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:a});return o}async function Oe(e,t,n,r,i){if(!i.transactionId?.trim())throw c({code:`VALIDATION_ERROR`,message:`transactionId is required.`});if(!i.receiverParty?.trim())throw c({code:`VALIDATION_ERROR`,message:`receiverParty is required.`});if(![`1`,`2`,`4`].includes(i.receiverIdentifierType))throw c({code:`VALIDATION_ERROR`,message:`receiverIdentifierType must be "1", "2", or "4".`});let a=Math.round(i.amount);if(!Number.isFinite(a)||a<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be a whole number ≥ 1 (got ${i.amount}).`});if(!i.resultUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`resultUrl is required.`});if(!i.queueTimeOutUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`queueTimeOutUrl is required.`});let o={Initiator:r,SecurityCredential:n,CommandID:`TransactionReversal`,TransactionID:i.transactionId,Amount:String(a),ReceiverParty:String(i.receiverParty),RecieverIdentifierType:i.receiverIdentifierType,ResultURL:i.resultUrl,QueueTimeOutURL:i.queueTimeOutUrl,Remarks:i.remarks??`Transaction Reversal`,Occasion:i.occasion??``},{data:s}=await d(`${e}/mpesa/reversal/v1/request`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:o});return s}function ke(e){return e.Result.ResultCode===0}function Ae(e){return e.Result.TransactionID??null}function je(e){return e.Result.ConversationID}function W(e){let t=e.replace(/\D/g,``),n;if(t.startsWith(`254`)&&t.length===12)n=t;else if(t.startsWith(`0`)&&t.length===10)n=`254${t.slice(1)}`;else if(t.length===9)n=`254${t}`;else throw new s({code:`INVALID_PHONE`,message:`Cannot parse "${e}". Use 07XXXXXXXX, 2547XXXXXXXX, or +2547XXXXXXXX.`});if(n.length!==12)throw new s({code:`INVALID_PHONE`,message:`"${e}" normalised to "${n}" — expected 12 digits.`});return n}function G(e,t,n){return btoa(`${e}${t}${n}`)}function K(){let e=new Date,t=e=>e.toString().padStart(2,`0`);return[e.getFullYear(),t(e.getMonth()+1),t(e.getDate()),t(e.getHours()),t(e.getMinutes()),t(e.getSeconds())].join(``)}async function Me(e,t,n){let r=Math.round(n.amount);if(r<1)throw new s({code:`VALIDATION_ERROR`,message:`Amount must be at least KES 1 (got ${n.amount} which rounds to ${r}).`});let i=K(),a=n.partyB??n.shortCode,o={BusinessShortCode:n.shortCode,Password:G(n.shortCode,n.passKey,i),Timestamp:i,TransactionType:n.transactionType??`CustomerPayBillOnline`,Amount:r,PartyA:W(n.phoneNumber),PartyB:a,PhoneNumber:W(n.phoneNumber),CallBackURL:n.callbackUrl,AccountReference:n.accountReference.slice(0,12),TransactionDesc:n.transactionDesc.slice(0,13)},{data:c}=await d(`${e}/mpesa/stkpush/v1/processrequest`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:o,retries:5,retryDelay:3e3});return c}async function Ne(e,t,n){let r=K(),i={BusinessShortCode:n.shortCode,Password:G(n.shortCode,n.passKey,r),Timestamp:r,CheckoutRequestID:n.checkoutRequestId},{data:a}=await d(`${e}/mpesa/stkpushquery/v1/query`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:i});return a}function q(e){return e.ResultCode===0}function Pe(e,t){let n=e.Body.stkCallback;if(q(n))return n.CallbackMetadata.Item.find(e=>e.Name===t)?.Value}const Fe=`572572`,J=`PayTaxToKRA`;async function Y(e,t,n,r,i){let a=Math.round(i.amount);if(!Number.isFinite(a)||a<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be a whole number ≥ 1 (got ${i.amount} which rounds to ${a}).`});if(!i.partyA)throw c({code:`VALIDATION_ERROR`,message:`partyA is required — your M-PESA business shortcode from which tax is deducted.`});if(!i.accountReference?.trim())throw c({code:`VALIDATION_ERROR`,message:`accountReference is required — the Payment Registration Number (PRN) issued by KRA.`});if(!i.resultUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`resultUrl is required — Safaricom POSTs the tax remittance result here.`});if(!i.queueTimeOutUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`queueTimeOutUrl is required — Safaricom calls this on request timeout.`});let o={Initiator:r,SecurityCredential:n,CommandID:J,SenderIdentifierType:`4`,RecieverIdentifierType:`4`,Amount:String(a),PartyA:String(i.partyA),PartyB:i.partyB??`572572`,AccountReference:i.accountReference,Remarks:i.remarks??`Tax Remittance`,QueueTimeOutURL:i.queueTimeOutUrl,ResultURL:i.resultUrl},{data:s}=await d(`${e}/mpesa/b2b/v1/remittax`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:o});return s}async function Ie(e,t,n,r,i){if(!i.transactionId)throw c({code:`VALIDATION_ERROR`,message:`transactionId is required`});if(!i.partyA)throw c({code:`VALIDATION_ERROR`,message:`partyA is required (your business shortcode, till number, or MSISDN)`});if(!i.identifierType)throw c({code:`VALIDATION_ERROR`,message:`identifierType is required: "1" (MSISDN) | "2" (Till) | "4" (ShortCode)`});if(!i.resultUrl)throw c({code:`VALIDATION_ERROR`,message:`resultUrl is required — Safaricom POSTs the transaction result here`});if(!i.queueTimeOutUrl)throw c({code:`VALIDATION_ERROR`,message:`queueTimeOutUrl is required — Safaricom calls this on timeout`});let a={Initiator:r,SecurityCredential:n,CommandID:i.commandId??`TransactionStatusQuery`,TransactionID:i.transactionId,PartyA:i.partyA,IdentifierType:i.identifierType,ResultURL:i.resultUrl,QueueTimeOutURL:i.queueTimeOutUrl,Remarks:i.remarks??`Transaction Status Query`,Occasion:i.occasion??``},{data:o}=await d(`${e}/mpesa/transactionstatus/v1/query`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:a});return o}const X={sandbox:`https://sandbox.safaricom.co.ke`,production:`https://api.safaricom.co.ke`};var Le=class{constructor(e){if(o(this,`config`,void 0),o(this,`tokenManager`,void 0),o(this,`baseUrl`,void 0),!e.consumerKey||!e.consumerSecret)throw new s({code:`INVALID_CREDENTIALS`,message:`consumerKey and consumerSecret are required.`});this.config=e,this.baseUrl=X[e.environment],this.tokenManager=new f(e.consumerKey,e.consumerSecret,this.baseUrl)}getToken(){return this.tokenManager.getAccessToken()}async buildSecurityCredential(){if(this.config.securityCredential)return this.config.securityCredential;if(!this.config.initiatorPassword)throw new s({code:`INVALID_CREDENTIALS`,message:`Provide securityCredential (pre-encrypted) OR (initiatorPassword + certificatePath/certificatePem).`});let t;if(this.config.certificatePem)t=this.config.certificatePem;else if(this.config.certificatePath)t=await e(this.config.certificatePath,`utf-8`);else throw new s({code:`INVALID_CREDENTIALS`,message:`certificatePath or certificatePem is required to encrypt the initiator password.`});return p(this.config.initiatorPassword,t)}requireInitiator(e){let t=this.config.initiatorName??``;if(!t)throw new s({code:`VALIDATION_ERROR`,message:`initiatorName is required for ${e}.`});return t}async stkPushSafe(e){try{return y(await this.stkPush(e))}catch(e){return b(e)}}async stkPush(e){let t=this.config.lipaNaMpesaShortCode??``,n=this.config.lipaNaMpesaPassKey??``;if(!t||!n)throw new s({code:`VALIDATION_ERROR`,message:`lipaNaMpesaShortCode and lipaNaMpesaPassKey are required for STK Push.`});let r=await this.getToken();return Me(this.baseUrl,r,{...e,shortCode:t,passKey:n})}async stkQuery(e){let t=this.config.lipaNaMpesaShortCode??``,n=this.config.lipaNaMpesaPassKey??``;if(!t||!n)throw new s({code:`VALIDATION_ERROR`,message:`lipaNaMpesaShortCode and lipaNaMpesaPassKey are required for STK Query.`});let r=await this.getToken();return Ne(this.baseUrl,r,{...e,shortCode:t,passKey:n})}async transactionStatus(e){let t=this.requireInitiator(`Transaction Status`),[n,r]=await Promise.all([this.getToken(),this.buildSecurityCredential()]);return Ie(this.baseUrl,n,r,t,e)}async accountBalance(e){let t=this.requireInitiator(`Account Balance`),[n,r]=await Promise.all([this.getToken(),this.buildSecurityCredential()]);return re(this.baseUrl,n,r,t,e)}async reverseTransaction(e){let t=this.requireInitiator(`Reversal`),[n,r]=await Promise.all([this.getToken(),this.buildSecurityCredential()]);return Oe(this.baseUrl,n,r,t,e)}async generateDynamicQR(e){let t=await this.getToken();return De(this.baseUrl,t,e)}async registerC2BUrls(e){let t=await this.getToken();return V(this.baseUrl,t,e)}async simulateC2B(e){let t=await this.getToken();return H(this.baseUrl,t,e)}async remitTax(e){let t=this.requireInitiator(`Tax Remittance`),[n,r]=await Promise.all([this.getToken(),this.buildSecurityCredential()]);return Y(this.baseUrl,n,r,t,e)}async b2bExpressCheckout(e){let t=await this.getToken();return x(this.baseUrl,t,e)}async b2cPayment(e){let t=this.requireInitiator(`B2C Payment`),[n,r]=await Promise.all([this.getToken(),this.buildSecurityCredential()]);return C(this.baseUrl,n,r,t,e)}async billManagerOptIn(e){let t=await this.getToken();return R(this.baseUrl,t,e)}async sendInvoice(e){let t=await this.getToken();return z(this.baseUrl,t,e)}async sendBulkInvoices(e){let t=await this.getToken();return he(this.baseUrl,t,e)}async cancelInvoice(e){let t=await this.getToken();return ge(this.baseUrl,t,e)}clearTokenCache(){this.tokenManager.clearCache()}get environment(){return this.config.environment}};const Re={maxRetries:1/0,initialDelay:1e3,maxDelay:36e5,backoffMultiplier:2,maxRetryDuration:720*60*60*1e3};async function ze(e,t={}){let n={...Re,...t},r=n.initialDelay,i=0,a=Date.now();for(;i<n.maxRetries;){if(i++,Date.now()-a>n.maxRetryDuration)return{success:!1,attempts:i,error:Error(`Max retry duration exceeded`)};try{return{success:!0,data:await e(),attempts:i}}catch(e){let t=e instanceof Error?e:Error(String(e));if(t.message.includes(`4`))return{success:!1,attempts:i,error:t};i<n.maxRetries&&(await new Promise(e=>setTimeout(e,r)),r=Math.min(r*n.backoffMultiplier,n.maxDelay))}}return{success:!1,attempts:i,error:Error(`Max retries exceeded`)}}const Z=[`196.201.214.200`,`196.201.214.206`,`196.201.213.114`,`196.201.214.207`,`196.201.214.208`,`196.201.213.44`,`196.201.212.127`,`196.201.212.138`,`196.201.212.129`,`196.201.212.136`,`196.201.212.74`,`196.201.212.69`];function Q(e,t=Z){return t.includes(e)}function $(e){try{let t=e;return t?.Body?.stkCallback?t:null}catch{return null}}function Be(e,t={}){if(!t.skipIPCheck&&t.requestIP&&!Q(t.requestIP,t.allowedIPs))return{success:!1,eventType:null,data:null,error:`IP address ${t.requestIP} is not in the Safaricom whitelist`};let n=$(e);return n?{success:!0,eventType:`stk_push`,data:n}:{success:!1,eventType:null,data:null,error:`Unknown or malformed webhook payload`}}function Ve(e){let t=(e.Body?.stkCallback?.CallbackMetadata?.Item)?.find(e=>e.Name===`MpesaReceiptNumber`);return t?String(t.Value):null}function He(e){let t=(e.Body?.stkCallback?.CallbackMetadata?.Item)?.find(e=>e.Name===`Amount`);return t?Number(t.Value):null}function Ue(e){let t=(e.Body?.stkCallback?.CallbackMetadata?.Item)?.find(e=>e.Name===`PhoneNumber`);return t?String(t.Value):null}function We(e){return e.Body?.stkCallback?.ResultCode===0}export{X as DARAJA_BASE_URLS,Fe as KRA_SHORTCODE,Le as Mpesa,s as PesafyError,Z as SAFARICOM_IPS,J as TAX_COMMAND_ID,ye as acceptC2BValidation,xe as acknowledgeC2BConfirmation,c as createError,p as encryptSecurityCredential,b as err,He as extractAmount,Ue as extractPhoneNumber,Ve as extractTransactionId,W as formatPhoneNumber,W as formatSafaricomPhone,ae as getAccountBalanceParam,de as getB2BAmount,pe as getB2BConversationId,fe as getB2BRequestId,ue as getB2BTransactionId,A as getB2CAmount,D as getB2CConversationId,P as getB2CCurrency,F as getB2CDebitAccountBalance,M as getB2CDebitPartyCharges,I as getB2CInitiatorAccountBalance,O as getB2COriginatorConversationId,N as getB2CReceiverPublicName,k as getB2CResultDesc,L as getB2CResultParam,j as getB2CTransactionCompletedTime,E as getB2CTransactionId,Ce as getC2BAccountRef,Se as getC2BAmount,we as getC2BCustomerName,U as getC2BTransactionId,Pe as getCallbackValue,je as getReversalConversationId,Ae as getReversalTransactionId,K as getTimestamp,Be as handleWebhook,d as httpRequest,x as initiateB2BExpressCheckout,C as initiateB2CPayment,oe as isAccountBalanceSuccess,le as isB2BCheckoutCallback,ce as isB2BCheckoutCancelled,S as isB2BCheckoutSuccess,T as isB2CFailure,me as isB2CResult,w as isB2CSuccess,Ee as isBuyGoodsPayment,ve as isC2BPayload,Te as isPaybillPayment,l as isPesafyError,ke as isReversalSuccess,q as isStkCallbackSuccess,We as isSuccessfulCallback,y as ok,ie as parseAccountBalance,$ as parseStkPushWebhook,V as registerC2BUrls,be as rejectC2BValidation,Y as remitTax,ze as retryWithBackoff,H as simulateC2B,m as toKesAmount,h as toMsisdn,ne as toNonEmpty,g as toPaybill,v as toShortCode,_ as toTill,Q as verifyWebhookIP};
@@ -1,4 +1,5 @@
1
- import { t as PesafyError } from "./errors-DL4bkMZV.mjs";
1
+ import { t as PesafyError } from "./errors.mjs";
2
+
2
3
  //#region src/utils/phone/index.ts
3
4
  /** Normalises any common Kenyan phone format to 254XXXXXXXXX (12 digits) */
4
5
  function formatSafaricomPhone(phone) {
@@ -17,5 +18,6 @@ function formatSafaricomPhone(phone) {
17
18
  });
18
19
  return n;
19
20
  }
21
+
20
22
  //#endregion
21
- export { formatSafaricomPhone };
23
+ export { formatSafaricomPhone };
@@ -0,0 +1 @@
1
+ import{readFile as e}from"node:fs/promises";import{constants as t,publicEncrypt as n}from"node:crypto";function r(e){"@babel/helpers - typeof";return r=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},r(e)}function i(e,t){if(r(e)!=`object`||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var i=n.call(e,t||`default`);if(r(i)!=`object`)return i;throw TypeError(`@@toPrimitive must return a primitive value.`)}return(t===`string`?String:Number)(e)}function a(e){var t=i(e,`string`);return r(t)==`symbol`?t:t+``}function o(e,t,n){return(t=a(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var s=class e extends Error{constructor(t){super(t.message),o(this,`code`,void 0),o(this,`statusCode`,void 0),o(this,`response`,void 0),o(this,`requestId`,void 0),o(this,`cause`,void 0),o(this,`retryable`,void 0),Object.defineProperty(this,`name`,{value:`PesafyError`}),this.code=t.code,this.statusCode=t.statusCode,this.response=t.response,this.requestId=t.requestId,this.cause=t.cause,this.retryable=t.retryable??(t.code===`NETWORK_ERROR`||t.code===`TIMEOUT`||t.code===`RATE_LIMITED`||t.code===`REQUEST_FAILED`),Error.captureStackTrace&&Error.captureStackTrace(this,e)}get isValidation(){return this.code===`VALIDATION_ERROR`}get isAuth(){return this.code===`AUTH_FAILED`||this.code===`INVALID_CREDENTIALS`}toJSON(){return{name:this.name,code:this.code,message:this.message,statusCode:this.statusCode,requestId:this.requestId,retryable:this.retryable}}};function c(e){return new s(e)}const l=new Set([429,500,502,503,504]);function u(e){return new Promise(t=>setTimeout(t,e))}function d(e){let t=e*.25;return e+(Math.random()*t*2-t)}async function f(e,t){let n=t.retries??4,r=t.retryDelay??2e3,i=t.timeout??3e4,a={"Content-Type":`application/json`,Accept:`application/json`,...t.headers};t.idempotencyKey&&(a[`Idempotency-Key`]=t.idempotencyKey);let o={method:t.method,headers:a,...t.body===void 0?{}:{body:JSON.stringify(t.body)}},c=null;for(let a=0;a<=n;a++){if(a>0){let i=d(r*2**(a-1));console.warn(`[pesafy] Retry ${a}/${n} → ${t.method} ${e} in ${Math.round(i)} ms`),await u(i)}let f=new AbortController,p=setTimeout(()=>f.abort(),i),m;try{m=await fetch(e,{...o,signal:f.signal})}catch(t){if(clearTimeout(p),c=t instanceof Error&&t.name===`AbortError`?new s({code:`TIMEOUT`,message:`Request to ${e} timed out after ${i} ms`,cause:t,retryable:!0}):new s({code:`NETWORK_ERROR`,message:`Network error: ${t instanceof Error?t.message:String(t)}`,cause:t,retryable:!0}),a<n)continue;throw c}finally{clearTimeout(p)}let h=``,g=null,_=m.headers.get(`content-type`)??``;try{h=await m.text(),h&&(g=_.includes(`application/json`)?JSON.parse(h):h)}catch{g=h||null}let v={};if(m.headers.forEach((e,t)=>{v[t]=e}),m.ok)return{data:g,status:m.status,headers:v};let y=l.has(m.status),b=typeof g==`object`&&g?g:{},x=b.errorMessage??b.ResponseDescription??b.resultDesc??h??`HTTP ${m.status}`;if(c=new s({code:y?`REQUEST_FAILED`:`API_ERROR`,message:x,statusCode:m.status,response:g,retryable:y,...typeof b.requestId==`string`?{requestId:b.requestId}:{}}),!(y&&a<n))throw c}throw c}var p=class{constructor(e,t,n){o(this,`consumerKey`,void 0),o(this,`consumerSecret`,void 0),o(this,`baseUrl`,void 0),o(this,`cachedToken`,null),o(this,`tokenExpiresAt`,0),this.consumerKey=e,this.consumerSecret=t,this.baseUrl=n}getBasicAuthHeader(){let e=`${this.consumerKey}:${this.consumerSecret}`;return`Basic ${Buffer.from(e,`utf-8`).toString(`base64`)}`}async getAccessToken(){let e=Date.now()/1e3;if(this.cachedToken&&this.tokenExpiresAt>e+60)return this.cachedToken;let t=await f(`${this.baseUrl}/oauth/v1/generate?grant_type=client_credentials`,{method:`GET`,headers:{Authorization:this.getBasicAuthHeader()}}),{access_token:n,expires_in:r}=t.data;if(!n)throw new s({code:`AUTH_FAILED`,message:`Daraja did not return an access token. Check your consumer key and secret.`,response:t.data});return this.cachedToken=n,this.tokenExpiresAt=e+(r??3600),this.cachedToken}clearCache(){this.cachedToken=null,this.tokenExpiresAt=0}};function m(e,r){try{let i=Buffer.from(e,`utf-8`);return n({key:r,padding:t.RSA_PKCS1_PADDING},i).toString(`base64`)}catch(e){throw new s({code:`ENCRYPTION_FAILED`,message:`Failed to encrypt security credential. Ensure the certificate PEM is valid and matches the environment (sandbox/production).`,cause:e})}}function h(e){return{ok:!0,data:e}}function g(e){return{ok:!1,error:e}}async function _(e,t,n,r,i){if(!i.partyA?.trim())throw c({code:`VALIDATION_ERROR`,message:`partyA is required.`});if(![`1`,`2`,`4`].includes(i.identifierType))throw c({code:`VALIDATION_ERROR`,message:`identifierType must be "1" (MSISDN), "2" (Till), or "4" (ShortCode).`});if(!i.resultUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`resultUrl is required.`});if(!i.queueTimeOutUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`queueTimeOutUrl is required.`});let a={Initiator:r,SecurityCredential:n,CommandID:`AccountBalance`,PartyA:String(i.partyA),IdentifierType:i.identifierType,ResultURL:i.resultUrl,QueueTimeOutURL:i.queueTimeOutUrl,Remarks:i.remarks??`Account Balance Query`},{data:o}=await f(`${e}/mpesa/accountbalance/v1/query`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:a});return o}function v(){try{if(typeof crypto<`u`&&crypto.randomUUID)return crypto.randomUUID()}catch{}return`${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`}async function y(e,t,n){if(!n.primaryShortCode?.trim())throw c({code:`VALIDATION_ERROR`,message:`primaryShortCode is required — the merchant's till number (debit party).`});if(!n.receiverShortCode?.trim())throw c({code:`VALIDATION_ERROR`,message:`receiverShortCode is required — the vendor's Paybill account (credit party).`});let r=Math.round(n.amount);if(!Number.isFinite(r)||r<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be a whole number ≥ 1 (got ${n.amount} which rounds to ${r}).`});if(!n.paymentRef?.trim())throw c({code:`VALIDATION_ERROR`,message:`paymentRef is required — shown in the merchant's USSD prompt as the payment reference.`});if(!n.callbackUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`callbackUrl is required — Safaricom POSTs the transaction result here.`});if(!n.partnerName?.trim())throw c({code:`VALIDATION_ERROR`,message:`partnerName is required — your friendly name shown in the merchant's USSD prompt.`});let i={primaryShortCode:String(n.primaryShortCode),receiverShortCode:String(n.receiverShortCode),amount:String(r),paymentRef:n.paymentRef,callbackUrl:n.callbackUrl,partnerName:n.partnerName,RequestRefID:n.requestRefId??v()},{data:a}=await f(`${e}/v1/ussdpush/get-msisdn`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:i});return a}async function b(e,t,n,r,i){if(!i.commandId)throw c({code:`VALIDATION_ERROR`,message:`commandId is required: "BusinessPayToBulk" | "BusinessPayment" | "SalaryPayment" | "PromotionPayment"`});let a=[`BusinessPayToBulk`,`BusinessPayment`,`SalaryPayment`,`PromotionPayment`];if(!a.includes(i.commandId))throw c({code:`VALIDATION_ERROR`,message:`commandId must be one of: ${a.join(`, `)}. Got: "${i.commandId}"`});let o=Math.round(i.amount);if(!Number.isFinite(o)||o<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be a whole number ≥ 1 (got ${i.amount} which rounds to ${o}).`});if(!i.partyA?.trim())throw c({code:`VALIDATION_ERROR`,message:`partyA is required — your business shortcode from which money is deducted.`});if(!i.partyB?.trim())throw c({code:`VALIDATION_ERROR`,message:`partyB is required — the recipient shortcode (BusinessPayToBulk) or customer MSISDN (other commands).`});if(!i.accountReference?.trim())throw c({code:`VALIDATION_ERROR`,message:`accountReference is required — a reference for this transaction.`});if(!i.resultUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`resultUrl is required — Safaricom POSTs the B2C result here.`});if(!i.queueTimeOutUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`queueTimeOutUrl is required — Safaricom calls this on request timeout.`});let s={Initiator:r,SecurityCredential:n,CommandID:i.commandId,SenderIdentifierType:i.senderIdentifierType??`4`,RecieverIdentifierType:i.receiverIdentifierType??`4`,Amount:String(o),PartyA:String(i.partyA),PartyB:String(i.partyB),AccountReference:i.accountReference,Remarks:i.remarks??`B2C Payment`,QueueTimeOutURL:i.queueTimeOutUrl,ResultURL:i.resultUrl};i.requester?.trim()&&(s.Requester=String(i.requester));let{data:l}=await f(`${e}/mpesa/b2b/v1/paymentrequest`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:s});return l}async function x(e,t,n){if(!n.shortcode?.trim())throw c({code:`VALIDATION_ERROR`,message:`shortcode is required.`});if(!n.email?.trim())throw c({code:`VALIDATION_ERROR`,message:`email is required.`});if(!n.callbackUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`callbackUrl is required.`});let r={shortcode:n.shortcode,email:n.email,officialContact:n.officialContact,sendReminders:n.sendReminders,logo:n.logo??``,callbackUrl:n.callbackUrl},{data:i}=await f(`${e}/v1/billmanager-invoice/optin`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:r});return i}async function S(e,t,n){if(!n.externalReference?.trim())throw c({code:`VALIDATION_ERROR`,message:`externalReference is required.`});if(!n.partyA?.trim())throw c({code:`VALIDATION_ERROR`,message:`partyA (customer MSISDN) is required.`});let r=Math.round(n.amount);if(!Number.isFinite(r)||r<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be ≥ 1 (got ${n.amount}).`});let i={externalReference:n.externalReference,billingPeriod:n.billingPeriod,invoiceName:n.invoiceName,dueDate:n.dueDate,accountReference:n.accountReference,amount:String(r),partyA:n.partyA,invoiceItems:n.invoiceItems?.map(e=>({itemName:e.itemName,amount:String(Math.round(e.amount))}))??[]},{data:a}=await f(`${e}/v1/billmanager-invoice/single-invoicing`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:i});return a}async function C(e,t,n){if(!n.invoices?.length)throw c({code:`VALIDATION_ERROR`,message:`invoices array must not be empty.`});if(n.invoices.length>1e3)throw c({code:`VALIDATION_ERROR`,message:`Maximum 1 000 invoices per bulk request.`});let{data:r}=await f(`${e}/v1/billmanager-invoice/bulk-invoicing`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:n.invoices});return r}async function w(e,t,n){if(!n.externalReference?.trim())throw c({code:`VALIDATION_ERROR`,message:`externalReference is required.`});let{data:r}=await f(`${e}/v1/billmanager-invoice/cancel-single-invoice`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:[{externalReference:n.externalReference}]});return r}const T=[`mpesa`,`safaricom`,`.exe`,`.exec`,`cmd`,`sql`,`query`];function E(e,t){if(!e||!e.trim())throw c({code:`VALIDATION_ERROR`,message:`${t} is required`});let n=e.toLowerCase();for(let e of T)if(n.includes(e))throw c({code:`VALIDATION_ERROR`,message:`${t} must not contain the keyword "${e}". Daraja rejects URLs containing: M-PESA, Safaricom, exe, exec, cmd, sql, query.`})}async function D(e,t,n){if(!n.shortCode)throw c({code:`VALIDATION_ERROR`,message:`shortCode is required`});if(!n.responseType)throw c({code:`VALIDATION_ERROR`,message:`responseType is required: "Completed" or "Cancelled" (sentence case, exactly as spelled)`});if(n.responseType!==`Completed`&&n.responseType!==`Cancelled`)throw c({code:`VALIDATION_ERROR`,message:`responseType must be exactly "Completed" or "Cancelled" (sentence case). Got: "${String(n.responseType)}"`});E(n.confirmationUrl,`confirmationUrl`),E(n.validationUrl,`validationUrl`);let r=n.apiVersion??`v2`,i={ShortCode:String(n.shortCode),ResponseType:n.responseType,ConfirmationURL:n.confirmationUrl,ValidationURL:n.validationUrl},{data:a}=await f(`${e}/mpesa/c2b/${r}/registerurl`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:i});return a}async function O(e,t,n){if(!e.includes(`sandbox`))throw c({code:`VALIDATION_ERROR`,message:`C2B simulate is only available in the Sandbox environment. In production, customers initiate payments via M-PESA App, USSD, or SIM Toolkit.`});if(!n.shortCode)throw c({code:`VALIDATION_ERROR`,message:`shortCode is required.`});if(n.commandId!==`CustomerPayBillOnline`&&n.commandId!==`CustomerBuyGoodsOnline`)throw c({code:`VALIDATION_ERROR`,message:`commandId must be "CustomerPayBillOnline" or "CustomerBuyGoodsOnline". Got: "${String(n.commandId)}"`});let r=Math.round(n.amount);if(!Number.isFinite(r)||r<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be a whole number ≥ 1 (got ${n.amount}).`});if(!n.msisdn)throw c({code:`VALIDATION_ERROR`,message:`msisdn is required. Sandbox test MSISDN: 254708374149.`});let i=n.commandId===`CustomerBuyGoodsOnline`,a=n.apiVersion??`v2`,o={ShortCode:Number(n.shortCode),CommandID:n.commandId,Amount:r,Msisdn:Number(n.msisdn)};i||(o.BillRefNumber=n.billRefNumber??``),i&&Object.prototype.hasOwnProperty.call(o,`BillRefNumber`)&&(delete o.BillRefNumber,console.warn(`[pesafy/simulateC2B] BillRefNumber leaked into Buy Goods payload — removed. This is a library bug; please report it.`));let{data:s}=await f(`${e}/mpesa/c2b/${a}/simulate`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:o});return s}async function k(e,t,n){if(!n.merchantName?.trim())throw c({code:`VALIDATION_ERROR`,message:`merchantName is required`});if(!n.refNo?.trim())throw c({code:`VALIDATION_ERROR`,message:`refNo (transaction reference) is required`});let r=Math.round(n.amount);if(!Number.isFinite(r)||r<1)throw c({code:`VALIDATION_ERROR`,message:`Amount must be at least 1 (got ${n.amount} which rounds to ${r}).`});if(!n.trxCode)throw c({code:`VALIDATION_ERROR`,message:`trxCode is required. Supported values: "BG" | "WA" | "PB" | "SM" | "SB"`});if(!n.cpi?.trim())throw c({code:`VALIDATION_ERROR`,message:`cpi (Credit Party Identifier) is required — e.g. till number, paybill, or MSISDN`});let i=n.size??300;if(i<1)throw c({code:`VALIDATION_ERROR`,message:`size must be a positive number of pixels`});let a={MerchantName:n.merchantName,RefNo:n.refNo,Amount:r,TrxCode:n.trxCode,CPI:n.cpi,Size:String(i)},{data:o}=await f(`${e}/mpesa/qrcode/v1/generate`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:a});return o}async function A(e,t,n,r,i){if(!i.transactionId?.trim())throw c({code:`VALIDATION_ERROR`,message:`transactionId is required.`});if(!i.receiverParty?.trim())throw c({code:`VALIDATION_ERROR`,message:`receiverParty is required.`});if(![`1`,`2`,`4`].includes(i.receiverIdentifierType))throw c({code:`VALIDATION_ERROR`,message:`receiverIdentifierType must be "1", "2", or "4".`});let a=Math.round(i.amount);if(!Number.isFinite(a)||a<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be a whole number ≥ 1 (got ${i.amount}).`});if(!i.resultUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`resultUrl is required.`});if(!i.queueTimeOutUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`queueTimeOutUrl is required.`});let o={Initiator:r,SecurityCredential:n,CommandID:`TransactionReversal`,TransactionID:i.transactionId,Amount:String(a),ReceiverParty:String(i.receiverParty),RecieverIdentifierType:i.receiverIdentifierType,ResultURL:i.resultUrl,QueueTimeOutURL:i.queueTimeOutUrl,Remarks:i.remarks??`Transaction Reversal`,Occasion:i.occasion??``},{data:s}=await f(`${e}/mpesa/reversal/v1/request`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:o});return s}function j(e){let t=e.replace(/\D/g,``),n;if(t.startsWith(`254`)&&t.length===12)n=t;else if(t.startsWith(`0`)&&t.length===10)n=`254${t.slice(1)}`;else if(t.length===9)n=`254${t}`;else throw new s({code:`INVALID_PHONE`,message:`Cannot parse "${e}". Use 07XXXXXXXX, 2547XXXXXXXX, or +2547XXXXXXXX.`});if(n.length!==12)throw new s({code:`INVALID_PHONE`,message:`"${e}" normalised to "${n}" — expected 12 digits.`});return n}function M(e,t,n){return btoa(`${e}${t}${n}`)}function N(){let e=new Date,t=e=>e.toString().padStart(2,`0`);return[e.getFullYear(),t(e.getMonth()+1),t(e.getDate()),t(e.getHours()),t(e.getMinutes()),t(e.getSeconds())].join(``)}async function P(e,t,n){let r=Math.round(n.amount);if(r<1)throw new s({code:`VALIDATION_ERROR`,message:`Amount must be at least KES 1 (got ${n.amount} which rounds to ${r}).`});let i=N(),a=n.partyB??n.shortCode,o={BusinessShortCode:n.shortCode,Password:M(n.shortCode,n.passKey,i),Timestamp:i,TransactionType:n.transactionType??`CustomerPayBillOnline`,Amount:r,PartyA:j(n.phoneNumber),PartyB:a,PhoneNumber:j(n.phoneNumber),CallBackURL:n.callbackUrl,AccountReference:n.accountReference.slice(0,12),TransactionDesc:n.transactionDesc.slice(0,13)},{data:c}=await f(`${e}/mpesa/stkpush/v1/processrequest`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:o,retries:5,retryDelay:3e3});return c}async function F(e,t,n){let r=N(),i={BusinessShortCode:n.shortCode,Password:M(n.shortCode,n.passKey,r),Timestamp:r,CheckoutRequestID:n.checkoutRequestId},{data:a}=await f(`${e}/mpesa/stkpushquery/v1/query`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:i});return a}async function I(e,t,n,r,i){let a=Math.round(i.amount);if(!Number.isFinite(a)||a<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be a whole number ≥ 1 (got ${i.amount} which rounds to ${a}).`});if(!i.partyA)throw c({code:`VALIDATION_ERROR`,message:`partyA is required — your M-PESA business shortcode from which tax is deducted.`});if(!i.accountReference?.trim())throw c({code:`VALIDATION_ERROR`,message:`accountReference is required — the Payment Registration Number (PRN) issued by KRA.`});if(!i.resultUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`resultUrl is required — Safaricom POSTs the tax remittance result here.`});if(!i.queueTimeOutUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`queueTimeOutUrl is required — Safaricom calls this on request timeout.`});let o={Initiator:r,SecurityCredential:n,CommandID:`PayTaxToKRA`,SenderIdentifierType:`4`,RecieverIdentifierType:`4`,Amount:String(a),PartyA:String(i.partyA),PartyB:i.partyB??`572572`,AccountReference:i.accountReference,Remarks:i.remarks??`Tax Remittance`,QueueTimeOutURL:i.queueTimeOutUrl,ResultURL:i.resultUrl},{data:s}=await f(`${e}/mpesa/b2b/v1/remittax`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:o});return s}async function L(e,t,n,r,i){if(!i.transactionId)throw c({code:`VALIDATION_ERROR`,message:`transactionId is required`});if(!i.partyA)throw c({code:`VALIDATION_ERROR`,message:`partyA is required (your business shortcode, till number, or MSISDN)`});if(!i.identifierType)throw c({code:`VALIDATION_ERROR`,message:`identifierType is required: "1" (MSISDN) | "2" (Till) | "4" (ShortCode)`});if(!i.resultUrl)throw c({code:`VALIDATION_ERROR`,message:`resultUrl is required — Safaricom POSTs the transaction result here`});if(!i.queueTimeOutUrl)throw c({code:`VALIDATION_ERROR`,message:`queueTimeOutUrl is required — Safaricom calls this on timeout`});let a={Initiator:r,SecurityCredential:n,CommandID:i.commandId??`TransactionStatusQuery`,TransactionID:i.transactionId,PartyA:i.partyA,IdentifierType:i.identifierType,ResultURL:i.resultUrl,QueueTimeOutURL:i.queueTimeOutUrl,Remarks:i.remarks??`Transaction Status Query`,Occasion:i.occasion??``},{data:o}=await f(`${e}/mpesa/transactionstatus/v1/query`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:a});return o}const R={sandbox:`https://sandbox.safaricom.co.ke`,production:`https://api.safaricom.co.ke`};var z=class{constructor(e){if(o(this,`config`,void 0),o(this,`tokenManager`,void 0),o(this,`baseUrl`,void 0),!e.consumerKey||!e.consumerSecret)throw new s({code:`INVALID_CREDENTIALS`,message:`consumerKey and consumerSecret are required.`});this.config=e,this.baseUrl=R[e.environment],this.tokenManager=new p(e.consumerKey,e.consumerSecret,this.baseUrl)}getToken(){return this.tokenManager.getAccessToken()}async buildSecurityCredential(){if(this.config.securityCredential)return this.config.securityCredential;if(!this.config.initiatorPassword)throw new s({code:`INVALID_CREDENTIALS`,message:`Provide securityCredential (pre-encrypted) OR (initiatorPassword + certificatePath/certificatePem).`});let t;if(this.config.certificatePem)t=this.config.certificatePem;else if(this.config.certificatePath)t=await e(this.config.certificatePath,`utf-8`);else throw new s({code:`INVALID_CREDENTIALS`,message:`certificatePath or certificatePem is required to encrypt the initiator password.`});return m(this.config.initiatorPassword,t)}requireInitiator(e){let t=this.config.initiatorName??``;if(!t)throw new s({code:`VALIDATION_ERROR`,message:`initiatorName is required for ${e}.`});return t}async stkPushSafe(e){try{return h(await this.stkPush(e))}catch(e){return g(e)}}async stkPush(e){let t=this.config.lipaNaMpesaShortCode??``,n=this.config.lipaNaMpesaPassKey??``;if(!t||!n)throw new s({code:`VALIDATION_ERROR`,message:`lipaNaMpesaShortCode and lipaNaMpesaPassKey are required for STK Push.`});let r=await this.getToken();return P(this.baseUrl,r,{...e,shortCode:t,passKey:n})}async stkQuery(e){let t=this.config.lipaNaMpesaShortCode??``,n=this.config.lipaNaMpesaPassKey??``;if(!t||!n)throw new s({code:`VALIDATION_ERROR`,message:`lipaNaMpesaShortCode and lipaNaMpesaPassKey are required for STK Query.`});let r=await this.getToken();return F(this.baseUrl,r,{...e,shortCode:t,passKey:n})}async transactionStatus(e){let t=this.requireInitiator(`Transaction Status`),[n,r]=await Promise.all([this.getToken(),this.buildSecurityCredential()]);return L(this.baseUrl,n,r,t,e)}async accountBalance(e){let t=this.requireInitiator(`Account Balance`),[n,r]=await Promise.all([this.getToken(),this.buildSecurityCredential()]);return _(this.baseUrl,n,r,t,e)}async reverseTransaction(e){let t=this.requireInitiator(`Reversal`),[n,r]=await Promise.all([this.getToken(),this.buildSecurityCredential()]);return A(this.baseUrl,n,r,t,e)}async generateDynamicQR(e){let t=await this.getToken();return k(this.baseUrl,t,e)}async registerC2BUrls(e){let t=await this.getToken();return D(this.baseUrl,t,e)}async simulateC2B(e){let t=await this.getToken();return O(this.baseUrl,t,e)}async remitTax(e){let t=this.requireInitiator(`Tax Remittance`),[n,r]=await Promise.all([this.getToken(),this.buildSecurityCredential()]);return I(this.baseUrl,n,r,t,e)}async b2bExpressCheckout(e){let t=await this.getToken();return y(this.baseUrl,t,e)}async b2cPayment(e){let t=this.requireInitiator(`B2C Payment`),[n,r]=await Promise.all([this.getToken(),this.buildSecurityCredential()]);return b(this.baseUrl,n,r,t,e)}async billManagerOptIn(e){let t=await this.getToken();return x(this.baseUrl,t,e)}async sendInvoice(e){let t=await this.getToken();return S(this.baseUrl,t,e)}async sendBulkInvoices(e){let t=await this.getToken();return C(this.baseUrl,t,e)}async cancelInvoice(e){let t=await this.getToken();return w(this.baseUrl,t,e)}clearTokenCache(){this.tokenManager.clearCache()}get environment(){return this.config.environment}};const B=[`196.201.214.200`,`196.201.214.206`,`196.201.213.114`,`196.201.214.207`,`196.201.214.208`,`196.201.213.44`,`196.201.212.127`,`196.201.212.138`,`196.201.212.129`,`196.201.212.136`,`196.201.212.74`,`196.201.212.69`];function V(e,t=B){return t.includes(e)}function H(e){try{let t=e;return t?.Body?.stkCallback?t:null}catch{return null}}export{s as i,V as n,z as r,H as t};