pesafy 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE +21 -0
  3. package/dist/adapters/express.cjs +1 -0
  4. package/dist/{express/index.d.cts → adapters/express.d.cts} +23 -51
  5. package/dist/{express/index.d.mts → adapters/express.d.ts} +23 -51
  6. package/dist/adapters/express.js +1 -0
  7. package/dist/adapters/fastify.cjs +1 -1607
  8. package/dist/adapters/fastify.d.cts +2 -30
  9. package/dist/adapters/fastify.d.ts +21 -0
  10. package/dist/adapters/fastify.js +1 -0
  11. package/dist/adapters/hono.cjs +1 -1651
  12. package/dist/adapters/hono.d.cts +2 -30
  13. package/dist/adapters/hono.d.ts +27 -0
  14. package/dist/adapters/hono.js +1 -0
  15. package/dist/adapters/nextjs.cjs +1 -1655
  16. package/dist/adapters/nextjs.d.cts +3 -30
  17. package/dist/adapters/{nextjs.d.mts → nextjs.d.ts} +3 -30
  18. package/dist/adapters/nextjs.js +1 -0
  19. package/dist/{cli/index.mjs → cli.js} +10 -9
  20. package/dist/encryption.js +22 -0
  21. package/dist/{cli/errors-DL4bkMZV.mjs → errors.js} +2 -1
  22. package/dist/index.cjs +1 -2124
  23. package/dist/index.d.cts +37 -64
  24. package/dist/{index.d.mts → index.d.ts} +37 -64
  25. package/dist/index.js +1 -0
  26. package/dist/{cli/phone-5wwAaQ_8.mjs → phone.js} +4 -2
  27. package/dist/signature-verifier.cjs +1 -0
  28. package/dist/signature-verifier.js +1 -0
  29. package/dist/{adapters/fastify.d.mts → types.d.cts} +2 -22
  30. package/dist/types.d.ts +29 -0
  31. package/dist/{cli/utils-BzEKV3nJ.mjs → utils.js} +4 -2
  32. package/dist/webhook-handler.cjs +1 -0
  33. package/dist/webhook-handler.js +1 -0
  34. package/package.json +93 -48
  35. package/dist/adapters/fastify.cjs.map +0 -1
  36. package/dist/adapters/fastify.mjs +0 -1606
  37. package/dist/adapters/fastify.mjs.map +0 -1
  38. package/dist/adapters/hono.cjs.map +0 -1
  39. package/dist/adapters/hono.d.mts +0 -55
  40. package/dist/adapters/hono.mjs +0 -1650
  41. package/dist/adapters/hono.mjs.map +0 -1
  42. package/dist/adapters/nextjs.cjs.map +0 -1
  43. package/dist/adapters/nextjs.mjs +0 -1651
  44. package/dist/adapters/nextjs.mjs.map +0 -1
  45. package/dist/cli/encryption-BA-_xrIW.mjs +0 -45
  46. package/dist/cli/encryption-CkSveeYj.cjs +0 -45
  47. package/dist/cli/errors-Bscvlb7X.cjs +0 -45
  48. package/dist/cli/index.cjs +0 -559
  49. package/dist/cli/phone-BD4QmEyl.cjs +0 -21
  50. package/dist/cli/utils-Dg9Gv_D3.cjs +0 -31
  51. package/dist/components/react/index.d.mts +0 -1
  52. package/dist/components/react/index.mjs +0 -1
  53. package/dist/express/index.cjs +0 -2201
  54. package/dist/express/index.cjs.map +0 -1
  55. package/dist/express/index.mjs +0 -2199
  56. package/dist/express/index.mjs.map +0 -1
  57. package/dist/index.cjs.map +0 -1
  58. package/dist/index.mjs +0 -2050
  59. package/dist/index.mjs.map +0 -1
  60. /package/dist/{components/react/index.d.cts → react/index.d.ts} +0 -0
  61. /package/dist/{components/react/index.cjs → react/index.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # pesafy
2
2
 
3
+ ## 0.5.1
4
+
5
+ ### Patch Changes
6
+
7
+ - reduce size increase speed
8
+
3
9
  ## 0.5.0
4
10
 
5
11
  ### Minor Changes
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lewis Odero
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../signature-verifier.cjs`),t=require(`../webhook-handler.cjs`);function n(e){return e.resultCode===`0`}function r(e){return e.resultCode===`4001`}function i(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 a(e){return n(e)?e.transactionId??null:null}function o(e){return Number(e.amount)}function s(e){return n(e)?e.conversationID??null:null}function c(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 l(e){return e.Result.ResultCode===0}function u(e){return e.Result.TransactionID??null}function d(e){return e.Result.ConversationID}function f(e){return e.Result.OriginatorConversationID}function p(e){let t=m(e,`Amount`);return t===void 0?null:Number(t)}function m(e,t){let n=e.Result.ResultParameters?.ResultParameter;if(n)return(Array.isArray(n)?n:[n]).find(e=>e.Key===t)?.Value}function h(e){return{ResultCode:`0`,ResultDesc:`Accepted`,...e?{ThirdPartyTransID:e}:{}}}function g(t){if(!t.consumerKey||!t.consumerSecret)throw new e.i({code:`INVALID_CREDENTIALS`,message:`consumerKey and consumerSecret are required`});if(!t.lipaNaMpesaShortCode||!t.lipaNaMpesaPassKey)throw new e.i({code:`VALIDATION_ERROR`,message:`lipaNaMpesaShortCode and lipaNaMpesaPassKey are required for STK Push`});if(!t.callbackUrl)throw new e.i({code:`VALIDATION_ERROR`,message:`callbackUrl is required for STK Push callbacks`});return{mpesa:new e.r(t)}}function _(t,n){if(n instanceof e.i){let e=n.statusCode??400;t.status(e).json({error:n.code,message:n.message,statusCode:e});return}t.status(500).json({error:`REQUEST_FAILED`,message:`An unexpected error occurred while processing the M-Pesa request`})}function v(e){return e.headers[`x-forwarded-for`]?.split(`,`)[0]?.trim()??e.ip??``}function y(m,y){let{mpesa:b}=g(y);return m.post(`/mpesa/express/stk-push`,async(t,n,r)=>{try{let r=t.body;if(!r||typeof r.amount!=`number`||r.amount<=0)throw new e.i({code:`VALIDATION_ERROR`,message:`amount must be a positive number`});if(!r.phoneNumber)throw new e.i({code:`VALIDATION_ERROR`,message:`phoneNumber is required`});let i=await b.stkPush({amount:r.amount,phoneNumber:r.phoneNumber,callbackUrl:y.callbackUrl,accountReference:r.accountReference??`PESAFY-${Date.now().toString(36).toUpperCase()}`,transactionDesc:r.transactionDesc??`Payment`,transactionType:r.transactionType,partyB:r.partyB});n.status(200).json(i)}catch(e){if(n.headersSent)return r(e);_(n,e)}}),m.post(`/mpesa/express/stk-query`,async(t,n,r)=>{try{let r=t.body;if(!r?.checkoutRequestId)throw new e.i({code:`VALIDATION_ERROR`,message:`checkoutRequestId is required`});let i=await b.stkQuery({checkoutRequestId:r.checkoutRequestId});n.status(200).json(i)}catch(e){if(n.headersSent)return r(e);_(n,e)}}),m.post(`/mpesa/express/callback`,(e,n)=>{let r=v(e),i=t.i(e.body,{requestIP:r,skipIPCheck:y.skipIPCheck});if(!i.success)return console.error(`[pesafy] STK Push webhook rejected:`,i.error),n.status(200).json({ResultCode:0,ResultDesc:`Accepted`});let a=i.data;t.a(a)?console.info(`[pesafy] STK Push success:`,{receiptNumber:t.r(a),amount:t.t(a),phone:t.n(a)}):console.warn(`[pesafy] STK Push failed:`,{resultCode:a.Body.stkCallback.ResultCode,resultDesc:a.Body.stkCallback.ResultDesc}),n.status(200).json({ResultCode:0,ResultDesc:`Accepted`})}),m.post(`/mpesa/transaction-status/query`,async(t,n,r)=>{try{if(!y.resultUrl||!y.queueTimeOutUrl)throw new e.i({code:`VALIDATION_ERROR`,message:`resultUrl and queueTimeOutUrl must be set in config to use transaction status routes`});let r=t.body;if(!r?.transactionId)throw new e.i({code:`VALIDATION_ERROR`,message:`transactionId is required`});if(!r.partyA)throw new e.i({code:`VALIDATION_ERROR`,message:`partyA is required`});if(!r.identifierType)throw new e.i({code:`VALIDATION_ERROR`,message:`identifierType is required: "1" | "2" | "4"`});let i=await b.transactionStatus({transactionId:r.transactionId,partyA:r.partyA,identifierType:r.identifierType,resultUrl:y.resultUrl,queueTimeOutUrl:y.queueTimeOutUrl,remarks:r.remarks,occasion:r.occasion});n.status(200).json(i)}catch(e){if(n.headersSent)return r(e);_(n,e)}}),m.post(`/mpesa/transaction-status/result`,(e,t)=>{let n=e.body?.Result;n&&(n.ResultCode===0?console.info(`[pesafy] Transaction Status result (success):`,{transactionId:n.TransactionID,conversationId:n.ConversationID,resultDesc:n.ResultDesc}):console.warn(`[pesafy] Transaction Status result (failed):`,{resultCode:n.ResultCode,resultDesc:n.ResultDesc,transactionId:n.TransactionID})),t.status(200).json({ResultCode:0,ResultDesc:`Accepted`})}),m.post(`/mpesa/c2b/register-url`,async(t,n,r)=>{try{let r=t.body,i=r.shortCode??y.c2bShortCode,a=r.confirmationUrl??y.c2bConfirmationUrl,o=r.validationUrl??y.c2bValidationUrl,s=r.responseType??y.c2bResponseType??`Completed`,c=r.apiVersion??y.c2bApiVersion??`v2`;if(!i)throw new e.i({code:`VALIDATION_ERROR`,message:`shortCode is required`});if(!a)throw new e.i({code:`VALIDATION_ERROR`,message:`confirmationUrl is required`});if(!o)throw new e.i({code:`VALIDATION_ERROR`,message:`validationUrl is required`});let l=await b.registerC2BUrls({shortCode:i,responseType:s,confirmationUrl:a,validationUrl:o,apiVersion:c});n.status(200).json(l)}catch(e){if(n.headersSent)return r(e);_(n,e)}}),m.post(`/mpesa/c2b/simulate`,async(t,n,r)=>{try{let r=t.body;if(!r?.commandId)throw new e.i({code:`VALIDATION_ERROR`,message:`commandId is required: "CustomerPayBillOnline" | "CustomerBuyGoodsOnline"`});if(!r.amount||r.amount<=0)throw new e.i({code:`VALIDATION_ERROR`,message:`amount must be a positive number`});if(!r.msisdn)throw new e.i({code:`VALIDATION_ERROR`,message:`msisdn is required`});let i=await b.simulateC2B({shortCode:r.shortCode??y.c2bShortCode??``,commandId:r.commandId,amount:r.amount,msisdn:r.msisdn,billRefNumber:r.billRefNumber,apiVersion:y.c2bApiVersion??`v2`});n.status(200).json(i)}catch(e){if(n.headersSent)return r(e);_(n,e)}}),m.post(`/mpesa/c2b/validation`,async(t,n)=>{if(!y.skipIPCheck){let r=v(t);if(!e.n(r))return console.error(`[pesafy] C2B validation rejected — IP not in Safaricom whitelist:`,r),n.status(200).json({ResultCode:`0`,ResultDesc:`Accepted`})}let r=t.body;try{let e;return e=y.onC2BValidation?await y.onC2BValidation(r):h(),console.info(`[pesafy] C2B validation response:`,{transactionId:r.TransID,amount:r.TransAmount,billRef:r.BillRefNumber,resultCode:e.ResultCode}),n.status(200).json(e)}catch(e){return console.error(`[pesafy] C2B validation hook threw an error:`,e),n.status(200).json({ResultCode:`0`,ResultDesc:`Accepted`})}}),m.post(`/mpesa/c2b/confirmation`,(e,t)=>{let n=e.body;console.info(`[pesafy] C2B confirmation received:`,{transactionId:n.TransID,amount:n.TransAmount,shortCode:n.BusinessShortCode,billRef:n.BillRefNumber,transactionType:n.TransactionType,transTime:n.TransTime,balance:n.OrgAccountBalance}),y.onC2BConfirmation&&Promise.resolve(y.onC2BConfirmation(n)).catch(e=>{console.error(`[pesafy] C2B confirmation hook error:`,e)}),t.status(200).json({ResultCode:0,ResultDesc:`Success`})}),m.post(`/mpesa/tax/remit`,async(t,n,r)=>{try{if(!y.taxResultUrl||!y.taxQueueTimeOutUrl)throw new e.i({code:`VALIDATION_ERROR`,message:`taxResultUrl and taxQueueTimeOutUrl must be set in config to use tax remittance routes`});let r=t.body;if(!r||typeof r.amount!=`number`||r.amount<=0)throw new e.i({code:`VALIDATION_ERROR`,message:`amount must be a positive number`});if(!r.accountReference)throw new e.i({code:`VALIDATION_ERROR`,message:`accountReference is required — the KRA PRN`});let i=r.partyA??y.taxPartyA??``;if(!i)throw new e.i({code:`VALIDATION_ERROR`,message:`partyA is required — set taxPartyA in config or provide in request body`});let a=await b.remitTax({amount:r.amount,partyA:i,accountReference:r.accountReference,resultUrl:y.taxResultUrl,queueTimeOutUrl:y.taxQueueTimeOutUrl,remarks:r.remarks});n.status(200).json(a)}catch(e){if(n.headersSent)return r(e);_(n,e)}}),m.post(`/mpesa/tax/result`,(e,t)=>{let n=e.body,r=n?.Result;r&&(r.ResultCode===0?console.info(`[pesafy] Tax Remittance result (success):`,{transactionId:r.TransactionID,conversationId:r.ConversationID,resultDesc:r.ResultDesc}):console.warn(`[pesafy] Tax Remittance result (failed):`,{resultCode:r.ResultCode,resultDesc:r.ResultDesc,transactionId:r.TransactionID})),y.onTaxRemittanceResult&&n&&Promise.resolve(y.onTaxRemittanceResult(n)).catch(e=>{console.error(`[pesafy] Tax Remittance result hook error:`,e)}),t.status(200).json({ResultCode:0,ResultDesc:`Accepted`})}),m.post(`/mpesa/b2b/checkout`,async(t,n,r)=>{try{let r=t.body;if(!r?.primaryShortCode)throw new e.i({code:`VALIDATION_ERROR`,message:`primaryShortCode is required`});if(!r.amount||r.amount<=0)throw new e.i({code:`VALIDATION_ERROR`,message:`amount must be a positive number`});if(!r.paymentRef)throw new e.i({code:`VALIDATION_ERROR`,message:`paymentRef is required`});if(!r.partnerName)throw new e.i({code:`VALIDATION_ERROR`,message:`partnerName is required`});let i=r.receiverShortCode??y.b2bReceiverShortCode??``;if(!i)throw new e.i({code:`VALIDATION_ERROR`,message:`receiverShortCode is required — set b2bReceiverShortCode in config or provide in request body`});let a=r.callbackUrl??y.b2bCallbackUrl??``;if(!a)throw new e.i({code:`VALIDATION_ERROR`,message:`callbackUrl is required — set b2bCallbackUrl in config or provide in request body`});let o=await b.b2bExpressCheckout({primaryShortCode:r.primaryShortCode,receiverShortCode:i,amount:r.amount,paymentRef:r.paymentRef,callbackUrl:a,partnerName:r.partnerName,requestRefId:r.requestRefId});n.status(200).json(o)}catch(e){if(n.headersSent)return r(e);_(n,e)}}),m.post(`/mpesa/b2b/callback`,(e,t)=>{let c=e.body;if(!i(c))return console.error(`[pesafy] B2B callback received unrecognised payload:`,JSON.stringify(c).slice(0,200)),t.status(200).json({ResultCode:0,ResultDesc:`Accepted`});let l=c;n(l)?console.info(`[pesafy] B2B Express Checkout success:`,{transactionId:a(l),conversationId:s(l),amount:o(l),requestId:l.requestId,status:l.status}):r(l)?console.warn(`[pesafy] B2B Express Checkout cancelled by merchant:`,{resultCode:l.resultCode,resultDesc:l.resultDesc,requestId:l.requestId,amount:o(l)}):console.warn(`[pesafy] B2B Express Checkout unknown result:`,{resultCode:l.resultCode,resultDesc:l.resultDesc}),y.onB2BCheckoutCallback&&Promise.resolve(y.onB2BCheckoutCallback(l)).catch(e=>{console.error(`[pesafy] B2B callback hook error:`,e)}),t.status(200).json({ResultCode:0,ResultDesc:`Accepted`})}),m.post(`/mpesa/b2c/payment`,async(t,n,r)=>{try{if(!y.b2cResultUrl||!y.b2cQueueTimeOutUrl)throw new e.i({code:`VALIDATION_ERROR`,message:`b2cResultUrl and b2cQueueTimeOutUrl must be set in config to use B2C routes`});let r=t.body;if(!r?.commandId)throw new e.i({code:`VALIDATION_ERROR`,message:`commandId is required: "BusinessPayToBulk" | "BusinessPayment" | "SalaryPayment" | "PromotionPayment"`});if(!r.amount||r.amount<=0)throw new e.i({code:`VALIDATION_ERROR`,message:`amount must be a positive number`});if(!r.partyB)throw new e.i({code:`VALIDATION_ERROR`,message:`partyB is required — the recipient shortcode (BusinessPayToBulk) or customer MSISDN`});if(!r.accountReference)throw new e.i({code:`VALIDATION_ERROR`,message:`accountReference is required`});let i=r.partyA??y.b2cPartyA??``;if(!i)throw new e.i({code:`VALIDATION_ERROR`,message:`partyA is required — set b2cPartyA in config or provide in request body`});let a=await b.b2cPayment({commandId:r.commandId,amount:r.amount,partyA:i,partyB:r.partyB,accountReference:r.accountReference,requester:r.requester,remarks:r.remarks,resultUrl:r.resultUrl??y.b2cResultUrl,queueTimeOutUrl:r.queueTimeOutUrl??y.b2cQueueTimeOutUrl});n.status(200).json(a)}catch(e){if(n.headersSent)return r(e);_(n,e)}}),m.post(`/mpesa/b2c/result`,(e,t)=>{let n=e.body;if(!c(n))return console.error(`[pesafy] B2C result received unrecognised payload:`,JSON.stringify(n).slice(0,200)),t.status(200).json({ResultCode:0,ResultDesc:`Accepted`});let r=n;l(r)?console.info(`[pesafy] B2C payment result (success):`,{transactionId:u(r),conversationId:d(r),originatorConversationId:f(r),amount:p(r),resultDesc:r.Result.ResultDesc}):console.warn(`[pesafy] B2C payment result (failed):`,{resultCode:r.Result.ResultCode,resultDesc:r.Result.ResultDesc,transactionId:r.Result.TransactionID,conversationId:d(r),originatorConversationId:f(r)}),y.onB2CResult&&Promise.resolve(y.onB2CResult(r)).catch(e=>{console.error(`[pesafy] B2C result hook error:`,e)}),t.status(200).json({ResultCode:0,ResultDesc:`Accepted`})}),m}exports.createMpesaExpressClient=g,exports.createMpesaExpressRouter=y;
@@ -1,3 +1,4 @@
1
+ import { n as MpesaConfig, t as Environment } from "../types.cjs";
1
2
  import { Router } from "express";
2
3
 
3
4
  //#region src/mpesa/stk-push/types.d.ts
@@ -13,7 +14,7 @@ import { Router } from "express";
13
14
  * CustomerPayBillOnline → Paybill numbers (PartyB = shortcode)
14
15
  * CustomerBuyGoodsOnline → Till numbers (PartyB = till number)
15
16
  */
16
- type TransactionType = "CustomerPayBillOnline" | "CustomerBuyGoodsOnline";
17
+ type TransactionType = 'CustomerPayBillOnline' | 'CustomerBuyGoodsOnline';
17
18
  interface StkPushRequest {
18
19
  /** Transaction amount (minimum KES 1, must round to a whole number ≥ 1) */
19
20
  amount: number;
@@ -138,7 +139,7 @@ interface TransactionStatusRequest {
138
139
  * "4" = Organisation ShortCode (Paybill / B2C) ← most common
139
140
  * Daraja field: IdentifierType
140
141
  */
141
- identifierType: "1" | "2" | "4";
142
+ identifierType: '1' | '2' | '4';
142
143
  /**
143
144
  * URL where Safaricom POSTs the final result.
144
145
  * Must be publicly accessible.
@@ -168,34 +169,6 @@ interface TransactionStatusResponse {
168
169
  OriginatorConversationID?: string;
169
170
  }
170
171
  //#endregion
171
- //#region src/mpesa/types.d.ts
172
- /**
173
- * Core M-Pesa / Daraja configuration types
174
- */
175
- type Environment = "sandbox" | "production";
176
- interface MpesaConfig {
177
- consumerKey: string;
178
- consumerSecret: string;
179
- environment: Environment;
180
- lipaNaMpesaShortCode?: string;
181
- lipaNaMpesaPassKey?: string;
182
- initiatorName?: string;
183
- initiatorPassword?: string;
184
- certificatePath?: string;
185
- certificatePem?: string;
186
- /**
187
- * Pre-computed base64 SecurityCredential — skips RSA encryption.
188
- * Use when you encrypt at startup outside the library.
189
- */
190
- securityCredential?: string;
191
- /** Override default retry count (4) for all API calls */
192
- retries?: number;
193
- /** Override default base retry delay in ms (2000) */
194
- retryDelay?: number;
195
- /** Override default per-request timeout in ms (30000) */
196
- timeout?: number;
197
- }
198
- //#endregion
199
172
  //#region src/utils/errors/index.d.ts
200
173
  /**
201
174
  * Pesafy error types — single source of truth.
@@ -288,7 +261,7 @@ interface AccountBalanceRequest {
288
261
  * "4" = Organisation ShortCode (most common — Paybill/B2C)
289
262
  * Daraja field: IdentifierType
290
263
  */
291
- identifierType: "1" | "2" | "4";
264
+ identifierType: '1' | '2' | '4';
292
265
  /**
293
266
  * URL where Safaricom POSTs the balance result.
294
267
  * Must be publicly accessible. HTTPS required in production.
@@ -447,7 +420,7 @@ type B2BExpressCheckoutCallback = B2BExpressCheckoutCallbackSuccess | B2BExpress
447
420
  * PromotionPayment — Payment of promotions/bonuses
448
421
  * BusinessPayToBulk — Load funds to a B2C shortcode for bulk disbursement
449
422
  */
450
- type B2CCommandID = "BusinessPayment" | "SalaryPayment" | "PromotionPayment" | "BusinessPayToBulk";
423
+ type B2CCommandID = 'BusinessPayment' | 'SalaryPayment' | 'PromotionPayment' | 'BusinessPayToBulk';
451
424
  interface B2CRequest {
452
425
  /**
453
426
  * The type of transaction. Use "BusinessPayToBulk" for account top-up.
@@ -485,14 +458,14 @@ interface B2CRequest {
485
458
  * Daraja field: SenderIdentifierType
486
459
  * Default: "4"
487
460
  */
488
- senderIdentifierType?: "4";
461
+ senderIdentifierType?: '4';
489
462
  /**
490
463
  * Type of the receiver (PartyB) identifier.
491
464
  * For this API, only "4" (Organisation ShortCode) is allowed.
492
465
  * Daraja field: RecieverIdentifierType
493
466
  * Default: "4"
494
467
  */
495
- receiverIdentifierType?: "4";
468
+ receiverIdentifierType?: '4';
496
469
  /**
497
470
  * A reference for this transaction (e.g. invoice number, batch reference).
498
471
  * Daraja field: AccountReference
@@ -540,7 +513,7 @@ interface B2CResponse {
540
513
  * - Any unknown future key Daraja may return is still accepted.
541
514
  * - The `no-redundant-type-constituents` ESLint rule is not triggered.
542
515
  */
543
- type B2CResultParameterKey = "DebitAccountBalance" | "Amount" | "DebitPartyAffectedAccountBalance" | "TransCompletedTime" | "DebitPartyCharges" | "ReceiverPartyPublicName" | "Currency" | "InitiatorAccountCurrentBalance" | "B2CRecipientIsRegisteredCustomer" | "B2CChargesPaidAccountAvailableFunds" | "B2CWorkingAccountAvailableFunds" | "B2CUtilityAccountAvailableFunds" | (string & {});
516
+ type B2CResultParameterKey = 'DebitAccountBalance' | 'Amount' | 'DebitPartyAffectedAccountBalance' | 'TransCompletedTime' | 'DebitPartyCharges' | 'ReceiverPartyPublicName' | 'Currency' | 'InitiatorAccountCurrentBalance' | 'B2CRecipientIsRegisteredCustomer' | 'B2CChargesPaidAccountAvailableFunds' | 'B2CWorkingAccountAvailableFunds' | 'B2CUtilityAccountAvailableFunds' | (string & {});
544
517
  interface B2CResultParameter {
545
518
  Key: B2CResultParameterKey;
546
519
  Value: string | number;
@@ -592,7 +565,7 @@ interface BillManagerOptInRequest {
592
565
  /** Your logo URL (public HTTPS link) */
593
566
  officialContact: string;
594
567
  /** Sender name shown on push notifications */
595
- sendReminders: "1" | "0";
568
+ sendReminders: '1' | '0';
596
569
  /** Logo URL */
597
570
  logo?: string;
598
571
  /** Callback URL for payment confirmations */
@@ -660,7 +633,7 @@ interface BillManagerCancelInvoiceResponse {
660
633
  * Ref: Customer To Business (C2B) — Daraja Developer Portal
661
634
  */
662
635
  /** C2B API version. v2 is recommended; v1 sends SHA256-hashed MSISDN. */
663
- type C2BApiVersion = "v1" | "v2";
636
+ type C2BApiVersion = 'v1' | 'v2';
664
637
  /**
665
638
  * What M-PESA should do if your Validation URL is unreachable or times out.
666
639
  * "Completed" — M-PESA automatically completes the transaction.
@@ -669,7 +642,7 @@ type C2BApiVersion = "v1" | "v2";
669
642
  * NOTE: Must be exactly "Completed" or "Cancelled" (sentence case, no typos).
670
643
  * Daraja docs: "the words Cancelled/Completed must be in sentence case and well-spelled."
671
644
  */
672
- type C2BResponseType = "Completed" | "Cancelled";
645
+ type C2BResponseType = 'Completed' | 'Cancelled';
673
646
  interface C2BRegisterUrlRequest {
674
647
  /**
675
648
  * Your M-PESA Paybill or Till shortcode.
@@ -716,7 +689,7 @@ interface C2BRegisterUrlResponse {
716
689
  *
717
690
  * NOTE: Simulation is ONLY supported in Sandbox, NOT in production.
718
691
  */
719
- type C2BCommandID = "CustomerPayBillOnline" | "CustomerBuyGoodsOnline";
692
+ type C2BCommandID = 'CustomerPayBillOnline' | 'CustomerBuyGoodsOnline';
720
693
  interface C2BSimulateRequest {
721
694
  /**
722
695
  * Your M-PESA Paybill or Till shortcode.
@@ -808,7 +781,7 @@ interface C2BValidationPayload {
808
781
  * "C2B00015" — Invalid Short code
809
782
  * "C2B00016" — Other Error
810
783
  */
811
- type C2BValidationResultCode = "0" | "C2B00011" | "C2B00012" | "C2B00013" | "C2B00014" | "C2B00015" | "C2B00016";
784
+ type C2BValidationResultCode = '0' | 'C2B00011' | 'C2B00012' | 'C2B00013' | 'C2B00014' | 'C2B00015' | 'C2B00016';
812
785
  interface C2BValidationResponse {
813
786
  /**
814
787
  * "0" = Accept the transaction.
@@ -822,7 +795,7 @@ interface C2BValidationResponse {
822
795
  * "Accepted" when ResultCode is "0".
823
796
  * "Rejected" when ResultCode is a non-zero error code.
824
797
  */
825
- ResultDesc: "Accepted" | "Rejected";
798
+ ResultDesc: 'Accepted' | 'Rejected';
826
799
  /**
827
800
  * Optional. If set, this value is echoed back in the Confirmation callback
828
801
  * as ThirdPartyTransID. Useful for correlating validation → confirmation.
@@ -883,7 +856,7 @@ interface C2BConfirmationPayload {
883
856
  * SM: Send Money (Mobile number)
884
857
  * SB: Sent to Business (Business number CPI in MSISDN format)
885
858
  */
886
- type QRTransactionCode = "BG" | "WA" | "PB" | "SM" | "SB";
859
+ type QRTransactionCode = 'BG' | 'WA' | 'PB' | 'SM' | 'SB';
887
860
  interface DynamicQRRequest {
888
861
  /**
889
862
  * Name of the Company / M-Pesa Merchant Name.
@@ -975,7 +948,7 @@ interface ReversalRequest {
975
948
  * "4" = Organisation ShortCode
976
949
  * Daraja field: RecieverIdentifierType
977
950
  */
978
- receiverIdentifierType: "1" | "2" | "4";
951
+ receiverIdentifierType: '1' | '2' | '4';
979
952
  /**
980
953
  * URL where Safaricom POSTs the reversal result.
981
954
  * Daraja field: ResultURL
@@ -1077,7 +1050,7 @@ interface TaxRemittanceResponse {
1077
1050
  * - Any unknown future key Daraja may return is still accepted.
1078
1051
  * - The `no-redundant-type-constituents` ESLint rule is not triggered.
1079
1052
  */
1080
- type TaxRemittanceResultParameterKey = "DebitAccountBalance" | "Amount" | "DebitPartyAffectedAccountBalance" | "TransCompletedTime" | "DebitPartyCharges" | "ReceiverPartyPublicName" | "Currency" | "InitiatorAccountCurrentBalance" | (string & {});
1053
+ type TaxRemittanceResultParameterKey = 'DebitAccountBalance' | 'Amount' | 'DebitPartyAffectedAccountBalance' | 'TransCompletedTime' | 'DebitPartyCharges' | 'ReceiverPartyPublicName' | 'Currency' | 'InitiatorAccountCurrentBalance' | (string & {});
1081
1054
  interface TaxRemittanceResultParameter {
1082
1055
  Key: TaxRemittanceResultParameterKey;
1083
1056
  Value: string | number;
@@ -1118,9 +1091,9 @@ declare class Mpesa {
1118
1091
  * Like stkPush() but returns Result<T> instead of throwing.
1119
1092
  * Ideal for application-level code that prefers not to use try/catch.
1120
1093
  */
1121
- stkPushSafe(request: Omit<StkPushRequest, "shortCode" | "passKey">): Promise<Result<Awaited<ReturnType<typeof this.stkPush>>>>;
1122
- stkPush(request: Omit<StkPushRequest, "shortCode" | "passKey">): Promise<StkPushResponse>;
1123
- stkQuery(request: Omit<StkQueryRequest, "shortCode" | "passKey">): Promise<StkQueryResponse>;
1094
+ stkPushSafe(request: Omit<StkPushRequest, 'shortCode' | 'passKey'>): Promise<Result<Awaited<ReturnType<typeof this.stkPush>>>>;
1095
+ stkPush(request: Omit<StkPushRequest, 'shortCode' | 'passKey'>): Promise<StkPushResponse>;
1096
+ stkQuery(request: Omit<StkQueryRequest, 'shortCode' | 'passKey'>): Promise<StkQueryResponse>;
1124
1097
  transactionStatus(request: TransactionStatusRequest): Promise<TransactionStatusResponse>;
1125
1098
  /**
1126
1099
  * Queries the balance of your M-PESA shortcode.
@@ -1220,8 +1193,8 @@ interface MpesaExpressConfig extends MpesaConfig {
1220
1193
  c2bShortCode?: string;
1221
1194
  c2bConfirmationUrl?: string;
1222
1195
  c2bValidationUrl?: string;
1223
- c2bResponseType?: "Completed" | "Cancelled";
1224
- c2bApiVersion?: "v1" | "v2";
1196
+ c2bResponseType?: 'Completed' | 'Cancelled';
1197
+ c2bApiVersion?: 'v1' | 'v2';
1225
1198
  onC2BValidation?: (payload: C2BValidationPayload) => Promise<C2BValidationResponse> | C2BValidationResponse;
1226
1199
  onC2BConfirmation?: (payload: C2BConfirmationPayload) => Promise<void> | void;
1227
1200
  taxPartyA?: string;
@@ -1318,5 +1291,4 @@ declare function createMpesaExpressClient(config: MpesaExpressConfig): {
1318
1291
  */
1319
1292
  declare function createMpesaExpressRouter(router: Router, config: MpesaExpressConfig): Router;
1320
1293
  //#endregion
1321
- export { MpesaExpressConfig, createMpesaExpressClient, createMpesaExpressRouter };
1322
- //# sourceMappingURL=index.d.cts.map
1294
+ export { MpesaExpressConfig, createMpesaExpressClient, createMpesaExpressRouter };
@@ -1,3 +1,4 @@
1
+ import { n as MpesaConfig, t as Environment } from "../types.js";
1
2
  import { Router } from "express";
2
3
 
3
4
  //#region src/mpesa/stk-push/types.d.ts
@@ -13,7 +14,7 @@ import { Router } from "express";
13
14
  * CustomerPayBillOnline → Paybill numbers (PartyB = shortcode)
14
15
  * CustomerBuyGoodsOnline → Till numbers (PartyB = till number)
15
16
  */
16
- type TransactionType = "CustomerPayBillOnline" | "CustomerBuyGoodsOnline";
17
+ type TransactionType = 'CustomerPayBillOnline' | 'CustomerBuyGoodsOnline';
17
18
  interface StkPushRequest {
18
19
  /** Transaction amount (minimum KES 1, must round to a whole number ≥ 1) */
19
20
  amount: number;
@@ -138,7 +139,7 @@ interface TransactionStatusRequest {
138
139
  * "4" = Organisation ShortCode (Paybill / B2C) ← most common
139
140
  * Daraja field: IdentifierType
140
141
  */
141
- identifierType: "1" | "2" | "4";
142
+ identifierType: '1' | '2' | '4';
142
143
  /**
143
144
  * URL where Safaricom POSTs the final result.
144
145
  * Must be publicly accessible.
@@ -168,34 +169,6 @@ interface TransactionStatusResponse {
168
169
  OriginatorConversationID?: string;
169
170
  }
170
171
  //#endregion
171
- //#region src/mpesa/types.d.ts
172
- /**
173
- * Core M-Pesa / Daraja configuration types
174
- */
175
- type Environment = "sandbox" | "production";
176
- interface MpesaConfig {
177
- consumerKey: string;
178
- consumerSecret: string;
179
- environment: Environment;
180
- lipaNaMpesaShortCode?: string;
181
- lipaNaMpesaPassKey?: string;
182
- initiatorName?: string;
183
- initiatorPassword?: string;
184
- certificatePath?: string;
185
- certificatePem?: string;
186
- /**
187
- * Pre-computed base64 SecurityCredential — skips RSA encryption.
188
- * Use when you encrypt at startup outside the library.
189
- */
190
- securityCredential?: string;
191
- /** Override default retry count (4) for all API calls */
192
- retries?: number;
193
- /** Override default base retry delay in ms (2000) */
194
- retryDelay?: number;
195
- /** Override default per-request timeout in ms (30000) */
196
- timeout?: number;
197
- }
198
- //#endregion
199
172
  //#region src/utils/errors/index.d.ts
200
173
  /**
201
174
  * Pesafy error types — single source of truth.
@@ -288,7 +261,7 @@ interface AccountBalanceRequest {
288
261
  * "4" = Organisation ShortCode (most common — Paybill/B2C)
289
262
  * Daraja field: IdentifierType
290
263
  */
291
- identifierType: "1" | "2" | "4";
264
+ identifierType: '1' | '2' | '4';
292
265
  /**
293
266
  * URL where Safaricom POSTs the balance result.
294
267
  * Must be publicly accessible. HTTPS required in production.
@@ -447,7 +420,7 @@ type B2BExpressCheckoutCallback = B2BExpressCheckoutCallbackSuccess | B2BExpress
447
420
  * PromotionPayment — Payment of promotions/bonuses
448
421
  * BusinessPayToBulk — Load funds to a B2C shortcode for bulk disbursement
449
422
  */
450
- type B2CCommandID = "BusinessPayment" | "SalaryPayment" | "PromotionPayment" | "BusinessPayToBulk";
423
+ type B2CCommandID = 'BusinessPayment' | 'SalaryPayment' | 'PromotionPayment' | 'BusinessPayToBulk';
451
424
  interface B2CRequest {
452
425
  /**
453
426
  * The type of transaction. Use "BusinessPayToBulk" for account top-up.
@@ -485,14 +458,14 @@ interface B2CRequest {
485
458
  * Daraja field: SenderIdentifierType
486
459
  * Default: "4"
487
460
  */
488
- senderIdentifierType?: "4";
461
+ senderIdentifierType?: '4';
489
462
  /**
490
463
  * Type of the receiver (PartyB) identifier.
491
464
  * For this API, only "4" (Organisation ShortCode) is allowed.
492
465
  * Daraja field: RecieverIdentifierType
493
466
  * Default: "4"
494
467
  */
495
- receiverIdentifierType?: "4";
468
+ receiverIdentifierType?: '4';
496
469
  /**
497
470
  * A reference for this transaction (e.g. invoice number, batch reference).
498
471
  * Daraja field: AccountReference
@@ -540,7 +513,7 @@ interface B2CResponse {
540
513
  * - Any unknown future key Daraja may return is still accepted.
541
514
  * - The `no-redundant-type-constituents` ESLint rule is not triggered.
542
515
  */
543
- type B2CResultParameterKey = "DebitAccountBalance" | "Amount" | "DebitPartyAffectedAccountBalance" | "TransCompletedTime" | "DebitPartyCharges" | "ReceiverPartyPublicName" | "Currency" | "InitiatorAccountCurrentBalance" | "B2CRecipientIsRegisteredCustomer" | "B2CChargesPaidAccountAvailableFunds" | "B2CWorkingAccountAvailableFunds" | "B2CUtilityAccountAvailableFunds" | (string & {});
516
+ type B2CResultParameterKey = 'DebitAccountBalance' | 'Amount' | 'DebitPartyAffectedAccountBalance' | 'TransCompletedTime' | 'DebitPartyCharges' | 'ReceiverPartyPublicName' | 'Currency' | 'InitiatorAccountCurrentBalance' | 'B2CRecipientIsRegisteredCustomer' | 'B2CChargesPaidAccountAvailableFunds' | 'B2CWorkingAccountAvailableFunds' | 'B2CUtilityAccountAvailableFunds' | (string & {});
544
517
  interface B2CResultParameter {
545
518
  Key: B2CResultParameterKey;
546
519
  Value: string | number;
@@ -592,7 +565,7 @@ interface BillManagerOptInRequest {
592
565
  /** Your logo URL (public HTTPS link) */
593
566
  officialContact: string;
594
567
  /** Sender name shown on push notifications */
595
- sendReminders: "1" | "0";
568
+ sendReminders: '1' | '0';
596
569
  /** Logo URL */
597
570
  logo?: string;
598
571
  /** Callback URL for payment confirmations */
@@ -660,7 +633,7 @@ interface BillManagerCancelInvoiceResponse {
660
633
  * Ref: Customer To Business (C2B) — Daraja Developer Portal
661
634
  */
662
635
  /** C2B API version. v2 is recommended; v1 sends SHA256-hashed MSISDN. */
663
- type C2BApiVersion = "v1" | "v2";
636
+ type C2BApiVersion = 'v1' | 'v2';
664
637
  /**
665
638
  * What M-PESA should do if your Validation URL is unreachable or times out.
666
639
  * "Completed" — M-PESA automatically completes the transaction.
@@ -669,7 +642,7 @@ type C2BApiVersion = "v1" | "v2";
669
642
  * NOTE: Must be exactly "Completed" or "Cancelled" (sentence case, no typos).
670
643
  * Daraja docs: "the words Cancelled/Completed must be in sentence case and well-spelled."
671
644
  */
672
- type C2BResponseType = "Completed" | "Cancelled";
645
+ type C2BResponseType = 'Completed' | 'Cancelled';
673
646
  interface C2BRegisterUrlRequest {
674
647
  /**
675
648
  * Your M-PESA Paybill or Till shortcode.
@@ -716,7 +689,7 @@ interface C2BRegisterUrlResponse {
716
689
  *
717
690
  * NOTE: Simulation is ONLY supported in Sandbox, NOT in production.
718
691
  */
719
- type C2BCommandID = "CustomerPayBillOnline" | "CustomerBuyGoodsOnline";
692
+ type C2BCommandID = 'CustomerPayBillOnline' | 'CustomerBuyGoodsOnline';
720
693
  interface C2BSimulateRequest {
721
694
  /**
722
695
  * Your M-PESA Paybill or Till shortcode.
@@ -808,7 +781,7 @@ interface C2BValidationPayload {
808
781
  * "C2B00015" — Invalid Short code
809
782
  * "C2B00016" — Other Error
810
783
  */
811
- type C2BValidationResultCode = "0" | "C2B00011" | "C2B00012" | "C2B00013" | "C2B00014" | "C2B00015" | "C2B00016";
784
+ type C2BValidationResultCode = '0' | 'C2B00011' | 'C2B00012' | 'C2B00013' | 'C2B00014' | 'C2B00015' | 'C2B00016';
812
785
  interface C2BValidationResponse {
813
786
  /**
814
787
  * "0" = Accept the transaction.
@@ -822,7 +795,7 @@ interface C2BValidationResponse {
822
795
  * "Accepted" when ResultCode is "0".
823
796
  * "Rejected" when ResultCode is a non-zero error code.
824
797
  */
825
- ResultDesc: "Accepted" | "Rejected";
798
+ ResultDesc: 'Accepted' | 'Rejected';
826
799
  /**
827
800
  * Optional. If set, this value is echoed back in the Confirmation callback
828
801
  * as ThirdPartyTransID. Useful for correlating validation → confirmation.
@@ -883,7 +856,7 @@ interface C2BConfirmationPayload {
883
856
  * SM: Send Money (Mobile number)
884
857
  * SB: Sent to Business (Business number CPI in MSISDN format)
885
858
  */
886
- type QRTransactionCode = "BG" | "WA" | "PB" | "SM" | "SB";
859
+ type QRTransactionCode = 'BG' | 'WA' | 'PB' | 'SM' | 'SB';
887
860
  interface DynamicQRRequest {
888
861
  /**
889
862
  * Name of the Company / M-Pesa Merchant Name.
@@ -975,7 +948,7 @@ interface ReversalRequest {
975
948
  * "4" = Organisation ShortCode
976
949
  * Daraja field: RecieverIdentifierType
977
950
  */
978
- receiverIdentifierType: "1" | "2" | "4";
951
+ receiverIdentifierType: '1' | '2' | '4';
979
952
  /**
980
953
  * URL where Safaricom POSTs the reversal result.
981
954
  * Daraja field: ResultURL
@@ -1077,7 +1050,7 @@ interface TaxRemittanceResponse {
1077
1050
  * - Any unknown future key Daraja may return is still accepted.
1078
1051
  * - The `no-redundant-type-constituents` ESLint rule is not triggered.
1079
1052
  */
1080
- type TaxRemittanceResultParameterKey = "DebitAccountBalance" | "Amount" | "DebitPartyAffectedAccountBalance" | "TransCompletedTime" | "DebitPartyCharges" | "ReceiverPartyPublicName" | "Currency" | "InitiatorAccountCurrentBalance" | (string & {});
1053
+ type TaxRemittanceResultParameterKey = 'DebitAccountBalance' | 'Amount' | 'DebitPartyAffectedAccountBalance' | 'TransCompletedTime' | 'DebitPartyCharges' | 'ReceiverPartyPublicName' | 'Currency' | 'InitiatorAccountCurrentBalance' | (string & {});
1081
1054
  interface TaxRemittanceResultParameter {
1082
1055
  Key: TaxRemittanceResultParameterKey;
1083
1056
  Value: string | number;
@@ -1118,9 +1091,9 @@ declare class Mpesa {
1118
1091
  * Like stkPush() but returns Result<T> instead of throwing.
1119
1092
  * Ideal for application-level code that prefers not to use try/catch.
1120
1093
  */
1121
- stkPushSafe(request: Omit<StkPushRequest, "shortCode" | "passKey">): Promise<Result<Awaited<ReturnType<typeof this.stkPush>>>>;
1122
- stkPush(request: Omit<StkPushRequest, "shortCode" | "passKey">): Promise<StkPushResponse>;
1123
- stkQuery(request: Omit<StkQueryRequest, "shortCode" | "passKey">): Promise<StkQueryResponse>;
1094
+ stkPushSafe(request: Omit<StkPushRequest, 'shortCode' | 'passKey'>): Promise<Result<Awaited<ReturnType<typeof this.stkPush>>>>;
1095
+ stkPush(request: Omit<StkPushRequest, 'shortCode' | 'passKey'>): Promise<StkPushResponse>;
1096
+ stkQuery(request: Omit<StkQueryRequest, 'shortCode' | 'passKey'>): Promise<StkQueryResponse>;
1124
1097
  transactionStatus(request: TransactionStatusRequest): Promise<TransactionStatusResponse>;
1125
1098
  /**
1126
1099
  * Queries the balance of your M-PESA shortcode.
@@ -1220,8 +1193,8 @@ interface MpesaExpressConfig extends MpesaConfig {
1220
1193
  c2bShortCode?: string;
1221
1194
  c2bConfirmationUrl?: string;
1222
1195
  c2bValidationUrl?: string;
1223
- c2bResponseType?: "Completed" | "Cancelled";
1224
- c2bApiVersion?: "v1" | "v2";
1196
+ c2bResponseType?: 'Completed' | 'Cancelled';
1197
+ c2bApiVersion?: 'v1' | 'v2';
1225
1198
  onC2BValidation?: (payload: C2BValidationPayload) => Promise<C2BValidationResponse> | C2BValidationResponse;
1226
1199
  onC2BConfirmation?: (payload: C2BConfirmationPayload) => Promise<void> | void;
1227
1200
  taxPartyA?: string;
@@ -1318,5 +1291,4 @@ declare function createMpesaExpressClient(config: MpesaExpressConfig): {
1318
1291
  */
1319
1292
  declare function createMpesaExpressRouter(router: Router, config: MpesaExpressConfig): Router;
1320
1293
  //#endregion
1321
- export { MpesaExpressConfig, createMpesaExpressClient, createMpesaExpressRouter };
1322
- //# sourceMappingURL=index.d.mts.map
1294
+ export { MpesaExpressConfig, createMpesaExpressClient, createMpesaExpressRouter };
@@ -0,0 +1 @@
1
+ import{i as e,n as t,r as n}from"../signature-verifier.js";import{a as r,i,n as a,r as o,t as s}from"../webhook-handler.js";function c(e){return e.resultCode===`0`}function l(e){return e.resultCode===`4001`}function u(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 d(e){return c(e)?e.transactionId??null:null}function f(e){return Number(e.amount)}function p(e){return c(e)?e.conversationID??null:null}function m(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 h(e){return e.Result.ResultCode===0}function g(e){return e.Result.TransactionID??null}function _(e){return e.Result.ConversationID}function v(e){return e.Result.OriginatorConversationID}function y(e){let t=b(e,`Amount`);return t===void 0?null:Number(t)}function b(e,t){let n=e.Result.ResultParameters?.ResultParameter;if(n)return(Array.isArray(n)?n:[n]).find(e=>e.Key===t)?.Value}function x(e){return{ResultCode:`0`,ResultDesc:`Accepted`,...e?{ThirdPartyTransID:e}:{}}}function S(t){if(!t.consumerKey||!t.consumerSecret)throw new e({code:`INVALID_CREDENTIALS`,message:`consumerKey and consumerSecret are required`});if(!t.lipaNaMpesaShortCode||!t.lipaNaMpesaPassKey)throw new e({code:`VALIDATION_ERROR`,message:`lipaNaMpesaShortCode and lipaNaMpesaPassKey are required for STK Push`});if(!t.callbackUrl)throw new e({code:`VALIDATION_ERROR`,message:`callbackUrl is required for STK Push callbacks`});return{mpesa:new n(t)}}function C(t,n){if(n instanceof e){let e=n.statusCode??400;t.status(e).json({error:n.code,message:n.message,statusCode:e});return}t.status(500).json({error:`REQUEST_FAILED`,message:`An unexpected error occurred while processing the M-Pesa request`})}function w(e){return e.headers[`x-forwarded-for`]?.split(`,`)[0]?.trim()??e.ip??``}function T(n,b){let{mpesa:T}=S(b);return n.post(`/mpesa/express/stk-push`,async(t,n,r)=>{try{let r=t.body;if(!r||typeof r.amount!=`number`||r.amount<=0)throw new e({code:`VALIDATION_ERROR`,message:`amount must be a positive number`});if(!r.phoneNumber)throw new e({code:`VALIDATION_ERROR`,message:`phoneNumber is required`});let i=await T.stkPush({amount:r.amount,phoneNumber:r.phoneNumber,callbackUrl:b.callbackUrl,accountReference:r.accountReference??`PESAFY-${Date.now().toString(36).toUpperCase()}`,transactionDesc:r.transactionDesc??`Payment`,transactionType:r.transactionType,partyB:r.partyB});n.status(200).json(i)}catch(e){if(n.headersSent)return r(e);C(n,e)}}),n.post(`/mpesa/express/stk-query`,async(t,n,r)=>{try{let r=t.body;if(!r?.checkoutRequestId)throw new e({code:`VALIDATION_ERROR`,message:`checkoutRequestId is required`});let i=await T.stkQuery({checkoutRequestId:r.checkoutRequestId});n.status(200).json(i)}catch(e){if(n.headersSent)return r(e);C(n,e)}}),n.post(`/mpesa/express/callback`,(e,t)=>{let n=w(e),c=i(e.body,{requestIP:n,skipIPCheck:b.skipIPCheck});if(!c.success)return console.error(`[pesafy] STK Push webhook rejected:`,c.error),t.status(200).json({ResultCode:0,ResultDesc:`Accepted`});let l=c.data;r(l)?console.info(`[pesafy] STK Push success:`,{receiptNumber:o(l),amount:s(l),phone:a(l)}):console.warn(`[pesafy] STK Push failed:`,{resultCode:l.Body.stkCallback.ResultCode,resultDesc:l.Body.stkCallback.ResultDesc}),t.status(200).json({ResultCode:0,ResultDesc:`Accepted`})}),n.post(`/mpesa/transaction-status/query`,async(t,n,r)=>{try{if(!b.resultUrl||!b.queueTimeOutUrl)throw new e({code:`VALIDATION_ERROR`,message:`resultUrl and queueTimeOutUrl must be set in config to use transaction status routes`});let r=t.body;if(!r?.transactionId)throw new e({code:`VALIDATION_ERROR`,message:`transactionId is required`});if(!r.partyA)throw new e({code:`VALIDATION_ERROR`,message:`partyA is required`});if(!r.identifierType)throw new e({code:`VALIDATION_ERROR`,message:`identifierType is required: "1" | "2" | "4"`});let i=await T.transactionStatus({transactionId:r.transactionId,partyA:r.partyA,identifierType:r.identifierType,resultUrl:b.resultUrl,queueTimeOutUrl:b.queueTimeOutUrl,remarks:r.remarks,occasion:r.occasion});n.status(200).json(i)}catch(e){if(n.headersSent)return r(e);C(n,e)}}),n.post(`/mpesa/transaction-status/result`,(e,t)=>{let n=e.body?.Result;n&&(n.ResultCode===0?console.info(`[pesafy] Transaction Status result (success):`,{transactionId:n.TransactionID,conversationId:n.ConversationID,resultDesc:n.ResultDesc}):console.warn(`[pesafy] Transaction Status result (failed):`,{resultCode:n.ResultCode,resultDesc:n.ResultDesc,transactionId:n.TransactionID})),t.status(200).json({ResultCode:0,ResultDesc:`Accepted`})}),n.post(`/mpesa/c2b/register-url`,async(t,n,r)=>{try{let r=t.body,i=r.shortCode??b.c2bShortCode,a=r.confirmationUrl??b.c2bConfirmationUrl,o=r.validationUrl??b.c2bValidationUrl,s=r.responseType??b.c2bResponseType??`Completed`,c=r.apiVersion??b.c2bApiVersion??`v2`;if(!i)throw new e({code:`VALIDATION_ERROR`,message:`shortCode is required`});if(!a)throw new e({code:`VALIDATION_ERROR`,message:`confirmationUrl is required`});if(!o)throw new e({code:`VALIDATION_ERROR`,message:`validationUrl is required`});let l=await T.registerC2BUrls({shortCode:i,responseType:s,confirmationUrl:a,validationUrl:o,apiVersion:c});n.status(200).json(l)}catch(e){if(n.headersSent)return r(e);C(n,e)}}),n.post(`/mpesa/c2b/simulate`,async(t,n,r)=>{try{let r=t.body;if(!r?.commandId)throw new e({code:`VALIDATION_ERROR`,message:`commandId is required: "CustomerPayBillOnline" | "CustomerBuyGoodsOnline"`});if(!r.amount||r.amount<=0)throw new e({code:`VALIDATION_ERROR`,message:`amount must be a positive number`});if(!r.msisdn)throw new e({code:`VALIDATION_ERROR`,message:`msisdn is required`});let i=await T.simulateC2B({shortCode:r.shortCode??b.c2bShortCode??``,commandId:r.commandId,amount:r.amount,msisdn:r.msisdn,billRefNumber:r.billRefNumber,apiVersion:b.c2bApiVersion??`v2`});n.status(200).json(i)}catch(e){if(n.headersSent)return r(e);C(n,e)}}),n.post(`/mpesa/c2b/validation`,async(e,n)=>{if(!b.skipIPCheck){let r=w(e);if(!t(r))return console.error(`[pesafy] C2B validation rejected — IP not in Safaricom whitelist:`,r),n.status(200).json({ResultCode:`0`,ResultDesc:`Accepted`})}let r=e.body;try{let e;return e=b.onC2BValidation?await b.onC2BValidation(r):x(),console.info(`[pesafy] C2B validation response:`,{transactionId:r.TransID,amount:r.TransAmount,billRef:r.BillRefNumber,resultCode:e.ResultCode}),n.status(200).json(e)}catch(e){return console.error(`[pesafy] C2B validation hook threw an error:`,e),n.status(200).json({ResultCode:`0`,ResultDesc:`Accepted`})}}),n.post(`/mpesa/c2b/confirmation`,(e,t)=>{let n=e.body;console.info(`[pesafy] C2B confirmation received:`,{transactionId:n.TransID,amount:n.TransAmount,shortCode:n.BusinessShortCode,billRef:n.BillRefNumber,transactionType:n.TransactionType,transTime:n.TransTime,balance:n.OrgAccountBalance}),b.onC2BConfirmation&&Promise.resolve(b.onC2BConfirmation(n)).catch(e=>{console.error(`[pesafy] C2B confirmation hook error:`,e)}),t.status(200).json({ResultCode:0,ResultDesc:`Success`})}),n.post(`/mpesa/tax/remit`,async(t,n,r)=>{try{if(!b.taxResultUrl||!b.taxQueueTimeOutUrl)throw new e({code:`VALIDATION_ERROR`,message:`taxResultUrl and taxQueueTimeOutUrl must be set in config to use tax remittance routes`});let r=t.body;if(!r||typeof r.amount!=`number`||r.amount<=0)throw new e({code:`VALIDATION_ERROR`,message:`amount must be a positive number`});if(!r.accountReference)throw new e({code:`VALIDATION_ERROR`,message:`accountReference is required — the KRA PRN`});let i=r.partyA??b.taxPartyA??``;if(!i)throw new e({code:`VALIDATION_ERROR`,message:`partyA is required — set taxPartyA in config or provide in request body`});let a=await T.remitTax({amount:r.amount,partyA:i,accountReference:r.accountReference,resultUrl:b.taxResultUrl,queueTimeOutUrl:b.taxQueueTimeOutUrl,remarks:r.remarks});n.status(200).json(a)}catch(e){if(n.headersSent)return r(e);C(n,e)}}),n.post(`/mpesa/tax/result`,(e,t)=>{let n=e.body,r=n?.Result;r&&(r.ResultCode===0?console.info(`[pesafy] Tax Remittance result (success):`,{transactionId:r.TransactionID,conversationId:r.ConversationID,resultDesc:r.ResultDesc}):console.warn(`[pesafy] Tax Remittance result (failed):`,{resultCode:r.ResultCode,resultDesc:r.ResultDesc,transactionId:r.TransactionID})),b.onTaxRemittanceResult&&n&&Promise.resolve(b.onTaxRemittanceResult(n)).catch(e=>{console.error(`[pesafy] Tax Remittance result hook error:`,e)}),t.status(200).json({ResultCode:0,ResultDesc:`Accepted`})}),n.post(`/mpesa/b2b/checkout`,async(t,n,r)=>{try{let r=t.body;if(!r?.primaryShortCode)throw new e({code:`VALIDATION_ERROR`,message:`primaryShortCode is required`});if(!r.amount||r.amount<=0)throw new e({code:`VALIDATION_ERROR`,message:`amount must be a positive number`});if(!r.paymentRef)throw new e({code:`VALIDATION_ERROR`,message:`paymentRef is required`});if(!r.partnerName)throw new e({code:`VALIDATION_ERROR`,message:`partnerName is required`});let i=r.receiverShortCode??b.b2bReceiverShortCode??``;if(!i)throw new e({code:`VALIDATION_ERROR`,message:`receiverShortCode is required — set b2bReceiverShortCode in config or provide in request body`});let a=r.callbackUrl??b.b2bCallbackUrl??``;if(!a)throw new e({code:`VALIDATION_ERROR`,message:`callbackUrl is required — set b2bCallbackUrl in config or provide in request body`});let o=await T.b2bExpressCheckout({primaryShortCode:r.primaryShortCode,receiverShortCode:i,amount:r.amount,paymentRef:r.paymentRef,callbackUrl:a,partnerName:r.partnerName,requestRefId:r.requestRefId});n.status(200).json(o)}catch(e){if(n.headersSent)return r(e);C(n,e)}}),n.post(`/mpesa/b2b/callback`,(e,t)=>{let n=e.body;if(!u(n))return console.error(`[pesafy] B2B callback received unrecognised payload:`,JSON.stringify(n).slice(0,200)),t.status(200).json({ResultCode:0,ResultDesc:`Accepted`});let r=n;c(r)?console.info(`[pesafy] B2B Express Checkout success:`,{transactionId:d(r),conversationId:p(r),amount:f(r),requestId:r.requestId,status:r.status}):l(r)?console.warn(`[pesafy] B2B Express Checkout cancelled by merchant:`,{resultCode:r.resultCode,resultDesc:r.resultDesc,requestId:r.requestId,amount:f(r)}):console.warn(`[pesafy] B2B Express Checkout unknown result:`,{resultCode:r.resultCode,resultDesc:r.resultDesc}),b.onB2BCheckoutCallback&&Promise.resolve(b.onB2BCheckoutCallback(r)).catch(e=>{console.error(`[pesafy] B2B callback hook error:`,e)}),t.status(200).json({ResultCode:0,ResultDesc:`Accepted`})}),n.post(`/mpesa/b2c/payment`,async(t,n,r)=>{try{if(!b.b2cResultUrl||!b.b2cQueueTimeOutUrl)throw new e({code:`VALIDATION_ERROR`,message:`b2cResultUrl and b2cQueueTimeOutUrl must be set in config to use B2C routes`});let r=t.body;if(!r?.commandId)throw new e({code:`VALIDATION_ERROR`,message:`commandId is required: "BusinessPayToBulk" | "BusinessPayment" | "SalaryPayment" | "PromotionPayment"`});if(!r.amount||r.amount<=0)throw new e({code:`VALIDATION_ERROR`,message:`amount must be a positive number`});if(!r.partyB)throw new e({code:`VALIDATION_ERROR`,message:`partyB is required — the recipient shortcode (BusinessPayToBulk) or customer MSISDN`});if(!r.accountReference)throw new e({code:`VALIDATION_ERROR`,message:`accountReference is required`});let i=r.partyA??b.b2cPartyA??``;if(!i)throw new e({code:`VALIDATION_ERROR`,message:`partyA is required — set b2cPartyA in config or provide in request body`});let a=await T.b2cPayment({commandId:r.commandId,amount:r.amount,partyA:i,partyB:r.partyB,accountReference:r.accountReference,requester:r.requester,remarks:r.remarks,resultUrl:r.resultUrl??b.b2cResultUrl,queueTimeOutUrl:r.queueTimeOutUrl??b.b2cQueueTimeOutUrl});n.status(200).json(a)}catch(e){if(n.headersSent)return r(e);C(n,e)}}),n.post(`/mpesa/b2c/result`,(e,t)=>{let n=e.body;if(!m(n))return console.error(`[pesafy] B2C result received unrecognised payload:`,JSON.stringify(n).slice(0,200)),t.status(200).json({ResultCode:0,ResultDesc:`Accepted`});let r=n;h(r)?console.info(`[pesafy] B2C payment result (success):`,{transactionId:g(r),conversationId:_(r),originatorConversationId:v(r),amount:y(r),resultDesc:r.Result.ResultDesc}):console.warn(`[pesafy] B2C payment result (failed):`,{resultCode:r.Result.ResultCode,resultDesc:r.Result.ResultDesc,transactionId:r.Result.TransactionID,conversationId:_(r),originatorConversationId:v(r)}),b.onB2CResult&&Promise.resolve(b.onB2CResult(r)).catch(e=>{console.error(`[pesafy] B2C result hook error:`,e)}),t.status(200).json({ResultCode:0,ResultDesc:`Accepted`})}),n}export{S as createMpesaExpressClient,T as createMpesaExpressRouter};