@singularity-payments/nextjs 0.1.0-alpha.3 → 0.1.0-alpha.4

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.d.mts CHANGED
@@ -9,11 +9,13 @@ interface MpesaRouteHandlers {
9
9
  stkCallback: RouteHandler;
10
10
  c2bValidation: RouteHandler;
11
11
  c2bConfirmation: RouteHandler;
12
+ b2cResult: RouteHandler;
13
+ b2cTimeout: RouteHandler;
12
14
  catchAll: RouteHandler;
13
15
  }
14
16
 
15
17
  /**
16
- * Create Next.js route handlers for M-Pesa callbacks
18
+ * Create Next.js route handlers for M-Pesa callbacks and client-side API requests
17
19
  */
18
20
  declare function createMpesaHandlers(client: MpesaClient): MpesaRouteHandlers;
19
21
 
package/dist/index.d.ts CHANGED
@@ -9,11 +9,13 @@ interface MpesaRouteHandlers {
9
9
  stkCallback: RouteHandler;
10
10
  c2bValidation: RouteHandler;
11
11
  c2bConfirmation: RouteHandler;
12
+ b2cResult: RouteHandler;
13
+ b2cTimeout: RouteHandler;
12
14
  catchAll: RouteHandler;
13
15
  }
14
16
 
15
17
  /**
16
- * Create Next.js route handlers for M-Pesa callbacks
18
+ * Create Next.js route handlers for M-Pesa callbacks and client-side API requests
17
19
  */
18
20
  declare function createMpesaHandlers(client: MpesaClient): MpesaRouteHandlers;
19
21
 
package/dist/index.js CHANGED
@@ -99,18 +99,96 @@ function createMpesaHandlers(client) {
99
99
  }
100
100
  },
101
101
  /**
102
- * Catch-all handler for all M-Pesa webhooks and API requests
102
+ * B2C result handler
103
+ * Handles callbacks from M-Pesa after B2C requests
104
+ */
105
+ b2cResult: {
106
+ POST: async (request) => {
107
+ try {
108
+ const body = await request.json();
109
+ const parsed = client.getCallbackHandler().parseB2CCallback(body);
110
+ console.log("B2C Result:", parsed);
111
+ return import_server.NextResponse.json(
112
+ {
113
+ ResultCode: 0,
114
+ ResultDesc: "Accepted"
115
+ },
116
+ { status: 200 }
117
+ );
118
+ } catch (error) {
119
+ console.error("B2C Result error:", error);
120
+ return import_server.NextResponse.json(
121
+ {
122
+ ResultCode: 1,
123
+ ResultDesc: "Processing failed"
124
+ },
125
+ { status: 200 }
126
+ );
127
+ }
128
+ }
129
+ },
130
+ /**
131
+ * B2C timeout handler
132
+ * Handles timeout notifications from M-Pesa for B2C requests
133
+ */
134
+ b2cTimeout: {
135
+ POST: async (request) => {
136
+ try {
137
+ const body = await request.json();
138
+ console.log("B2C Timeout:", body);
139
+ return import_server.NextResponse.json(
140
+ {
141
+ ResultCode: 0,
142
+ ResultDesc: "Timeout received"
143
+ },
144
+ { status: 200 }
145
+ );
146
+ } catch (error) {
147
+ console.error("B2C Timeout error:", error);
148
+ return import_server.NextResponse.json(
149
+ {
150
+ ResultCode: 1,
151
+ ResultDesc: "Processing failed"
152
+ },
153
+ { status: 200 }
154
+ );
155
+ }
156
+ }
157
+ },
158
+ /**
159
+ * Catch-all handler for all M-Pesa webhooks and client-side API requests
103
160
  * Routes based on URL path segment
104
161
  *
105
162
  * Usage: app/api/mpesa/[...mpesa]/route.ts
106
163
  * export const { POST } = mpesa.handlers.catchAll;
107
164
  *
108
165
  * Supported endpoints:
166
+ *
167
+ * WEBHOOKS (from M-Pesa):
109
168
  * - /api/mpesa/callback or /api/mpesa/stk-callback - STK Push callbacks
110
169
  * - /api/mpesa/validation or /api/mpesa/c2b-validation - C2B validation
111
170
  * - /api/mpesa/confirmation or /api/mpesa/c2b-confirmation - C2B confirmation
171
+ * - /api/mpesa/b2c-result - B2C result callback
172
+ * - /api/mpesa/b2c-timeout - B2C timeout callback
173
+ * - /api/mpesa/b2b-result - B2B result callback
174
+ * - /api/mpesa/b2b-timeout - B2B timeout callback
175
+ * - /api/mpesa/balance-result - Account balance result
176
+ * - /api/mpesa/balance-timeout - Account balance timeout
177
+ * - /api/mpesa/reversal-result - Reversal result
178
+ * - /api/mpesa/reversal-timeout - Reversal timeout
179
+ * - /api/mpesa/status-result - Transaction status result
180
+ * - /api/mpesa/status-timeout - Transaction status timeout
181
+ *
182
+ * CLIENT APIs (from the frontend package, will recode react soon):
112
183
  * - /api/mpesa/stk-push - Initiate STK Push request
113
184
  * - /api/mpesa/stk-query - Query STK Push status
185
+ * - /api/mpesa/b2c - Initiate B2C payment
186
+ * - /api/mpesa/b2b - Initiate B2B payment
187
+ * - /api/mpesa/balance - Query account balance
188
+ * - /api/mpesa/transaction-status - Query transaction status
189
+ * - /api/mpesa/reversal - Reverse a transaction
190
+ * - /api/mpesa/register-c2b - Register C2B URLs
191
+ * - /api/mpesa/generate-qr - Generate dynamic QR code
114
192
  */
