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.
- package/CHANGELOG.md +6 -0
- package/LICENSE +21 -0
- package/dist/adapters/express.cjs +1 -0
- package/dist/{express/index.d.cts → adapters/express.d.cts} +23 -51
- package/dist/{express/index.d.mts → adapters/express.d.ts} +23 -51
- package/dist/adapters/express.js +1 -0
- package/dist/adapters/fastify.cjs +1 -1607
- package/dist/adapters/fastify.d.cts +2 -30
- package/dist/adapters/fastify.d.ts +21 -0
- package/dist/adapters/fastify.js +1 -0
- package/dist/adapters/hono.cjs +1 -1651
- package/dist/adapters/hono.d.cts +2 -30
- package/dist/adapters/hono.d.ts +27 -0
- package/dist/adapters/hono.js +1 -0
- package/dist/adapters/nextjs.cjs +1 -1655
- package/dist/adapters/nextjs.d.cts +3 -30
- package/dist/adapters/{nextjs.d.mts → nextjs.d.ts} +3 -30
- package/dist/adapters/nextjs.js +1 -0
- package/dist/{cli/index.mjs → cli.js} +10 -9
- package/dist/encryption.js +22 -0
- package/dist/{cli/errors-DL4bkMZV.mjs → errors.js} +2 -1
- package/dist/index.cjs +1 -2124
- package/dist/index.d.cts +37 -64
- package/dist/{index.d.mts → index.d.ts} +37 -64
- package/dist/index.js +1 -0
- package/dist/{cli/phone-5wwAaQ_8.mjs → phone.js} +4 -2
- package/dist/signature-verifier.cjs +1 -0
- package/dist/signature-verifier.js +1 -0
- package/dist/{adapters/fastify.d.mts → types.d.cts} +2 -22
- package/dist/types.d.ts +29 -0
- package/dist/{cli/utils-BzEKV3nJ.mjs → utils.js} +4 -2
- package/dist/webhook-handler.cjs +1 -0
- package/dist/webhook-handler.js +1 -0
- package/package.json +93 -48
- package/dist/adapters/fastify.cjs.map +0 -1
- package/dist/adapters/fastify.mjs +0 -1606
- package/dist/adapters/fastify.mjs.map +0 -1
- package/dist/adapters/hono.cjs.map +0 -1
- package/dist/adapters/hono.d.mts +0 -55
- package/dist/adapters/hono.mjs +0 -1650
- package/dist/adapters/hono.mjs.map +0 -1
- package/dist/adapters/nextjs.cjs.map +0 -1
- package/dist/adapters/nextjs.mjs +0 -1651
- package/dist/adapters/nextjs.mjs.map +0 -1
- package/dist/cli/encryption-BA-_xrIW.mjs +0 -45
- package/dist/cli/encryption-CkSveeYj.cjs +0 -45
- package/dist/cli/errors-Bscvlb7X.cjs +0 -45
- package/dist/cli/index.cjs +0 -559
- package/dist/cli/phone-BD4QmEyl.cjs +0 -21
- package/dist/cli/utils-Dg9Gv_D3.cjs +0 -31
- package/dist/components/react/index.d.mts +0 -1
- package/dist/components/react/index.mjs +0 -1
- package/dist/express/index.cjs +0 -2201
- package/dist/express/index.cjs.map +0 -1
- package/dist/express/index.mjs +0 -2199
- package/dist/express/index.mjs.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.mjs +0 -2050
- package/dist/index.mjs.map +0 -1
- /package/dist/{components/react/index.d.cts → react/index.d.ts} +0 -0
- /package/dist/{components/react/index.cjs → react/index.js} +0 -0
package/CHANGELOG.md
CHANGED
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 =
|
|
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:
|
|
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:
|
|
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 =
|
|
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?:
|
|
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?:
|
|
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 =
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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:
|
|
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 =
|
|
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:
|
|
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 =
|
|
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,
|
|
1122
|
-
stkPush(request: Omit<StkPushRequest,
|
|
1123
|
-
stkQuery(request: Omit<StkQueryRequest,
|
|
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?:
|
|
1224
|
-
c2bApiVersion?:
|
|
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 =
|
|
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:
|
|
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:
|
|
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 =
|
|
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?:
|
|
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?:
|
|
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 =
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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:
|
|
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 =
|
|
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:
|
|
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 =
|
|
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,
|
|
1122
|
-
stkPush(request: Omit<StkPushRequest,
|
|
1123
|
-
stkQuery(request: Omit<StkQueryRequest,
|
|
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?:
|
|
1224
|
-
c2bApiVersion?:
|
|
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};
|