mycontext-cli 2.0.29 → 2.0.31

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.
Files changed (54) hide show
  1. package/README.md +119 -25
  2. package/dist/agents/implementations/CodeGenSubAgent.d.ts +4 -0
  3. package/dist/agents/implementations/CodeGenSubAgent.d.ts.map +1 -1
  4. package/dist/agents/implementations/CodeGenSubAgent.js +108 -22
  5. package/dist/agents/implementations/CodeGenSubAgent.js.map +1 -1
  6. package/dist/agents/implementations/DesignPipelineAgent.d.ts.map +1 -1
  7. package/dist/agents/implementations/DesignPipelineAgent.js +21 -16
  8. package/dist/agents/implementations/DesignPipelineAgent.js.map +1 -1
  9. package/dist/cli.js +11 -1
  10. package/dist/cli.js.map +1 -1
  11. package/dist/commands/generate-components.d.ts +5 -0
  12. package/dist/commands/generate-components.d.ts.map +1 -1
  13. package/dist/commands/generate-components.js +138 -12
  14. package/dist/commands/generate-components.js.map +1 -1
  15. package/dist/commands/generate.d.ts +4 -0
  16. package/dist/commands/generate.d.ts.map +1 -1
  17. package/dist/commands/generate.js +74 -56
  18. package/dist/commands/generate.js.map +1 -1
  19. package/dist/commands/init.d.ts +9 -0
  20. package/dist/commands/init.d.ts.map +1 -1
  21. package/dist/commands/init.js +237 -61
  22. package/dist/commands/init.js.map +1 -1
  23. package/dist/config/intent-dictionary.json +47 -4
  24. package/dist/config/model-versions.json +26 -0
  25. package/dist/package.json +6 -2
  26. package/dist/templates/instantdb/db.template.ts +14 -0
  27. package/dist/templates/instantdb/home-client.template.tsx +127 -0
  28. package/dist/templates/instantdb/page.template.tsx +5 -0
  29. package/dist/templates/instantdb/perms.template.ts +9 -0
  30. package/dist/templates/instantdb/schema.template.ts +28 -0
  31. package/dist/templates/playbooks/instantdb-integration.md +851 -0
  32. package/dist/templates/playbooks/mpesa-integration.md +652 -0
  33. package/dist/templates/pm-integration-config.json +20 -0
  34. package/dist/templates/ui-spec-examples.md +318 -0
  35. package/dist/templates/ui-spec-templates.json +244 -0
  36. package/dist/types/intent-dictionary.d.ts +61 -0
  37. package/dist/types/intent-dictionary.d.ts.map +1 -1
  38. package/dist/utils/envExampleGenerator.d.ts.map +1 -1
  39. package/dist/utils/envExampleGenerator.js +36 -27
  40. package/dist/utils/envExampleGenerator.js.map +1 -1
  41. package/dist/utils/hostedApiClient.d.ts +10 -3
  42. package/dist/utils/hostedApiClient.d.ts.map +1 -1
  43. package/dist/utils/hostedApiClient.js +144 -209
  44. package/dist/utils/hostedApiClient.js.map +1 -1
  45. package/dist/utils/hybridAIClient.d.ts.map +1 -1
  46. package/dist/utils/hybridAIClient.js +8 -26
  47. package/dist/utils/hybridAIClient.js.map +1 -1
  48. package/dist/utils/openRouterClient.d.ts.map +1 -1
  49. package/dist/utils/openRouterClient.js +4 -14
  50. package/dist/utils/openRouterClient.js.map +1 -1
  51. package/dist/utils/unifiedDesignContextLoader.d.ts.map +1 -1
  52. package/dist/utils/unifiedDesignContextLoader.js +25 -12
  53. package/dist/utils/unifiedDesignContextLoader.js.map +1 -1
  54. package/package.json +6 -2
