pesafy 0.4.1 → 0.4.3
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/dist/index.cjs +377 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +588 -53
- package/dist/index.d.ts +588 -53
- package/dist/index.js +351 -58
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var crypto = require('crypto');
|
|
3
|
+
var crypto$1 = require('crypto');
|
|
4
4
|
|
|
5
5
|
// Pesafy - Payment Gateway Library
|
|
6
6
|
// https://github.com/levos-snr/pesafy
|
|
@@ -45,11 +45,11 @@ function createError(options) {
|
|
|
45
45
|
function encryptSecurityCredential(initiatorPassword, certificatePem) {
|
|
46
46
|
try {
|
|
47
47
|
const passwordBuffer = Buffer.from(initiatorPassword, "utf-8");
|
|
48
|
-
const encrypted = crypto.publicEncrypt(
|
|
48
|
+
const encrypted = crypto$1.publicEncrypt(
|
|
49
49
|
{
|
|
50
50
|
key: certificatePem,
|
|
51
51
|
// RSA_PKCS1_PADDING = 1 (NOT RSA_PKCS1_OAEP_PADDING = 4)
|
|
52
|
-
padding: crypto.constants.RSA_PKCS1_PADDING
|
|
52
|
+
padding: crypto$1.constants.RSA_PKCS1_PADDING
|
|
53
53
|
},
|
|
54
54
|
passwordBuffer
|
|
55
55
|
);
|
|
@@ -217,6 +217,255 @@ var TokenManager = class {
|
|
|
217
217
|
}
|
|
218
218
|
};
|
|
219
219
|
|
|
220
|
+
// src/mpesa/b2b-express-checkout/initiate.ts
|
|
221
|
+
function generateRequestRefId() {
|
|
222
|
+
try {
|
|
223
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
224
|
+
return crypto.randomUUID();
|
|
225
|
+
}
|
|
226
|
+
} catch {
|
|
227
|
+
}
|
|
228
|
+
return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`;
|
|
229
|
+
}
|
|
230
|
+
async function initiateB2BExpressCheckout(baseUrl, accessToken, request) {
|
|
231
|
+
if (!request.primaryShortCode?.trim()) {
|
|
232
|
+
throw createError({
|
|
233
|
+
code: "VALIDATION_ERROR",
|
|
234
|
+
message: "primaryShortCode is required \u2014 the merchant's till number (debit party)."
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
if (!request.receiverShortCode?.trim()) {
|
|
238
|
+
throw createError({
|
|
239
|
+
code: "VALIDATION_ERROR",
|
|
240
|
+
message: "receiverShortCode is required \u2014 the vendor's Paybill account (credit party)."
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
const amount = Math.round(request.amount);
|
|
244
|
+
if (!Number.isFinite(amount) || amount < 1) {
|
|
245
|
+
throw createError({
|
|
246
|
+
code: "VALIDATION_ERROR",
|
|
247
|
+
message: `amount must be a whole number \u2265 1 (got ${request.amount} which rounds to ${amount}).`
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
if (!request.paymentRef?.trim()) {
|
|
251
|
+
throw createError({
|
|
252
|
+
code: "VALIDATION_ERROR",
|
|
253
|
+
message: "paymentRef is required \u2014 shown in the merchant's USSD prompt as the payment reference."
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
if (!request.callbackUrl?.trim()) {
|
|
257
|
+
throw createError({
|
|
258
|
+
code: "VALIDATION_ERROR",
|
|
259
|
+
message: "callbackUrl is required \u2014 Safaricom POSTs the transaction result here."
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
if (!request.partnerName?.trim()) {
|
|
263
|
+
throw createError({
|
|
264
|
+
code: "VALIDATION_ERROR",
|
|
265
|
+
message: "partnerName is required \u2014 your friendly name shown in the merchant's USSD prompt."
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
const payload = {
|
|
269
|
+
primaryShortCode: String(request.primaryShortCode),
|
|
270
|
+
receiverShortCode: String(request.receiverShortCode),
|
|
271
|
+
amount: String(amount),
|
|
272
|
+
paymentRef: request.paymentRef,
|
|
273
|
+
callbackUrl: request.callbackUrl,
|
|
274
|
+
partnerName: request.partnerName,
|
|
275
|
+
RequestRefID: request.requestRefId ?? generateRequestRefId()
|
|
276
|
+
};
|
|
277
|
+
const { data } = await httpRequest(
|
|
278
|
+
`${baseUrl}/v1/ussdpush/get-msisdn`,
|
|
279
|
+
{
|
|
280
|
+
method: "POST",
|
|
281
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
282
|
+
body: payload
|
|
283
|
+
}
|
|
284
|
+
);
|
|
285
|
+
return data;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// src/mpesa/b2b-express-checkout/webhooks.ts
|
|
289
|
+
function isB2BCheckoutSuccess(callback) {
|
|
290
|
+
return callback.resultCode === "0";
|
|
291
|
+
}
|
|
292
|
+
function isB2BCheckoutCancelled(callback) {
|
|
293
|
+
return callback.resultCode === "4001";
|
|
294
|
+
}
|
|
295
|
+
function isB2BCheckoutCallback(body) {
|
|
296
|
+
if (!body || typeof body !== "object") return false;
|
|
297
|
+
const b = body;
|
|
298
|
+
return typeof b["resultCode"] === "string" && typeof b["requestId"] === "string" && typeof b["amount"] === "string";
|
|
299
|
+
}
|
|
300
|
+
function getB2BTransactionId(callback) {
|
|
301
|
+
if (!isB2BCheckoutSuccess(callback)) return null;
|
|
302
|
+
return callback.transactionId ?? null;
|
|
303
|
+
}
|
|
304
|
+
function getB2BAmount(callback) {
|
|
305
|
+
return Number(callback.amount);
|
|
306
|
+
}
|
|
307
|
+
function getB2BRequestId(callback) {
|
|
308
|
+
return callback.requestId;
|
|
309
|
+
}
|
|
310
|
+
function getB2BConversationId(callback) {
|
|
311
|
+
if (!isB2BCheckoutSuccess(callback)) return null;
|
|
312
|
+
return callback.conversationID ?? null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/mpesa/b2c/payment.ts
|
|
316
|
+
async function initiateB2CPayment(baseUrl, accessToken, securityCredential, initiatorName, request) {
|
|
317
|
+
if (!request.commandId) {
|
|
318
|
+
throw createError({
|
|
319
|
+
code: "VALIDATION_ERROR",
|
|
320
|
+
message: 'commandId is required: "BusinessPayToBulk" | "BusinessPayment" | "SalaryPayment" | "PromotionPayment"'
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
const validCommandIds = [
|
|
324
|
+
"BusinessPayToBulk",
|
|
325
|
+
"BusinessPayment",
|
|
326
|
+
"SalaryPayment",
|
|
327
|
+
"PromotionPayment"
|
|
328
|
+
];
|
|
329
|
+
if (!validCommandIds.includes(request.commandId)) {
|
|
330
|
+
throw createError({
|
|
331
|
+
code: "VALIDATION_ERROR",
|
|
332
|
+
message: `commandId must be one of: ${validCommandIds.join(", ")}. Got: "${request.commandId}"`
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
const amount = Math.round(request.amount);
|
|
336
|
+
if (!Number.isFinite(amount) || amount < 1) {
|
|
337
|
+
throw createError({
|
|
338
|
+
code: "VALIDATION_ERROR",
|
|
339
|
+
message: `amount must be a whole number \u2265 1 (got ${request.amount} which rounds to ${amount}).`
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
if (!request.partyA?.trim()) {
|
|
343
|
+
throw createError({
|
|
344
|
+
code: "VALIDATION_ERROR",
|
|
345
|
+
message: "partyA is required \u2014 your business shortcode from which money is deducted."
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
if (!request.partyB?.trim()) {
|
|
349
|
+
throw createError({
|
|
350
|
+
code: "VALIDATION_ERROR",
|
|
351
|
+
message: "partyB is required \u2014 the recipient shortcode (BusinessPayToBulk) or customer MSISDN (other commands)."
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
if (!request.accountReference?.trim()) {
|
|
355
|
+
throw createError({
|
|
356
|
+
code: "VALIDATION_ERROR",
|
|
357
|
+
message: "accountReference is required \u2014 a reference for this transaction."
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
if (!request.resultUrl?.trim()) {
|
|
361
|
+
throw createError({
|
|
362
|
+
code: "VALIDATION_ERROR",
|
|
363
|
+
message: "resultUrl is required \u2014 Safaricom POSTs the B2C result here."
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
if (!request.queueTimeOutUrl?.trim()) {
|
|
367
|
+
throw createError({
|
|
368
|
+
code: "VALIDATION_ERROR",
|
|
369
|
+
message: "queueTimeOutUrl is required \u2014 Safaricom calls this on request timeout."
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
const payload = {
|
|
373
|
+
Initiator: initiatorName,
|
|
374
|
+
SecurityCredential: securityCredential,
|
|
375
|
+
CommandID: request.commandId,
|
|
376
|
+
SenderIdentifierType: request.senderIdentifierType ?? "4",
|
|
377
|
+
RecieverIdentifierType: request.receiverIdentifierType ?? "4",
|
|
378
|
+
Amount: String(amount),
|
|
379
|
+
PartyA: String(request.partyA),
|
|
380
|
+
PartyB: String(request.partyB),
|
|
381
|
+
AccountReference: request.accountReference,
|
|
382
|
+
Remarks: request.remarks ?? "B2C Payment",
|
|
383
|
+
QueueTimeOutURL: request.queueTimeOutUrl,
|
|
384
|
+
ResultURL: request.resultUrl
|
|
385
|
+
};
|
|
386
|
+
if (request.requester?.trim()) {
|
|
387
|
+
payload["Requester"] = String(request.requester);
|
|
388
|
+
}
|
|
389
|
+
const { data } = await httpRequest(
|
|
390
|
+
`${baseUrl}/mpesa/b2b/v1/paymentrequest`,
|
|
391
|
+
{
|
|
392
|
+
method: "POST",
|
|
393
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
394
|
+
body: payload
|
|
395
|
+
}
|
|
396
|
+
);
|
|
397
|
+
return data;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// src/mpesa/b2c/webhooks.ts
|
|
401
|
+
function isB2CResult(body) {
|
|
402
|
+
if (!body || typeof body !== "object") return false;
|
|
403
|
+
const b = body;
|
|
404
|
+
if (!b["Result"] || typeof b["Result"] !== "object") return false;
|
|
405
|
+
const result = b["Result"];
|
|
406
|
+
return typeof result["ResultCode"] === "number" && typeof result["ConversationID"] === "string";
|
|
407
|
+
}
|
|
408
|
+
function isB2CSuccess(result) {
|
|
409
|
+
return result.Result.ResultCode === 0;
|
|
410
|
+
}
|
|
411
|
+
function isB2CFailure(result) {
|
|
412
|
+
return result.Result.ResultCode !== 0;
|
|
413
|
+
}
|
|
414
|
+
function getB2CTransactionId(result) {
|
|
415
|
+
return result.Result.TransactionID ?? null;
|
|
416
|
+
}
|
|
417
|
+
function getB2CConversationId(result) {
|
|
418
|
+
return result.Result.ConversationID;
|
|
419
|
+
}
|
|
420
|
+
function getB2COriginatorConversationId(result) {
|
|
421
|
+
return result.Result.OriginatorConversationID;
|
|
422
|
+
}
|
|
423
|
+
function getB2CResultDesc(result) {
|
|
424
|
+
return result.Result.ResultDesc;
|
|
425
|
+
}
|
|
426
|
+
function getB2CAmount(result) {
|
|
427
|
+
const value = getB2CResultParam(result, "Amount");
|
|
428
|
+
if (value === void 0) return null;
|
|
429
|
+
return Number(value);
|
|
430
|
+
}
|
|
431
|
+
function getB2CTransactionCompletedTime(result) {
|
|
432
|
+
const value = getB2CResultParam(result, "TransCompletedTime");
|
|
433
|
+
if (value === void 0) return null;
|
|
434
|
+
return String(value);
|
|
435
|
+
}
|
|
436
|
+
function getB2CDebitPartyCharges(result) {
|
|
437
|
+
const value = getB2CResultParam(result, "DebitPartyCharges");
|
|
438
|
+
if (value === void 0 || value === "") return null;
|
|
439
|
+
return String(value);
|
|
440
|
+
}
|
|
441
|
+
function getB2CReceiverPublicName(result) {
|
|
442
|
+
const value = getB2CResultParam(result, "ReceiverPartyPublicName");
|
|
443
|
+
if (value === void 0) return null;
|
|
444
|
+
return String(value);
|
|
445
|
+
}
|
|
446
|
+
function getB2CCurrency(result) {
|
|
447
|
+
const value = getB2CResultParam(result, "Currency");
|
|
448
|
+
if (value === void 0) return "KES";
|
|
449
|
+
return String(value);
|
|
450
|
+
}
|
|
451
|
+
function getB2CDebitAccountBalance(result) {
|
|
452
|
+
const value = getB2CResultParam(result, "DebitAccountBalance");
|
|
453
|
+
if (value === void 0) return null;
|
|
454
|
+
return String(value);
|
|
455
|
+
}
|
|
456
|
+
function getB2CInitiatorAccountBalance(result) {
|
|
457
|
+
const value = getB2CResultParam(result, "InitiatorAccountCurrentBalance");
|
|
458
|
+
if (value === void 0) return null;
|
|
459
|
+
return String(value);
|
|
460
|
+
}
|
|
461
|
+
function getB2CResultParam(result, key) {
|
|
462
|
+
const params = result.Result.ResultParameters?.ResultParameter;
|
|
463
|
+
if (!params) return void 0;
|
|
464
|
+
const paramArray = Array.isArray(params) ? params : [params];
|
|
465
|
+
const item = paramArray.find((p) => p.Key === key);
|
|
466
|
+
return item?.Value;
|
|
467
|
+
}
|
|
468
|
+
|
|
220
469
|
// src/mpesa/c2b/register-url.ts
|
|
221
470
|
var FORBIDDEN_URL_KEYWORDS = [
|
|
222
471
|
"mpesa",
|
|
@@ -732,7 +981,7 @@ var Mpesa = class {
|
|
|
732
981
|
}
|
|
733
982
|
return encryptSecurityCredential(this.config.initiatorPassword, cert);
|
|
734
983
|
}
|
|
735
|
-
// ── STK Push
|
|
984
|
+
// ── STK Push ───────────────────────────────────────────────────────────────
|
|
736
985
|
/**
|
|
737
986
|
* M-Pesa Express — sends a payment prompt to the customer's phone.
|
|
738
987
|
*
|
|
@@ -789,6 +1038,7 @@ var Mpesa = class {
|
|
|
789
1038
|
passKey
|
|
790
1039
|
});
|
|
791
1040
|
}
|
|
1041
|
+
// ── Transaction Status ─────────────────────────────────────────────────────
|
|
792
1042
|
/**
|
|
793
1043
|
* Transaction Status — queries the result of a completed M-Pesa transaction.
|
|
794
1044
|
*
|
|
@@ -827,116 +1077,71 @@ var Mpesa = class {
|
|
|
827
1077
|
request
|
|
828
1078
|
);
|
|
829
1079
|
}
|
|
830
|
-
// ── Dynamic QR Code
|
|
1080
|
+
// ── Dynamic QR Code ────────────────────────────────────────────────────────
|
|
831
1081
|
/**
|
|
832
1082
|
* Dynamic QR — generates an M-PESA QR code for LNM merchant payments.
|
|
833
1083
|
*
|
|
834
|
-
* Customers scan the code with My Safaricom App or M-PESA app to
|
|
835
|
-
* capture the till/paybill number and amount, then authorize payment.
|
|
836
|
-
*
|
|
837
1084
|
* @example
|
|
838
1085
|
* const res = await mpesa.generateDynamicQR({
|
|
839
1086
|
* merchantName: "My Shop",
|
|
840
1087
|
* refNo: "INV-001",
|
|
841
1088
|
* amount: 500,
|
|
842
|
-
* trxCode: "BG",
|
|
1089
|
+
* trxCode: "BG",
|
|
843
1090
|
* cpi: "373132",
|
|
844
1091
|
* size: 300,
|
|
845
1092
|
* });
|
|
846
|
-
*
|
|
847
|
-
* // res.QRCode is a base64-encoded PNG — render in an <img> tag:
|
|
848
1093
|
* // <img src={`data:image/png;base64,${res.QRCode}`} />
|
|
849
1094
|
*/
|
|
850
1095
|
async generateDynamicQR(request) {
|
|
851
1096
|
const token = await this.getToken();
|
|
852
1097
|
return generateDynamicQR(this.baseUrl, token, request);
|
|
853
1098
|
}
|
|
854
|
-
// ── C2B Register URL
|
|
1099
|
+
// ── C2B Register URL ───────────────────────────────────────────────────────
|
|
855
1100
|
/**
|
|
856
1101
|
* Registers your Confirmation and Validation URLs with M-PESA.
|
|
857
1102
|
*
|
|
858
|
-
* Use v2 (default) for new integrations — callbacks include a masked MSISDN.
|
|
859
|
-
* Use v1 only if you need SHA256-hashed MSISDN in callbacks.
|
|
860
|
-
*
|
|
861
|
-
* Sandbox: URLs can be re-registered freely (overwriting existing ones).
|
|
862
|
-
* Production: One-time call. To change URLs, delete them via Daraja Self
|
|
863
|
-
* Services → URL Management, then call this again.
|
|
864
|
-
*
|
|
865
|
-
* URL rules (Daraja docs — enforced by this library):
|
|
866
|
-
* ✓ Must be publicly accessible
|
|
867
|
-
* ✓ Production: HTTPS required
|
|
868
|
-
* ✗ Must NOT contain: M-PESA, Safaricom, exe, exec, cmd, sql, query
|
|
869
|
-
* ✗ Do NOT use ngrok, mockbin, requestbin in production
|
|
870
|
-
* ✓ responseType must be exactly "Completed" or "Cancelled" (sentence case)
|
|
871
|
-
*
|
|
872
|
-
* External Validation (optional):
|
|
873
|
-
* By default it is disabled. To enable, email apisupport@safaricom.co.ke.
|
|
874
|
-
* When enabled, Safaricom calls your validationUrl before processing payment.
|
|
875
|
-
* You must respond within ~8 seconds.
|
|
876
|
-
*
|
|
877
1103
|
* @example
|
|
878
1104
|
* await mpesa.registerC2BUrls({
|
|
879
1105
|
* shortCode: "600984",
|
|
880
1106
|
* responseType: "Completed",
|
|
881
1107
|
* confirmationUrl: "https://yourdomain.com/mpesa/c2b/confirmation",
|
|
882
1108
|
* validationUrl: "https://yourdomain.com/mpesa/c2b/validation",
|
|
883
|
-
* apiVersion: "v2",
|
|
1109
|
+
* apiVersion: "v2",
|
|
884
1110
|
* });
|
|
885
1111
|
*/
|
|
886
1112
|
async registerC2BUrls(request) {
|
|
887
1113
|
const token = await this.getToken();
|
|
888
1114
|
return registerC2BUrls(this.baseUrl, token, request);
|
|
889
1115
|
}
|
|
890
|
-
// ── C2B Simulate (Sandbox ONLY)
|
|
1116
|
+
// ── C2B Simulate (Sandbox ONLY) ────────────────────────────────────────────
|
|
891
1117
|
/**
|
|
892
1118
|
* Simulates a C2B customer payment. SANDBOX ONLY.
|
|
893
1119
|
*
|
|
894
|
-
* In production, real customers initiate payments via M-PESA App, USSD,
|
|
895
|
-
* or SIM Toolkit — simulation is not available.
|
|
896
|
-
*
|
|
897
|
-
* The API version used here should match the version used when registering URLs.
|
|
898
|
-
*
|
|
899
1120
|
* @example
|
|
900
1121
|
* await mpesa.simulateC2B({
|
|
901
1122
|
* shortCode: 600984,
|
|
902
1123
|
* commandId: "CustomerPayBillOnline",
|
|
903
1124
|
* amount: 10,
|
|
904
|
-
* msisdn: 254708374149,
|
|
905
|
-
* billRefNumber: "INV-001",
|
|
906
|
-
* apiVersion: "v2",
|
|
1125
|
+
* msisdn: 254708374149,
|
|
1126
|
+
* billRefNumber: "INV-001",
|
|
1127
|
+
* apiVersion: "v2",
|
|
907
1128
|
* });
|
|
908
1129
|
*/
|
|
909
1130
|
async simulateC2B(request) {
|
|
910
1131
|
const token = await this.getToken();
|
|
911
1132
|
return simulateC2B(this.baseUrl, token, request);
|
|
912
1133
|
}
|
|
913
|
-
// ── Tax Remittance
|
|
1134
|
+
// ── Tax Remittance ─────────────────────────────────────────────────────────
|
|
914
1135
|
/**
|
|
915
1136
|
* Tax Remittance — remits tax to Kenya Revenue Authority (KRA) via M-PESA.
|
|
916
1137
|
*
|
|
917
|
-
* Requires:
|
|
918
|
-
* - initiatorName in config
|
|
919
|
-
* - initiatorPassword + certificate (or pre-computed securityCredential)
|
|
920
|
-
*
|
|
921
|
-
* This is ASYNCHRONOUS. The synchronous response only confirms receipt.
|
|
922
|
-
* Final details are POSTed to your resultUrl.
|
|
923
|
-
*
|
|
924
|
-
* Prerequisites (from Daraja docs):
|
|
925
|
-
* - Prior integration with KRA for tax declaration.
|
|
926
|
-
* - A Payment Registration Number (PRN) from KRA.
|
|
927
|
-
* - Initiator with "Tax Remittance ORG API" role on M-PESA org portal.
|
|
928
|
-
*
|
|
929
|
-
* Fixed values (set automatically — do NOT override unless Safaricom changes them):
|
|
930
|
-
* CommandID: "PayTaxToKRA"
|
|
931
|
-
* SenderIdentifierType: "4"
|
|
932
|
-
* RecieverIdentifierType: "4"
|
|
933
|
-
* PartyB: "572572" (KRA shortcode)
|
|
1138
|
+
* Requires: initiatorName + certificate (or pre-computed securityCredential).
|
|
934
1139
|
*
|
|
935
1140
|
* @example
|
|
936
1141
|
* await mpesa.remitTax({
|
|
937
1142
|
* amount: 5000,
|
|
938
1143
|
* partyA: "888880",
|
|
939
|
-
* accountReference: "PRN1234XN",
|
|
1144
|
+
* accountReference: "PRN1234XN",
|
|
940
1145
|
* resultUrl: "https://yourdomain.com/mpesa/tax/result",
|
|
941
1146
|
* queueTimeOutUrl: "https://yourdomain.com/mpesa/tax/timeout",
|
|
942
1147
|
* remarks: "Monthly PAYE remittance",
|
|
@@ -956,6 +1161,94 @@ var Mpesa = class {
|
|
|
956
1161
|
]);
|
|
957
1162
|
return remitTax(this.baseUrl, token, securityCred, initiator, request);
|
|
958
1163
|
}
|
|
1164
|
+
// ── B2B Express Checkout ───────────────────────────────────────────────────
|
|
1165
|
+
/**
|
|
1166
|
+
* B2B Express Checkout — initiates a USSD Push to a merchant's till.
|
|
1167
|
+
*
|
|
1168
|
+
* @example
|
|
1169
|
+
* const res = await mpesa.b2bExpressCheckout({
|
|
1170
|
+
* primaryShortCode: "000001",
|
|
1171
|
+
* receiverShortCode: "000002",
|
|
1172
|
+
* amount: 5000,
|
|
1173
|
+
* paymentRef: "INV-001",
|
|
1174
|
+
* callbackUrl: "https://yourdomain.com/mpesa/b2b/callback",
|
|
1175
|
+
* partnerName: "My Vendor Co.",
|
|
1176
|
+
* });
|
|
1177
|
+
*/
|
|
1178
|
+
async b2bExpressCheckout(request) {
|
|
1179
|
+
const token = await this.getToken();
|
|
1180
|
+
return initiateB2BExpressCheckout(this.baseUrl, token, request);
|
|
1181
|
+
}
|
|
1182
|
+
// ── B2C Payment ────────────────────────────────────────────────────────────
|
|
1183
|
+
/**
|
|
1184
|
+
* B2C Payment — sends money from a business to customers, or loads funds
|
|
1185
|
+
* to a B2C shortcode for bulk disbursement.
|
|
1186
|
+
*
|
|
1187
|
+
* Requires: initiatorName + (initiatorPassword + certificate) OR securityCredential.
|
|
1188
|
+
* The initiator must have the appropriate role for the chosen CommandID.
|
|
1189
|
+
*
|
|
1190
|
+
* This is ASYNCHRONOUS. The synchronous response only confirms receipt.
|
|
1191
|
+
* Final details are POSTed to your resultUrl.
|
|
1192
|
+
*
|
|
1193
|
+
* CommandID options:
|
|
1194
|
+
* "BusinessPayToBulk" — Load funds to a B2C shortcode (Account Top Up)
|
|
1195
|
+
* "BusinessPayment" — Direct unsecured payment to a customer
|
|
1196
|
+
* "SalaryPayment" — Salary disbursement to a customer
|
|
1197
|
+
* "PromotionPayment" — Promotion/bonus payment to a customer
|
|
1198
|
+
*
|
|
1199
|
+
* Required M-PESA org portal roles:
|
|
1200
|
+
* BusinessPayToBulk → "Org Business Pay to Bulk API initiator"
|
|
1201
|
+
* BusinessPayment → "Org Business Payment API initiator"
|
|
1202
|
+
* SalaryPayment → "Org Salary Payment API initiator"
|
|
1203
|
+
* PromotionPayment → "Org Promotion Payment API initiator"
|
|
1204
|
+
*
|
|
1205
|
+
* @example
|
|
1206
|
+
* // B2C Account Top Up (load funds to B2C shortcode)
|
|
1207
|
+
* await mpesa.b2cPayment({
|
|
1208
|
+
* commandId: "BusinessPayToBulk",
|
|
1209
|
+
* amount: 10000,
|
|
1210
|
+
* partyA: "600979",
|
|
1211
|
+
* partyB: "600000",
|
|
1212
|
+
* accountReference: "BATCH-001",
|
|
1213
|
+
* resultUrl: "https://yourdomain.com/mpesa/b2c/result",
|
|
1214
|
+
* queueTimeOutUrl: "https://yourdomain.com/mpesa/b2c/timeout",
|
|
1215
|
+
* remarks: "Monthly salary batch load",
|
|
1216
|
+
* });
|
|
1217
|
+
*
|
|
1218
|
+
* @example
|
|
1219
|
+
* // Direct customer payment
|
|
1220
|
+
* await mpesa.b2cPayment({
|
|
1221
|
+
* commandId: "SalaryPayment",
|
|
1222
|
+
* amount: 5000,
|
|
1223
|
+
* partyA: "600979",
|
|
1224
|
+
* partyB: "254712345678",
|
|
1225
|
+
* accountReference: "SAL-JAN-2024",
|
|
1226
|
+
* requester: "254712345678",
|
|
1227
|
+
* resultUrl: "https://yourdomain.com/mpesa/b2c/result",
|
|
1228
|
+
* queueTimeOutUrl: "https://yourdomain.com/mpesa/b2c/timeout",
|
|
1229
|
+
* remarks: "January salary",
|
|
1230
|
+
* });
|
|
1231
|
+
*/
|
|
1232
|
+
async b2cPayment(request) {
|
|
1233
|
+
const initiator = this.config.initiatorName ?? "";
|
|
1234
|
+
if (!initiator) {
|
|
1235
|
+
throw new PesafyError({
|
|
1236
|
+
code: "VALIDATION_ERROR",
|
|
1237
|
+
message: "initiatorName is required for B2C Payment"
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
const [token, securityCred] = await Promise.all([
|
|
1241
|
+
this.getToken(),
|
|
1242
|
+
this.buildSecurityCredential()
|
|
1243
|
+
]);
|
|
1244
|
+
return initiateB2CPayment(
|
|
1245
|
+
this.baseUrl,
|
|
1246
|
+
token,
|
|
1247
|
+
securityCred,
|
|
1248
|
+
initiator,
|
|
1249
|
+
request
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
959
1252
|
/** Force the cached OAuth token to be refreshed on the next API call */
|
|
960
1253
|
clearTokenCache() {
|
|
961
1254
|
this.tokenManager.clearCache();
|
|
@@ -1094,6 +1387,22 @@ exports.extractAmount = extractAmount;
|
|
|
1094
1387
|
exports.extractPhoneNumber = extractPhoneNumber;
|
|
1095
1388
|
exports.extractTransactionId = extractTransactionId;
|
|
1096
1389
|
exports.formatPhoneNumber = formatSafaricomPhone;
|
|
1390
|
+
exports.getB2BAmount = getB2BAmount;
|
|
1391
|
+
exports.getB2BConversationId = getB2BConversationId;
|
|
1392
|
+
exports.getB2BRequestId = getB2BRequestId;
|
|
1393
|
+
exports.getB2BTransactionId = getB2BTransactionId;
|
|
1394
|
+
exports.getB2CAmount = getB2CAmount;
|
|
1395
|
+
exports.getB2CConversationId = getB2CConversationId;
|
|
1396
|
+
exports.getB2CCurrency = getB2CCurrency;
|
|
1397
|
+
exports.getB2CDebitAccountBalance = getB2CDebitAccountBalance;
|
|
1398
|
+
exports.getB2CDebitPartyCharges = getB2CDebitPartyCharges;
|
|
1399
|
+
exports.getB2CInitiatorAccountBalance = getB2CInitiatorAccountBalance;
|
|
1400
|
+
exports.getB2COriginatorConversationId = getB2COriginatorConversationId;
|
|
1401
|
+
exports.getB2CReceiverPublicName = getB2CReceiverPublicName;
|
|
1402
|
+
exports.getB2CResultDesc = getB2CResultDesc;
|
|
1403
|
+
exports.getB2CResultParam = getB2CResultParam;
|
|
1404
|
+
exports.getB2CTransactionCompletedTime = getB2CTransactionCompletedTime;
|
|
1405
|
+
exports.getB2CTransactionId = getB2CTransactionId;
|
|
1097
1406
|
exports.getC2BAccountRef = getC2BAccountRef;
|
|
1098
1407
|
exports.getC2BAmount = getC2BAmount;
|
|
1099
1408
|
exports.getC2BCustomerName = getC2BCustomerName;
|
|
@@ -1101,6 +1410,14 @@ exports.getC2BTransactionId = getC2BTransactionId;
|
|
|
1101
1410
|
exports.getCallbackValue = getCallbackValue;
|
|
1102
1411
|
exports.getTimestamp = getTimestamp;
|
|
1103
1412
|
exports.handleWebhook = handleWebhook;
|
|
1413
|
+
exports.initiateB2BExpressCheckout = initiateB2BExpressCheckout;
|
|
1414
|
+
exports.initiateB2CPayment = initiateB2CPayment;
|
|
1415
|
+
exports.isB2BCheckoutCallback = isB2BCheckoutCallback;
|
|
1416
|
+
exports.isB2BCheckoutCancelled = isB2BCheckoutCancelled;
|
|
1417
|
+
exports.isB2BCheckoutSuccess = isB2BCheckoutSuccess;
|
|
1418
|
+
exports.isB2CFailure = isB2CFailure;
|
|
1419
|
+
exports.isB2CResult = isB2CResult;
|
|
1420
|
+
exports.isB2CSuccess = isB2CSuccess;
|
|
1104
1421
|
exports.isBuyGoodsPayment = isBuyGoodsPayment;
|
|
1105
1422
|
exports.isC2BPayload = isC2BPayload;
|
|
1106
1423
|
exports.isPaybillPayment = isPaybillPayment;
|