@singularity-payments/core 0.1.0-alpha.0
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/LICENSE +201 -0
- package/dist/index.d.mts +369 -0
- package/dist/index.d.ts +369 -0
- package/dist/index.js +896 -0
- package/dist/index.mjs +858 -0
- package/package.json +34 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
type Environment = "sandbox" | "production";
|
|
2
|
+
interface MpesaConfig {
|
|
3
|
+
consumerKey: string;
|
|
4
|
+
consumerSecret: string;
|
|
5
|
+
passkey: string;
|
|
6
|
+
shortcode: string;
|
|
7
|
+
environment: Environment;
|
|
8
|
+
callbackUrl?: string;
|
|
9
|
+
timeoutUrl?: string;
|
|
10
|
+
resultUrl?: string;
|
|
11
|
+
}
|
|
12
|
+
interface MpesaPlugin {
|
|
13
|
+
name: string;
|
|
14
|
+
init: (client: any) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface STKPushRequest {
|
|
18
|
+
amount: number;
|
|
19
|
+
phoneNumber: string;
|
|
20
|
+
accountReference: string;
|
|
21
|
+
transactionDesc: string;
|
|
22
|
+
callbackUrl?: string;
|
|
23
|
+
}
|
|
24
|
+
interface STKPushResponse {
|
|
25
|
+
MerchantRequestID: string;
|
|
26
|
+
CheckoutRequestID: string;
|
|
27
|
+
ResponseCode: string;
|
|
28
|
+
ResponseDescription: string;
|
|
29
|
+
CustomerMessage: string;
|
|
30
|
+
}
|
|
31
|
+
interface TransactionStatusRequest {
|
|
32
|
+
CheckoutRequestID: string;
|
|
33
|
+
}
|
|
34
|
+
interface TransactionStatusResponse {
|
|
35
|
+
ResponseCode: string;
|
|
36
|
+
ResponseDescription: string;
|
|
37
|
+
MerchantRequestID: string;
|
|
38
|
+
CheckoutRequestID: string;
|
|
39
|
+
ResultCode: string;
|
|
40
|
+
ResultDesc: string;
|
|
41
|
+
}
|
|
42
|
+
interface C2BRegisterRequest {
|
|
43
|
+
shortCode: string;
|
|
44
|
+
responseType: "Completed" | "Cancelled";
|
|
45
|
+
confirmationURL: string;
|
|
46
|
+
validationURL: string;
|
|
47
|
+
}
|
|
48
|
+
interface CallbackMetadata {
|
|
49
|
+
Item: Array<{
|
|
50
|
+
Name: string;
|
|
51
|
+
Value: string | number;
|
|
52
|
+
}>;
|
|
53
|
+
}
|
|
54
|
+
interface STKCallback {
|
|
55
|
+
Body: {
|
|
56
|
+
stkCallback: {
|
|
57
|
+
MerchantRequestID: string;
|
|
58
|
+
CheckoutRequestID: string;
|
|
59
|
+
ResultCode: number;
|
|
60
|
+
ResultDesc: string;
|
|
61
|
+
CallbackMetadata?: CallbackMetadata;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
interface C2BCallback {
|
|
66
|
+
TransactionType: string;
|
|
67
|
+
TransID: string;
|
|
68
|
+
TransTime: string;
|
|
69
|
+
TransAmount: string;
|
|
70
|
+
BusinessShortCode: string;
|
|
71
|
+
BillRefNumber: string;
|
|
72
|
+
InvoiceNumber?: string;
|
|
73
|
+
OrgAccountBalance?: string;
|
|
74
|
+
ThirdPartyTransID?: string;
|
|
75
|
+
MSISDN: string;
|
|
76
|
+
FirstName?: string;
|
|
77
|
+
MiddleName?: string;
|
|
78
|
+
LastName?: string;
|
|
79
|
+
}
|
|
80
|
+
interface C2BRegisterResponse {
|
|
81
|
+
OriginatorCoversationID: string;
|
|
82
|
+
ResponseCode: string;
|
|
83
|
+
ResponseDescription: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface ParsedCallbackData {
|
|
87
|
+
merchantRequestId: string;
|
|
88
|
+
CheckoutRequestID: string;
|
|
89
|
+
resultCode: number;
|
|
90
|
+
resultDescription: string;
|
|
91
|
+
amount?: number;
|
|
92
|
+
mpesaReceiptNumber?: string;
|
|
93
|
+
transactionDate?: string;
|
|
94
|
+
phoneNumber?: string;
|
|
95
|
+
isSuccess: boolean;
|
|
96
|
+
errorMessage?: string;
|
|
97
|
+
}
|
|
98
|
+
interface ParsedC2BCallback {
|
|
99
|
+
transactionType: string;
|
|
100
|
+
transactionId: string;
|
|
101
|
+
transactionTime: string;
|
|
102
|
+
amount: number;
|
|
103
|
+
businessShortCode: string;
|
|
104
|
+
billRefNumber: string;
|
|
105
|
+
invoiceNumber?: string;
|
|
106
|
+
msisdn: string;
|
|
107
|
+
firstName?: string;
|
|
108
|
+
middleName?: string;
|
|
109
|
+
lastName?: string;
|
|
110
|
+
}
|
|
111
|
+
interface CallbackHandlerOptions {
|
|
112
|
+
onSuccess?: (data: ParsedCallbackData) => void | Promise<void>;
|
|
113
|
+
onFailure?: (data: ParsedCallbackData) => void | Promise<void>;
|
|
114
|
+
onCallback?: (data: ParsedCallbackData) => void | Promise<void>;
|
|
115
|
+
onC2BConfirmation?: (data: ParsedC2BCallback) => void | Promise<void>;
|
|
116
|
+
onC2BValidation?: (data: ParsedC2BCallback) => Promise<boolean>;
|
|
117
|
+
validateIp?: boolean;
|
|
118
|
+
allowedIps?: string[];
|
|
119
|
+
isDuplicate?: (CheckoutRequestID: string) => boolean | Promise<boolean>;
|
|
120
|
+
logger?: {
|
|
121
|
+
info: (message: string, data?: any) => void;
|
|
122
|
+
error: (message: string, data?: any) => void;
|
|
123
|
+
warn: (message: string, data?: any) => void;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
declare class MpesaCallbackHandler {
|
|
127
|
+
private options;
|
|
128
|
+
private readonly SAFARICOM_IPS;
|
|
129
|
+
constructor(options?: CallbackHandlerOptions);
|
|
130
|
+
/**
|
|
131
|
+
* Validate that the callback is from a trusted IP
|
|
132
|
+
*/
|
|
133
|
+
validateCallbackIp(ipAddress: string): boolean;
|
|
134
|
+
/**
|
|
135
|
+
* Parse STK Push callback data from M-Pesa
|
|
136
|
+
*/
|
|
137
|
+
parseCallback(callback: STKCallback): ParsedCallbackData;
|
|
138
|
+
/**
|
|
139
|
+
* Parse C2B callback data
|
|
140
|
+
*/
|
|
141
|
+
parseC2BCallback(callback: C2BCallback): ParsedC2BCallback;
|
|
142
|
+
/**
|
|
143
|
+
* Extract metadata from STK callback
|
|
144
|
+
*/
|
|
145
|
+
private extractMetadata;
|
|
146
|
+
/**
|
|
147
|
+
* Format transaction date from M-Pesa format (YYYYMMDDHHmmss) to ISO
|
|
148
|
+
*/
|
|
149
|
+
private formatTransactionDate;
|
|
150
|
+
/**
|
|
151
|
+
* Check if callback indicates success
|
|
152
|
+
*/
|
|
153
|
+
isSuccess(data: ParsedCallbackData): boolean;
|
|
154
|
+
/**
|
|
155
|
+
* Check if callback indicates failure
|
|
156
|
+
*/
|
|
157
|
+
isFailure(data: ParsedCallbackData): boolean;
|
|
158
|
+
/**
|
|
159
|
+
* Get a readable error message based on the code
|
|
160
|
+
*/
|
|
161
|
+
getErrorMessage(resultCode: number): string;
|
|
162
|
+
/**
|
|
163
|
+
* Handle STK Push callback and invoke appropriate handlers
|
|
164
|
+
*/
|
|
165
|
+
handleCallback(callback: STKCallback, ipAddress?: string): Promise<void>;
|
|
166
|
+
/**
|
|
167
|
+
* Handle C2B validation request
|
|
168
|
+
* Returns true if validation passes, false otherwise
|
|
169
|
+
*/
|
|
170
|
+
handleC2BValidation(callback: C2BCallback): Promise<boolean>;
|
|
171
|
+
/**
|
|
172
|
+
* Handle C2B confirmation
|
|
173
|
+
*/
|
|
174
|
+
handleC2BConfirmation(callback: C2BCallback): Promise<void>;
|
|
175
|
+
/**
|
|
176
|
+
* Create a standard callback response for M-Pesa
|
|
177
|
+
*/
|
|
178
|
+
createCallbackResponse(success?: boolean, message?: string): object;
|
|
179
|
+
/**
|
|
180
|
+
* Internal logging helper
|
|
181
|
+
*/
|
|
182
|
+
private log;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
interface RetryOptions {
|
|
186
|
+
maxRetries?: number;
|
|
187
|
+
initialDelayMs?: number;
|
|
188
|
+
maxDelayMs?: number;
|
|
189
|
+
backoffMultiplier?: number;
|
|
190
|
+
retryableStatusCodes?: number[];
|
|
191
|
+
onRetry?: (error: Error, attempt: number) => void;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Retry a function with exponential backoff
|
|
195
|
+
*/
|
|
196
|
+
declare function retryWithBackoff<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
197
|
+
|
|
198
|
+
interface RateLimiterOptions {
|
|
199
|
+
maxRequests: number;
|
|
200
|
+
windowMs: number;
|
|
201
|
+
keyPrefix?: string;
|
|
202
|
+
}
|
|
203
|
+
declare class RateLimiter {
|
|
204
|
+
private store;
|
|
205
|
+
private options;
|
|
206
|
+
private cleanupInterval;
|
|
207
|
+
constructor(options: RateLimiterOptions);
|
|
208
|
+
/**
|
|
209
|
+
* Check if request is allowed
|
|
210
|
+
*/
|
|
211
|
+
checkLimit(key: string): Promise<void>;
|
|
212
|
+
/**
|
|
213
|
+
* Get current usage for a key
|
|
214
|
+
*/
|
|
215
|
+
getUsage(key: string): {
|
|
216
|
+
count: number;
|
|
217
|
+
remaining: number;
|
|
218
|
+
resetAt: number;
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* Reset rate limit for a key
|
|
222
|
+
*/
|
|
223
|
+
reset(key: string): void;
|
|
224
|
+
/**
|
|
225
|
+
* Clear all rate limits
|
|
226
|
+
*/
|
|
227
|
+
resetAll(): void;
|
|
228
|
+
/**
|
|
229
|
+
* Start cleanup interval
|
|
230
|
+
*/
|
|
231
|
+
private startCleanup;
|
|
232
|
+
/**
|
|
233
|
+
* Stop cleanup interval
|
|
234
|
+
*/
|
|
235
|
+
destroy(): void;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Create a Redis-backed rate limiter (for distributed systems)
|
|
239
|
+
*/
|
|
240
|
+
interface RedisLike {
|
|
241
|
+
get(key: string): Promise<string | null>;
|
|
242
|
+
set(key: string, value: string, mode: string, duration: number): Promise<void>;
|
|
243
|
+
incr(key: string): Promise<number>;
|
|
244
|
+
expire(key: string, seconds: number): Promise<void>;
|
|
245
|
+
}
|
|
246
|
+
declare class RedisRateLimiter {
|
|
247
|
+
private options;
|
|
248
|
+
private redis;
|
|
249
|
+
constructor(redis: RedisLike, options: RateLimiterOptions);
|
|
250
|
+
checkLimit(key: string): Promise<void>;
|
|
251
|
+
reset(key: string): Promise<void>;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
interface MpesaClientOptions {
|
|
255
|
+
callbackOptions?: CallbackHandlerOptions;
|
|
256
|
+
retryOptions?: RetryOptions;
|
|
257
|
+
rateLimitOptions?: {
|
|
258
|
+
enabled?: boolean;
|
|
259
|
+
maxRequests?: number;
|
|
260
|
+
windowMs?: number;
|
|
261
|
+
redis?: RedisLike;
|
|
262
|
+
};
|
|
263
|
+
requestTimeout?: number;
|
|
264
|
+
}
|
|
265
|
+
declare class MpesaClient {
|
|
266
|
+
private config;
|
|
267
|
+
private auth;
|
|
268
|
+
private plugins;
|
|
269
|
+
private callbackHandler;
|
|
270
|
+
private retryOptions;
|
|
271
|
+
private rateLimiter;
|
|
272
|
+
private readonly REQUEST_TIMEOUT;
|
|
273
|
+
constructor(config: MpesaConfig, options?: MpesaClientOptions);
|
|
274
|
+
/**
|
|
275
|
+
* Make HTTP request with error handling
|
|
276
|
+
*/
|
|
277
|
+
private makeRequest;
|
|
278
|
+
/**
|
|
279
|
+
* Validate phone number format
|
|
280
|
+
*/
|
|
281
|
+
private validateAndFormatPhone;
|
|
282
|
+
/**
|
|
283
|
+
* Add a plugin to extend functionality
|
|
284
|
+
*/
|
|
285
|
+
use(plugin: MpesaPlugin): this;
|
|
286
|
+
/**
|
|
287
|
+
* Initiate STK Push (Lipa Na M-Pesa Online)
|
|
288
|
+
*/
|
|
289
|
+
stkPush(request: STKPushRequest): Promise<STKPushResponse>;
|
|
290
|
+
/**
|
|
291
|
+
* Query STK Push transaction status
|
|
292
|
+
*/
|
|
293
|
+
stkQuery(request: TransactionStatusRequest): Promise<TransactionStatusResponse>;
|
|
294
|
+
/**
|
|
295
|
+
* Register C2B URLs for validation and confirmation
|
|
296
|
+
*/
|
|
297
|
+
registerC2BUrl(request: C2BRegisterRequest): Promise<C2BRegisterResponse>;
|
|
298
|
+
/**
|
|
299
|
+
* Get the callback handler instance
|
|
300
|
+
*/
|
|
301
|
+
getCallbackHandler(): MpesaCallbackHandler;
|
|
302
|
+
/**
|
|
303
|
+
* Handle an incoming STK Push callback
|
|
304
|
+
* Returns M-Pesa compliant response
|
|
305
|
+
*/
|
|
306
|
+
handleSTKCallback(callback: STKCallback, ipAddress?: string): Promise<object>;
|
|
307
|
+
/**
|
|
308
|
+
* Handle C2B validation request
|
|
309
|
+
*/
|
|
310
|
+
handleC2BValidation(callback: C2BCallback): Promise<object>;
|
|
311
|
+
/**
|
|
312
|
+
* Handle C2B confirmation
|
|
313
|
+
*/
|
|
314
|
+
handleC2BConfirmation(callback: C2BCallback): Promise<object>;
|
|
315
|
+
/**
|
|
316
|
+
* Parse STK callback without handling (for testing)
|
|
317
|
+
*/
|
|
318
|
+
parseSTKCallback(callback: STKCallback): ParsedCallbackData;
|
|
319
|
+
/**
|
|
320
|
+
* Parse C2B callback without handling (for testing)
|
|
321
|
+
*/
|
|
322
|
+
parseC2BCallback(callback: C2BCallback): ParsedC2BCallback;
|
|
323
|
+
/**
|
|
324
|
+
* Get configuration (for plugins)
|
|
325
|
+
*/
|
|
326
|
+
getConfig(): MpesaConfig;
|
|
327
|
+
/**
|
|
328
|
+
* Get rate limiter usage for a key
|
|
329
|
+
*/
|
|
330
|
+
getRateLimitUsage(key: string): {
|
|
331
|
+
count: number;
|
|
332
|
+
remaining: number;
|
|
333
|
+
resetAt: number;
|
|
334
|
+
} | null;
|
|
335
|
+
/**
|
|
336
|
+
* Cleanup resources
|
|
337
|
+
*/
|
|
338
|
+
destroy(): void;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
declare class MpesaError extends Error {
|
|
342
|
+
code?: string | undefined;
|
|
343
|
+
statusCode?: number | undefined;
|
|
344
|
+
details?: any | undefined;
|
|
345
|
+
constructor(message: string, code?: string | undefined, statusCode?: number | undefined, details?: any | undefined);
|
|
346
|
+
}
|
|
347
|
+
declare class MpesaAuthError extends MpesaError {
|
|
348
|
+
constructor(message: string, details?: any);
|
|
349
|
+
}
|
|
350
|
+
declare class MpesaValidationError extends MpesaError {
|
|
351
|
+
constructor(message: string, details?: any);
|
|
352
|
+
}
|
|
353
|
+
declare class MpesaNetworkError extends MpesaError {
|
|
354
|
+
isRetryable: boolean;
|
|
355
|
+
constructor(message: string, isRetryable: boolean, details?: any);
|
|
356
|
+
}
|
|
357
|
+
declare class MpesaTimeoutError extends MpesaError {
|
|
358
|
+
constructor(message: string, details?: any);
|
|
359
|
+
}
|
|
360
|
+
declare class MpesaRateLimitError extends MpesaError {
|
|
361
|
+
retryAfter?: number | undefined;
|
|
362
|
+
constructor(message: string, retryAfter?: number | undefined, details?: any);
|
|
363
|
+
}
|
|
364
|
+
declare class MpesaApiError extends MpesaError {
|
|
365
|
+
responseBody?: any | undefined;
|
|
366
|
+
constructor(message: string, code: string, statusCode: number, responseBody?: any | undefined);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export { type C2BCallback, type C2BRegisterRequest, type C2BRegisterResponse, type CallbackHandlerOptions, type Environment, MpesaApiError, MpesaAuthError, MpesaCallbackHandler, MpesaClient, type MpesaClientOptions, type MpesaConfig, MpesaError, MpesaNetworkError, type MpesaPlugin, MpesaRateLimitError, MpesaTimeoutError, MpesaValidationError, type ParsedC2BCallback, type ParsedCallbackData, RateLimiter, type RateLimiterOptions, type RedisLike, RedisRateLimiter, type RetryOptions, type STKCallback, type STKPushRequest, type STKPushResponse, type TransactionStatusRequest, type TransactionStatusResponse, retryWithBackoff };
|