@@ -0,0 +1,652 @@
1
+ ---
2
+ id: mpesa-integration
3
+ title: M-Pesa Integration Guide
4
+ description: Complete M-Pesa STK Push and C2B integration for Next.js applications
5
+ category: payment
6
+ tags: ["mpesa", "payment", "stk-push", "c2b", "daraja-api", "nextjs"]
7
+ author: MyContext
8
+ version: 1.0.0
9
+ createdAt: "2024-01-01T00:00:00.000Z"
10
+ updatedAt: "2024-01-01T00:00:00.000Z"
11
+ difficulty: intermediate
12
+ estimatedTime: "2-4 hours"
13
+ prerequisites:
14
+ [
15
+ "Next.js",
16
+ "Safaricom Daraja API credentials",
17
+ "Database (Supabase/PostgreSQL)",
18
+ ]
19
+ relatedPlaybooks: ["stripe-integration", "payment-webhooks"]
20
+ ---
21
+
22
+ # M-Pesa Integration Technical Guide
23
+
24
+ ## Overview
25
+
26
+ This guide provides a comprehensive M-Pesa integration supporting both **STK Push** (Prompt-to-Pay) and **C2B** (Customer-to-Business) payment methods for Next.js applications.
27
+
28
+ ## Prerequisites
29
+
30
+ - Safaricom Daraja API credentials (Consumer Key and Secret)
31
+ - Next.js application
32
+ - Database (Supabase/PostgreSQL recommended)
33
+ - HTTPS domain for callbacks
34
+
35
+ ## Environment Setup
36
+
37
+ ```bash
38
+ # Required environment variables
39
+ MPESA_CONSUMER_KEY=your_consumer_key
40
+ MPESA_CONSUMER_SECRET=your_consumer_secret
41
+ MPESA_BUSINESS_SHORT_CODE=your_shortcode
42
+ MPESA_PASSKEY=your_passkey
43
+ MPESA_CALLBACK_URL=https://your-domain.com/api/payment-callback
44
+ MPESA_C2B_VALIDATION_URL=https://your-domain.com/api/payment/c2b/validation
45
+ MPESA_C2B_CONFIRMATION_URL=https://your-domain.com/api/payment/c2b/confirmation
46
+ ```
47
+
48
+ ## Database Schema
49
+
50
+ ### M-Pesa Transactions Table
51
+
52
+ ```sql
53
+ CREATE TABLE mpesa_transactions (
54
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
55
+ checkout_request_id VARCHAR(255),
56
+ merchant_request_id VARCHAR(255),
57
+ order_id UUID REFERENCES orders(id),
58
+ phone_number VARCHAR(20),
59
+ amount DECIMAL(10,2) NOT NULL,
60
+ actual_amount DECIMAL(10,2),
61
+ account_reference VARCHAR(255),
62
+ transaction_desc VARCHAR(255),
63
+ mpesa_receipt_number VARCHAR(255),
64
+ transaction_date VARCHAR(20),
65
+ status VARCHAR(50) DEFAULT 'pending',
66
+ result_code INTEGER,
67
+ result_desc VARCHAR(255),
68
+ error_message TEXT,
69
+ created_at TIMESTAMP DEFAULT NOW(),
70
+ updated_at TIMESTAMP DEFAULT NOW()
71
+ );
72
+ ```
73
+
74
+ ### C2B Requests Table
75
+
76
+ ```sql
77
+ CREATE TABLE mpesa_c2b_requests (
78
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
79
+ transaction_type VARCHAR(50),
80
+ trans_id VARCHAR(255) UNIQUE,
81
+ trans_time VARCHAR(20),
82
+ trans_amount DECIMAL(10,2),
83
+ business_short_code VARCHAR(20),
84
+ bill_ref_number VARCHAR(255),
85
+ invoice_number VARCHAR(255),
86
+ org_account_balance DECIMAL(10,2),
87
+ third_party_trans_id VARCHAR(255),
88
+ msisdn VARCHAR(20),
89
+ first_name VARCHAR(100),
90
+ middle_name VARCHAR(100),
91
+ last_name VARCHAR(100),
92
+ request_type VARCHAR(20),
93
+ status VARCHAR(50) DEFAULT 'pending',
94
+ reconciliation_status VARCHAR(50) DEFAULT 'unmatched',
95
+ matched_order_id UUID REFERENCES orders(id),
96
+ created_at TIMESTAMP DEFAULT NOW()
97
+ );
98
+ ```
99
+
100
+ ## STK Push Implementation
101
+
102
+ ### 1. STK Push Initiation API
103
+
104
+ ```typescript
105
+ // app/api/payment/stk/route.ts
106
+ import { NextRequest, NextResponse } from "next/server";
107
+
108
+ export async function POST(request: NextRequest) {
109
+ try {
110
+ const { orderId, phoneNumber, amount, accountReference } =
111
+ await request.json();
112
+
113
+ // Get M-Pesa configuration
114
+ const mpesaConfig = {
115
+ consumerKey: process.env.MPESA_CONSUMER_KEY!,
116
+ consumerSecret: process.env.MPESA_CONSUMER_SECRET!,
117
+ businessShortCode: process.env.MPESA_BUSINESS_SHORT_CODE!,
118
+ passkey: process.env.MPESA_PASSKEY!,
119
+ callbackUrl: process.env.MPESA_CALLBACK_URL!,
120
+ };
121
+
122
+ // Generate access token
123
+ const accessToken = await generateAccessToken(
124
+ mpesaConfig.consumerKey,
125
+ mpesaConfig.consumerSecret
126
+ );
127
+
128
+ // Generate password
129
+ const timestamp = new Date()
130
+ .toISOString()
131
+ .replace(/[^0-9]/g, "")
132
+ .slice(0, -3);
133
+ const password = Buffer.from(
134
+ `${mpesaConfig.businessShortCode}${mpesaConfig.passkey}${timestamp}`
135
+ ).toString("base64");
136
+
137
+ // STK Push payload
138
+ const stkPushPayload = {
139
+ BusinessShortCode: mpesaConfig.businessShortCode,
140
+ Password: password,
141
+ Timestamp: timestamp,
142
+ TransactionType: "CustomerPayBillOnline",
143
+ Amount: Math.round(amount),
144
+ PartyA: phoneNumber.replace("+", ""),
145
+ PartyB: mpesaConfig.businessShortCode,
146
+ PhoneNumber: phoneNumber.replace("+", ""),
147
+ CallBackURL: mpesaConfig.callbackUrl,
148
+ AccountReference: accountReference || `ORDER-${orderId}`,
149
+ TransactionDesc: "Payment",
150
+ };
151
+
152
+ // Make API call to M-Pesa
153
+ const response = await fetch(
154
+ "https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest",
155
+ {
156
+ method: "POST",
157
+ headers: {
158
+ Authorization: `Bearer ${accessToken}`,
159
+ "Content-Type": "application/json",
160
+ },
161
+ body: JSON.stringify(stkPushPayload),
162
+ }
163
+ );
164
+
165
+ const result = await response.json();
166
+
167
+ if (result.ResponseCode === "0") {
168
+ // Store transaction in database
169
+ await storeTransaction({
170
+ checkoutRequestId: result.CheckoutRequestID,
171
+ merchantRequestId: result.MerchantRequestID,
172
+ orderId,
173
+ phoneNumber,
174
+ amount,
175
+ accountReference,
176
+ status: "pending",
177
+ });
178
+ }
179
+
180
+ return NextResponse.json(result);
181
+ } catch (error) {
182
+ console.error("STK Push error:", error);
183
+ return NextResponse.json({ error: "STK Push failed" }, { status: 500 });
184
+ }
185
+ }
186
+
187
+ async function generateAccessToken(
188
+ consumerKey: string,
189
+ consumerSecret: string
190
+ ): Promise<string> {
191
+ const response = await fetch(
192
+ "https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials",
193
+ {
194
+ method: "GET",
195
+ headers: {
196
+ Authorization: `Basic ${Buffer.from(
197
+ `${consumerKey}:${consumerSecret}`
198
+ ).toString("base64")}`,
199
+ },
200
+ }
201
+ );
202
+
203
+ const data = await response.json();
204
+ return data.access_token;
205
+ }
206
+ ```
207
+
208
+ ### 2. STK Push Callback Processing
209
+
210
+ ```typescript
211
+ // app/api/payment-callback/route.ts
212
+ import { NextRequest, NextResponse } from "next/server";
213
+
214
+ export async function POST(request: NextRequest) {
215
+ try {
216
+ const body = await request.json();
217
+
218
+ // Handle different callback structures
219
+ let stkCallback;
220
+ if (body.Body && body.Body.stkCallback) {
221
+ stkCallback = body.Body.stkCallback;
222
+ } else if (body.stkCallback) {
223
+ stkCallback = body.stkCallback;
224
+ } else if (body.Body) {
225
+ stkCallback = body.Body;
226
+ } else {
227
+ stkCallback = body;
228
+ }
229
+
230
+ const { ResultCode, ResultDesc, CheckoutRequestID, CallbackMetadata } =
231
+ stkCallback;
232
+
233
+ if (ResultCode === 0) {
234
+ // Payment successful
235
+ const metadata = CallbackMetadata?.Item || [];
236
+ const getMetadataValue = (name: string) =>
237
+ metadata.find((item: any) => item.Name === name)?.Value;
238
+
239
+ const amount = getMetadataValue("Amount");
240
+ const mpesaReceiptNumber = getMetadataValue("MpesaReceiptNumber");
241
+ const transactionDate = getMetadataValue("TransactionDate");
242
+ const phoneNumber = getMetadataValue("PhoneNumber");
243
+
244
+ // Update transaction record
245
+ await updateTransaction(CheckoutRequestID, {
246
+ mpesa_receipt_number: mpesaReceiptNumber,
247
+ transaction_date: transactionDate,
248
+ actual_amount: amount,
249
+ status: "completed",
250
+ result_code: ResultCode,
251
+ result_desc: ResultDesc,
252
+ });
253
+
254
+ // Update order status
255
+ await updateOrderStatus(CheckoutRequestID, {
256
+ payment_status: "paid",
257
+ mpesa_receipt_number: mpesaReceiptNumber,
258
+ });
259
+ } else {
260
+ // Payment failed
261
+ await updateTransaction(CheckoutRequestID, {
262
+ status: "failed",
263
+ result_code: ResultCode,
264
+ result_desc: ResultDesc,
265
+ error_message: ResultDesc,
266
+ });
267
+ }
268
+
269
+ return NextResponse.json({ ResultCode: 0, ResultDesc: "Success" });
270
+ } catch (error) {
271
+ console.error("Callback processing error:", error);
272
+ return NextResponse.json(
273
+ { ResultCode: 1, ResultDesc: "Error" },
274
+ { status: 500 }
275
+ );
276
+ }
277
+ }
278
+ ```
279
+
280
+ ## C2B Implementation
281
+
282
+ ### 1. C2B Validation
283
+
284
+ ```typescript
285
+ // app/api/payment/c2b/validation/route.ts
286
+ import { NextRequest, NextResponse } from "next/server";
287
+
288
+ export async function POST(request: NextRequest) {
289
+ try {
290
+ const {
291
+ TransactionType,
292
+ TransID,
293
+ TransTime,
294
+ TransAmount,
295
+ BusinessShortCode,
296
+ BillRefNumber,
297
+ InvoiceNumber,
298
+ OrgAccountBalance,
299
+ ThirdPartyTransID,
300
+ MSISDN,
301
+ FirstName,
302
+ MiddleName,
303
+ LastName,
304
+ } = await request.json();
305
+
306
+ // Amount validation
307
+ const amount = parseFloat(TransAmount);
308
+ if (amount < 10) {
309
+ return NextResponse.json({
310
+ ResultCode: "C2B00012",
311
+ ResultDesc: "Amount below minimum threshold",
312
+ });
313
+ }
314
+
315
+ if (amount > 100000) {
316
+ return NextResponse.json({
317
+ ResultCode: "C2B00013",
318
+ ResultDesc: "Amount above maximum threshold",
319
+ });
320
+ }
321
+
322
+ // Business shortcode validation
323
+ const isValidShortcode = await validateBusinessShortcode(BusinessShortCode);
324
+ if (!isValidShortcode) {
325
+ return NextResponse.json({
326
+ ResultCode: "C2B00014",
327
+ ResultDesc: "Invalid business shortcode",
328
+ });
329
+ }
330
+
331
+ return NextResponse.json({
332
+ ResultCode: "0",
333
+ ResultDesc: "Accepted",
334
+ });
335
+ } catch (error) {
336
+ console.error("C2B validation error:", error);
337
+ return NextResponse.json({
338
+ ResultCode: "C2B00015",
339
+ ResultDesc: "Validation error",
340
+ });
341
+ }
342
+ }
343
+ ```
344
+
345
+ ### 2. C2B Confirmation
346
+
347
+ ```typescript
348
+ // app/api/payment/c2b/confirmation/route.ts
349
+ import { NextRequest, NextResponse } from "next/server";
350
+
351
+ export async function POST(request: NextRequest) {
352
+ try {
353
+ const {
354
+ TransactionType,
355
+ TransID,
356
+ TransTime,
357
+ TransAmount,
358
+ BusinessShortCode,
359
+ BillRefNumber,
360
+ InvoiceNumber,
361
+ OrgAccountBalance,
362
+ ThirdPartyTransID,
363
+ MSISDN,
364
+ FirstName,
365
+ MiddleName,
366
+ LastName,
367
+ } = await request.json();
368
+
369
+ // Check for duplicate processing
370
+ const existingRequest = await getC2BRequest(TransID);
371
+ if (existingRequest) {
372
+ return NextResponse.json({
373
+ ResultCode: "0",
374
+ ResultDesc: "Already processed",
375
+ });
376
+ }
377
+
378
+ // Store C2B request
379
+ await storeC2BRequest({
380
+ transactionType: TransactionType,
381
+ transId: TransID,
382
+ transTime: TransTime,
383
+ transAmount: parseFloat(TransAmount),
384
+ businessShortCode: BusinessShortCode,
385
+ billRefNumber: BillRefNumber,
386
+ invoiceNumber: InvoiceNumber,
387
+ orgAccountBalance: parseFloat(OrgAccountBalance),
388
+ thirdPartyTransId: ThirdPartyTransID,
389
+ msisdn: MSISDN,
390
+ firstName: FirstName,
391
+ middleName: MiddleName,
392
+ lastName: LastName,
393
+ requestType: "confirmation",
394
+ status: "pending",
395
+ });
396
+
397
+ // Attempt to match with existing order
398
+ const matchedOrder = await matchPaymentWithOrder({
399
+ phoneNumber: MSISDN,
400
+ amount: parseFloat(TransAmount),
401
+ billRefNumber: BillRefNumber,
402
+ });
403
+
404
+ if (matchedOrder) {
405
+ await updateC2BRequest(TransID, {
406
+ reconciliationStatus: "matched",
407
+ matchedOrderId: matchedOrder.id,
408
+ });
409
+
410
+ await updateOrderStatus(matchedOrder.id, {
411
+ payment_status: "paid",
412
+ mpesa_receipt_number: TransID,
413
+ });
414
+ }
415
+
416
+ return NextResponse.json({
417
+ ResultCode: "0",
418
+ ResultDesc: "Success",
419
+ });
420
+ } catch (error) {
421
+ console.error("C2B confirmation error:", error);
422
+ return NextResponse.json({
423
+ ResultCode: "1",
424
+ ResultDesc: "Error",
425
+ });
426
+ }
427
+ }
428
+ ```
429
+
430
+ ## Frontend Integration
431
+
432
+ ### Payment Component
433
+
434
+ ```typescript
435
+ // components/PaymentForm.tsx
436
+ "use client";
437
+
438
+ import { useState } from "react";
439
+ import { Button } from "@/components/ui/button";
440
+ import { Input } from "@/components/ui/input";
441
+ import { Label } from "@/components/ui/label";
442
+
443
+ interface PaymentFormProps {
444
+ orderId: string;
445
+ amount: number;
446
+ onSuccess: (receiptNumber: string) => void;
447
+ onError: (error: string) => void;
448
+ }
449
+
450
+ export function PaymentForm({
451
+ orderId,
452
+ amount,
453
+ onSuccess,
454
+ onError,
455
+ }: PaymentFormProps) {
456
+ const [phoneNumber, setPhoneNumber] = useState("");
457
+ const [isLoading, setIsLoading] = useState(false);
458
+
459
+ const handleSTKPush = async () => {
460
+ if (!phoneNumber) {
461
+ onError("Phone number is required");
462
+ return;
463
+ }
464
+
465
+ setIsLoading(true);
466
+ try {
467
+ const response = await fetch("/api/payment/stk", {
468
+ method: "POST",
469
+ headers: { "Content-Type": "application/json" },
470
+ body: JSON.stringify({
471
+ orderId,
472
+ phoneNumber,
473
+ amount,
474
+ accountReference: `ORDER-${orderId}`,
475
+ }),
476
+ });
477
+
478
+ const result = await response.json();
479
+
480
+ if (result.ResponseCode === "0") {
481
+ // Start polling for payment status
482
+ pollPaymentStatus(orderId);
483
+ } else {
484
+ onError(result.ResponseDescription || "Payment initiation failed");
485
+ }
486
+ } catch (error) {
487
+ onError("Network error occurred");
488
+ } finally {
489
+ setIsLoading(false);
490
+ }
491
+ };
492
+
493
+ const pollPaymentStatus = async (orderId: string) => {
494
+ const maxAttempts = 30; // 5 minutes with 10-second intervals
495
+ let attempts = 0;
496
+
497
+ const interval = setInterval(async () => {
498
+ attempts++;
499
+
500
+ try {
501
+ const response = await fetch(`/api/payment/status?orderId=${orderId}`);
502
+ const status = await response.json();
503
+
504
+ if (status.payment_status === "paid") {
505
+ clearInterval(interval);
506
+ onSuccess(status.mpesa_receipt_number);
507
+ } else if (status.payment_status === "failed") {
508
+ clearInterval(interval);
509
+ onError("Payment failed");
510
+ } else if (attempts >= maxAttempts) {
511
+ clearInterval(interval);
512
+ onError("Payment timeout");
513
+ }
514
+ } catch (error) {
515
+ if (attempts >= maxAttempts) {
516
+ clearInterval(interval);
517
+ onError("Status check failed");
518
+ }
519
+ }
520
+ }, 10000); // Check every 10 seconds
521
+ };
522
+
523
+ return (
524
+ <div className="space-y-4">
525
+ <div>
526
+ <Label htmlFor="phone">Phone Number</Label>
527
+ <Input
528
+ id="phone"
529
+ type="tel"
530
+ placeholder="+254712345678"
531
+ value={phoneNumber}
532
+ onChange={(e) => setPhoneNumber(e.target.value)}
533
+ />
534
+ </div>
535
+
536
+ <div className="text-center">
537
+ <p className="text-sm text-gray-600 mb-2">
538
+ Amount: KES {amount.toLocaleString()}
539
+ </p>
540
+ <Button onClick={handleSTKPush} disabled={isLoading} className="w-full">
541
+ {isLoading ? "Processing..." : "Pay with M-Pesa"}
542
+ </Button>
543
+ </div>
544
+ </div>
545
+ );
546
+ }
547
+ ```
548
+
549
+ ## Error Handling
550
+
551
+ ### Common M-Pesa Error Codes
552
+
553
+ ```typescript
554
+ const errorMessages = {
555
+ 1032: "Payment cancelled by user",
556
+ 1037: "User timeout - no response",
557
+ 1: "Insufficient balance",
558
+ 2001: "Invalid PIN entered",
559
+ 1019: "Transaction expired",
560
+ 1001: "User busy - another transaction in progress",
561
+ 1025: "Request processing error",
562
+ 9999: "System error",
563
+ };
564
+
565
+ export function getErrorMessage(resultCode: number): string {
566
+ return errorMessages[resultCode] || "Unknown error occurred";
567
+ }
568
+ ```
569
+
570
+ ## Security Best Practices
571
+
572
+ 1. **Validate all inputs** - Sanitize phone numbers and amounts
573
+ 2. **Use HTTPS only** - Secure all payment endpoints
574
+ 3. **Implement rate limiting** - Prevent abuse of payment endpoints
575
+ 4. **Log all transactions** - Maintain audit trail
576
+ 5. **Validate callbacks** - Verify webhook authenticity
577
+ 6. **Store sensitive data securely** - Use environment variables
578
+
579
+ ## Testing
580
+
581
+ ### Test STK Push
582
+
583
+ ```typescript
584
+ // Test STK Push with sandbox
585
+ const testSTKPush = async () => {
586
+ const response = await fetch("/api/payment/stk", {
587
+ method: "POST",
588
+ headers: { "Content-Type": "application/json" },
589
+ body: JSON.stringify({
590
+ orderId: "test-order-123",
591
+ phoneNumber: "+254708374149", // Test number
592
+ amount: 100,
593
+ accountReference: "TEST-123",
594
+ }),
595
+ });
596
+
597
+ return response.json();
598
+ };
599
+ ```
600
+
601
+ ## Monitoring
602
+
603
+ ### Key Metrics to Track
604
+
605
+ - Transaction success rate
606
+ - Average processing time
607
+ - Error rates by type
608
+ - Payment completion rates
609
+ - Callback processing time
610
+
611
+ ### Database Queries
612
+
613
+ ```sql
614
+ -- Success rate
615
+ SELECT
616
+ COUNT(*) as total,
617
+ SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as successful,
618
+ ROUND(SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) as success_rate
619
+ FROM mpesa_transactions
620
+ WHERE created_at >= NOW() - INTERVAL '24 hours';
621
+
622
+ -- Error analysis
623
+ SELECT result_desc, COUNT(*) as count
624
+ FROM mpesa_transactions
625
+ WHERE status = 'failed'
626
+ AND created_at >= NOW() - INTERVAL '24 hours'
627
+ GROUP BY result_desc
628
+ ORDER BY count DESC;
629
+ ```
630
+
631
+ ## Troubleshooting
632
+
633
+ ### Common Issues
634
+
635
+ 1. **STK Push Timeout**
636
+
637
+ - Check network connectivity
638
+ - Verify callback URL accessibility
639
+ - Check M-Pesa service status
640
+
641
+ 2. **C2B Payment Not Matching**
642
+
643
+ - Verify phone number format
644
+ - Check amount precision
645
+ - Review matching strategies
646
+
647
+ 3. **Configuration Errors**
648
+ - Validate environment variables
649
+ - Check business shortcode
650
+ - Verify API credentials
651
+
652
+ This playbook provides a complete, production-ready M-Pesa integration that can be used as a reference for any Next.js application requiring mobile payment functionality.
@@ -0,0 +1,20 @@
1
+ {
2
+ "pmEndpoint": "https://your-mycontext-pm-instance.com/api",
3
+ "webhookUrl": "https://your-mycontext-pm-instance.com/webhook",
4
+ "apiKey": "your-api-key-here",
5
+ "projectId": "your-project-id",
6
+ "syncInterval": 60,
7
+ "enableRealTimeSync": true,
8
+ "retryAttempts": 3,
9
+ "timeout": 30000,
10
+ "_comments": {
11
+ "pmEndpoint": "The base URL of your mycontext PM instance",
12
+ "webhookUrl": "URL where progress updates should be sent",
13
+ "apiKey": "Authentication key for PM API access",
14
+ "projectId": "The specific project ID to integrate with",
15
+ "syncInterval": "How often to sync (minutes)",
16
+ "enableRealTimeSync": "Enable automatic periodic syncing",
17
+ "retryAttempts": "Number of retry attempts for failed requests",
18
+ "timeout": "Request timeout in milliseconds"
19
+ }
20
+ }