115
193
  catchAll: {
116
194
  POST: async (request) => {
@@ -118,60 +196,266 @@ function createMpesaHandlers(client) {
118
196
  const segments = pathname.split("/").filter(Boolean);
119
197
  const lastSegment = segments[segments.length - 1];
120
198
  try {
121
- switch (lastSegment) {
122
- // STK Push callback from M-Pesa
123
- case "callback":
124
- case "stk-callback":
125
- return handlers.stkCallback.POST(request);
126
- // C2B validation callback from M-Pesa
127
- case "validation":
128
- case "c2b-validation":
129
- return handlers.c2bValidation.POST(request);
130
- // C2B confirmation callback from M-Pesa
131
- case "confirmation":
132
- case "c2b-confirmation":
133
- return handlers.c2bConfirmation.POST(request);
134
- // Initiate STK Push request to M-Pesa
135
- case "stk-push": {
136
- const body = await request.json();
137
- const { amount, phoneNumber, accountReference, transactionDesc } = body;
138
- if (!amount || !phoneNumber) {
139
- return import_server.NextResponse.json(
140
- { error: "Amount and phone number are required" },
141
- { status: 400 }
142
- );
143
- }
144
- const response = await client.stkPush({
145
- amount: Number(amount),
146
- phoneNumber: String(phoneNumber),
147
- accountReference: accountReference || "Payment",
148
- transactionDesc: transactionDesc || "Payment"
149
- });
150
- return import_server.NextResponse.json(response);
199
+ if (lastSegment === "callback" || lastSegment === "stk-callback") {
200
+ return handlers.stkCallback.POST(request);
201
+ }
202
+ if (lastSegment === "validation" || lastSegment === "c2b-validation") {
203
+ return handlers.c2bValidation.POST(request);
204
+ }
205
+ if (lastSegment === "confirmation" || lastSegment === "c2b-confirmation") {
206
+ return handlers.c2bConfirmation.POST(request);
207
+ }
208
+ if (lastSegment === "b2c-result") {
209
+ return handlers.b2cResult.POST(request);
210
+ }
211
+ if (lastSegment === "b2c-timeout") {
212
+ return handlers.b2cTimeout.POST(request);
213
+ }
214
+ if (lastSegment === "b2b-result") {
215
+ const body = await request.json();
216
+ const parsed = client.getCallbackHandler().parseB2BCallback(body);
217
+ console.log("B2B Result:", parsed);
218
+ return import_server.NextResponse.json({ ResultCode: 0, ResultDesc: "Accepted" });
219
+ }
220
+ if (lastSegment === "b2b-timeout") {
221
+ const body = await request.json();
222
+ console.log("B2B Timeout:", body);
223
+ return import_server.NextResponse.json({
224
+ ResultCode: 0,
225
+ ResultDesc: "Timeout received"
226
+ });
227
+ }
228
+ if (lastSegment === "balance-result") {
229
+ const body = await request.json();
230
+ const parsed = client.getCallbackHandler().parseAccountBalanceCallback(body);
231
+ console.log("Balance Result:", parsed);
232
+ return import_server.NextResponse.json({ ResultCode: 0, ResultDesc: "Accepted" });
233
+ }
234
+ if (lastSegment === "balance-timeout") {
235
+ const body = await request.json();
236
+ console.log("Balance Timeout:", body);
237
+ return import_server.NextResponse.json({
238
+ ResultCode: 0,
239
+ ResultDesc: "Timeout received"
240
+ });
241
+ }
242
+ if (lastSegment === "status-result") {
243
+ const body = await request.json();
244
+ const parsed = client.getCallbackHandler().parseTransactionStatusCallback(body);
245
+ console.log("Status Result:", parsed);
246
+ return import_server.NextResponse.json({ ResultCode: 0, ResultDesc: "Accepted" });
247
+ }
248
+ if (lastSegment === "status-timeout") {
249
+ const body = await request.json();
250
+ console.log("Status Timeout:", body);
251
+ return import_server.NextResponse.json({
252
+ ResultCode: 0,
253
+ ResultDesc: "Timeout received"
254
+ });
255
+ }
256
+ if (lastSegment === "reversal-result") {
257
+ const body = await request.json();
258
+ const parsed = client.getCallbackHandler().parseReversalCallback(body);
259
+ console.log("Reversal Result:", parsed);
260
+ return import_server.NextResponse.json({ ResultCode: 0, ResultDesc: "Accepted" });
261
+ }
262
+ if (lastSegment === "reversal-timeout") {
263
+ const body = await request.json();
264
+ console.log("Reversal Timeout:", body);
265
+ return import_server.NextResponse.json({
266
+ ResultCode: 0,
267
+ ResultDesc: "Timeout received"
268
+ });
269
+ }
270
+ if (lastSegment === "stk-push") {
271
+ const body = await request.json();
272
+ const {
273
+ amount,
274
+ phoneNumber,
275
+ accountReference,
276
+ transactionDesc,
277
+ callbackUrl
278
+ } = body;
279
+ if (!amount || !phoneNumber) {
280
+ return import_server.NextResponse.json(
281
+ { error: "Amount and phone number are required" },
282
+ { status: 400 }
283
+ );
284
+ }
285
+ const response = await client.stkPush({
286
+ amount: Number(amount),
287
+ phoneNumber: String(phoneNumber),
288
+ accountReference: accountReference || "Payment",
289
+ transactionDesc: transactionDesc || "Payment",
290
+ callbackUrl
291
+ });
292
+ return import_server.NextResponse.json(response);
293
+ }
294
+ if (lastSegment === "stk-query") {
295
+ const body = await request.json();
296
+ const { CheckoutRequestID } = body;
297
+ if (!CheckoutRequestID) {
298
+ return import_server.NextResponse.json(
299
+ { error: "CheckoutRequestID is required" },
300
+ { status: 400 }
301
+ );
302
+ }
303
+ const response = await client.stkQuery({ CheckoutRequestID });
304
+ return import_server.NextResponse.json(response);
305
+ }
306
+ if (lastSegment === "b2c") {
307
+ const body = await request.json();
308
+ const {
309
+ amount,
310
+ phoneNumber,
311
+ commandID,
312
+ remarks,
313
+ occasion,
314
+ resultUrl,
315
+ timeoutUrl
316
+ } = body;
317
+ if (!amount || !phoneNumber || !commandID) {
318
+ return import_server.NextResponse.json(
319
+ { error: "Amount, phone number, and command ID are required" },
320
+ { status: 400 }
321
+ );
322
+ }
323
+ const response = await client.b2c({
324
+ amount: Number(amount),
325
+ phoneNumber: String(phoneNumber),
326
+ commandID,
327
+ remarks: remarks || "Payment",
328
+ occasion,
329
+ resultUrl,
330
+ timeoutUrl
331
+ });
332
+ return import_server.NextResponse.json(response);
333
+ }
334
+ if (lastSegment === "b2b") {
335
+ const body = await request.json();
336
+ const {
337
+ amount,
338
+ partyB,
339
+ commandID,
340
+ senderIdentifierType,
341
+ receiverIdentifierType,
342
+ accountReference,
343
+ remarks,
344
+ resultUrl,
345
+ timeoutUrl
346
+ } = body;
347
+ if (!amount || !partyB || !commandID || !accountReference) {
348
+ return import_server.NextResponse.json(
349
+ {
350
+ error: "Amount, partyB, commandID, and account reference are required"
351
+ },
352
+ { status: 400 }
353
+ );
151
354
  }
152
- // Query STK Push transaction status
153
- case "stk-query": {
154
- const body = await request.json();
155
- const { CheckoutRequestID } = body;
156
- if (!CheckoutRequestID) {
157
- return import_server.NextResponse.json(
158
- { error: "CheckoutRequestID is required" },
159
- { status: 400 }
160
- );
161
- }
162
- const response = await client.stkQuery({ CheckoutRequestID });
163
- return import_server.NextResponse.json(response);
355
+ const response = await client.b2b({
356
+ amount: Number(amount),
357
+ partyB: String(partyB),
358
+ commandID,
359
+ senderIdentifierType,
360
+ receiverIdentifierType,
361
+ accountReference: String(accountReference),
362
+ remarks: remarks || "Payment",
363
+ resultUrl,
364
+ timeoutUrl
365
+ });
366
+ return import_server.NextResponse.json(response);
367
+ }
368
+ if (lastSegment === "balance") {
369
+ const body = await request.json();
370
+ const response = await client.accountBalance(
371
+ body
372
+ );
373
+ return import_server.NextResponse.json(response);
374
+ }
375
+ if (lastSegment === "transaction-status") {
376
+ const body = await request.json();
377
+ const { transactionID } = body;
378
+ if (!transactionID) {
379
+ return import_server.NextResponse.json(
380
+ { error: "Transaction ID is required" },
381
+ { status: 400 }
382
+ );
383
+ }
384
+ const response = await client.transactionStatus(body);
385
+ return import_server.NextResponse.json(response);
386
+ }
387
+ if (lastSegment === "reversal") {
388
+ const body = await request.json();
389
+ const { transactionID, amount } = body;
390
+ if (!transactionID || !amount) {
391
+ return import_server.NextResponse.json(
392
+ { error: "Transaction ID and amount are required" },
393
+ { status: 400 }
394
+ );
395
+ }
396
+ const response = await client.reversal(body);
397
+ return import_server.NextResponse.json(response);
398
+ }
399
+ if (lastSegment === "register-c2b") {
400
+ const body = await request.json();
401
+ const { shortCode, responseType, confirmationURL, validationURL } = body;
402
+ if (!confirmationURL || !validationURL) {
403
+ return import_server.NextResponse.json(
404
+ { error: "Confirmation URL and validation URL are required" },
405
+ { status: 400 }
406
+ );
164
407
  }
165
- // Unknown endpoint
166
- default:
408
+ const response = await client.registerC2BUrl({
409
+ shortCode,
410
+ responseType,
411
+ confirmationURL,
412
+ validationURL
413
+ });
414
+ return import_server.NextResponse.json(response);
415
+ }
416
+ if (lastSegment === "generate-qr") {
417
+ const body = await request.json();
418
+ const {
419
+ merchantName,
420
+ refNo,
421
+ amount,
422
+ transactionType,
423
+ creditPartyIdentifier,
424
+ size
425
+ } = body;
426
+ if (size != "500" && size != "300") {
427
+ return import_server.NextResponse.json(
428
+ {
429
+ error: "Size must be either 500 or 300"
430
+ },
431
+ { status: 400 }
432
+ );
433
+ }
434
+ if (!merchantName || !refNo || !amount || !transactionType || !creditPartyIdentifier) {
167
435
  return import_server.NextResponse.json(
168
436
  {
169
- ResultCode: 1,
170
- ResultDesc: `Unknown endpoint: ${lastSegment}`
437
+ error: "Merchant name, reference number, amount, transaction type, and credit party identifier are required"
171
438
  },
172
- { status: 404 }
439
+ { status: 400 }
173
440
  );
441
+ }
442
+ const response = await client.generateDynamicQR({
443
+ merchantName,
444
+ refNo,
445
+ amount: Number(amount),
446
+ transactionType,
447
+ creditPartyIdentifier,
448
+ size
449
+ });
450
+ return import_server.NextResponse.json(response);
174
451
  }
452
+ return import_server.NextResponse.json(
453
+ {
454
+ ResultCode: 1,
455
+ ResultDesc: `Unknown endpoint: ${lastSegment}`
456
+ },
457
+ { status: 404 }
458
+ );
175
459
  } catch (error) {
176
460
  console.error(`M-Pesa ${lastSegment} error:`, error);
177
461
  return import_server.NextResponse.json(
package/dist/index.mjs CHANGED
@@ -73,18 +73,96 @@ function createMpesaHandlers(client) {
73
73
  }
74
74
  },
75
75
  /**
76
- * Catch-all handler for all M-Pesa webhooks and API requests
76
+ * B2C result handler
77
+ * Handles callbacks from M-Pesa after B2C requests
78
+ */
79
+ b2cResult: {
80
+ POST: async (request) => {
81
+ try {
82
+ const body = await request.json();
83
+ const parsed = client.getCallbackHandler().parseB2CCallback(body);
84
+ console.log("B2C Result:", parsed);
85
+ return NextResponse.json(
86
+ {
87
+ ResultCode: 0,
88
+ ResultDesc: "Accepted"
89
+ },
90
+ { status: 200 }
91
+ );
92
+ } catch (error) {
93
+ console.error("B2C Result error:", error);
94
+ return NextResponse.json(
95
+ {
96
+ ResultCode: 1,
97
+ ResultDesc: "Processing failed"
98
+ },
99
+ { status: 200 }
100
+ );
101
+ }
102
+ }
103
+ },
104
+ /**
105
+ * B2C timeout handler
106
+ * Handles timeout notifications from M-Pesa for B2C requests
107
+ */
108
+ b2cTimeout: {
109
+ POST: async (request) => {
110
+ try {
111
+ const body = await request.json();
112
+ console.log("B2C Timeout:", body);
113
+ return NextResponse.json(
114
+ {
115
+ ResultCode: 0,
116
+ ResultDesc: "Timeout received"
117
+ },
118
+ { status: 200 }
119
+ );
120
+ } catch (error) {
121
+ console.error("B2C Timeout error:", error);
122
+ return NextResponse.json(
123
+ {
124
+ ResultCode: 1,
125
+ ResultDesc: "Processing failed"
126
+ },
127
+ { status: 200 }
128
+ );
129
+ }
130
+ }
131
+ },
132
+ /**
133
+ * Catch-all handler for all M-Pesa webhooks and client-side API requests
77
134
  * Routes based on URL path segment
78
135
  *
79
136
  * Usage: app/api/mpesa/[...mpesa]/route.ts
80
137
  * export const { POST } = mpesa.handlers.catchAll;
81
138
  *
82
139
  * Supported endpoints:
140
+ *
141
+ * WEBHOOKS (from M-Pesa):
83
142
  * - /api/mpesa/callback or /api/mpesa/stk-callback - STK Push callbacks
84
143
  * - /api/mpesa/validation or /api/mpesa/c2b-validation - C2B validation
85
144
  * - /api/mpesa/confirmation or /api/mpesa/c2b-confirmation - C2B confirmation
145
+ * - /api/mpesa/b2c-result - B2C result callback
146
+ * - /api/mpesa/b2c-timeout - B2C timeout callback
147
+ * - /api/mpesa/b2b-result - B2B result callback
148
+ * - /api/mpesa/b2b-timeout - B2B timeout callback
149
+ * - /api/mpesa/balance-result - Account balance result
150
+ * - /api/mpesa/balance-timeout - Account balance timeout
151
+ * - /api/mpesa/reversal-result - Reversal result
152
+ * - /api/mpesa/reversal-timeout - Reversal timeout
153
+ * - /api/mpesa/status-result - Transaction status result
154
+ * - /api/mpesa/status-timeout - Transaction status timeout
155
+ *
156
+ * CLIENT APIs (from the frontend package, will recode react soon):
86
157
  * - /api/mpesa/stk-push - Initiate STK Push request
87
158
  * - /api/mpesa/stk-query - Query STK Push status
159
+ * - /api/mpesa/b2c - Initiate B2C payment
160
+ * - /api/mpesa/b2b - Initiate B2B payment
161
+ * - /api/mpesa/balance - Query account balance
162
+ * - /api/mpesa/transaction-status - Query transaction status
163
+ * - /api/mpesa/reversal - Reverse a transaction
164
+ * - /api/mpesa/register-c2b - Register C2B URLs
165
+ * - /api/mpesa/generate-qr - Generate dynamic QR code
88
166
  */
89
167
  catchAll: {
90
168
  POST: async (request) => {
@@ -92,60 +170,266 @@ function createMpesaHandlers(client) {
92
170
  const segments = pathname.split("/").filter(Boolean);
93
171
  const lastSegment = segments[segments.length - 1];
94
172
  try {
95
- switch (lastSegment) {
96
- // STK Push callback from M-Pesa
97
- case "callback":
98
- case "stk-callback":
99
- return handlers.stkCallback.POST(request);
100
- // C2B validation callback from M-Pesa
101
- case "validation":
102
- case "c2b-validation":
103
- return handlers.c2bValidation.POST(request);
104
- // C2B confirmation callback from M-Pesa
105
- case "confirmation":
106
- case "c2b-confirmation":
107
- return handlers.c2bConfirmation.POST(request);
108
- // Initiate STK Push request to M-Pesa
109
- case "stk-push": {
110
- const body = await request.json();
111
- const { amount, phoneNumber, accountReference, transactionDesc } = body;
112
- if (!amount || !phoneNumber) {
113
- return NextResponse.json(
114
- { error: "Amount and phone number are required" },
115
- { status: 400 }
116
- );
117
- }
118
- const response = await client.stkPush({
119
- amount: Number(amount),
120
- phoneNumber: String(phoneNumber),
121
- accountReference: accountReference || "Payment",
122
- transactionDesc: transactionDesc || "Payment"
123
- });
124
- return NextResponse.json(response);
173
+ if (lastSegment === "callback" || lastSegment === "stk-callback") {
174
+ return handlers.stkCallback.POST(request);
175
+ }
176
+ if (lastSegment === "validation" || lastSegment === "c2b-validation") {
177
+ return handlers.c2bValidation.POST(request);
178
+ }
179
+ if (lastSegment === "confirmation" || lastSegment === "c2b-confirmation") {
180
+ return handlers.c2bConfirmation.POST(request);
181
+ }
182
+ if (lastSegment === "b2c-result") {
183
+ return handlers.b2cResult.POST(request);
184
+ }
185
+ if (lastSegment === "b2c-timeout") {
186
+ return handlers.b2cTimeout.POST(request);
187
+ }
188
+ if (lastSegment === "b2b-result") {
189
+ const body = await request.json();
190
+ const parsed = client.getCallbackHandler().parseB2BCallback(body);
191
+ console.log("B2B Result:", parsed);
192
+ return NextResponse.json({ ResultCode: 0, ResultDesc: "Accepted" });
193
+ }
194
+ if (lastSegment === "b2b-timeout") {
195
+ const body = await request.json();
196
+ console.log("B2B Timeout:", body);
197
+ return NextResponse.json({
198
+ ResultCode: 0,
199
+ ResultDesc: "Timeout received"
200
+ });
201
+ }
202
+ if (lastSegment === "balance-result") {
203
+ const body = await request.json();
204
+ const parsed = client.getCallbackHandler().parseAccountBalanceCallback(body);
205
+ console.log("Balance Result:", parsed);
206
+ return NextResponse.json({ ResultCode: 0, ResultDesc: "Accepted" });
207
+ }
208
+ if (lastSegment === "balance-timeout") {
209
+ const body = await request.json();
210
+ console.log("Balance Timeout:", body);
211
+ return NextResponse.json({
212
+ ResultCode: 0,
213
+ ResultDesc: "Timeout received"
214
+ });
215
+ }
216
+ if (lastSegment === "status-result") {
217
+ const body = await request.json();
218
+ const parsed = client.getCallbackHandler().parseTransactionStatusCallback(body);
219
+ console.log("Status Result:", parsed);
220
+ return NextResponse.json({ ResultCode: 0, ResultDesc: "Accepted" });
221
+ }
222
+ if (lastSegment === "status-timeout") {
223
+ const body = await request.json();
224
+ console.log("Status Timeout:", body);
225
+ return NextResponse.json({
226
+ ResultCode: 0,
227
+ ResultDesc: "Timeout received"
228
+ });
229
+ }
230
+ if (lastSegment === "reversal-result") {
231
+ const body = await request.json();
232
+ const parsed = client.getCallbackHandler().parseReversalCallback(body);
233
+ console.log("Reversal Result:", parsed);
234
+ return NextResponse.json({ ResultCode: 0, ResultDesc: "Accepted" });
235
+ }
236
+ if (lastSegment === "reversal-timeout") {
237
+ const body = await request.json();
238
+ console.log("Reversal Timeout:", body);
239
+ return NextResponse.json({
240
+ ResultCode: 0,
241
+ ResultDesc: "Timeout received"
242
+ });
243
+ }
244
+ if (lastSegment === "stk-push") {
245
+ const body = await request.json();
246
+ const {
247
+ amount,
248
+ phoneNumber,
249
+ accountReference,
250
+ transactionDesc,
251
+ callbackUrl
252
+ } = body;
253
+ if (!amount || !phoneNumber) {
254
+ return NextResponse.json(
255
+ { error: "Amount and phone number are required" },
256
+ { status: 400 }
257
+ );
258
+ }
259
+ const response = await client.stkPush({
260
+ amount: Number(amount),
261
+ phoneNumber: String(phoneNumber),
262
+ accountReference: accountReference || "Payment",
263
+ transactionDesc: transactionDesc || "Payment",
264
+ callbackUrl
265
+ });
266
+ return NextResponse.json(response);
267
+ }
268
+ if (lastSegment === "stk-query") {
269
+ const body = await request.json();
270
+ const { CheckoutRequestID } = body;
271
+ if (!CheckoutRequestID) {
272
+ return NextResponse.json(
273
+ { error: "CheckoutRequestID is required" },
274
+ { status: 400 }
275
+ );
276
+ }
277
+ const response = await client.stkQuery({ CheckoutRequestID });
278
+ return NextResponse.json(response);
279
+ }
280
+ if (lastSegment === "b2c") {
281
+ const body = await request.json();
282
+ const {
283
+ amount,
284
+ phoneNumber,
285
+ commandID,
286
+ remarks,
287
+ occasion,
288
+ resultUrl,
289
+ timeoutUrl
290
+ } = body;
291
+ if (!amount || !phoneNumber || !commandID) {
292
+ return NextResponse.json(
293
+ { error: "Amount, phone number, and command ID are required" },
294
+ { status: 400 }
295
+ );
296
+ }
297
+ const response = await client.b2c({
298
+ amount: Number(amount),
299
+ phoneNumber: String(phoneNumber),
300
+ commandID,
301
+ remarks: remarks || "Payment",
302
+ occasion,
303
+ resultUrl,
304
+ timeoutUrl
305
+ });
306
+ return NextResponse.json(response);
307
+ }
308
+ if (lastSegment === "b2b") {
309
+ const body = await request.json();
310
+ const {
311
+ amount,
312
+ partyB,
313
+ commandID,
314
+ senderIdentifierType,
315
+ receiverIdentifierType,
316
+ accountReference,
317
+ remarks,
318
+ resultUrl,
319
+ timeoutUrl
320
+ } = body;
321
+ if (!amount || !partyB || !commandID || !accountReference) {
322
+ return NextResponse.json(
323
+ {
324
+ error: "Amount, partyB, commandID, and account reference are required"
325
+ },
326
+ { status: 400 }
327
+ );
125
328
  }
126
- // Query STK Push transaction status
127
- case "stk-query": {
128
- const body = await request.json();
129
- const { CheckoutRequestID } = body;
130
- if (!CheckoutRequestID) {
131
- return NextResponse.json(
132
- { error: "CheckoutRequestID is required" },
133
- { status: 400 }
134
- );
135
- }
136
- const response = await client.stkQuery({ CheckoutRequestID });
137
- return NextResponse.json(response);
329
+ const response = await client.b2b({
330
+ amount: Number(amount),
331
+ partyB: String(partyB),
332
+ commandID,
333
+ senderIdentifierType,
334
+ receiverIdentifierType,
335
+ accountReference: String(accountReference),
336
+ remarks: remarks || "Payment",
337
+ resultUrl,
338
+ timeoutUrl
339
+ });
340
+ return NextResponse.json(response);
341
+ }
342
+ if (lastSegment === "balance") {
343
+ const body = await request.json();
344
+ const response = await client.accountBalance(
345
+ body
346
+ );
347
+ return NextResponse.json(response);
348
+ }
349
+ if (lastSegment === "transaction-status") {
350
+ const body = await request.json();
351
+ const { transactionID } = body;
352
+ if (!transactionID) {
353
+ return NextResponse.json(
354
+ { error: "Transaction ID is required" },
355
+ { status: 400 }
356
+ );
357
+ }
358
+ const response = await client.transactionStatus(body);
359
+ return NextResponse.json(response);
360
+ }
361
+ if (lastSegment === "reversal") {
362
+ const body = await request.json();
363
+ const { transactionID, amount } = body;
364
+ if (!transactionID || !amount) {
365
+ return NextResponse.json(
366
+ { error: "Transaction ID and amount are required" },
367
+ { status: 400 }
368
+ );
369
+ }
370
+ const response = await client.reversal(body);
371
+ return NextResponse.json(response);
372
+ }
373
+ if (lastSegment === "register-c2b") {
374
+ const body = await request.json();
375
+ const { shortCode, responseType, confirmationURL, validationURL } = body;
376
+ if (!confirmationURL || !validationURL) {
377
+ return NextResponse.json(
378
+ { error: "Confirmation URL and validation URL are required" },
379
+ { status: 400 }
380
+ );
138
381
  }
139
- // Unknown endpoint
140
- default:
382
+ const response = await client.registerC2BUrl({
383
+ shortCode,
384
+ responseType,
385
+ confirmationURL,
386
+ validationURL
387
+ });
388
+ return NextResponse.json(response);
389
+ }
390
+ if (lastSegment === "generate-qr") {
391
+ const body = await request.json();
392
+ const {
393
+ merchantName,
394
+ refNo,
395
+ amount,
396
+ transactionType,
397
+ creditPartyIdentifier,
398
+ size
399
+ } = body;
400
+ if (size != "500" && size != "300") {
401
+ return NextResponse.json(
402
+ {
403
+ error: "Size must be either 500 or 300"
404
+ },
405
+ { status: 400 }
406
+ );
407
+ }
408
+ if (!merchantName || !refNo || !amount || !transactionType || !creditPartyIdentifier) {
141
409
  return NextResponse.json(
142
410
  {
143
- ResultCode: 1,
144
- ResultDesc: `Unknown endpoint: ${lastSegment}`
411
+ error: "Merchant name, reference number, amount, transaction type, and credit party identifier are required"
145
412
  },
146
- { status: 404 }
413
+ { status: 400 }
147
414
  );
415
+ }
416
+ const response = await client.generateDynamicQR({
417
+ merchantName,
418
+ refNo,
419
+ amount: Number(amount),
420
+ transactionType,
421
+ creditPartyIdentifier,
422
+ size
423
+ });
424
+ return NextResponse.json(response);
148
425
  }
426
+ return NextResponse.json(
427
+ {
428
+ ResultCode: 1,
429
+ ResultDesc: `Unknown endpoint: ${lastSegment}`
430
+ },
431
+ { status: 404 }
432
+ );
149
433
  } catch (error) {
150
434
  console.error(`M-Pesa ${lastSegment} error:`, error);
151
435
  return NextResponse.json(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@singularity-payments/nextjs",
3
- "version": "0.1.0-alpha.3",
3
+ "version": "0.1.0-alpha.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -31,7 +31,7 @@
31
31
  "next": ">=13.0.0"
32
32
  },
33
33
  "dependencies": {
34
- "@singularity-payments/core": "0.1.0-alpha.0"
34
+ "@singularity-payments/core": "0.1.0-alpha.1"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/node": "^25.0.3",