@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.
@@ -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 };