# NonSTK C2B Callback URL Registration - Complete Documentation

## Overview

This document describes the implementation of separate NonSTK (Non-SIM Toolkit) C2B callback URL registration for the LendCore M-Pesa integration. This allows the system to register and manage two independent C2B configurations with Daraja:

1. **Regular C2B** - For standard bill reference-based payments
2. **NonSTK C2B** - For Paybill payments without USSD prompt (separate short code)

---

## Table of Contents

1. [Architecture](#architecture)
2. [Configuration](#configuration)
3. [API Endpoints](#api-endpoints)
4. [Implementation Details](#implementation-details)
5. [Usage Examples](#usage-examples)
6. [Files Modified/Created](#files-modifiedcreated)
7. [Updating Callback URLs](#updating-callback-urls)
8. [Troubleshooting](#troubleshooting)

---

## Architecture

### Configuration Hierarchy

```
┌─────────────────────────────────────────────────────────┐
│                      .env File                          │
│  (Environment-specific URL values)                      │
└──────────────────┬──────────────────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────────────────┐
│                  config/mpesa.php                       │
│  (Configuration loader from .env)                       │
│                                                         │
│  mpesa.c2b:                                             │
│    ├─ short_code                                        │
│    ├─ validation_url                                    │
│    ├─ confirmation_url                                  │
│    └─ nonstk: (nested config for NonSTK)               │
│        ├─ short_code                                    │
│        ├─ validation_url                                │
│        ├─ confirmation_url                              │
│        └─ response_type                                 │
└──────────────────┬──────────────────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────────────────┐
│              MpesaService (Service Layer)               │
│                                                         │
│  - registerC2bUrls()                                    │
│  - registerC2bNonStkUrls()                              │
└──────────────────┬──────────────────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────────────────┐
│           MpesaApiService (HTTP Client)                 │
│                                                         │
│  Sends POST requests to Daraja API                      │
└──────────────────┬──────────────────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────────────────┐
│            Daraja Sandbox/Production API                │
│                                                         │
│  /mpesa/c2b/v2/registerurl endpoint                     │
└─────────────────────────────────────────────────────────┘
```

### Request Flow Diagram

```
┌──────────────────────┐
│   Admin User         │
│   (with API Token)   │
└──────────┬───────────┘
           │
           ▼
┌──────────────────────────────────────────────────────────┐
│  POST /api/payment/c2b/nonstk/register-urls              │
│  (MpesaCallbackController::registerNonStkCallbackUrls)  │
└──────────┬───────────────────────────────────────────────┘
           │
           ▼
┌──────────────────────────────────────────────────────────┐
│  MpesaService::registerC2bNonStkUrls()                   │
│                                                          │
│  1. Fetch config('mpesa.c2b.nonstk')                    │
│  2. Build payload with URLs from .env                    │
│  3. Log the registration attempt                         │
└──────────┬───────────────────────────────────────────────┘
           │
           ▼
┌──────────────────────────────────────────────────────────┐
│  MpesaApiService::post('c2b_register', $payload)         │
│                                                          │
│  1. Authenticate with M-Pesa                             │
│  2. POST request to Daraja C2B register endpoint         │
│  3. Return response                                      │
└──────────┬───────────────────────────────────────────────┘
           │
           ▼
┌──────────────────────────────────────────────────────────┐
│  Response returned to Client                             │
│                                                          │
│  {                                                       │
│    "message": "Non-STK C2B callback URLs registered...", │
│    "result": { "success": true, ... }                   │
│  }                                                       │
└──────────────────────────────────────────────────────────┘
```

---

## Configuration

### Environment Variables (.env)

```env
# Regular C2B Settings (for normal bill reference payments)
MPESA_C2B_SHORT_CODE=174379
MPESA_C2B_VALIDATION_URL=https://your-domain.com/api/payment/c2b/validation
MPESA_C2B_CONFIRMATION_URL=https://your-domain.com/api/payment/c2b/confirmation
MPESA_C2B_RESPONSE_TYPE=Completed

# Non-STK C2B Settings (for Paybill without USSD - separate short code)
MPESA_C2B_NONSTK_SHORT_CODE=600999
MPESA_C2B_NONSTK_VALIDATION_URL=https://your-domain.com/api/payment/c2b/nonstk/validation
MPESA_C2B_NONSTK_CONFIRMATION_URL=https://your-domain.com/api/payment/c2b/nonstk/confirmation
MPESA_C2B_NONSTK_RESPONSE_TYPE=Completed
```

### Environment Selection

Set `MPESA_ENV=sandbox` for Daraja sandbox testing and `MPESA_ENV=production` for live production requests. `config/mpesa.php` uses this value to choose the correct Daraja URLs for OAuth, B2C, C2B registration, and STK push.

All callback URLs for regular C2B, Non-STK C2B, STK, and B2C disbursement should be defined in `.env` so the application can switch cleanly between sandbox and production.

### Configuration File (config/mpesa.php)

```php
'c2b' => [
    'short_code' => env('MPESA_C2B_SHORT_CODE', env('MPESA_SHORTCODE')),
    'validation_url' => env('MPESA_C2B_VALIDATION_URL', env('MPESA_CALLBACK_URL')),
    'confirmation_url' => env('MPESA_C2B_CONFIRMATION_URL', env('MPESA_CALLBACK_URL')),
    'response_type' => env('MPESA_C2B_RESPONSE_TYPE', 'Completed'),

    'nonstk' => [
        'short_code' => env('MPESA_C2B_NONSTK_SHORT_CODE'),
        'validation_url' => env('MPESA_C2B_NONSTK_VALIDATION_URL'),
        'confirmation_url' => env('MPESA_C2B_NONSTK_CONFIRMATION_URL'),
        'response_type' => env('MPESA_C2B_NONSTK_RESPONSE_TYPE', 'Completed'),
    ],
],
```

---

## API Endpoints

### 1. Register Regular C2B Callbacks

**Route:** `POST /api/payment/register-callbacks`

**Authentication:** Required (Bearer token with admin role)

**Description:** Registers the regular C2B callback URLs with M-Pesa Daraja for standard bill reference payments.

**Request:**
```bash
curl -X POST http://localhost:8000/api/payment/register-callbacks \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  -H "Content-Type: application/json"
```

**Response (Success - 200):**
```json
{
  "message": "Callback URLs registered successfully.",
  "result": {
    "ResponseCode": "0",
    "ResponseDescription": "Success"
  }
}
```

**Response (Error - 422):**
```json
{
  "message": "Failed to register callback URLs.",
  "error": "Error details from M-Pesa API"
}
```

**Configuration Used:**
- Short Code: `MPESA_C2B_SHORT_CODE`
- Validation URL: `MPESA_C2B_VALIDATION_URL`
- Confirmation URL: `MPESA_C2B_CONFIRMATION_URL`

---

### 2. Register NonSTK C2B Callbacks

**Route:** `POST /api/payment/c2b/nonstk/register-urls`

**Authentication:** Required (Bearer token with admin role)

**Description:** Registers the NonSTK C2B callback URLs with M-Pesa Daraja for non-SIM Toolkit Paybill payments (without USSD prompt).

**Request:**
```bash
curl -X POST http://localhost:8000/api/payment/c2b/nonstk/register-urls \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  -H "Content-Type: application/json"
```

**Response (Success - 200):**
```json
{
  "message": "Non-STK C2B callback URLs registered successfully to Daraja sandbox.",
  "result": {
    "ResponseCode": "0",
    "ResponseDescription": "Success"
  }
}
```

**Response (Error - 422):**
```json
{
  "message": "Failed to register Non-STK callback URLs.",
  "error": "Error details from M-Pesa API"
}
```

**Configuration Used:**
- Short Code: `MPESA_C2B_NONSTK_SHORT_CODE`
- Validation URL: `MPESA_C2B_NONSTK_VALIDATION_URL`
- Confirmation URL: `MPESA_C2B_NONSTK_CONFIRMATION_URL`

---

### 3. C2B Validation (Regular)

**Route:** `POST /api/payment/c2b/validation`

**Authentication:** Not required (M-Pesa initiates this)

**Description:** M-Pesa validation endpoint for regular C2B payments. Always returns success.

**Request from M-Pesa:**
```json
{
  "TransactionType": "Pay Bill Online",
  "TransID": "LDV92SV60SZ",
  "TransTime": "20130704111653",
  "TransAmount": "1000",
  "BusinessShortCode": "174379",
  "BillRefNumber": "00000",
  "InvoiceNumber": "",
  "OrgAccountBalance": "",
  "ThirdPartyTransID": "",
  "MSISDN": "254723123456",
  "FirstName": "John"
}
```

**Response:**
```json
{
  "ResultCode": "0",
  "ResultDesc": "Accepted"
}
```

---

### 4. C2B Confirmation (Regular)

**Route:** `POST /api/payment/c2b/confirmation`

**Authentication:** Not required (M-Pesa initiates this)

**Description:** M-Pesa callback for regular C2B payments. Processes payment and allocates to user loans.

**Business Logic:**
1. Find user by national ID (BillRefNumber)
2. If user found:
   - Record payment for user's active loans
   - Process repayment allocation
3. If user not found:
   - Save to suspense account for manual review

---

### 5. NonSTK C2B Validation

**Route:** `POST /api/payment/c2b/nonstk/validation`

**Authentication:** Not required (M-Pesa initiates this)

**Description:** M-Pesa validation endpoint for NonSTK C2B payments. Delegates to base `c2bValidation()` method.

---

### 6. NonSTK C2B Confirmation

**Route:** `POST /api/payment/c2b/nonstk/confirmation`

**Authentication:** Not required (M-Pesa initiates this)

**Description:** M-Pesa callback for NonSTK C2B payments. Delegates to base `c2bConfirmation()` method.

---

## Implementation Details

### MpesaService Methods

#### registerC2bUrls()

```php
public function registerC2bUrls(): array
{
    $c2bConfig = config('mpesa.c2b');

    $requestPayload = [
        'ShortCode' => $c2bConfig['short_code'],
        'ResponseType' => $c2bConfig['response_type'] ?? 'Completed',
        'ConfirmationURL' => $c2bConfig['confirmation_url'],
        'ValidationURL' => $c2bConfig['validation_url'],
    ];

    Log::info('Registering C2B URLs to Daraja sandbox', [
        'short_code' => $requestPayload['ShortCode'],
        'confirmation_url' => $requestPayload['ConfirmationURL'],
        'validation_url' => $requestPayload['ValidationURL'],
    ]);

    return $this->apiService->post('c2b_register', $requestPayload);
}
```

**Key Features:**
- Reads directly from `config('mpesa.c2b')`
- Logs all registration attempts for audit trail
- Sends POST request to Daraja via MpesaApiService

#### registerC2bNonStkUrls()

```php
public function registerC2bNonStkUrls(): array
{
    $c2bNonStkConfig = config('mpesa.c2b.nonstk');

    $requestPayload = [
        'ShortCode' => $c2bNonStkConfig['short_code'],
        'ResponseType' => $c2bNonStkConfig['response_type'] ?? 'Completed',
        'ConfirmationURL' => $c2bNonStkConfig['confirmation_url'],
        'ValidationURL' => $c2bNonStkConfig['validation_url'],
    ];

    Log::info('Registering Non-STK C2B URLs to Daraja sandbox', [
        'short_code' => $requestPayload['ShortCode'],
        'confirmation_url' => $requestPayload['ConfirmationURL'],
        'validation_url' => $requestPayload['ValidationURL'],
    ]);

    return $this->apiService->post('c2b_register', $requestPayload);
}
```

**Key Features:**
- Reads from dedicated `config('mpesa.c2b.nonstk')` config
- Uses separate short code and URLs from .env
- Independent registration process from regular C2B
- Logs all details for debugging

### MpesaCallbackController Methods

#### registerCallbackUrls()

```php
public function registerCallbackUrls(Request $request)
{
    try {
        $mpesaService = new MpesaService(new MpesaApiService(new MpesaAuthService()));
        $result = $mpesaService->registerC2bUrls();

        return response()->json([
            'message' => 'Callback URLs registered successfully.',
            'result' => $result,
        ], 200);
    } catch (\Throwable $e) {
        return response()->json([
            'message' => 'Failed to register callback URLs.',
            'error' => $e->getMessage(),
        ], 422);
    }
}
```

#### registerNonStkCallbackUrls()

```php
public function registerNonStkCallbackUrls(Request $request)
{
    try {
        $mpesaService = new MpesaService(new MpesaApiService(new MpesaAuthService()));
        $result = $mpesaService->registerC2bNonStkUrls();

        return response()->json([
            'message' => 'Non-STK C2B callback URLs registered successfully to Daraja sandbox.',
            'result' => $result,
        ], 200);
    } catch (\Throwable $e) {
        return response()->json([
            'message' => 'Failed to register Non-STK callback URLs.',
            'error' => $e->getMessage(),
        ], 422);
    }
}
```

### Wrapper Methods (For Clarity)

```php
public function c2bNonStkValidation(Request $request)
{
    return $this->c2bValidation($request);
}

public function c2bNonStkConfirmation(Request $request)
{
    return $this->c2bConfirmation($request);
}
```

These wrapper methods:
- Maintain code clarity by naming methods explicitly as "NonSTK"
- Delegate to base methods to avoid code duplication
- Keep the business logic in one place

---

## Usage Examples

### Example 1: Register NonSTK URLs Using cURL

```bash
# Get admin token first (you need to create this)
ADMIN_TOKEN="YOUR_ADMIN_TOKEN_HERE"

# Register NonSTK C2B URLs
curl -X POST http://localhost:8000/api/payment/c2b/nonstk/register-urls \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json"
```

### Example 2: Using the PHP Registration Script

```bash
cd c:\xampp\htdocs\LendCore
php register_nonstk_urls.php
```

**Output:**
```
====================================
NonSTK C2B URL Registration Script
====================================

Admin User: admin@lendcore.com
Token: eyJ0eXAiOiJKV1QiLCJhbGc...

NonSTK Configuration from .env:
  Short Code: 600999
  Validation URL: https://flawed-exactly-jerrell.ngrok-free.dev/api/payment/c2b/nonstk/validation
  Confirmation URL: https://flawed-exactly-jerrell.ngrok-free.dev/api/payment/c2b/nonstk/confirmation

Calling: POST http://localhost:8000/api/payment/c2b/nonstk/register-urls

HTTP Status Code: 200
Response:
{
  "message": "Non-STK C2B callback URLs registered successfully to Daraja sandbox.",
  "result": {
    "ResponseCode": "0",
    "ResponseDescription": "Success"
  }
}

====================================
SUCCESS: NonSTK URLs registered!
====================================
```

### Example 3: Registering from Postman

1. **Create a new POST request:**
   - URL: `http://localhost:8000/api/payment/c2b/nonstk/register-urls`

2. **Add Authorization Header:**
   - Type: Bearer Token
   - Token: `YOUR_ADMIN_TOKEN`

3. **Send request**
   - Response should be 200 with success message

---

## Files Modified/Created

### Modified Files

#### 1. `app/Http/Controllers/Api/MpesaCallbackController.php`
- Added `registerNonStkCallbackUrls()` method
- Added `c2bNonStkValidation()` wrapper method
- Added `c2bNonStkConfirmation()` wrapper method

**Changes:**
```php
// New methods added
public function registerNonStkCallbackUrls(Request $request)
public function c2bNonStkValidation(Request $request)
public function c2bNonStkConfirmation(Request $request)
```

#### 2. `app/Services/MpesaService.php`
- Updated `registerC2bUrls()` to use config values directly
- Added `registerC2bNonStkUrls()` method

**Changes:**
```php
// Updated method
public function registerC2bUrls(): array

// New method added
public function registerC2bNonStkUrls(): array
```

#### 3. `routes/api.php`
- Added route for NonSTK registration endpoint

**Route added:**
```php
Route::post('/payment/c2b/nonstk/register-urls', [MpesaCallbackController::class, 'registerNonStkCallbackUrls'])->middleware(['admin']);
```

### Created Files

#### 1. `register_nonstk_urls.php`
Convenience script for registering NonSTK C2B URLs to Daraja.

**Features:**
- Finds admin user in database
- Creates temporary API token
- Makes HTTP request to registration endpoint
- Displays response and status

**Usage:**
```bash
php register_nonstk_urls.php
```

---

## Updating Callback URLs

### Step 1: Update .env Variables

Edit `.env` file and update the URLs:

```env
# Update these values
MPESA_C2B_NONSTK_VALIDATION_URL=https://new-domain.com/api/payment/c2b/nonstk/validation
MPESA_C2B_NONSTK_CONFIRMATION_URL=https://new-domain.com/api/payment/c2b/nonstk/confirmation
```

### Step 2: Clear Config Cache (Optional)

```bash
php artisan config:clear
```

### Step 3: Register New URLs with Daraja

```bash
# Option 1: Using the registration script
php register_nonstk_urls.php

# Option 2: Using cURL
curl -X POST http://localhost:8000/api/payment/c2b/nonstk/register-urls \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  -H "Content-Type: application/json"
```

### Step 4: Verify Registration

Check application logs:
```bash
tail -f storage/logs/laravel.log | grep "Registering Non-STK C2B URLs"
```

Expected log output:
```
[2026-05-27 14:30:45] local.INFO: Registering Non-STK C2B URLs to Daraja sandbox {
  "short_code": "600999",
  "confirmation_url": "https://new-domain.com/api/payment/c2b/nonstk/confirmation",
  "validation_url": "https://new-domain.com/api/payment/c2b/nonstk/validation"
}
```

---

## Troubleshooting

### Issue 1: "No admin user found in database"

**Symptom:** When running `php register_nonstk_urls.php`, you get:
```
Error: No admin user found in database.
```

**Solution:**
1. Create an admin user:
   ```bash
   php artisan tinker
   > \App\Models\User::factory()->admin()->create(['email' => 'admin@lendcore.com']);
   > exit
   ```

2. Or check existing users:
   ```bash
   php artisan tinker
   > \App\Models\User::where('role', 'admin')->get();
   ```

---

### Issue 2: "OriginatorConversationID mismatch" in logs

**Symptom:** C2B callbacks fail with OriginatorConversationID mismatch

**Possible Causes:**
1. URLs registered with wrong short code
2. M-Pesa sending callbacks to different endpoint
3. Configuration mismatch between regular and NonSTK

**Solution:**
1. Verify `.env` values are correct:
   ```bash
   grep "MPESA_C2B_NONSTK" .env
   ```

2. Re-register URLs:
   ```bash
   php register_nonstk_urls.php
   ```

3. Check M-Pesa console to confirm URLs registered

---

### Issue 3: "CURL Error: SSL certificate problem"

**Symptom:** When running script: `CURL Error: SSL certificate problem`

**Solution:**
1. For development, SSL verification is disabled in the script
2. For production, ensure certificates are valid
3. Check ngrok status if using ngrok:
   ```bash
   # Your ngrok URL should be accessible
   curl -I https://your-ngrok-url.com/api/payment/c2b/nonstk/validation
   ```

---

### Issue 4: "Failed to register callback URLs" with HTTP 422

**Symptom:** API returns 422 error when registering

**Debugging Steps:**
1. Check application logs:
   ```bash
   tail -f storage/logs/laravel.log
   ```

2. Verify M-Pesa credentials in `.env`:
   ```bash
   grep "MPESA_KEY\|MPESA_SECRET" .env
   ```

3. Test M-Pesa authentication:
   ```bash
   php artisan tinker
   > (new \App\Services\MpesaAuthService())->getAccessToken();
   ```

4. Check if endpoints are correct in `config/mpesa.php`

---

### Issue 5: Regular C2B and NonSTK using same short code

**Symptom:** Both endpoints registered with same short code (174379 instead of separate codes)

**Solution:**
1. Verify `.env` has separate short codes:
   ```bash
   grep "MPESA_.*SHORT_CODE" .env
   ```
   Should show:
   ```
   MPESA_SHORTCODE=174379
   MPESA_C2B_NONSTK_SHORT_CODE=600999
   ```

2. If values are missing, set them:
   ```bash
   echo "MPESA_C2B_NONSTK_SHORT_CODE=600999" >> .env
   ```

3. Re-register NonSTK URLs

---

## Best Practices

### 1. Keep URLs Updated

- Update URLs in `.env` before registering with Daraja
- Always re-register after changing URLs
- Use version control to track URL changes

### 2. Use Environment-Specific Configs

```env
# Development
MPESA_C2B_NONSTK_VALIDATION_URL=https://dev-domain.ngrok-free.dev/api/payment/c2b/nonstk/validation

# Production (commented out during development)
# MPESA_C2B_NONSTK_VALIDATION_URL=https://production-domain.com/api/payment/c2b/nonstk/validation
```

### 3. Monitor Logs

```bash
# Watch for registration logs in real-time
tail -f storage/logs/laravel.log | grep "C2B"
```

### 4. Test Endpoints

```bash
# Verify endpoints are accessible before registering
curl -I https://your-domain.com/api/payment/c2b/nonstk/validation
curl -I https://your-domain.com/api/payment/c2b/nonstk/confirmation
```

### 5. Document Short Codes

Keep a spreadsheet of which short codes are for which environment:
- **Development**: 600999 (NonSTK)
- **Staging**: 600998 (NonSTK)
- **Production**: [Actual production short code]

---

## Testing Checklist

- [ ] All .env variables are set correctly
- [ ] config/mpesa.php loads .env values properly
- [ ] Admin user exists in database
- [ ] Registration script can create token
- [ ] Endpoints are accessible from Daraja
- [ ] M-Pesa Daraja console shows registered URLs
- [ ] Regular C2B endpoint works separately
- [ ] NonSTK C2B endpoint works separately
- [ ] Logs show successful registration
- [ ] Test C2B payment from M-Pesa sandbox

---

## Summary

This implementation provides:

✅ **Separation of Concerns** - Regular and NonSTK C2B are completely independent  
✅ **Configuration-Driven** - All URLs managed via `.env`  
✅ **Audit Trail** - All registrations logged for debugging  
✅ **Easy Updates** - Change URLs in `.env` and re-register  
✅ **Admin-Only Access** - Registration endpoints require admin authentication  
✅ **Convenience Script** - PHP script for easy registration  
✅ **Error Handling** - Comprehensive error messages and logging  

---

**Last Updated:** May 27, 2026  
**Version:** 1.0  
**Author:** LendCore Development Team

