# Separate C2B Callback Registration Architecture

# Objective

Ensure the new non-STK Paybill C2B implementation uses completely separate callback URLs from the existing C2B/STK flows already present in the application.

This prevents:
- callback collisions
- STK callback contamination
- mixed repayment processing
- accidental loan misrouting
- shared callback business logic conflicts

---

# Important Architectural Rule

The non-STK Paybill repayment flow MUST have:

- separate routes
- separate controller methods
- separate registration logic
- separate environment variables
- separate shortcode configuration

from:
- STK Push
- existing C2B callbacks
- checkout request callbacks
- B2C flows

---

# Required Separation Structure

## Existing STK / Payment Flow

These must remain untouched.

Example existing routes:

```http id="8p3m3g"
POST /api/mpesa/stk/callback
POST /api/mpesa/callback
POST /api/payment/callback
```

These continue handling:
- STK Push
- checkout requests
- app-initiated payments

---

# New Dedicated Non-STK Paybill Flow

The new Paybill implementation must use isolated routes:

```http id="bqarf6"
POST /api/payment/c2b/nonstk/validation
POST /api/payment/c2b/nonstk/confirmation
POST /api/payment/c2b/nonstk/register-urls
```

These routes must ONLY handle:
- manual Paybill payments
- National ID BillRef matching
- non-STK borrower repayments

---

# Recommended Route Definitions

## `routes/api.php`

```php id="4tp78v"
Route::prefix('payment/c2b/nonstk')->group(function () {

    Route::post('/validation', [
        MpesaCallbackController::class,
        'c2bValidation'
    ]);

    Route::post('/confirmation', [
        MpesaCallbackController::class,
        'c2bConfirmation'
    ]);

    Route::post('/register-urls', [
        MpesaCallbackController::class,
        'registerC2bUrls'
    ]);

});
```

---

# Required Environment Variables

The non-STK C2B flow must use dedicated environment variables.

### Daraja Environment Selection

Set `MPESA_ENV=sandbox` for Daraja sandbox testing and `MPESA_ENV=production` for live production traffic. The application reads this from `.env` and selects the correct Daraja endpoints for registration, STK push, and B2C/C2B callbacks.

## `.env`

```env id="gg5l8u"
# Non-STK C2B Paybill

MPESA_C2B_SHORT_CODE=600999

MPESA_C2B_VALIDATION_URL=https://yourdomain.com/api/payment/c2b/nonstk/validation

MPESA_C2B_CONFIRMATION_URL=https://yourdomain.com/api/payment/c2b/nonstk/confirmation
```

---

# Existing STK Variables Must Remain Separate

```env id="yzw2w4"
# Existing STK Push

MPESA_SHORTCODE=174379

MPESA_CALLBACK_URL=https://yourdomain.com/api/mpesa/stk/callback
```

---

# Callback Registration Requirements

# Critical Requirement

The new registration endpoint MUST NOT overwrite:
- existing STK callbacks
- existing C2B callbacks
- existing Daraja integrations

---

# Dedicated Registration Method

## `MpesaCallbackController.php`

### Required Method

```php id="jznx2n"
registerC2bUrls()
```

Purpose:
- register ONLY non-STK Paybill URLs
- isolate C2B repayment flow
- preserve existing integrations

---

# Registration Endpoint

## Daraja Sandbox / Production Endpoint

```text id="7e5w3f"
POST /mpesa/c2b/v2/registerurl
```

---

# Registration Payload

```json id="ecdkmw"
{
  "ShortCode": "600999",
  "ResponseType": "Completed",
  "ConfirmationURL": "https://yourdomain.com/api/payment/c2b/nonstk/confirmation",
  "ValidationURL": "https://yourdomain.com/api/payment/c2b/nonstk/validation"
}
```

---

# Non-STK URL Registration Request

## API request

```http
POST /api/payment/c2b/nonstk/register-urls HTTP/1.1
Host: yourdomain.com
Authorization: Bearer <admin_token>
Accept: application/json
Content-Type: application/json
```

## Request body

No request payload is required. The endpoint reads the configured non-STK C2B values from `config/mpesa.php` / environment variables.

## Expected response

```json
{
  "message": "Callback URLs registered successfully.",
  "result": {
    "ConversationID": "AG_20240524_00006d4f4ff6f1234567",
    "OriginatorConversationID": "123-456-789",
    "ResponseDescription": "success"
  }
}
```

> The call must be made with an admin-authenticated token. The endpoint will register only the non-STK C2B URLs when `MPESA_C2B_NONSTK_*` values are configured.

---

# Important Separation Rules

## DO NOT reuse:

```text id="r9r8a7"
/api/mpesa/callback
```

---

## DO NOT reuse:

```text id="xfv3wy"
/api/payment/callback
```

---

## DO NOT mix:

- STK callbacks
- manual Paybill callbacks
- checkout request callbacks
- B2C callbacks

inside the same endpoint.

---

# Recommended Controller Structure

## Existing Controller Methods

```php id="i2fvij"
stkCallback()
checkoutCallback()
b2cCallback()
```

---

# Implemented Non-STK C2B Documentation

## Active routes now supported

- `POST /api/payment/c2b/nonstk/validation`
- `POST /api/payment/c2b/nonstk/confirmation`
- `POST /api/payment/c2b/nonstk/register-urls`

