pesafy 0.5.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # pesafy
2
2
 
3
+ ## 0.5.2
4
+
5
+ ### Patch Changes
6
+
7
+ - migrate from cjs/esm to only esm
8
+
3
9
  ## 0.5.1
4
10
 
5
11
  ### Patch Changes
@@ -40,14 +46,20 @@
40
46
 
41
47
  ### Patch Changes
42
48
 
43
- - C2B URL registration panel with per-shortcode tracking (Paybill + Buy Goods), dual registration support, and session-persisted status badges"
49
+ - C2B URL registration panel with per-shortcode tracking (Paybill + Buy Goods),
50
+ dual registration support, and session-persisted status badges"
44
51
 
45
52
  ## 0.3.12
46
53
 
47
54
  ### Patch Changes
48
55
 
49
- - 047c30f: The payload was always sending BillRefNumber: request.billRefNumber ?? "". For CustomerBuyGoodsOnline, Daraja treats any BillRefNumber field — even "" — as an invalid AccountReference and rejects the request (400 or 503). The fix conditionally includes the field only for Paybill
50
- - fix error handling and URLs only registered for 600977, never for 600000. The log shows registerC2BUrls shortCode=600977 but then simulateC2B shortCode=600000
56
+ - 047c30f: The payload was always sending BillRefNumber: request.billRefNumber
57
+ ?? "". For CustomerBuyGoodsOnline, Daraja treats any BillRefNumber field
58
+ even "" — as an invalid AccountReference and rejects the request (400 or 503).
59
+ The fix conditionally includes the field only for Paybill
60
+ - fix error handling and URLs only registered for 600977, never for 600000. The
61
+ log shows registerC2BUrls shortCode=600977 but then simulateC2B
62
+ shortCode=600000
51
63
 
52
64
  ## 0.3.11
53
65
 
@@ -59,7 +71,17 @@
59
71
 
60
72
  ### Patch Changes
61
73
 
62
- - Added Dynamic QR Code support to the pesafy SDK as a new mpesa/dynamic-qr module. This introduces three new files — types.ts, generate.ts, and index.ts — under src/mpesa/dynamic-qr/, implementing the Safaricom Daraja POST /mpesa/qrcode/v1/generate endpoint. The Mpesa class gains a new generateDynamicQR() method that handles token retrieval and delegates to the internal generateDynamicQR function, which validates all inputs (merchant name, reference, amount, transaction code, CPI, and size) before dispatching the request via the existing httpRequest utility. Three new public types — DynamicQRRequest, DynamicQRResponse, and QRTransactionCode — are exported from the root src/index.ts. No breaking changes were introduced; all existing APIs remain untouched.
74
+ - Added Dynamic QR Code support to the pesafy SDK as a new mpesa/dynamic-qr
75
+ module. This introduces three new files — types.ts, generate.ts, and index.ts
76
+ — under src/mpesa/dynamic-qr/, implementing the Safaricom Daraja POST
77
+ /mpesa/qrcode/v1/generate endpoint. The Mpesa class gains a new
78
+ generateDynamicQR() method that handles token retrieval and delegates to the
79
+ internal generateDynamicQR function, which validates all inputs (merchant
80
+ name, reference, amount, transaction code, CPI, and size) before dispatching
81
+ the request via the existing httpRequest utility. Three new public types —
82
+ DynamicQRRequest, DynamicQRResponse, and QRTransactionCode — are exported from
83
+ the root src/index.ts. No breaking changes were introduced; all existing APIs
84
+ remain untouched.
63
85
 
64
86
  ## 0.3.9
65
87
 
@@ -71,7 +93,15 @@
71
93
 
72
94
  ### Patch Changes
73
95
 
74
- - Fixed four blocking TypeScript compilation errors before prepublish by extending the `ErrorCode` union to include `API_ERROR`, `NETWORK_ERROR`, and `TIMEOUT`, which resolved type assignment issues in the HTTP layer. Consolidated a duplicate `PesafyError` class (previously split between `index.ts` and `types.ts`) into a single authoritative implementation that preserves all fields, including `requestId`, `toJSON()`, and stack trace handling. Also corrected `x-forwarded-for` IP extraction in the Express webhook handler using safe optional chaining to prevent potential undefined access from the `split()` result.
96
+ - Fixed four blocking TypeScript compilation errors before prepublish by
97
+ extending the `ErrorCode` union to include `API_ERROR`, `NETWORK_ERROR`, and
98
+ `TIMEOUT`, which resolved type assignment issues in the HTTP layer.
99
+ Consolidated a duplicate `PesafyError` class (previously split between
100
+ `index.ts` and `types.ts`) into a single authoritative implementation that
101
+ preserves all fields, including `requestId`, `toJSON()`, and stack trace
102
+ handling. Also corrected `x-forwarded-for` IP extraction in the Express
103
+ webhook handler using safe optional chaining to prevent potential undefined
104
+ access from the `split()` result.
75
105
 
76
106
  ## 0.3.7
77
107
 
@@ -95,7 +125,9 @@
95
125
 
96
126
  ### Patch Changes
97
127
 
98
- - f983f64: The /v2/simulate path is the most critical bug — it will cause persistent 500.003.1001 errors in sandbox just like the /v2/registerurl issue you already fixed.
128
+ - f983f64: The /v2/simulate path is the most critical bug — it will cause
129
+ persistent 500.003.1001 errors in sandbox just like the /v2/registerurl issue
130
+ you already fixed.
99
131
 
100
132
  ## 0.3.3
101
133
 
@@ -113,21 +145,67 @@
113
145
 
114
146
  ### Patch Changes
115
147
 
116
- - **C2B module rewrite & phone utility refactor** — Fixed `simulateC2B` where `CommandID` was hardcoded to `CustomerPayBillOnline`, making `CustomerBuyGoodsOnline` (Till) payments impossible. Corrected `BillRefNumber` logic so the field is omitted entirely for BuyGoods requests rather than defaulting to the string `"default"`, which caused a `400` from Daraja. Fixed `Msisdn` being sent as a quoted string instead of a JSON number as the spec requires. Removed the phantom `ConversationID` field from both `C2BSimulateResponse` and `C2BRegisterUrlResponse` — Daraja never returns this field for C2B, only `OriginatorCoversationID`. Extracted phone formatting into a shared `src/utils/phone.ts` module with two intentionally separate formatters: `formatSafaricomPhone` (strict, Safaricom/Airtel only, for STK Push) and `formatKenyanMsisdn` (permissive, all Kenyan networks, for C2B and B2C), eliminating duplicated logic across modules. Added the `C2BCommandId` union type and full callback payload types — `C2BValidationPayload`, `C2BConfirmationPayload`, and `C2BCallbackPayload` — to enable type-safe webhook handlers.
148
+ - **C2B module rewrite & phone utility refactor** — Fixed `simulateC2B` where
149
+ `CommandID` was hardcoded to `CustomerPayBillOnline`, making
150
+ `CustomerBuyGoodsOnline` (Till) payments impossible. Corrected `BillRefNumber`
151
+ logic so the field is omitted entirely for BuyGoods requests rather than
152
+ defaulting to the string `"default"`, which caused a `400` from Daraja. Fixed
153
+ `Msisdn` being sent as a quoted string instead of a JSON number as the spec
154
+ requires. Removed the phantom `ConversationID` field from both
155
+ `C2BSimulateResponse` and `C2BRegisterUrlResponse` — Daraja never returns this
156
+ field for C2B, only `OriginatorCoversationID`. Extracted phone formatting into
157
+ a shared `src/utils/phone.ts` module with two intentionally separate
158
+ formatters: `formatSafaricomPhone` (strict, Safaricom/Airtel only, for STK
159
+ Push) and `formatKenyanMsisdn` (permissive, all Kenyan networks, for C2B and
160
+ B2C), eliminating duplicated logic across modules. Added the `C2BCommandId`
161
+ union type and full callback payload types — `C2BValidationPayload`,
162
+ `C2BConfirmationPayload`, and `C2BCallbackPayload` — to enable type-safe
163
+ webhook handlers.
117
164
  - fbfa311: fix url patch and add elemt accountref
118
- - aa9c176: **C2B module rewrite & phone utility refactor** — Fixed `simulateC2B` where `CommandID` was hardcoded to `CustomerPayBillOnline`, making `CustomerBuyGoodsOnline` (Till) payments impossible. Corrected `BillRefNumber` logic so the field is omitted entirely for BuyGoods requests rather than defaulting to the string `"default"`, which caused a `400` from Daraja. Fixed `Msisdn` being sent as a quoted string instead of a JSON number as the spec requires. Removed the phantom `ConversationID` field from both `C2BSimulateResponse` and `C2BRegisterUrlResponse` — Daraja never returns this field for C2B, only `OriginatorCoversationID`. Extracted phone formatting into a shared `src/utils/phone.ts` module with two intentionally separate formatters: `formatSafaricomPhone` (strict, Safaricom/Airtel only, for STK Push) and `formatKenyanMsisdn` (permissive, all Kenyan networks, for C2B and B2C), eliminating duplicated logic across modules. Added the `C2BCommandId` union type and full callback payload types — `C2BValidationPayload`, `C2BConfirmationPayload`, and `C2BCallbackPayload` — to enable type-safe webhook handlers.
165
+ - aa9c176: **C2B module rewrite & phone utility refactor** — Fixed `simulateC2B`
166
+ where `CommandID` was hardcoded to `CustomerPayBillOnline`, making
167
+ `CustomerBuyGoodsOnline` (Till) payments impossible. Corrected `BillRefNumber`
168
+ logic so the field is omitted entirely for BuyGoods requests rather than
169
+ defaulting to the string `"default"`, which caused a `400` from Daraja. Fixed
170
+ `Msisdn` being sent as a quoted string instead of a JSON number as the spec
171
+ requires. Removed the phantom `ConversationID` field from both
172
+ `C2BSimulateResponse` and `C2BRegisterUrlResponse` — Daraja never returns this
173
+ field for C2B, only `OriginatorCoversationID`. Extracted phone formatting into
174
+ a shared `src/utils/phone.ts` module with two intentionally separate
175
+ formatters: `formatSafaricomPhone` (strict, Safaricom/Airtel only, for STK
176
+ Push) and `formatKenyanMsisdn` (permissive, all Kenyan networks, for C2B and
177
+ B2C), eliminating duplicated logic across modules. Added the `C2BCommandId`
178
+ union type and full callback payload types — `C2BValidationPayload`,
179
+ `C2BConfirmationPayload`, and `C2BCallbackPayload` — to enable type-safe
180
+ webhook handlers.
119
181
 
120
182
  ## 0.3.0
121
183
 
122
184
  ### Minor Changes
123
185
 
124
- - fe95c2b: The C2B M-Pesa implementation in the pesafy package has been comprehensively overhauled for full Daraja C2B v2 compliance, including a complete rewrite of types.ts with all 13 callback fields (C2BCallbackPayload), validation responses, rejection codes, and command/response unions; fixes to register-url.ts ensuring ValidationURL is always sent (defaulting to confirmationUrl); enhanced simulate.ts supporting both CustomerPayBillOnline and CustomerBuyGoodsOnline with proper BillRefNumber handling, amount validation, and Msisdn casting; new c2b index exports; root index public exports; and improved http client error reporting. Dashboard updates add c2bRegisterUrls and c2bSimulate actions in mpesaActions.ts, fix http.ts webhook handlers with 6 missing fields (TransactionType, TransTime, etc.), upsert logic via createTransaction, a new getByShortCode query in businesses.ts, and a complete C2B tab in PaymentsPage.tsx featuring URL registration, simulation forms, and unified transactions—addressing prior gaps like hardcoded commands, missing UI, incomplete types, and non-upserting handlers.
186
+ - fe95c2b: The C2B M-Pesa implementation in the pesafy package has been
187
+ comprehensively overhauled for full Daraja C2B v2 compliance, including a
188
+ complete rewrite of types.ts with all 13 callback fields (C2BCallbackPayload),
189
+ validation responses, rejection codes, and command/response unions; fixes to
190
+ register-url.ts ensuring ValidationURL is always sent (defaulting to
191
+ confirmationUrl); enhanced simulate.ts supporting both CustomerPayBillOnline
192
+ and CustomerBuyGoodsOnline with proper BillRefNumber handling, amount
193
+ validation, and Msisdn casting; new c2b index exports; root index public
194
+ exports; and improved http client error reporting. Dashboard updates add
195
+ c2bRegisterUrls and c2bSimulate actions in mpesaActions.ts, fix http.ts
196
+ webhook handlers with 6 missing fields (TransactionType, TransTime, etc.),
197
+ upsert logic via createTransaction, a new getByShortCode query in
198
+ businesses.ts, and a complete C2B tab in PaymentsPage.tsx featuring URL
199
+ registration, simulation forms, and unified transactions—addressing prior gaps
200
+ like hardcoded commands, missing UI, incomplete types, and non-upserting
201
+ handlers.
125
202
 
126
203
  ## 0.2.4
127
204
 
128
205
  ### Patch Changes
129
206
 
130
- - 4a535ef: Request failed with status 500 but swallows the actual Daraja error body — it parses it into data but never includes it in the error message.
207
+ - 4a535ef: Request failed with status 500 but swallows the actual Daraja error
208
+ body — it parses it into data but never includes it in the error message.
131
209
 
132
210
  ## 0.2.3
133
211
 
@@ -152,14 +230,14 @@
152
230
  ### Minor Changes
153
231
 
154
232
  - Add M-Pesa Express (STK Push) support
155
- - Add `processStkPush` to initiate STK Push payment prompts on a
156
- customer's phone via POST /mpesa/stkpush/v1/processrequest
157
- - Add `queryStkPush` to check the status of an STK Push transaction
158
- via POST /mpesa/stkpushquery/v1/query
233
+ - Add `processStkPush` to initiate STK Push payment prompts on a customer's
234
+ phone via POST /mpesa/stkpush/v1/processrequest
235
+ - Add `queryStkPush` to check the status of an STK Push transaction via POST
236
+ /mpesa/stkpushquery/v1/query
159
237
  - Add `StkPushRequest`, `StkPushResponse`, `StkQueryRequest`,
160
238
  `StkQueryResponse` and `TransactionType` types
161
- - Add `formatPhoneNumber` utility to normalize Kenyan phone numbers
162
- to 254 format
239
+ - Add `formatPhoneNumber` utility to normalize Kenyan phone numbers to 254
240
+ format
163
241
  - Add `getStkPushPassword` to generate Base64(Shortcode+Passkey+Timestamp)
164
242
  - Add `getTimestamp` helper for Daraja-formatted timestamps