These are added alongside existing endpoints and do not replace or override them.

## Config structure

The implementation added dedicated config values under `config/mpesa.php`:

```php
'c2b' => [
    'short_code' => env('MPESA_C2B_SHORT_CODE', ...),
    'validation_url' => env('MPESA_C2B_VALIDATION_URL', ...),
    'confirmation_url' => env('MPESA_C2B_CONFIRMATION_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', ...),
    ],
],
```

### Supported environment variables

- `MPESA_C2B_NONSTK_SHORT_CODE`
- `MPESA_C2B_NONSTK_VALIDATION_URL`
- `MPESA_C2B_NONSTK_CONFIRMATION_URL`
- `MPESA_C2B_NONSTK_RESPONSE_TYPE`

These fall back to the existing generic C2B values when not present.

## Controller behavior

The callback controller now exposes separate wrapper methods for non-STK callbacks:

- `c2bNonStkValidation()`
- `c2bNonStkConfirmation()`

They reuse the existing validation/confirmation logic while keeping the route namespace isolated.

## Callback registration

The admin registration endpoint now also supports the non-STK path:

- `POST /api/payment/c2b/nonstk/register-urls`

This calls the same `registerC2bUrls()` logic in `MpesaService`, which now selects the non-STK config values when available.

## Safety guarantee

- Existing M-Pesa flows remain unchanged.
- Generic C2B and STK routes continue to work.
- Non-STK Paybill uses isolated environment variables and route paths.
- Registration of non-STK URLs will not overwrite the existing STK or generic C2B registration if properly configured.

## National ID mapping

The C2B confirmation endpoint now supports BillRefNumber matching to user national IDs:

1. **Reference pattern matching** (in order of priority):
   - `RP-<id>` → Repayment lookup
   - `LN-<id>` → Loan lookup
   - `<numeric>` → Loan ID, then National ID lookup

2. **National ID lookup flow**:
   - If BillRefNumber is numeric and doesn't match a loan ID
   - Search for user with matching national_id
   - If user found, fetch their earliest active (approved) loan
   - Apply C2B payment to that loan

3. **Database setup**:
   - All users have been seeded with random 6-10 digit national IDs
   - Assign specific IDs via: `php artisan user:assign-national-id email@domain.com 25529406`

---

# New Dedicated Methods

```php id="7h0u9v"
c2bValidation()
c2bConfirmation()
registerC2bUrls()
findC2bReference()
```

---

# Recommended Service Separation

## Existing Services

```php id="6gh7sr"
handleStkPayment()
handleCheckoutRequest()
```

---

# New Dedicated Services

```php id="f55xyv"
handleC2bPaybillPayment()
processNonStkRepayment()
recordSuspenseTransaction()
```

---

# Callback Isolation Flow

```text id="u6e7wd"
Borrower
    ↓
Manual Paybill Payment
    ↓
Safaricom C2B Callback
    ↓
/api/payment/c2b/nonstk/confirmation
    ↓
Save Raw Payload
    ↓
Queue Processing
    ↓
Apply Repayment
```

---

# Existing STK Flow Remains Independent

```text id="g13dlj"
Mobile App
    ↓
STK Push Initiated
    ↓
Safaricom Callback
    ↓
/api/mpesa/stk/callback
    ↓
STK Processing Logic
```

---

# Sandbox Registration Flow

```text id="79t8o6"
Generate OAuth Token
        ↓
Register Non-STK URLs
        ↓
Sandbox Stores URLs
        ↓
Simulate Paybill Transaction
        ↓
Sandbox Sends Callback
        ↓
Non-STK Confirmation Endpoint
```

---

# Sandbox Simulation Example

## Endpoint

```text id="0w48m0"
https://sandbox.safaricom.co.ke/mpesa/c2b/v1/simulate
```

---

## Payload

```json id="mwfvlm"
{
  "ShortCode": "600999",
  "CommandID": "CustomerPayBillOnline",
  "Amount": "1500",
  "Msisdn": "254712345678",
  "BillRefNumber": "25529406"
}
```

---

# Required Processing Logic

The non-STK flow must:

- accept callbacks immediately
- store raw payloads
- prevent duplicate `TransID`
- process repayments asynchronously
- map National ID to borrower
- support suspense handling
- preserve audit logs

---

# Queue Processing Requirement

Callbacks must NEVER:
- process heavy business logic synchronously
- calculate repayment schedules inline
- perform long-running database operations

Instead:

```text id="cqz6ch"
Receive Callback
      ↓
Save Transaction
      ↓
Dispatch Queue Job
      ↓
Return HTTP 200 Immediately
```

---

# Recommended Database Isolation

## Separate Tables

### `unmatched_transactions`

Purpose:
Store unmatched non-STK Paybill transactions.

---

### `mpesa_callbacks`

Purpose:
Store raw callback logs separately from STK flows.

---

# Security Requirements

- validate Safaricom source where possible
- prevent duplicate transaction processing
- sanitize incoming payloads
- preserve immutable callback logs
- isolate STK and non-STK repayment processing

---

# Final Architecture Rule

The new manual Paybill repayment system must behave like an independent payment channel.

It must:
- use isolated routes
- use isolated registration URLs
- use isolated processing logic
- use isolated repayment mapping
- avoid interfering with existing STK Push flows
- preserve backward compatibility
- support future scaling independently