165
243
  - Export all STK Push types and utilities from the package root
@@ -1176,19 +1176,8 @@ declare class Mpesa {
1176
1176
  //#endregion
1177
1177
  //#region src/express/index.d.ts
1178
1178
  interface MpesaExpressConfig extends MpesaConfig {
1179
- /**
1180
- * Full public URL Safaricom will POST STK Push callbacks to.
1181
- * @example "https://yourdomain.com/api/mpesa/express/callback"
1182
- */
1183
1179
  callbackUrl: string;
1184
- /**
1185
- * Full public URL Safaricom will POST Transaction Status results to.
1186
- * Required when using transactionStatus routes.
1187
- */
1188
1180
  resultUrl?: string;
1189
- /**
1190
- * Full public URL Safaricom calls on queue timeout.
1191
- */
1192
1181
  queueTimeOutUrl?: string;
1193
1182
  c2bShortCode?: string;
1194
1183
  c2bConfirmationUrl?: string;
@@ -1204,91 +1193,15 @@ interface MpesaExpressConfig extends MpesaConfig {
1204
1193
  b2bReceiverShortCode?: string;
1205
1194
  b2bCallbackUrl?: string;
1206
1195
  onB2BCheckoutCallback?: (callback: B2BExpressCheckoutCallback) => Promise<void> | void;
1207
- /**
1208
- * Your business shortcode from which B2C money is deducted.
1209
- * Used as the default partyA for B2C payments when not in the request body.
1210
- */
1211
1196
  b2cPartyA?: string;
1212
- /**
1213
- * Full public URL Safaricom POSTs B2C results to.
1214
- * Required when using B2C routes.
1215
- * @example "https://yourdomain.com/api/mpesa/b2c/result"
1216
- */
1217
1197
  b2cResultUrl?: string;
1218
- /**
1219
- * Full public URL Safaricom calls on B2C queue timeout.
1220
- * Required when using B2C routes.
1221
- * @example "https://yourdomain.com/api/mpesa/b2c/timeout"
1222
- */
1223
1198
  b2cQueueTimeOutUrl?: string;
1224
- /**
1225
- * Optional hook called when a B2C result arrives at the result URL.
1226
- * Called for BOTH successful and failed results.
1227
- *
1228
- * Fire-and-forget — 200 response to Safaricom is sent immediately.
1229
- * Errors in this hook are logged but do NOT affect the response.
1230
- *
1231
- * @example
1232
- * onB2CResult: async (result) => {
1233
- * if (isB2CSuccess(result)) {
1234
- * await db.disbursements.markCompleted({
1235
- * transactionId: getB2CTransactionId(result),
1236
- * amount: getB2CAmount(result),
1237
- * });
1238
- * }
1239
- * }
1240
- */
1241
1199
  onB2CResult?: (result: B2CResult) => Promise<void> | void;
1242
- /**
1243
- * Skip Safaricom IP verification on callback routes.
1244
- * ONLY set true in local development — never in production.
1245
- */
1246
1200
  skipIPCheck?: boolean;
1247
1201
  }
1248
1202
  declare function createMpesaExpressClient(config: MpesaExpressConfig): {
1249
1203
  mpesa: Mpesa;
1250
1204
  };
1251
- /**
1252
- * Attaches all M-Pesa routes to the given Express Router.
1253
- *
1254
- * @example
1255
- * import express from "express";
1256
- * import {
1257
- * createMpesaExpressRouter,
1258
- * acceptC2BValidation,
1259
- * rejectC2BValidation,
1260
- * isB2CSuccess,
1261
- * getB2CTransactionId,
1262
- * getB2CAmount,
1263
- * } from "pesafy/express";
1264
- *
1265
- * const router = express.Router();
1266
- * createMpesaExpressRouter(router, {
1267
- * consumerKey: process.env.MPESA_CONSUMER_KEY!,
1268
- * consumerSecret: process.env.MPESA_CONSUMER_SECRET!,
1269
- * environment: "sandbox",
1270
- * lipaNaMpesaShortCode: "174379",
1271
- * lipaNaMpesaPassKey: "bfb279...",
1272
- * callbackUrl: "https://yourdomain.com/mpesa/express/callback",
1273
- * initiatorName: "testapi",
1274
- * initiatorPassword: "Safaricom123!",
1275
- * certificatePath: "./SandboxCertificate.cer",
1276
- * // B2C
1277
- * b2cPartyA: "600979",
1278
- * b2cResultUrl: "https://yourdomain.com/mpesa/b2c/result",
1279
- * b2cQueueTimeOutUrl: "https://yourdomain.com/mpesa/b2c/timeout",
1280
- * onB2CResult: async (result) => {
1281
- * if (isB2CSuccess(result)) {
1282
- * await db.disbursements.markCompleted({
1283
- * transactionId: getB2CTransactionId(result),
1284
- * amount: getB2CAmount(result),
1285
- * });
1286
- * }
1287
- * },
1288
- * skipIPCheck: true, // local dev only
1289
- * });
1290
- * app.use("/api", router);
1291
- */
1292
1205
  declare function createMpesaExpressRouter(router: Router, config: MpesaExpressConfig): Router;
1293
1206
  //#endregion
1294
1207
  export { MpesaExpressConfig, createMpesaExpressClient, createMpesaExpressRouter };
@@ -1 +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};
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`,...r.transactionType===void 0?{}:{transactionType:r.transactionType},...r.partyB===void 0?{}:{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,...b.skipIPCheck===void 0?{}:{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;return 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,...r.remarks===void 0?{}:{remarks:r.remarks},...r.occasion===void 0?{}:{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,apiVersion:b.c2bApiVersion??`v2`,...r.billRefNumber===void 0?{}:{billRefNumber:r.billRefNumber}});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,...r.remarks===void 0?{}:{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,...r.requestRefId===void 0?{}:{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;return 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,resultUrl:r.resultUrl??b.b2cResultUrl,queueTimeOutUrl:r.queueTimeOutUrl??b.b2cQueueTimeOutUrl,...r.requester===void 0?{}:{requester:r.requester},...r.remarks===void 0?{}:{remarks:r.remarks}});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;return 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};
@@ -1 +1 @@
1
- import{i as e,n as t,r as n}from"../signature-verifier.js";function r(e){let t=e.headers[`x-forwarded-for`];return typeof t==`string`?t.split(`,`)[0]?.trim()??``:e.ip??``}function i(t,n){n instanceof e?t.status(n.statusCode??400).send({error:n.code,message:n.message}):t.status(500).send({error:`INTERNAL_ERROR`})}async function a(a,o){let s=new n(o);a.post(`/mpesa/stk-push`,async(t,n)=>{try{let n=t.body;if(!n.amount||n.amount<=0)throw new e({code:`VALIDATION_ERROR`,message:`amount must be > 0`});if(!n.phoneNumber)throw new e({code:`VALIDATION_ERROR`,message:`phoneNumber is required`});return await s.stkPush({amount:n.amount,phoneNumber:n.phoneNumber,callbackUrl:o.callbackUrl,accountReference:n.accountReference??`REF-${Date.now().toString(36).toUpperCase()}`,transactionDesc:n.transactionDesc??`Payment`,partyB:n.partyB})}catch(e){i(n,e)}}),a.post(`/mpesa/stk-query`,async(t,n)=>{try{let{checkoutRequestId:n}=t.body;if(!n)throw new e({code:`VALIDATION_ERROR`,message:`checkoutRequestId is required`});return await s.stkQuery({checkoutRequestId:n})}catch(e){i(n,e)}}),a.post(`/mpesa/callback`,async(e,n)=>{if(!o.skipIPCheck){let n=r(e);n&&!t(n)&&e.log.warn({ip:n},`[pesafy/fastify] Callback from unknown IP`)}let i=e.body?.Body?.stkCallback;if(i&&i.ResultCode===0){let t=i.CallbackMetadata?.Item??[],n=e=>t.find(t=>t.Name===e)?.Value??null;o.onStkSuccess&&Promise.resolve(o.onStkSuccess({receiptNumber:n(`MpesaReceiptNumber`),amount:n(`Amount`),phone:n(`PhoneNumber`)})).catch(e.log.error)}return{ResultCode:0,ResultDesc:`Accepted`}}),a.post(`/mpesa/balance`,async(t,n)=>{try{if(!o.resultUrl||!o.queueTimeOutUrl)throw new e({code:`VALIDATION_ERROR`,message:`resultUrl and queueTimeOutUrl must be set in config`});return await s.accountBalance({...t.body,resultUrl:o.resultUrl,queueTimeOutUrl:o.queueTimeOutUrl})}catch(e){i(n,e)}}),a.post(`/mpesa/balance/result`,e=>(e.log.info(e.body,`[pesafy/fastify] Balance result`),{ResultCode:0,ResultDesc:`Accepted`})),a.post(`/mpesa/reversal`,async(t,n)=>{try{if(!o.resultUrl||!o.queueTimeOutUrl)throw new e({code:`VALIDATION_ERROR`,message:`resultUrl and queueTimeOutUrl must be set in config`});return await s.reverseTransaction({...t.body,resultUrl:o.resultUrl,queueTimeOutUrl:o.queueTimeOutUrl})}catch(e){i(n,e)}}),a.post(`/mpesa/reversal/result`,e=>(e.log.info(e.body,`[pesafy/fastify] Reversal result`),{ResultCode:0,ResultDesc:`Accepted`}))}export{a as registerMpesaRoutes};
1
+ import{i as e,n as t,r as n}from"../signature-verifier.js";function r(e){let t=e.headers[`x-forwarded-for`];return typeof t==`string`?t.split(`,`)[0]?.trim()??``:e.ip??``}function i(t,n){n instanceof e?t.status(n.statusCode??400).send({error:n.code,message:n.message}):t.status(500).send({error:`INTERNAL_ERROR`})}async function a(a,o){let s=new n(o);a.post(`/mpesa/stk-push`,async(t,n)=>{try{let n=t.body;if(!n.amount||n.amount<=0)throw new e({code:`VALIDATION_ERROR`,message:`amount must be > 0`});if(!n.phoneNumber)throw new e({code:`VALIDATION_ERROR`,message:`phoneNumber is required`});return await s.stkPush({amount:n.amount,phoneNumber:n.phoneNumber,callbackUrl:o.callbackUrl,accountReference:n.accountReference??`REF-${Date.now().toString(36).toUpperCase()}`,transactionDesc:n.transactionDesc??`Payment`,...n.partyB===void 0?{}:{partyB:n.partyB}})}catch(e){i(n,e);return}}),a.post(`/mpesa/stk-query`,async(t,n)=>{try{let{checkoutRequestId:n}=t.body;if(!n)throw new e({code:`VALIDATION_ERROR`,message:`checkoutRequestId is required`});return await s.stkQuery({checkoutRequestId:n})}catch(e){i(n,e);return}}),a.post(`/mpesa/callback`,async(e,n)=>{if(!o.skipIPCheck){let n=r(e);n&&!t(n)&&e.log.warn({ip:n},`[pesafy/fastify] Callback from unknown IP`)}let i=e.body?.Body?.stkCallback;if(i&&i.ResultCode===0){let t=i.CallbackMetadata?.Item??[],n=e=>t.find(t=>t.Name===e)?.Value??null;o.onStkSuccess&&Promise.resolve(o.onStkSuccess({receiptNumber:n(`MpesaReceiptNumber`),amount:n(`Amount`),phone:n(`PhoneNumber`)})).catch(e.log.error)}return{ResultCode:0,ResultDesc:`Accepted`}}),a.post(`/mpesa/balance`,async(t,n)=>{try{if(!o.resultUrl||!o.queueTimeOutUrl)throw new e({code:`VALIDATION_ERROR`,message:`resultUrl and queueTimeOutUrl must be set in config`});return await s.accountBalance({...t.body,resultUrl:o.resultUrl,queueTimeOutUrl:o.queueTimeOutUrl})}catch(e){i(n,e);return}}),a.post(`/mpesa/balance/result`,e=>(e.log.info(e.body,`[pesafy/fastify] Balance result`),{ResultCode:0,ResultDesc:`Accepted`})),a.post(`/mpesa/reversal`,async(t,n)=>{try{if(!o.resultUrl||!o.queueTimeOutUrl)throw new e({code:`VALIDATION_ERROR`,message:`resultUrl and queueTimeOutUrl must be set in config`});return await s.reverseTransaction({...t.body,resultUrl:o.resultUrl,queueTimeOutUrl:o.queueTimeOutUrl})}catch(e){i(n,e);return}}),a.post(`/mpesa/reversal/result`,e=>(e.log.info(e.body,`[pesafy/fastify] Reversal result`),{ResultCode:0,ResultDesc:`Accepted`}))}export{a as registerMpesaRoutes};
@@ -1 +1 @@
1
- import{i as e,n as t,r as n}from"../signature-verifier.js";import{a as r,n as i,r as a,t as o}from"../webhook-handler.js";function s(e){return e.req.header(`x-forwarded-for`)?.split(`,`)[0]?.trim()??e.req.header(`cf-connecting-ip`)??e.req.header(`x-real-ip`)??``}function c(t,n){if(n instanceof e){let e=n.statusCode??400;return t.json({error:n.code,message:n.message},e)}return t.json({error:`INTERNAL_ERROR`,message:`An unexpected error occurred`},500)}function l(l,u){let d=new n(u);l.post(`/mpesa/express/stk-push`,async t=>{try{let n=await t.req.json();if(!n.amount||n.amount<=0)throw new e({code:`VALIDATION_ERROR`,message:`amount must be > 0`});if(!n.phoneNumber)throw new e({code:`VALIDATION_ERROR`,message:`phoneNumber is required`});let r=await d.stkPush({amount:n.amount,phoneNumber:n.phoneNumber,callbackUrl:u.callbackUrl,accountReference:n.accountReference??`REF-${Date.now().toString(36).toUpperCase()}`,transactionDesc:n.transactionDesc??`Payment`,partyB:n.partyB});return t.json(r)}catch(e){return c(t,e)}}),l.post(`/mpesa/express/stk-query`,async t=>{try{let{checkoutRequestId:n}=await t.req.json();if(!n)throw new e({code:`VALIDATION_ERROR`,message:`checkoutRequestId is required`});return t.json(await d.stkQuery({checkoutRequestId:n}))}catch(e){return c(t,e)}}),l.post(`/mpesa/express/callback`,async e=>{if(!u.skipIPCheck){let n=s(e);n&&!t(n)&&console.warn(`[pesafy/hono] STK callback from unknown IP:`,n)}let n=await e.req.json(),c=n?.Body?.stkCallback;if(!c)return e.json({ResultCode:0,ResultDesc:`Accepted`});if(r(n)){let e={receiptNumber:a(n),amount:o(n),phone:i(n)};console.info(`[pesafy/hono] STK success:`,e),u.onStkSuccess&&Promise.resolve(u.onStkSuccess(e)).catch(console.error)}else{let e={resultCode:c.ResultCode,resultDesc:c.ResultDesc};console.warn(`[pesafy/hono] STK failed:`,e),u.onStkFailure&&Promise.resolve(u.onStkFailure(e)).catch(console.error)}return e.json({ResultCode:0,ResultDesc:`Accepted`})}),l.post(`/mpesa/balance/query`,async t=>{try{if(!u.resultUrl||!u.queueTimeOutUrl)throw new e({code:`VALIDATION_ERROR`,message:`resultUrl and queueTimeOutUrl are required in config for balance queries`});let n=await t.req.json();return t.json(await d.accountBalance({...n,resultUrl:u.resultUrl,queueTimeOutUrl:u.queueTimeOutUrl}))}catch(e){return c(t,e)}}),l.post(`/mpesa/balance/result`,async e=>{let t=await e.req.json();return u.onAccountBalanceResult&&Promise.resolve(u.onAccountBalanceResult(t)).catch(console.error),console.info(`[pesafy/hono] Account balance result:`,JSON.stringify(t).slice(0,300)),e.json({ResultCode:0,ResultDesc:`Accepted`})}),l.post(`/mpesa/reversal/request`,async t=>{try{if(!u.resultUrl||!u.queueTimeOutUrl)throw new e({code:`VALIDATION_ERROR`,message:`resultUrl and queueTimeOutUrl are required in config for reversals`});let n={...await t.req.json(),resultUrl:u.resultUrl,queueTimeOutUrl:u.queueTimeOutUrl};return t.json(await d.reverseTransaction(n))}catch(e){return c(t,e)}}),l.post(`/mpesa/reversal/result`,async e=>{let t=await e.req.json();return u.onReversalResult&&Promise.resolve(u.onReversalResult(t)).catch(console.error),console.info(`[pesafy/hono] Reversal result:`,JSON.stringify(t).slice(0,300)),e.json({ResultCode:0,ResultDesc:`Accepted`})})}export{l as createMpesaHonoRouter};
1
+ import{i as e,n as t,r as n}from"../signature-verifier.js";import{a as r,n as i,r as a,t as o}from"../webhook-handler.js";function s(e){return e.req.header(`x-forwarded-for`)?.split(`,`)[0]?.trim()??e.req.header(`cf-connecting-ip`)??e.req.header(`x-real-ip`)??``}function c(t,n){if(n instanceof e){let e=n.statusCode??400;return t.json({error:n.code,message:n.message},e)}return t.json({error:`INTERNAL_ERROR`,message:`An unexpected error occurred`},500)}function l(l,u){let d=new n(u);l.post(`/mpesa/express/stk-push`,async t=>{try{let n=await t.req.json();if(!n.amount||n.amount<=0)throw new e({code:`VALIDATION_ERROR`,message:`amount must be > 0`});if(!n.phoneNumber)throw new e({code:`VALIDATION_ERROR`,message:`phoneNumber is required`});let r=await d.stkPush({amount:n.amount,phoneNumber:n.phoneNumber,callbackUrl:u.callbackUrl,accountReference:n.accountReference??`REF-${Date.now().toString(36).toUpperCase()}`,transactionDesc:n.transactionDesc??`Payment`,...n.partyB===void 0?{}:{partyB:n.partyB}});return t.json(r)}catch(e){return c(t,e)}}),l.post(`/mpesa/express/stk-query`,async t=>{try{let{checkoutRequestId:n}=await t.req.json();if(!n)throw new e({code:`VALIDATION_ERROR`,message:`checkoutRequestId is required`});return t.json(await d.stkQuery({checkoutRequestId:n}))}catch(e){return c(t,e)}}),l.post(`/mpesa/express/callback`,async e=>{if(!u.skipIPCheck){let n=s(e);n&&!t(n)&&console.warn(`[pesafy/hono] STK callback from unknown IP:`,n)}let n=await e.req.json(),c=n?.Body?.stkCallback;if(!c)return e.json({ResultCode:0,ResultDesc:`Accepted`});if(r(n)){let e={receiptNumber:a(n),amount:o(n),phone:i(n)};console.info(`[pesafy/hono] STK success:`,e),u.onStkSuccess&&Promise.resolve(u.onStkSuccess(e)).catch(console.error)}else{let e={resultCode:c.ResultCode,resultDesc:c.ResultDesc};console.warn(`[pesafy/hono] STK failed:`,e),u.onStkFailure&&Promise.resolve(u.onStkFailure(e)).catch(console.error)}return e.json({ResultCode:0,ResultDesc:`Accepted`})}),l.post(`/mpesa/balance/query`,async t=>{try{if(!u.resultUrl||!u.queueTimeOutUrl)throw new e({code:`VALIDATION_ERROR`,message:`resultUrl and queueTimeOutUrl are required in config for balance queries`});let n=await t.req.json();return t.json(await d.accountBalance({...n,resultUrl:u.resultUrl,queueTimeOutUrl:u.queueTimeOutUrl}))}catch(e){return c(t,e)}}),l.post(`/mpesa/balance/result`,async e=>{let t=await e.req.json();return u.onAccountBalanceResult&&Promise.resolve(u.onAccountBalanceResult(t)).catch(console.error),console.info(`[pesafy/hono] Account balance result:`,JSON.stringify(t).slice(0,300)),e.json({ResultCode:0,ResultDesc:`Accepted`})}),l.post(`/mpesa/reversal/request`,async t=>{try{if(!u.resultUrl||!u.queueTimeOutUrl)throw new e({code:`VALIDATION_ERROR`,message:`resultUrl and queueTimeOutUrl are required in config for reversals`});let n={...await t.req.json(),resultUrl:u.resultUrl,queueTimeOutUrl:u.queueTimeOutUrl};return t.json(await d.reverseTransaction(n))}catch(e){return c(t,e)}}),l.post(`/mpesa/reversal/result`,async e=>{let t=await e.req.json();return u.onReversalResult&&Promise.resolve(u.onReversalResult(t)).catch(console.error),console.info(`[pesafy/hono] Reversal result:`,JSON.stringify(t).slice(0,300)),e.json({ResultCode:0,ResultDesc:`Accepted`})})}export{l as createMpesaHonoRouter};
@@ -1 +1 @@
1
- import{i as e,n as t,r as n}from"../signature-verifier.js";function r(e,t=200){return new Response(JSON.stringify(e),{status:t,headers:{"Content-Type":`application/json`}})}function i(e){return e.headers.get(`x-forwarded-for`)?.split(`,`)[0]?.trim()??e.headers.get(`x-real-ip`)??``}function a(t){let i=new n(t);return async function(n){try{let a=await n.json();if(!a.amount||a.amount<=0)throw new e({code:`VALIDATION_ERROR`,message:`amount must be > 0`});if(!a.phoneNumber)throw new e({code:`VALIDATION_ERROR`,message:`phoneNumber is required`});return r(await i.stkPush({amount:a.amount,phoneNumber:a.phoneNumber,callbackUrl:t.callbackUrl,accountReference:a.accountReference??`REF-${Date.now().toString(36).toUpperCase()}`,transactionDesc:a.transactionDesc??`Payment`,partyB:a.partyB}))}catch(t){return t instanceof e?r({error:t.code,message:t.message},t.statusCode??400):r({error:`INTERNAL_ERROR`,message:`Unexpected error`},500)}}}function o(t){let i=new n(t);return async function(t){try{let{checkoutRequestId:n}=await t.json();if(!n)throw new e({code:`VALIDATION_ERROR`,message:`checkoutRequestId is required`});return r(await i.stkQuery({checkoutRequestId:n}))}catch(t){return t instanceof e?r({error:t.code,message:t.message},t.statusCode??400):r({error:`INTERNAL_ERROR`},500)}}}function s(e){return async function(n){if(!e.skipIPCheck){let e=i(n);e&&!t(e)&&console.warn(`[pesafy/nextjs] Callback from unknown IP:`,e)}let a=(await n.json())?.Body?.stkCallback;if(!a)return r({ResultCode:0,ResultDesc:`Accepted`});let o=a.ResultCode===0,s=a.CallbackMetadata?.Item,c=e=>s?.find(t=>t.Name===e)?.Value;return o&&e.onSuccess?Promise.resolve(e.onSuccess({receiptNumber:c(`MpesaReceiptNumber`),amount:c(`Amount`),phone:c(`PhoneNumber`)})).catch(console.error):!o&&e.onFailure&&Promise.resolve(e.onFailure({resultCode:a.ResultCode,resultDesc:a.ResultDesc})).catch(console.error),r({ResultCode:0,ResultDesc:`Accepted`})}}function c(t){let i=a(t),c=o(t),l=s(t),u=new n(t);return{async POST(n){let a=n.nextUrl?.pathname??``;if(a.endsWith(`stk-push`))return i(n);if(a.endsWith(`stk-query`))return c(n);if(a.endsWith(`callback`))return l(n);if(a.endsWith(`balance`))try{let e=await n.json();return r(await u.accountBalance({...e,resultUrl:t.resultUrl??``,queueTimeOutUrl:t.queueTimeOutUrl??``}))}catch(t){return t instanceof e?r({error:t.code,message:t.message},400):r({error:`INTERNAL_ERROR`},500)}return r({error:`NOT_FOUND`,message:`No handler for ${a}`},404)}}}export{c as createMpesaNextHandlers,s as createStkCallbackHandler,a as createStkPushHandler,o as createStkQueryHandler};
1
+ import{i as e,n as t,r as n}from"../signature-verifier.js";function r(e,t=200){return new Response(JSON.stringify(e),{status:t,headers:{"Content-Type":`application/json`}})}function i(e){return e.headers.get(`x-forwarded-for`)?.split(`,`)[0]?.trim()??e.headers.get(`x-real-ip`)??``}function a(t){let i=new n(t);return async function(n){try{let a=await n.json();if(!a.amount||a.amount<=0)throw new e({code:`VALIDATION_ERROR`,message:`amount must be > 0`});if(!a.phoneNumber)throw new e({code:`VALIDATION_ERROR`,message:`phoneNumber is required`});return r(await i.stkPush({amount:a.amount,phoneNumber:a.phoneNumber,callbackUrl:t.callbackUrl,accountReference:a.accountReference??`REF-${Date.now().toString(36).toUpperCase()}`,transactionDesc:a.transactionDesc??`Payment`,...a.partyB===void 0?{}:{partyB:a.partyB}}))}catch(t){return t instanceof e?r({error:t.code,message:t.message},t.statusCode??400):r({error:`INTERNAL_ERROR`,message:`Unexpected error`},500)}}}function o(t){let i=new n(t);return async function(t){try{let{checkoutRequestId:n}=await t.json();if(!n)throw new e({code:`VALIDATION_ERROR`,message:`checkoutRequestId is required`});return r(await i.stkQuery({checkoutRequestId:n}))}catch(t){return t instanceof e?r({error:t.code,message:t.message},t.statusCode??400):r({error:`INTERNAL_ERROR`},500)}}}function s(e){return async function(n){if(!e.skipIPCheck){let e=i(n);e&&!t(e)&&console.warn(`[pesafy/nextjs] Callback from unknown IP:`,e)}let a=(await n.json())?.Body?.stkCallback;if(!a)return r({ResultCode:0,ResultDesc:`Accepted`});let o=a.ResultCode===0,s=a.CallbackMetadata?.Item,c=e=>s?.find(t=>t.Name===e)?.Value;return o&&e.onSuccess?Promise.resolve(e.onSuccess({receiptNumber:c(`MpesaReceiptNumber`),amount:c(`Amount`),phone:c(`PhoneNumber`)})).catch(console.error):!o&&e.onFailure&&Promise.resolve(e.onFailure({resultCode:a.ResultCode,resultDesc:a.ResultDesc})).catch(console.error),r({ResultCode:0,ResultDesc:`Accepted`})}}function c(t){let i=a(t),c=o(t),l=s(t),u=new n(t);return{async POST(n){let a=n.nextUrl?.pathname??``;if(a.endsWith(`stk-push`))return i(n);if(a.endsWith(`stk-query`))return c(n);if(a.endsWith(`callback`))return l(n);if(a.endsWith(`balance`))try{let e=await n.json();return r(await u.accountBalance({...e,resultUrl:t.resultUrl??``,queueTimeOutUrl:t.queueTimeOutUrl??``}))}catch(t){return t instanceof e?r({error:t.code,message:t.message},400):r({error:`INTERNAL_ERROR`},500)}return r({error:`NOT_FOUND`,message:`No handler for ${a}`},404)}}}export{c as createMpesaNextHandlers,s as createStkCallbackHandler,a as createStkPushHandler,o as createStkQueryHandler};
@@ -283,7 +283,7 @@ async function cmdEncrypt(args) {
283
283
  process.exit(1);
284
284
  }
285
285
  try {
286
- const { encryptSecurityCredential } = await import("./encryption.js");
286
+ const { encryptSecurityCredential } = await import("./encryption.mjs");
287
287
  const { readFile } = await import("node:fs/promises");
288
288
  const pem = await readFile(resolve(process.cwd(), certPath), "utf-8");
289
289
  const credential = encryptSecurityCredential(password, pem);
@@ -298,7 +298,7 @@ async function cmdValidatePhone(args) {
298
298
  let phone = args[0];
299
299
  if (!phone) phone = await prompt("Phone number to validate");
300
300
  try {
301
- const { formatSafaricomPhone } = await import("./phone.js");
301
+ const { formatSafaricomPhone } = await import("./phone.mjs");
302
302
  const normalised = formatSafaricomPhone(phone);
303
303
  console.log(`\n${g("✔")} ${b(phone)} → ${c(normalised)}\n`);
304
304
  } catch (e) {
@@ -323,9 +323,9 @@ async function cmdStkPush(args) {
323
323
  const baseUrl = env["MPESA_ENVIRONMENT"] === "production" ? "https://api.safaricom.co.ke" : "https://sandbox.safaricom.co.ke";
324
324
  console.log(dim("\nFetching token…"));
325
325
  const token = await getToken(env["MPESA_CONSUMER_KEY"], env["MPESA_CONSUMER_SECRET"], baseUrl);
326
- const { formatSafaricomPhone } = await import("./phone.js");
326
+ const { formatSafaricomPhone } = await import("./phone.mjs");
327
327
  const msisdn = formatSafaricomPhone(phone);
328
- const { getStkPushPassword, getTimestamp } = await import("./utils.js");
328
+ const { getStkPushPassword, getTimestamp } = await import("./utils.mjs");
329
329
  const timestamp = getTimestamp();
330
330
  const password = getStkPushPassword(env["MPESA_SHORTCODE"], env["MPESA_PASSKEY"], timestamp);
331
331
  console.log(dim("Sending STK Push…\n"));
@@ -365,7 +365,7 @@ async function cmdStkQuery(args) {
365
365
  if (!checkoutId) checkoutId = await prompt("CheckoutRequestID");
366
366
  const baseUrl = env["MPESA_ENVIRONMENT"] === "production" ? "https://api.safaricom.co.ke" : "https://sandbox.safaricom.co.ke";
367
367
  const token = await getToken(env["MPESA_CONSUMER_KEY"], env["MPESA_CONSUMER_SECRET"], baseUrl);
368
- const { getStkPushPassword, getTimestamp } = await import("./utils.js");
368
+ const { getStkPushPassword, getTimestamp } = await import("./utils.mjs");
369
369
  const timestamp = getTimestamp();
370
370
  const password = getStkPushPassword(env["MPESA_SHORTCODE"], env["MPESA_PASSKEY"], timestamp);
371
371
  try {
@@ -394,7 +394,7 @@ async function cmdBalance(args) {
394
394
  const baseUrl = env["MPESA_ENVIRONMENT"] === "production" ? "https://api.safaricom.co.ke" : "https://sandbox.safaricom.co.ke";
395
395
  console.log(dim("\nFetching token…"));
396
396
  const token = await getToken(env["MPESA_CONSUMER_KEY"], env["MPESA_CONSUMER_SECRET"], baseUrl);
397
- const { encryptSecurityCredential } = await import("./encryption.js");
397
+ const { encryptSecurityCredential } = await import("./encryption.mjs");
398
398
  const { readFile } = await import("node:fs/promises");
399
399
  const certPath = env["MPESA_CERTIFICATE_PATH"] ?? "./SandboxCertificate.cer";
400
400
  const pem = await readFile(resolve(process.cwd(), certPath), "utf-8");
@@ -490,7 +490,7 @@ async function cmdReversal(args) {
490
490
  const queueTimeoutUrl = env["MPESA_QUEUE_TIMEOUT_URL"] ?? await prompt("Queue Timeout URL");
491
491
  const baseUrl = env["MPESA_ENVIRONMENT"] === "production" ? "https://api.safaricom.co.ke" : "https://sandbox.safaricom.co.ke";
492
492
  const token = await getToken(env["MPESA_CONSUMER_KEY"], env["MPESA_CONSUMER_SECRET"], baseUrl);
493
- const { encryptSecurityCredential } = await import("./encryption.js");
493
+ const { encryptSecurityCredential } = await import("./encryption.mjs");
494
494
  const { readFile } = await import("node:fs/promises");
495
495
  const certPath = env["MPESA_CERTIFICATE_PATH"] ?? "./SandboxCertificate.cer";
496
496
  const pem = await readFile(resolve(process.cwd(), certPath), "utf-8");
@@ -1,4 +1,4 @@
1
- import { t as PesafyError } from "./errors.js";
1
+ import { t as PesafyError } from "./errors.mjs";
2
2
  import { constants, publicEncrypt } from "node:crypto";
3
3
 
4
4
  //#region src/core/encryption/security-credentials.ts
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{readFile as e}from"node:fs/promises";import{constants as t,publicEncrypt as n}from"node:crypto";function r(e){"@babel/helpers - typeof";return r=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},r(e)}function i(e,t){if(r(e)!=`object`||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var i=n.call(e,t||`default`);if(r(i)!=`object`)return i;throw TypeError(`@@toPrimitive must return a primitive value.`)}return(t===`string`?String:Number)(e)}function a(e){var t=i(e,`string`);return r(t)==`symbol`?t:t+``}function o(e,t,n){return(t=a(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var s=class e extends Error{constructor(t){super(t.message),o(this,`code`,void 0),o(this,`statusCode`,void 0),o(this,`response`,void 0),o(this,`requestId`,void 0),o(this,`cause`,void 0),o(this,`retryable`,void 0),Object.defineProperty(this,`name`,{value:`PesafyError`}),this.code=t.code,this.statusCode=t.statusCode,this.response=t.response,this.requestId=t.requestId,this.cause=t.cause,this.retryable=t.retryable??(t.code===`NETWORK_ERROR`||t.code===`TIMEOUT`||t.code===`RATE_LIMITED`||t.code===`REQUEST_FAILED`),Error.captureStackTrace&&Error.captureStackTrace(this,e)}get isValidation(){return this.code===`VALIDATION_ERROR`}get isAuth(){return this.code===`AUTH_FAILED`||this.code===`INVALID_CREDENTIALS`}toJSON(){return{name:this.name,code:this.code,message:this.message,statusCode:this.statusCode,requestId:this.requestId,retryable:this.retryable}}};function c(e){return new s(e)}function l(e){return e instanceof s}const ee=new Set([429,500,502,503,504]);function te(e){return new Promise(t=>setTimeout(t,e))}function u(e){let t=e*.25;return e+(Math.random()*t*2-t)}async function d(e,t){let n=t.retries??4,r=t.retryDelay??2e3,i=t.timeout??3e4,a={"Content-Type":`application/json`,Accept:`application/json`,...t.headers};t.idempotencyKey&&(a[`Idempotency-Key`]=t.idempotencyKey);let o={method:t.method,headers:a,...t.body===void 0?{}:{body:JSON.stringify(t.body)}},c=null;for(let a=0;a<=n;a++){if(a>0){let i=u(r*2**(a-1));console.warn(`[pesafy] Retry ${a}/${n} → ${t.method} ${e} in ${Math.round(i)} ms`),await te(i)}let l=new AbortController,d=setTimeout(()=>l.abort(),i),f;try{f=await fetch(e,{...o,signal:l.signal})}catch(t){if(clearTimeout(d),c=t instanceof Error&&t.name===`AbortError`?new s({code:`TIMEOUT`,message:`Request to ${e} timed out after ${i} ms`,cause:t,retryable:!0}):new s({code:`NETWORK_ERROR`,message:`Network error: ${t instanceof Error?t.message:String(t)}`,cause:t,retryable:!0}),a<n)continue;throw c}finally{clearTimeout(d)}let p=``,m=null,h=f.headers.get(`content-type`)??``;try{p=await f.text(),p&&(m=h.includes(`application/json`)?JSON.parse(p):p)}catch{m=p||null}let g={};if(f.headers.forEach((e,t)=>{g[t]=e}),f.ok)return{data:m,status:f.status,headers:g};let _=ee.has(f.status),v=typeof m==`object`&&m?m:{},y=v.errorMessage??v.ResponseDescription??v.resultDesc??p??`HTTP ${f.status}`;if(c=new s({code:_?`REQUEST_FAILED`:`API_ERROR`,message:y,statusCode:f.status,response:m,requestId:v.requestId,retryable:_}),!(_&&a<n))throw c}throw c}var f=class{constructor(e,t,n){o(this,`consumerKey`,void 0),o(this,`consumerSecret`,void 0),o(this,`baseUrl`,void 0),o(this,`cachedToken`,null),o(this,`tokenExpiresAt`,0),this.consumerKey=e,this.consumerSecret=t,this.baseUrl=n}getBasicAuthHeader(){let e=`${this.consumerKey}:${this.consumerSecret}`;return`Basic ${Buffer.from(e,`utf-8`).toString(`base64`)}`}async getAccessToken(){let e=Date.now()/1e3;if(this.cachedToken&&this.tokenExpiresAt>e+60)return this.cachedToken;let t=await d(`${this.baseUrl}/oauth/v1/generate?grant_type=client_credentials`,{method:`GET`,headers:{Authorization:this.getBasicAuthHeader()}}),{access_token:n,expires_in:r}=t.data;if(!n)throw new s({code:`AUTH_FAILED`,message:`Daraja did not return an access token. Check your consumer key and secret.`,response:t.data});return this.cachedToken=n,this.tokenExpiresAt=e+(r??3600),this.cachedToken}clearCache(){this.cachedToken=null,this.tokenExpiresAt=0}};function p(e,r){try{let i=Buffer.from(e,`utf-8`);return n({key:r,padding:t.RSA_PKCS1_PADDING},i).toString(`base64`)}catch(e){throw new s({code:`ENCRYPTION_FAILED`,message:`Failed to encrypt security credential. Ensure the certificate PEM is valid and matches the environment (sandbox/production).`,cause:e})}}function m(e){let t=Math.round(e);if(!Number.isFinite(t)||t<1)throw TypeError(`KesAmount must be a whole number ≥ 1, got ${e}`);return t}function h(e){let t=e.replace(/\D/g,``),n;if(t.startsWith(`254`)&&t.length===12)n=t;else if(t.startsWith(`0`)&&t.length===10)n=`254${t.slice(1)}`;else if(t.length===9)n=`254${t}`;else throw TypeError(`Cannot normalise "${e}" to 254XXXXXXXXX. Use 07XX…, 2547XX…, or +2547XX….`);if(n.length!==12)throw TypeError(`Phone "${e}" normalised to "${n}" — expected 12 digits.`);return n}function g(e){return String(e)}function _(e){return String(e)}function v(e){return String(e)}function y(e){return{ok:!0,data:e}}function b(e){return{ok:!1,error:e}}function ne(e){if(!e.trim())throw TypeError(`String must not be empty`);return e}async function re(e,t,n,r,i){if(!i.partyA?.trim())throw c({code:`VALIDATION_ERROR`,message:`partyA is required.`});if(![`1`,`2`,`4`].includes(i.identifierType))throw c({code:`VALIDATION_ERROR`,message:`identifierType must be "1" (MSISDN), "2" (Till), or "4" (ShortCode).`});if(!i.resultUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`resultUrl is required.`});if(!i.queueTimeOutUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`queueTimeOutUrl is required.`});let a={Initiator:r,SecurityCredential:n,CommandID:`AccountBalance`,PartyA:String(i.partyA),IdentifierType:i.identifierType,ResultURL:i.resultUrl,QueueTimeOutURL:i.queueTimeOutUrl,Remarks:i.remarks??`Account Balance Query`},{data:o}=await d(`${e}/mpesa/accountbalance/v1/query`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:a});return o}function ie(e){let t=e.split(`|`),n=[];for(let e=0;e+2<t.length;e+=3){let r=t[e]?.trim(),i=t[e+1]?.trim(),a=t[e+2]?.trim();r&&i&&a!==void 0&&n.push({name:r,currency:i,amount:a})}return n}function ae(e,t){let n=e.Result.ResultParameters?.ResultParameter;if(n)return(Array.isArray(n)?n:[n]).find(e=>e.Key===t)?.Value}function oe(e){return e.Result.ResultCode===0}function se(){try{if(typeof crypto<`u`&&crypto.randomUUID)return crypto.randomUUID()}catch{}return`${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`}async function x(e,t,n){if(!n.primaryShortCode?.trim())throw c({code:`VALIDATION_ERROR`,message:`primaryShortCode is required — the merchant's till number (debit party).`});if(!n.receiverShortCode?.trim())throw c({code:`VALIDATION_ERROR`,message:`receiverShortCode is required — the vendor's Paybill account (credit party).`});let r=Math.round(n.amount);if(!Number.isFinite(r)||r<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be a whole number ≥ 1 (got ${n.amount} which rounds to ${r}).`});if(!n.paymentRef?.trim())throw c({code:`VALIDATION_ERROR`,message:`paymentRef is required — shown in the merchant's USSD prompt as the payment reference.`});if(!n.callbackUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`callbackUrl is required — Safaricom POSTs the transaction result here.`});if(!n.partnerName?.trim())throw c({code:`VALIDATION_ERROR`,message:`partnerName is required — your friendly name shown in the merchant's USSD prompt.`});let i={primaryShortCode:String(n.primaryShortCode),receiverShortCode:String(n.receiverShortCode),amount:String(r),paymentRef:n.paymentRef,callbackUrl:n.callbackUrl,partnerName:n.partnerName,RequestRefID:n.requestRefId??se()},{data:a}=await d(`${e}/v1/ussdpush/get-msisdn`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:i});return a}function S(e){return e.resultCode===`0`}function ce(e){return e.resultCode===`4001`}function le(e){if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.resultCode==`string`&&typeof t.requestId==`string`&&typeof t.amount==`string`}function ue(e){return S(e)?e.transactionId??null:null}function de(e){return Number(e.amount)}function fe(e){return e.requestId}function pe(e){return S(e)?e.conversationID??null:null}async function C(e,t,n,r,i){if(!i.commandId)throw c({code:`VALIDATION_ERROR`,message:`commandId is required: "BusinessPayToBulk" | "BusinessPayment" | "SalaryPayment" | "PromotionPayment"`});let a=[`BusinessPayToBulk`,`BusinessPayment`,`SalaryPayment`,`PromotionPayment`];if(!a.includes(i.commandId))throw c({code:`VALIDATION_ERROR`,message:`commandId must be one of: ${a.join(`, `)}. Got: "${i.commandId}"`});let o=Math.round(i.amount);if(!Number.isFinite(o)||o<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be a whole number ≥ 1 (got ${i.amount} which rounds to ${o}).`});if(!i.partyA?.trim())throw c({code:`VALIDATION_ERROR`,message:`partyA is required — your business shortcode from which money is deducted.`});if(!i.partyB?.trim())throw c({code:`VALIDATION_ERROR`,message:`partyB is required — the recipient shortcode (BusinessPayToBulk) or customer MSISDN (other commands).`});if(!i.accountReference?.trim())throw c({code:`VALIDATION_ERROR`,message:`accountReference is required — a reference for this transaction.`});if(!i.resultUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`resultUrl is required — Safaricom POSTs the B2C result here.`});if(!i.queueTimeOutUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`queueTimeOutUrl is required — Safaricom calls this on request timeout.`});let s={Initiator:r,SecurityCredential:n,CommandID:i.commandId,SenderIdentifierType:i.senderIdentifierType??`4`,RecieverIdentifierType:i.receiverIdentifierType??`4`,Amount:String(o),PartyA:String(i.partyA),PartyB:String(i.partyB),AccountReference:i.accountReference,Remarks:i.remarks??`B2C Payment`,QueueTimeOutURL:i.queueTimeOutUrl,ResultURL:i.resultUrl};i.requester?.trim()&&(s.Requester=String(i.requester));let{data:l}=await d(`${e}/mpesa/b2b/v1/paymentrequest`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:s});return l}function me(e){if(!e||typeof e!=`object`)return!1;let t=e;if(!t.Result||typeof t.Result!=`object`)return!1;let n=t.Result;return typeof n.ResultCode==`number`&&typeof n.ConversationID==`string`}function w(e){return e.Result.ResultCode===0}function T(e){return e.Result.ResultCode!==0}function E(e){return e.Result.TransactionID??null}function D(e){return e.Result.ConversationID}function O(e){return e.Result.OriginatorConversationID}function k(e){return e.Result.ResultDesc}function A(e){let t=L(e,`Amount`);return t===void 0?null:Number(t)}function j(e){let t=L(e,`TransCompletedTime`);return t===void 0?null:String(t)}function M(e){let t=L(e,`DebitPartyCharges`);return t===void 0||t===``?null:String(t)}function N(e){let t=L(e,`ReceiverPartyPublicName`);return t===void 0?null:String(t)}function P(e){let t=L(e,`Currency`);return t===void 0?`KES`:String(t)}function F(e){let t=L(e,`DebitAccountBalance`);return t===void 0?null:String(t)}function I(e){let t=L(e,`InitiatorAccountCurrentBalance`);return t===void 0?null:String(t)}function L(e,t){let n=e.Result.ResultParameters?.ResultParameter;if(n)return(Array.isArray(n)?n:[n]).find(e=>e.Key===t)?.Value}async function R(e,t,n){if(!n.shortcode?.trim())throw c({code:`VALIDATION_ERROR`,message:`shortcode is required.`});if(!n.email?.trim())throw c({code:`VALIDATION_ERROR`,message:`email is required.`});if(!n.callbackUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`callbackUrl is required.`});let r={shortcode:n.shortcode,email:n.email,officialContact:n.officialContact,sendReminders:n.sendReminders,logo:n.logo??``,callbackUrl:n.callbackUrl},{data:i}=await d(`${e}/v1/billmanager-invoice/optin`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:r});return i}async function z(e,t,n){if(!n.externalReference?.trim())throw c({code:`VALIDATION_ERROR`,message:`externalReference is required.`});if(!n.partyA?.trim())throw c({code:`VALIDATION_ERROR`,message:`partyA (customer MSISDN) is required.`});let r=Math.round(n.amount);if(!Number.isFinite(r)||r<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be ≥ 1 (got ${n.amount}).`});let i={externalReference:n.externalReference,billingPeriod:n.billingPeriod,invoiceName:n.invoiceName,dueDate:n.dueDate,accountReference:n.accountReference,amount:String(r),partyA:n.partyA,invoiceItems:n.invoiceItems?.map(e=>({itemName:e.itemName,amount:String(Math.round(e.amount))}))??[]},{data:a}=await d(`${e}/v1/billmanager-invoice/single-invoicing`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:i});return a}async function he(e,t,n){if(!n.invoices?.length)throw c({code:`VALIDATION_ERROR`,message:`invoices array must not be empty.`});if(n.invoices.length>1e3)throw c({code:`VALIDATION_ERROR`,message:`Maximum 1 000 invoices per bulk request.`});let{data:r}=await d(`${e}/v1/billmanager-invoice/bulk-invoicing`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:n.invoices});return r}async function ge(e,t,n){if(!n.externalReference?.trim())throw c({code:`VALIDATION_ERROR`,message:`externalReference is required.`});let{data:r}=await d(`${e}/v1/billmanager-invoice/cancel-single-invoice`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:[{externalReference:n.externalReference}]});return r}const _e=[`mpesa`,`safaricom`,`.exe`,`.exec`,`cmd`,`sql`,`query`];function B(e,t){if(!e||!e.trim())throw c({code:`VALIDATION_ERROR`,message:`${t} is required`});let n=e.toLowerCase();for(let e of _e)if(n.includes(e))throw c({code:`VALIDATION_ERROR`,message:`${t} must not contain the keyword "${e}". Daraja rejects URLs containing: M-PESA, Safaricom, exe, exec, cmd, sql, query.`})}async function V(e,t,n){if(!n.shortCode)throw c({code:`VALIDATION_ERROR`,message:`shortCode is required`});if(!n.responseType)throw c({code:`VALIDATION_ERROR`,message:`responseType is required: "Completed" or "Cancelled" (sentence case, exactly as spelled)`});if(n.responseType!==`Completed`&&n.responseType!==`Cancelled`)throw c({code:`VALIDATION_ERROR`,message:`responseType must be exactly "Completed" or "Cancelled" (sentence case). Got: "${String(n.responseType)}"`});B(n.confirmationUrl,`confirmationUrl`),B(n.validationUrl,`validationUrl`);let r=n.apiVersion??`v2`,i={ShortCode:String(n.shortCode),ResponseType:n.responseType,ConfirmationURL:n.confirmationUrl,ValidationURL:n.validationUrl},{data:a}=await d(`${e}/mpesa/c2b/${r}/registerurl`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:i});return a}async function H(e,t,n){if(!e.includes(`sandbox`))throw c({code:`VALIDATION_ERROR`,message:`C2B simulate is only available in the Sandbox environment. In production, customers initiate payments via M-PESA App, USSD, or SIM Toolkit.`});if(!n.shortCode)throw c({code:`VALIDATION_ERROR`,message:`shortCode is required.`});if(n.commandId!==`CustomerPayBillOnline`&&n.commandId!==`CustomerBuyGoodsOnline`)throw c({code:`VALIDATION_ERROR`,message:`commandId must be "CustomerPayBillOnline" or "CustomerBuyGoodsOnline". Got: "${String(n.commandId)}"`});let r=Math.round(n.amount);if(!Number.isFinite(r)||r<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be a whole number ≥ 1 (got ${n.amount}).`});if(!n.msisdn)throw c({code:`VALIDATION_ERROR`,message:`msisdn is required. Sandbox test MSISDN: 254708374149.`});let i=n.commandId===`CustomerBuyGoodsOnline`,a=n.apiVersion??`v2`,o={ShortCode:Number(n.shortCode),CommandID:n.commandId,Amount:r,Msisdn:Number(n.msisdn)};i||(o.BillRefNumber=n.billRefNumber??``),i&&Object.prototype.hasOwnProperty.call(o,`BillRefNumber`)&&(delete o.BillRefNumber,console.warn(`[pesafy/simulateC2B] BillRefNumber leaked into Buy Goods payload — removed. This is a library bug; please report it.`));let{data:s}=await d(`${e}/mpesa/c2b/${a}/simulate`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:o});return s}function ve(e){if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.TransID==`string`&&typeof t.BusinessShortCode==`string`&&typeof t.TransAmount==`string`}function ye(e){return{ResultCode:`0`,ResultDesc:`Accepted`,...e?{ThirdPartyTransID:e}:{}}}function be(e=`C2B00016`,t=`Rejected`){return{ResultCode:e,ResultDesc:t}}function xe(){return{ResultCode:0,ResultDesc:`Success`}}function Se(e){return Number(e.TransAmount)}function U(e){return e.TransID}function Ce(e){return e.BillRefNumber}function we(e){return[e.FirstName,e.MiddleName,e.LastName].filter(Boolean).join(` `).trim()}function Te(e){return e.TransactionType===`Pay Bill`||e.TransactionType===`CustomerPayBillOnline`}function Ee(e){return e.TransactionType===`Buy Goods`||e.TransactionType===`CustomerBuyGoodsOnline`}async function De(e,t,n){if(!n.merchantName?.trim())throw c({code:`VALIDATION_ERROR`,message:`merchantName is required`});if(!n.refNo?.trim())throw c({code:`VALIDATION_ERROR`,message:`refNo (transaction reference) is required`});let r=Math.round(n.amount);if(!Number.isFinite(r)||r<1)throw c({code:`VALIDATION_ERROR`,message:`Amount must be at least 1 (got ${n.amount} which rounds to ${r}).`});if(!n.trxCode)throw c({code:`VALIDATION_ERROR`,message:`trxCode is required. Supported values: "BG" | "WA" | "PB" | "SM" | "SB"`});if(!n.cpi?.trim())throw c({code:`VALIDATION_ERROR`,message:`cpi (Credit Party Identifier) is required — e.g. till number, paybill, or MSISDN`});let i=n.size??300;if(i<1)throw c({code:`VALIDATION_ERROR`,message:`size must be a positive number of pixels`});let a={MerchantName:n.merchantName,RefNo:n.refNo,Amount:r,TrxCode:n.trxCode,CPI:n.cpi,Size:String(i)},{data:o}=await d(`${e}/mpesa/qrcode/v1/generate`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:a});return o}async function Oe(e,t,n,r,i){if(!i.transactionId?.trim())throw c({code:`VALIDATION_ERROR`,message:`transactionId is required.`});if(!i.receiverParty?.trim())throw c({code:`VALIDATION_ERROR`,message:`receiverParty is required.`});if(![`1`,`2`,`4`].includes(i.receiverIdentifierType))throw c({code:`VALIDATION_ERROR`,message:`receiverIdentifierType must be "1", "2", or "4".`});let a=Math.round(i.amount);if(!Number.isFinite(a)||a<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be a whole number ≥ 1 (got ${i.amount}).`});if(!i.resultUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`resultUrl is required.`});if(!i.queueTimeOutUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`queueTimeOutUrl is required.`});let o={Initiator:r,SecurityCredential:n,CommandID:`TransactionReversal`,TransactionID:i.transactionId,Amount:String(a),ReceiverParty:String(i.receiverParty),RecieverIdentifierType:i.receiverIdentifierType,ResultURL:i.resultUrl,QueueTimeOutURL:i.queueTimeOutUrl,Remarks:i.remarks??`Transaction Reversal`,Occasion:i.occasion??``},{data:s}=await d(`${e}/mpesa/reversal/v1/request`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:o});return s}function ke(e){return e.Result.ResultCode===0}function Ae(e){return e.Result.TransactionID??null}function je(e){return e.Result.ConversationID}function W(e){let t=e.replace(/\D/g,``),n;if(t.startsWith(`254`)&&t.length===12)n=t;else if(t.startsWith(`0`)&&t.length===10)n=`254${t.slice(1)}`;else if(t.length===9)n=`254${t}`;else throw new s({code:`INVALID_PHONE`,message:`Cannot parse "${e}". Use 07XXXXXXXX, 2547XXXXXXXX, or +2547XXXXXXXX.`});if(n.length!==12)throw new s({code:`INVALID_PHONE`,message:`"${e}" normalised to "${n}" — expected 12 digits.`});return n}function G(e,t,n){return btoa(`${e}${t}${n}`)}function K(){let e=new Date,t=e=>e.toString().padStart(2,`0`);return[e.getFullYear(),t(e.getMonth()+1),t(e.getDate()),t(e.getHours()),t(e.getMinutes()),t(e.getSeconds())].join(``)}async function Me(e,t,n){let r=Math.round(n.amount);if(r<1)throw new s({code:`VALIDATION_ERROR`,message:`Amount must be at least KES 1 (got ${n.amount} which rounds to ${r}).`});let i=K(),a=n.partyB??n.shortCode,o={BusinessShortCode:n.shortCode,Password:G(n.shortCode,n.passKey,i),Timestamp:i,TransactionType:n.transactionType??`CustomerPayBillOnline`,Amount:r,PartyA:W(n.phoneNumber),PartyB:a,PhoneNumber:W(n.phoneNumber),CallBackURL:n.callbackUrl,AccountReference:n.accountReference.slice(0,12),TransactionDesc:n.transactionDesc.slice(0,13)},{data:c}=await d(`${e}/mpesa/stkpush/v1/processrequest`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:o,retries:5,retryDelay:3e3});return c}async function Ne(e,t,n){let r=K(),i={BusinessShortCode:n.shortCode,Password:G(n.shortCode,n.passKey,r),Timestamp:r,CheckoutRequestID:n.checkoutRequestId},{data:a}=await d(`${e}/mpesa/stkpushquery/v1/query`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:i});return a}function q(e){return e.ResultCode===0}function Pe(e,t){let n=e.Body.stkCallback;if(q(n))return n.CallbackMetadata.Item.find(e=>e.Name===t)?.Value}const Fe=`572572`,J=`PayTaxToKRA`;async function Y(e,t,n,r,i){let a=Math.round(i.amount);if(!Number.isFinite(a)||a<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be a whole number ≥ 1 (got ${i.amount} which rounds to ${a}).`});if(!i.partyA)throw c({code:`VALIDATION_ERROR`,message:`partyA is required — your M-PESA business shortcode from which tax is deducted.`});if(!i.accountReference?.trim())throw c({code:`VALIDATION_ERROR`,message:`accountReference is required — the Payment Registration Number (PRN) issued by KRA.`});if(!i.resultUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`resultUrl is required — Safaricom POSTs the tax remittance result here.`});if(!i.queueTimeOutUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`queueTimeOutUrl is required — Safaricom calls this on request timeout.`});let o={Initiator:r,SecurityCredential:n,CommandID:J,SenderIdentifierType:`4`,RecieverIdentifierType:`4`,Amount:String(a),PartyA:String(i.partyA),PartyB:i.partyB??`572572`,AccountReference:i.accountReference,Remarks:i.remarks??`Tax Remittance`,QueueTimeOutURL:i.queueTimeOutUrl,ResultURL:i.resultUrl},{data:s}=await d(`${e}/mpesa/b2b/v1/remittax`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:o});return s}async function Ie(e,t,n,r,i){if(!i.transactionId)throw c({code:`VALIDATION_ERROR`,message:`transactionId is required`});if(!i.partyA)throw c({code:`VALIDATION_ERROR`,message:`partyA is required (your business shortcode, till number, or MSISDN)`});if(!i.identifierType)throw c({code:`VALIDATION_ERROR`,message:`identifierType is required: "1" (MSISDN) | "2" (Till) | "4" (ShortCode)`});if(!i.resultUrl)throw c({code:`VALIDATION_ERROR`,message:`resultUrl is required — Safaricom POSTs the transaction result here`});if(!i.queueTimeOutUrl)throw c({code:`VALIDATION_ERROR`,message:`queueTimeOutUrl is required — Safaricom calls this on timeout`});let a={Initiator:r,SecurityCredential:n,CommandID:i.commandId??`TransactionStatusQuery`,TransactionID:i.transactionId,PartyA:i.partyA,IdentifierType:i.identifierType,ResultURL:i.resultUrl,QueueTimeOutURL:i.queueTimeOutUrl,Remarks:i.remarks??`Transaction Status Query`,Occasion:i.occasion??``},{data:o}=await d(`${e}/mpesa/transactionstatus/v1/query`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:a});return o}const X={sandbox:`https://sandbox.safaricom.co.ke`,production:`https://api.safaricom.co.ke`};var Le=class{constructor(e){if(o(this,`config`,void 0),o(this,`tokenManager`,void 0),o(this,`baseUrl`,void 0),!e.consumerKey||!e.consumerSecret)throw new s({code:`INVALID_CREDENTIALS`,message:`consumerKey and consumerSecret are required.`});this.config=e,this.baseUrl=X[e.environment],this.tokenManager=new f(e.consumerKey,e.consumerSecret,this.baseUrl)}getToken(){return this.tokenManager.getAccessToken()}async buildSecurityCredential(){if(this.config.securityCredential)return this.config.securityCredential;if(!this.config.initiatorPassword)throw new s({code:`INVALID_CREDENTIALS`,message:`Provide securityCredential (pre-encrypted) OR (initiatorPassword + certificatePath/certificatePem).`});let t;if(this.config.certificatePem)t=this.config.certificatePem;else if(this.config.certificatePath)t=await e(this.config.certificatePath,`utf-8`);else throw new s({code:`INVALID_CREDENTIALS`,message:`certificatePath or certificatePem is required to encrypt the initiator password.`});return p(this.config.initiatorPassword,t)}requireInitiator(e){let t=this.config.initiatorName??``;if(!t)throw new s({code:`VALIDATION_ERROR`,message:`initiatorName is required for ${e}.`});return t}async stkPushSafe(e){try{return y(await this.stkPush(e))}catch(e){return b(e)}}async stkPush(e){let t=this.config.lipaNaMpesaShortCode??``,n=this.config.lipaNaMpesaPassKey??``;if(!t||!n)throw new s({code:`VALIDATION_ERROR`,message:`lipaNaMpesaShortCode and lipaNaMpesaPassKey are required for STK Push.`});let r=await this.getToken();return Me(this.baseUrl,r,{...e,shortCode:t,passKey:n})}async stkQuery(e){let t=this.config.lipaNaMpesaShortCode??``,n=this.config.lipaNaMpesaPassKey??``;if(!t||!n)throw new s({code:`VALIDATION_ERROR`,message:`lipaNaMpesaShortCode and lipaNaMpesaPassKey are required for STK Query.`});let r=await this.getToken();return Ne(this.baseUrl,r,{...e,shortCode:t,passKey:n})}async transactionStatus(e){let t=this.requireInitiator(`Transaction Status`),[n,r]=await Promise.all([this.getToken(),this.buildSecurityCredential()]);return Ie(this.baseUrl,n,r,t,e)}async accountBalance(e){let t=this.requireInitiator(`Account Balance`),[n,r]=await Promise.all([this.getToken(),this.buildSecurityCredential()]);return re(this.baseUrl,n,r,t,e)}async reverseTransaction(e){let t=this.requireInitiator(`Reversal`),[n,r]=await Promise.all([this.getToken(),this.buildSecurityCredential()]);return Oe(this.baseUrl,n,r,t,e)}async generateDynamicQR(e){let t=await this.getToken();return De(this.baseUrl,t,e)}async registerC2BUrls(e){let t=await this.getToken();return V(this.baseUrl,t,e)}async simulateC2B(e){let t=await this.getToken();return H(this.baseUrl,t,e)}async remitTax(e){let t=this.requireInitiator(`Tax Remittance`),[n,r]=await Promise.all([this.getToken(),this.buildSecurityCredential()]);return Y(this.baseUrl,n,r,t,e)}async b2bExpressCheckout(e){let t=await this.getToken();return x(this.baseUrl,t,e)}async b2cPayment(e){let t=this.requireInitiator(`B2C Payment`),[n,r]=await Promise.all([this.getToken(),this.buildSecurityCredential()]);return C(this.baseUrl,n,r,t,e)}async billManagerOptIn(e){let t=await this.getToken();return R(this.baseUrl,t,e)}async sendInvoice(e){let t=await this.getToken();return z(this.baseUrl,t,e)}async sendBulkInvoices(e){let t=await this.getToken();return he(this.baseUrl,t,e)}async cancelInvoice(e){let t=await this.getToken();return ge(this.baseUrl,t,e)}clearTokenCache(){this.tokenManager.clearCache()}get environment(){return this.config.environment}};const Re={maxRetries:1/0,initialDelay:1e3,maxDelay:36e5,backoffMultiplier:2,maxRetryDuration:720*60*60*1e3};async function ze(e,t={}){let n={...Re,...t},r=n.initialDelay,i=0,a=Date.now();for(;i<n.maxRetries;){if(i++,Date.now()-a>n.maxRetryDuration)return{success:!1,attempts:i,error:Error(`Max retry duration exceeded`)};try{return{success:!0,data:await e(),attempts:i}}catch(e){let t=e instanceof Error?e:Error(String(e));if(t.message.includes(`4`))return{success:!1,attempts:i,error:t};i<n.maxRetries&&(await new Promise(e=>setTimeout(e,r)),r=Math.min(r*n.backoffMultiplier,n.maxDelay))}}return{success:!1,attempts:i,error:Error(`Max retries exceeded`)}}const Z=[`196.201.214.200`,`196.201.214.206`,`196.201.213.114`,`196.201.214.207`,`196.201.214.208`,`196.201.213.44`,`196.201.212.127`,`196.201.212.138`,`196.201.212.129`,`196.201.212.136`,`196.201.212.74`,`196.201.212.69`];function Q(e,t=Z){return t.includes(e)}function $(e){try{let t=e;return t?.Body?.stkCallback?t:null}catch{return null}}function Be(e,t={}){if(!t.skipIPCheck&&t.requestIP&&!Q(t.requestIP,t.allowedIPs))return{success:!1,eventType:null,data:null,error:`IP address ${t.requestIP} is not in the Safaricom whitelist`};let n=$(e);return n?{success:!0,eventType:`stk_push`,data:n}:{success:!1,eventType:null,data:null,error:`Unknown or malformed webhook payload`}}function Ve(e){let t=(e.Body?.stkCallback?.CallbackMetadata?.Item)?.find(e=>e.Name===`MpesaReceiptNumber`);return t?String(t.Value):null}function He(e){let t=(e.Body?.stkCallback?.CallbackMetadata?.Item)?.find(e=>e.Name===`Amount`);return t?Number(t.Value):null}function Ue(e){let t=(e.Body?.stkCallback?.CallbackMetadata?.Item)?.find(e=>e.Name===`PhoneNumber`);return t?String(t.Value):null}function We(e){return e.Body?.stkCallback?.ResultCode===0}export{X as DARAJA_BASE_URLS,Fe as KRA_SHORTCODE,Le as Mpesa,s as PesafyError,Z as SAFARICOM_IPS,J as TAX_COMMAND_ID,ye as acceptC2BValidation,xe as acknowledgeC2BConfirmation,c as createError,p as encryptSecurityCredential,b as err,He as extractAmount,Ue as extractPhoneNumber,Ve as extractTransactionId,W as formatPhoneNumber,W as formatSafaricomPhone,ae as getAccountBalanceParam,de as getB2BAmount,pe as getB2BConversationId,fe as getB2BRequestId,ue as getB2BTransactionId,A as getB2CAmount,D as getB2CConversationId,P as getB2CCurrency,F as getB2CDebitAccountBalance,M as getB2CDebitPartyCharges,I as getB2CInitiatorAccountBalance,O as getB2COriginatorConversationId,N as getB2CReceiverPublicName,k as getB2CResultDesc,L as getB2CResultParam,j as getB2CTransactionCompletedTime,E as getB2CTransactionId,Ce as getC2BAccountRef,Se as getC2BAmount,we as getC2BCustomerName,U as getC2BTransactionId,Pe as getCallbackValue,je as getReversalConversationId,Ae as getReversalTransactionId,K as getTimestamp,Be as handleWebhook,d as httpRequest,x as initiateB2BExpressCheckout,C as initiateB2CPayment,oe as isAccountBalanceSuccess,le as isB2BCheckoutCallback,ce as isB2BCheckoutCancelled,S as isB2BCheckoutSuccess,T as isB2CFailure,me as isB2CResult,w as isB2CSuccess,Ee as isBuyGoodsPayment,ve as isC2BPayload,Te as isPaybillPayment,l as isPesafyError,ke as isReversalSuccess,q as isStkCallbackSuccess,We as isSuccessfulCallback,y as ok,ie as parseAccountBalance,$ as parseStkPushWebhook,V as registerC2BUrls,be as rejectC2BValidation,Y as remitTax,ze as retryWithBackoff,H as simulateC2B,m as toKesAmount,h as toMsisdn,ne as toNonEmpty,g as toPaybill,v as toShortCode,_ as toTill,Q as verifyWebhookIP};
1
+ import{readFile as e}from"node:fs/promises";import{constants as t,publicEncrypt as n}from"node:crypto";function r(e){"@babel/helpers - typeof";return r=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},r(e)}function i(e,t){if(r(e)!=`object`||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var i=n.call(e,t||`default`);if(r(i)!=`object`)return i;throw TypeError(`@@toPrimitive must return a primitive value.`)}return(t===`string`?String:Number)(e)}function a(e){var t=i(e,`string`);return r(t)==`symbol`?t:t+``}function o(e,t,n){return(t=a(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var s=class e extends Error{constructor(t){super(t.message),o(this,`code`,void 0),o(this,`statusCode`,void 0),o(this,`response`,void 0),o(this,`requestId`,void 0),o(this,`cause`,void 0),o(this,`retryable`,void 0),Object.defineProperty(this,`name`,{value:`PesafyError`}),this.code=t.code,this.statusCode=t.statusCode,this.response=t.response,this.requestId=t.requestId,this.cause=t.cause,this.retryable=t.retryable??(t.code===`NETWORK_ERROR`||t.code===`TIMEOUT`||t.code===`RATE_LIMITED`||t.code===`REQUEST_FAILED`),Error.captureStackTrace&&Error.captureStackTrace(this,e)}get isValidation(){return this.code===`VALIDATION_ERROR`}get isAuth(){return this.code===`AUTH_FAILED`||this.code===`INVALID_CREDENTIALS`}toJSON(){return{name:this.name,code:this.code,message:this.message,statusCode:this.statusCode,requestId:this.requestId,retryable:this.retryable}}};function c(e){return new s(e)}function l(e){return e instanceof s}const ee=new Set([429,500,502,503,504]);function te(e){return new Promise(t=>setTimeout(t,e))}function u(e){let t=e*.25;return e+(Math.random()*t*2-t)}async function d(e,t){let n=t.retries??4,r=t.retryDelay??2e3,i=t.timeout??3e4,a={"Content-Type":`application/json`,Accept:`application/json`,...t.headers};t.idempotencyKey&&(a[`Idempotency-Key`]=t.idempotencyKey);let o={method:t.method,headers:a,...t.body===void 0?{}:{body:JSON.stringify(t.body)}},c=null;for(let a=0;a<=n;a++){if(a>0){let i=u(r*2**(a-1));console.warn(`[pesafy] Retry ${a}/${n} → ${t.method} ${e} in ${Math.round(i)} ms`),await te(i)}let l=new AbortController,d=setTimeout(()=>l.abort(),i),f;try{f=await fetch(e,{...o,signal:l.signal})}catch(t){if(clearTimeout(d),c=t instanceof Error&&t.name===`AbortError`?new s({code:`TIMEOUT`,message:`Request to ${e} timed out after ${i} ms`,cause:t,retryable:!0}):new s({code:`NETWORK_ERROR`,message:`Network error: ${t instanceof Error?t.message:String(t)}`,cause:t,retryable:!0}),a<n)continue;throw c}finally{clearTimeout(d)}let p=``,m=null,h=f.headers.get(`content-type`)??``;try{p=await f.text(),p&&(m=h.includes(`application/json`)?JSON.parse(p):p)}catch{m=p||null}let g={};if(f.headers.forEach((e,t)=>{g[t]=e}),f.ok)return{data:m,status:f.status,headers:g};let _=ee.has(f.status),v=typeof m==`object`&&m?m:{},y=v.errorMessage??v.ResponseDescription??v.resultDesc??p??`HTTP ${f.status}`;if(c=new s({code:_?`REQUEST_FAILED`:`API_ERROR`,message:y,statusCode:f.status,response:m,retryable:_,...typeof v.requestId==`string`?{requestId:v.requestId}:{}}),!(_&&a<n))throw c}throw c}var f=class{constructor(e,t,n){o(this,`consumerKey`,void 0),o(this,`consumerSecret`,void 0),o(this,`baseUrl`,void 0),o(this,`cachedToken`,null),o(this,`tokenExpiresAt`,0),this.consumerKey=e,this.consumerSecret=t,this.baseUrl=n}getBasicAuthHeader(){let e=`${this.consumerKey}:${this.consumerSecret}`;return`Basic ${Buffer.from(e,`utf-8`).toString(`base64`)}`}async getAccessToken(){let e=Date.now()/1e3;if(this.cachedToken&&this.tokenExpiresAt>e+60)return this.cachedToken;let t=await d(`${this.baseUrl}/oauth/v1/generate?grant_type=client_credentials`,{method:`GET`,headers:{Authorization:this.getBasicAuthHeader()}}),{access_token:n,expires_in:r}=t.data;if(!n)throw new s({code:`AUTH_FAILED`,message:`Daraja did not return an access token. Check your consumer key and secret.`,response:t.data});return this.cachedToken=n,this.tokenExpiresAt=e+(r??3600),this.cachedToken}clearCache(){this.cachedToken=null,this.tokenExpiresAt=0}};function p(e,r){try{let i=Buffer.from(e,`utf-8`);return n({key:r,padding:t.RSA_PKCS1_PADDING},i).toString(`base64`)}catch(e){throw new s({code:`ENCRYPTION_FAILED`,message:`Failed to encrypt security credential. Ensure the certificate PEM is valid and matches the environment (sandbox/production).`,cause:e})}}function m(e){let t=Math.round(e);if(!Number.isFinite(t)||t<1)throw TypeError(`KesAmount must be a whole number ≥ 1, got ${e}`);return t}function h(e){let t=e.replace(/\D/g,``),n;if(t.startsWith(`254`)&&t.length===12)n=t;else if(t.startsWith(`0`)&&t.length===10)n=`254${t.slice(1)}`;else if(t.length===9)n=`254${t}`;else throw TypeError(`Cannot normalise "${e}" to 254XXXXXXXXX. Use 07XX…, 2547XX…, or +2547XX….`);if(n.length!==12)throw TypeError(`Phone "${e}" normalised to "${n}" — expected 12 digits.`);return n}function g(e){return String(e)}function _(e){return String(e)}function v(e){return String(e)}function y(e){return{ok:!0,data:e}}function b(e){return{ok:!1,error:e}}function ne(e){if(!e.trim())throw TypeError(`String must not be empty`);return e}async function re(e,t,n,r,i){if(!i.partyA?.trim())throw c({code:`VALIDATION_ERROR`,message:`partyA is required.`});if(![`1`,`2`,`4`].includes(i.identifierType))throw c({code:`VALIDATION_ERROR`,message:`identifierType must be "1" (MSISDN), "2" (Till), or "4" (ShortCode).`});if(!i.resultUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`resultUrl is required.`});if(!i.queueTimeOutUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`queueTimeOutUrl is required.`});let a={Initiator:r,SecurityCredential:n,CommandID:`AccountBalance`,PartyA:String(i.partyA),IdentifierType:i.identifierType,ResultURL:i.resultUrl,QueueTimeOutURL:i.queueTimeOutUrl,Remarks:i.remarks??`Account Balance Query`},{data:o}=await d(`${e}/mpesa/accountbalance/v1/query`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:a});return o}function ie(e){let t=e.split(`|`),n=[];for(let e=0;e+2<t.length;e+=3){let r=t[e]?.trim(),i=t[e+1]?.trim(),a=t[e+2]?.trim();r&&i&&a!==void 0&&n.push({name:r,currency:i,amount:a})}return n}function ae(e,t){let n=e.Result.ResultParameters?.ResultParameter;if(n)return(Array.isArray(n)?n:[n]).find(e=>e.Key===t)?.Value}function oe(e){return e.Result.ResultCode===0}function se(){try{if(typeof crypto<`u`&&crypto.randomUUID)return crypto.randomUUID()}catch{}return`${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`}async function x(e,t,n){if(!n.primaryShortCode?.trim())throw c({code:`VALIDATION_ERROR`,message:`primaryShortCode is required — the merchant's till number (debit party).`});if(!n.receiverShortCode?.trim())throw c({code:`VALIDATION_ERROR`,message:`receiverShortCode is required — the vendor's Paybill account (credit party).`});let r=Math.round(n.amount);if(!Number.isFinite(r)||r<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be a whole number ≥ 1 (got ${n.amount} which rounds to ${r}).`});if(!n.paymentRef?.trim())throw c({code:`VALIDATION_ERROR`,message:`paymentRef is required — shown in the merchant's USSD prompt as the payment reference.`});if(!n.callbackUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`callbackUrl is required — Safaricom POSTs the transaction result here.`});if(!n.partnerName?.trim())throw c({code:`VALIDATION_ERROR`,message:`partnerName is required — your friendly name shown in the merchant's USSD prompt.`});let i={primaryShortCode:String(n.primaryShortCode),receiverShortCode:String(n.receiverShortCode),amount:String(r),paymentRef:n.paymentRef,callbackUrl:n.callbackUrl,partnerName:n.partnerName,RequestRefID:n.requestRefId??se()},{data:a}=await d(`${e}/v1/ussdpush/get-msisdn`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:i});return a}function S(e){return e.resultCode===`0`}function ce(e){return e.resultCode===`4001`}function le(e){if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.resultCode==`string`&&typeof t.requestId==`string`&&typeof t.amount==`string`}function ue(e){return S(e)?e.transactionId??null:null}function de(e){return Number(e.amount)}function fe(e){return e.requestId}function pe(e){return S(e)?e.conversationID??null:null}async function C(e,t,n,r,i){if(!i.commandId)throw c({code:`VALIDATION_ERROR`,message:`commandId is required: "BusinessPayToBulk" | "BusinessPayment" | "SalaryPayment" | "PromotionPayment"`});let a=[`BusinessPayToBulk`,`BusinessPayment`,`SalaryPayment`,`PromotionPayment`];if(!a.includes(i.commandId))throw c({code:`VALIDATION_ERROR`,message:`commandId must be one of: ${a.join(`, `)}. Got: "${i.commandId}"`});let o=Math.round(i.amount);if(!Number.isFinite(o)||o<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be a whole number ≥ 1 (got ${i.amount} which rounds to ${o}).`});if(!i.partyA?.trim())throw c({code:`VALIDATION_ERROR`,message:`partyA is required — your business shortcode from which money is deducted.`});if(!i.partyB?.trim())throw c({code:`VALIDATION_ERROR`,message:`partyB is required — the recipient shortcode (BusinessPayToBulk) or customer MSISDN (other commands).`});if(!i.accountReference?.trim())throw c({code:`VALIDATION_ERROR`,message:`accountReference is required — a reference for this transaction.`});if(!i.resultUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`resultUrl is required — Safaricom POSTs the B2C result here.`});if(!i.queueTimeOutUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`queueTimeOutUrl is required — Safaricom calls this on request timeout.`});let s={Initiator:r,SecurityCredential:n,CommandID:i.commandId,SenderIdentifierType:i.senderIdentifierType??`4`,RecieverIdentifierType:i.receiverIdentifierType??`4`,Amount:String(o),PartyA:String(i.partyA),PartyB:String(i.partyB),AccountReference:i.accountReference,Remarks:i.remarks??`B2C Payment`,QueueTimeOutURL:i.queueTimeOutUrl,ResultURL:i.resultUrl};i.requester?.trim()&&(s.Requester=String(i.requester));let{data:l}=await d(`${e}/mpesa/b2b/v1/paymentrequest`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:s});return l}function me(e){if(!e||typeof e!=`object`)return!1;let t=e;if(!t.Result||typeof t.Result!=`object`)return!1;let n=t.Result;return typeof n.ResultCode==`number`&&typeof n.ConversationID==`string`}function w(e){return e.Result.ResultCode===0}function T(e){return e.Result.ResultCode!==0}function E(e){return e.Result.TransactionID??null}function D(e){return e.Result.ConversationID}function O(e){return e.Result.OriginatorConversationID}function k(e){return e.Result.ResultDesc}function A(e){let t=L(e,`Amount`);return t===void 0?null:Number(t)}function j(e){let t=L(e,`TransCompletedTime`);return t===void 0?null:String(t)}function M(e){let t=L(e,`DebitPartyCharges`);return t===void 0||t===``?null:String(t)}function N(e){let t=L(e,`ReceiverPartyPublicName`);return t===void 0?null:String(t)}function P(e){let t=L(e,`Currency`);return t===void 0?`KES`:String(t)}function F(e){let t=L(e,`DebitAccountBalance`);return t===void 0?null:String(t)}function I(e){let t=L(e,`InitiatorAccountCurrentBalance`);return t===void 0?null:String(t)}function L(e,t){let n=e.Result.ResultParameters?.ResultParameter;if(n)return(Array.isArray(n)?n:[n]).find(e=>e.Key===t)?.Value}async function R(e,t,n){if(!n.shortcode?.trim())throw c({code:`VALIDATION_ERROR`,message:`shortcode is required.`});if(!n.email?.trim())throw c({code:`VALIDATION_ERROR`,message:`email is required.`});if(!n.callbackUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`callbackUrl is required.`});let r={shortcode:n.shortcode,email:n.email,officialContact:n.officialContact,sendReminders:n.sendReminders,logo:n.logo??``,callbackUrl:n.callbackUrl},{data:i}=await d(`${e}/v1/billmanager-invoice/optin`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:r});return i}async function z(e,t,n){if(!n.externalReference?.trim())throw c({code:`VALIDATION_ERROR`,message:`externalReference is required.`});if(!n.partyA?.trim())throw c({code:`VALIDATION_ERROR`,message:`partyA (customer MSISDN) is required.`});let r=Math.round(n.amount);if(!Number.isFinite(r)||r<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be ≥ 1 (got ${n.amount}).`});let i={externalReference:n.externalReference,billingPeriod:n.billingPeriod,invoiceName:n.invoiceName,dueDate:n.dueDate,accountReference:n.accountReference,amount:String(r),partyA:n.partyA,invoiceItems:n.invoiceItems?.map(e=>({itemName:e.itemName,amount:String(Math.round(e.amount))}))??[]},{data:a}=await d(`${e}/v1/billmanager-invoice/single-invoicing`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:i});return a}async function he(e,t,n){if(!n.invoices?.length)throw c({code:`VALIDATION_ERROR`,message:`invoices array must not be empty.`});if(n.invoices.length>1e3)throw c({code:`VALIDATION_ERROR`,message:`Maximum 1 000 invoices per bulk request.`});let{data:r}=await d(`${e}/v1/billmanager-invoice/bulk-invoicing`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:n.invoices});return r}async function ge(e,t,n){if(!n.externalReference?.trim())throw c({code:`VALIDATION_ERROR`,message:`externalReference is required.`});let{data:r}=await d(`${e}/v1/billmanager-invoice/cancel-single-invoice`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:[{externalReference:n.externalReference}]});return r}const _e=[`mpesa`,`safaricom`,`.exe`,`.exec`,`cmd`,`sql`,`query`];function B(e,t){if(!e||!e.trim())throw c({code:`VALIDATION_ERROR`,message:`${t} is required`});let n=e.toLowerCase();for(let e of _e)if(n.includes(e))throw c({code:`VALIDATION_ERROR`,message:`${t} must not contain the keyword "${e}". Daraja rejects URLs containing: M-PESA, Safaricom, exe, exec, cmd, sql, query.`})}async function V(e,t,n){if(!n.shortCode)throw c({code:`VALIDATION_ERROR`,message:`shortCode is required`});if(!n.responseType)throw c({code:`VALIDATION_ERROR`,message:`responseType is required: "Completed" or "Cancelled" (sentence case, exactly as spelled)`});if(n.responseType!==`Completed`&&n.responseType!==`Cancelled`)throw c({code:`VALIDATION_ERROR`,message:`responseType must be exactly "Completed" or "Cancelled" (sentence case). Got: "${String(n.responseType)}"`});B(n.confirmationUrl,`confirmationUrl`),B(n.validationUrl,`validationUrl`);let r=n.apiVersion??`v2`,i={ShortCode:String(n.shortCode),ResponseType:n.responseType,ConfirmationURL:n.confirmationUrl,ValidationURL:n.validationUrl},{data:a}=await d(`${e}/mpesa/c2b/${r}/registerurl`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:i});return a}async function H(e,t,n){if(!e.includes(`sandbox`))throw c({code:`VALIDATION_ERROR`,message:`C2B simulate is only available in the Sandbox environment. In production, customers initiate payments via M-PESA App, USSD, or SIM Toolkit.`});if(!n.shortCode)throw c({code:`VALIDATION_ERROR`,message:`shortCode is required.`});if(n.commandId!==`CustomerPayBillOnline`&&n.commandId!==`CustomerBuyGoodsOnline`)throw c({code:`VALIDATION_ERROR`,message:`commandId must be "CustomerPayBillOnline" or "CustomerBuyGoodsOnline". Got: "${String(n.commandId)}"`});let r=Math.round(n.amount);if(!Number.isFinite(r)||r<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be a whole number ≥ 1 (got ${n.amount}).`});if(!n.msisdn)throw c({code:`VALIDATION_ERROR`,message:`msisdn is required. Sandbox test MSISDN: 254708374149.`});let i=n.commandId===`CustomerBuyGoodsOnline`,a=n.apiVersion??`v2`,o={ShortCode:Number(n.shortCode),CommandID:n.commandId,Amount:r,Msisdn:Number(n.msisdn)};i||(o.BillRefNumber=n.billRefNumber??``),i&&Object.prototype.hasOwnProperty.call(o,`BillRefNumber`)&&(delete o.BillRefNumber,console.warn(`[pesafy/simulateC2B] BillRefNumber leaked into Buy Goods payload — removed. This is a library bug; please report it.`));let{data:s}=await d(`${e}/mpesa/c2b/${a}/simulate`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:o});return s}function ve(e){if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.TransID==`string`&&typeof t.BusinessShortCode==`string`&&typeof t.TransAmount==`string`}function ye(e){return{ResultCode:`0`,ResultDesc:`Accepted`,...e?{ThirdPartyTransID:e}:{}}}function be(e=`C2B00016`,t=`Rejected`){return{ResultCode:e,ResultDesc:t}}function xe(){return{ResultCode:0,ResultDesc:`Success`}}function Se(e){return Number(e.TransAmount)}function U(e){return e.TransID}function Ce(e){return e.BillRefNumber}function we(e){return[e.FirstName,e.MiddleName,e.LastName].filter(Boolean).join(` `).trim()}function Te(e){return e.TransactionType===`Pay Bill`||e.TransactionType===`CustomerPayBillOnline`}function Ee(e){return e.TransactionType===`Buy Goods`||e.TransactionType===`CustomerBuyGoodsOnline`}async function De(e,t,n){if(!n.merchantName?.trim())throw c({code:`VALIDATION_ERROR`,message:`merchantName is required`});if(!n.refNo?.trim())throw c({code:`VALIDATION_ERROR`,message:`refNo (transaction reference) is required`});let r=Math.round(n.amount);if(!Number.isFinite(r)||r<1)throw c({code:`VALIDATION_ERROR`,message:`Amount must be at least 1 (got ${n.amount} which rounds to ${r}).`});if(!n.trxCode)throw c({code:`VALIDATION_ERROR`,message:`trxCode is required. Supported values: "BG" | "WA" | "PB" | "SM" | "SB"`});if(!n.cpi?.trim())throw c({code:`VALIDATION_ERROR`,message:`cpi (Credit Party Identifier) is required — e.g. till number, paybill, or MSISDN`});let i=n.size??300;if(i<1)throw c({code:`VALIDATION_ERROR`,message:`size must be a positive number of pixels`});let a={MerchantName:n.merchantName,RefNo:n.refNo,Amount:r,TrxCode:n.trxCode,CPI:n.cpi,Size:String(i)},{data:o}=await d(`${e}/mpesa/qrcode/v1/generate`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:a});return o}async function Oe(e,t,n,r,i){if(!i.transactionId?.trim())throw c({code:`VALIDATION_ERROR`,message:`transactionId is required.`});if(!i.receiverParty?.trim())throw c({code:`VALIDATION_ERROR`,message:`receiverParty is required.`});if(![`1`,`2`,`4`].includes(i.receiverIdentifierType))throw c({code:`VALIDATION_ERROR`,message:`receiverIdentifierType must be "1", "2", or "4".`});let a=Math.round(i.amount);if(!Number.isFinite(a)||a<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be a whole number ≥ 1 (got ${i.amount}).`});if(!i.resultUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`resultUrl is required.`});if(!i.queueTimeOutUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`queueTimeOutUrl is required.`});let o={Initiator:r,SecurityCredential:n,CommandID:`TransactionReversal`,TransactionID:i.transactionId,Amount:String(a),ReceiverParty:String(i.receiverParty),RecieverIdentifierType:i.receiverIdentifierType,ResultURL:i.resultUrl,QueueTimeOutURL:i.queueTimeOutUrl,Remarks:i.remarks??`Transaction Reversal`,Occasion:i.occasion??``},{data:s}=await d(`${e}/mpesa/reversal/v1/request`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:o});return s}function ke(e){return e.Result.ResultCode===0}function Ae(e){return e.Result.TransactionID??null}function je(e){return e.Result.ConversationID}function W(e){let t=e.replace(/\D/g,``),n;if(t.startsWith(`254`)&&t.length===12)n=t;else if(t.startsWith(`0`)&&t.length===10)n=`254${t.slice(1)}`;else if(t.length===9)n=`254${t}`;else throw new s({code:`INVALID_PHONE`,message:`Cannot parse "${e}". Use 07XXXXXXXX, 2547XXXXXXXX, or +2547XXXXXXXX.`});if(n.length!==12)throw new s({code:`INVALID_PHONE`,message:`"${e}" normalised to "${n}" — expected 12 digits.`});return n}function G(e,t,n){return btoa(`${e}${t}${n}`)}function K(){let e=new Date,t=e=>e.toString().padStart(2,`0`);return[e.getFullYear(),t(e.getMonth()+1),t(e.getDate()),t(e.getHours()),t(e.getMinutes()),t(e.getSeconds())].join(``)}async function Me(e,t,n){let r=Math.round(n.amount);if(r<1)throw new s({code:`VALIDATION_ERROR`,message:`Amount must be at least KES 1 (got ${n.amount} which rounds to ${r}).`});let i=K(),a=n.partyB??n.shortCode,o={BusinessShortCode:n.shortCode,Password:G(n.shortCode,n.passKey,i),Timestamp:i,TransactionType:n.transactionType??`CustomerPayBillOnline`,Amount:r,PartyA:W(n.phoneNumber),PartyB:a,PhoneNumber:W(n.phoneNumber),CallBackURL:n.callbackUrl,AccountReference:n.accountReference.slice(0,12),TransactionDesc:n.transactionDesc.slice(0,13)},{data:c}=await d(`${e}/mpesa/stkpush/v1/processrequest`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:o,retries:5,retryDelay:3e3});return c}async function Ne(e,t,n){let r=K(),i={BusinessShortCode:n.shortCode,Password:G(n.shortCode,n.passKey,r),Timestamp:r,CheckoutRequestID:n.checkoutRequestId},{data:a}=await d(`${e}/mpesa/stkpushquery/v1/query`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:i});return a}function q(e){return e.ResultCode===0}function Pe(e,t){let n=e.Body.stkCallback;if(q(n))return n.CallbackMetadata.Item.find(e=>e.Name===t)?.Value}const Fe=`572572`,J=`PayTaxToKRA`;async function Y(e,t,n,r,i){let a=Math.round(i.amount);if(!Number.isFinite(a)||a<1)throw c({code:`VALIDATION_ERROR`,message:`amount must be a whole number ≥ 1 (got ${i.amount} which rounds to ${a}).`});if(!i.partyA)throw c({code:`VALIDATION_ERROR`,message:`partyA is required — your M-PESA business shortcode from which tax is deducted.`});if(!i.accountReference?.trim())throw c({code:`VALIDATION_ERROR`,message:`accountReference is required — the Payment Registration Number (PRN) issued by KRA.`});if(!i.resultUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`resultUrl is required — Safaricom POSTs the tax remittance result here.`});if(!i.queueTimeOutUrl?.trim())throw c({code:`VALIDATION_ERROR`,message:`queueTimeOutUrl is required — Safaricom calls this on request timeout.`});let o={Initiator:r,SecurityCredential:n,CommandID:J,SenderIdentifierType:`4`,RecieverIdentifierType:`4`,Amount:String(a),PartyA:String(i.partyA),PartyB:i.partyB??`572572`,AccountReference:i.accountReference,Remarks:i.remarks??`Tax Remittance`,QueueTimeOutURL:i.queueTimeOutUrl,ResultURL:i.resultUrl},{data:s}=await d(`${e}/mpesa/b2b/v1/remittax`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:o});return s}async function Ie(e,t,n,r,i){if(!i.transactionId)throw c({code:`VALIDATION_ERROR`,message:`transactionId is required`});if(!i.partyA)throw c({code:`VALIDATION_ERROR`,message:`partyA is required (your business shortcode, till number, or MSISDN)`});if(!i.identifierType)throw c({code:`VALIDATION_ERROR`,message:`identifierType is required: "1" (MSISDN) | "2" (Till) | "4" (ShortCode)`});if(!i.resultUrl)throw c({code:`VALIDATION_ERROR`,message:`resultUrl is required — Safaricom POSTs the transaction result here`});if(!i.queueTimeOutUrl)throw c({code:`VALIDATION_ERROR`,message:`queueTimeOutUrl is required — Safaricom calls this on timeout`});let a={Initiator:r,SecurityCredential:n,CommandID:i.commandId??`TransactionStatusQuery`,TransactionID:i.transactionId,PartyA:i.partyA,IdentifierType:i.identifierType,ResultURL:i.resultUrl,QueueTimeOutURL:i.queueTimeOutUrl,Remarks:i.remarks??`Transaction Status Query`,Occasion:i.occasion??``},{data:o}=await d(`${e}/mpesa/transactionstatus/v1/query`,{method:`POST`,headers:{Authorization:`Bearer ${t}`},body:a});return o}const X={sandbox:`https://sandbox.safaricom.co.ke`,production:`https://api.safaricom.co.ke`};var Le=class{constructor(e){if(o(this,`config`,void 0),o(this,`tokenManager`,void 0),o(this,`baseUrl`,void 0),!e.consumerKey||!e.consumerSecret)throw new s({code:`INVALID_CREDENTIALS`,message:`consumerKey and consumerSecret are required.`});this.config=e,this.baseUrl=X[e.environment],this.tokenManager=new f(e.consumerKey,e.consumerSecret,this.baseUrl)}getToken(){return this.tokenManager.getAccessToken()}async buildSecurityCredential(){if(this.config.securityCredential)return this.config.securityCredential;if(!this.config.initiatorPassword)throw new s({code:`INVALID_CREDENTIALS`,message:`Provide securityCredential (pre-encrypted) OR (initiatorPassword + certificatePath/certificatePem).`});let t;if(this.config.certificatePem)t=this.config.certificatePem;else if(this.config.certificatePath)t=await e(this.config.certificatePath,`utf-8`);else throw new s({code:`INVALID_CREDENTIALS`,message:`certificatePath or certificatePem is required to encrypt the initiator password.`});return p(this.config.initiatorPassword,t)}requireInitiator(e){let t=this.config.initiatorName??``;if(!t)throw new s({code:`VALIDATION_ERROR`,message:`initiatorName is required for ${e}.`});return t}async stkPushSafe(e){try{return y(await this.stkPush(e))}catch(e){return b(e)}}async stkPush(e){let t=this.config.lipaNaMpesaShortCode??``,n=this.config.lipaNaMpesaPassKey??``;if(!t||!n)throw new s({code:`VALIDATION_ERROR`,message:`lipaNaMpesaShortCode and lipaNaMpesaPassKey are required for STK Push.`});let r=await this.getToken();return Me(this.baseUrl,r,{...e,shortCode:t,passKey:n})}async stkQuery(e){let t=this.config.lipaNaMpesaShortCode??``,n=this.config.lipaNaMpesaPassKey??``;if(!t||!n)throw new s({code:`VALIDATION_ERROR`,message:`lipaNaMpesaShortCode and lipaNaMpesaPassKey are required for STK Query.`});let r=await this.getToken();return Ne(this.baseUrl,r,{...e,shortCode:t,passKey:n})}async transactionStatus(e){let t=this.requireInitiator(`Transaction Status`),[n,r]=await Promise.all([this.getToken(),this.buildSecurityCredential()]);return Ie(this.baseUrl,n,r,t,e)}async accountBalance(e){let t=this.requireInitiator(`Account Balance`),[n,r]=await Promise.all([this.getToken(),this.buildSecurityCredential()]);return re(this.baseUrl,n,r,t,e)}async reverseTransaction(e){let t=this.requireInitiator(`Reversal`),[n,r]=await Promise.all([this.getToken(),this.buildSecurityCredential()]);return Oe(this.baseUrl,n,r,t,e)}async generateDynamicQR(e){let t=await this.getToken();return De(this.baseUrl,t,e)}async registerC2BUrls(e){let t=await this.getToken();return V(this.baseUrl,t,e)}async simulateC2B(e){let t=await this.getToken();return H(this.baseUrl,t,e)}async remitTax(e){let t=this.requireInitiator(`Tax Remittance`),[n,r]=await Promise.all([this.getToken(),this.buildSecurityCredential()]);return Y(this.baseUrl,n,r,t,e)}async b2bExpressCheckout(e){let t=await this.getToken();return x(this.baseUrl,t,e)}async b2cPayment(e){let t=this.requireInitiator(`B2C Payment`),[n,r]=await Promise.all([this.getToken(),this.buildSecurityCredential()]);return C(this.baseUrl,n,r,t,e)}async billManagerOptIn(e){let t=await this.getToken();return R(this.baseUrl,t,e)}async sendInvoice(e){let t=await this.getToken();return z(this.baseUrl,t,e)}async sendBulkInvoices(e){let t=await this.getToken();return he(this.baseUrl,t,e)}async cancelInvoice(e){let t=await this.getToken();return ge(this.baseUrl,t,e)}clearTokenCache(){this.tokenManager.clearCache()}get environment(){return this.config.environment}};const Re={maxRetries:1/0,initialDelay:1e3,maxDelay:36e5,backoffMultiplier:2,maxRetryDuration:720*60*60*1e3};async function ze(e,t={}){let n={...Re,...t},r=n.initialDelay,i=0,a=Date.now();for(;i<n.maxRetries;){if(i++,Date.now()-a>n.maxRetryDuration)return{success:!1,attempts:i,error:Error(`Max retry duration exceeded`)};try{return{success:!0,data:await e(),attempts:i}}catch(e){let t=e instanceof Error?e:Error(String(e));if(t.message.includes(`4`))return{success:!1,attempts:i,error:t};i<n.maxRetries&&(await new Promise(e=>setTimeout(e,r)),r=Math.min(r*n.backoffMultiplier,n.maxDelay))}}return{success:!1,attempts:i,error:Error(`Max retries exceeded`)}}const Z=[`196.201.214.200`,`196.201.214.206`,`196.201.213.114`,`196.201.214.207`,`196.201.214.208`,`196.201.213.44`,`196.201.212.127`,`196.201.212.138`,`196.201.212.129`,`196.201.212.136`,`196.201.212.74`,`196.201.212.69`];function Q(e,t=Z){return t.includes(e)}function $(e){try{let t=e;return t?.Body?.stkCallback?t:null}catch{return null}}function Be(e,t={}){if(!t.skipIPCheck&&t.requestIP&&!Q(t.requestIP,t.allowedIPs))return{success:!1,eventType:null,data:null,error:`IP address ${t.requestIP} is not in the Safaricom whitelist`};let n=$(e);return n?{success:!0,eventType:`stk_push`,data:n}:{success:!1,eventType:null,data:null,error:`Unknown or malformed webhook payload`}}function Ve(e){let t=(e.Body?.stkCallback?.CallbackMetadata?.Item)?.find(e=>e.Name===`MpesaReceiptNumber`);return t?String(t.Value):null}function He(e){let t=(e.Body?.stkCallback?.CallbackMetadata?.Item)?.find(e=>e.Name===`Amount`);return t?Number(t.Value):null}function Ue(e){let t=(e.Body?.stkCallback?.CallbackMetadata?.Item)?.find(e=>e.Name===`PhoneNumber`);return t?String(t.Value):null}function We(e){return e.Body?.stkCallback?.ResultCode===0}export{X as DARAJA_BASE_URLS,Fe as KRA_SHORTCODE,Le as Mpesa,s as PesafyError,Z as SAFARICOM_IPS,J as TAX_COMMAND_ID,ye as acceptC2BValidation,xe as acknowledgeC2BConfirmation,c as createError,p as encryptSecurityCredential,b as err,He as extractAmount,Ue as extractPhoneNumber,Ve as extractTransactionId,W as formatPhoneNumber,W as formatSafaricomPhone,ae as getAccountBalanceParam,de as getB2BAmount,pe as getB2BConversationId,fe as getB2BRequestId,ue as getB2BTransactionId,A as getB2CAmount,D as getB2CConversationId,P as getB2CCurrency,F as getB2CDebitAccountBalance,M as getB2CDebitPartyCharges,I as getB2CInitiatorAccountBalance,O as getB2COriginatorConversationId,N as getB2CReceiverPublicName,k as getB2CResultDesc,L as getB2CResultParam,j as getB2CTransactionCompletedTime,E as getB2CTransactionId,Ce as getC2BAccountRef,Se as getC2BAmount,we as getC2BCustomerName,U as getC2BTransactionId,Pe as getCallbackValue,je as getReversalConversationId,Ae as getReversalTransactionId,K as getTimestamp,Be as handleWebhook,d as httpRequest,x as initiateB2BExpressCheckout,C as initiateB2CPayment,oe as isAccountBalanceSuccess,le as isB2BCheckoutCallback,ce as isB2BCheckoutCancelled,S as isB2BCheckoutSuccess,T as isB2CFailure,me as isB2CResult,w as isB2CSuccess,Ee as isBuyGoodsPayment,ve as isC2BPayload,Te as isPaybillPayment,l as isPesafyError,ke as isReversalSuccess,q as isStkCallbackSuccess,We as isSuccessfulCallback,y as ok,ie as parseAccountBalance,$ as parseStkPushWebhook,V as registerC2BUrls,be as rejectC2BValidation,Y as remitTax,ze as retryWithBackoff,H as simulateC2B,m as toKesAmount,h as toMsisdn,ne as toNonEmpty,g as toPaybill,v as toShortCode,_ as toTill,Q as verifyWebhookIP};
@@ -1,4 +1,4 @@
1
- import { t as PesafyError } from "./errors.js";
1
+ import { t as PesafyError } from "./errors.mjs";
2
2
 
3
3
  //#region src/utils/phone/index.ts
4
4
  /** Normalises any common Kenyan phone format to 254XXXXXXXXX (12 digits) */