# M-Pesa Payment Pathways

This document maps the runtime paths from incoming M-Pesa callbacks to the database `Transaction` and `PaymentAllocation` updates for the two primary flows used in LendCore:
- Non‑STK C2B (incoming C2B Paybill without USSD)
- Disbursement (B2C)

---

## 1) Non‑STK C2B Confirmation Pathway

1. M-Pesa posts the confirmation to:
   - `POST /api/payment/c2b/nonstk/confirmation`
   - Controller entry: [app/Http/Controllers/Api/MpesaCallbackController.php](app/Http/Controllers/Api/MpesaCallbackController.php#L351)
2. Controller delegates to `c2bConfirmation()` which:
   - Validates `BillRefNumber`, `TransID`, `TransAmount` ([app/Http/Controllers/Api/MpesaCallbackController.php](app/Http/Controllers/Api/MpesaCallbackController.php#L372)).
   - Looks up the `User` by `national_id` using `BillRefNumber`.
3. If `User` found: controller calls `RepaymentService::recordPaymentForUserActiveLoans()` to allocate payment across the user's active loans:
   - [app/Services/RepaymentService.php](app/Services/RepaymentService.php#L370)
4. `recordPaymentForUserActiveLoans()` iterates each active `Loan` and calls `recordLoanPayment()`:
   - [app/Services/RepaymentService.php](app/Services/RepaymentService.php#L216)
5. For each `Repayment` in the loan, `recordLoanPayment()` calls `recordPayment()` which:
   - Updates the `Repayment` (`amount_paid`, `balance`, `status`, `paid_at`).
   - Calls `TransactionService::recordPaymentTransaction()` to create a `Transaction` record with fields such as `user_id`, `loan_id`, `repayment_id`, `mpesa_transaction_id`, `mpesa_code`, `amount`, `status`, `callback_data`.
   - `TransactionService::recordPaymentTransaction`: [app/Services/TransactionService.php](app/Services/TransactionService.php#L13)
6. After creating the `Transaction`, `recordLoanPayment()` persists an allocation audit using `PaymentAllocation::create()` recording:
   - `transaction_id`, `loan_id`, `repayment_id`, `mpesa_transaction_id`, `batch_reference`, `amount`, `before_balance`, `after_balance`, `meta`.
   - Model: [app/Models/PaymentAllocation.php](app/Models/PaymentAllocation.php)
7. Final outcome (DB updates):
   - One or more `Transaction` rows created (type=`payment`, status=`completed`), see [app/Models/Transaction.php](app/Models/Transaction.php).
   - Multiple `PaymentAllocation` rows mapping amounts to specific `Repayment` rows.

Notes:
- If no `User` is found, the payload is saved to a suspense store via `SuspenseService::saveUnmatchedTransaction()` for manual review ([app/Http/Controllers/Api/MpesaCallbackController.php](app/Http/Controllers/Api/MpesaCallbackController.php#L414)).
- All callback URLs and environment selection are configured via `.env` variables (see `MPESA_C2B_NONSTK_*`, `MPESA_ENV`, etc.).

---

## 2) Disbursement (B2C) Pathway

1. Disbursement initiation (app → Daraja):
   - Admin triggers disbursement in the app (e.g., `LoanService::disburseLoan()`), which calls `MpesaService::sendB2cPayment()` to post to Daraja B2C endpoint.
   - Daraja endpoint used depends on `MPESA_ENV` via `config/mpesa.php` endpoints (`b2c.sandbox` vs `b2c.production`).
2. App records a pending `Transaction` for the disbursement with a generated `OriginatorConversationID` stored in `mpesa_code`.
   - See `TransactionService::recordDisbursementTransaction()` in [app/Services/TransactionService.php](app/Services/TransactionService.php#L200)
3. Daraja later calls back to `POST /api/payment/b2c/result` or `POST /api/payment/b2c/timeout`:
   - Controller handlers: [app/Http/Controllers/Api/MpesaCallbackController.php](app/Http/Controllers/Api/MpesaCallbackController.php#L201) (`b2cResult`, `b2cTimeout`).
4. `b2cResult()` flow:
   - Finds the disbursement `Transaction` by `OriginatorConversationID` using `TransactionService::findByMpesaCode()`.
   - Validates that the `mpesa_code` matches the `OriginatorConversationID` and determines success (`ResultCode === 0`).
   - Constructs a payload (status, `mpesa_transaction_id`, `mpesa_code`, `amount`, etc.).
   - Calls `LoanService::processMpesaCallback($loan, $payload)` to apply the callback to the loan/disbursement flow.
5. `LoanService::processMpesaCallback()` (called for disbursement) updates transaction state via `TransactionService::updateMpesaCallback()` and handles loan status transitions (e.g., mark loan `active` on successful disbursement).
   - See `TransactionService::updateMpesaCallback()` [app/Services/TransactionService.php](app/Services/TransactionService.php#L108)
6. Final outcome (DB updates):
   - `Transaction` row updated: `status` → `completed` or `failed`, `mpesa_transaction_id` filled, `processed_at` set.
   - Loan state updated (e.g., `approved` → `active`) and any disbursement-specific records persisted.

Notes:
- `QueueTimeOutURL` and `ResultURL` must be set in `.env` as `MPESA_B2C_TIMEOUT_URL` and `MPESA_B2C_RESULT_URL`.
- In sandbox, `TransactionID` may be missing; the code defensively uses `N/A` and attempts to extract a receipt from `ResultParameters`.

---

## Quick references
- Controller entrypoints: [app/Http/Controllers/Api/MpesaCallbackController.php](app/Http/Controllers/Api/MpesaCallbackController.php)
- Allocation logic and per‑loan loop: [app/Services/RepaymentService.php](app/Services/RepaymentService.php)
- Transaction creation and updates: [app/Services/TransactionService.php](app/Services/TransactionService.php)
- Allocation model: [app/Models/PaymentAllocation.php](app/Models/PaymentAllocation.php)
- Transaction model: [app/Models/Transaction.php](app/Models/Transaction.php)
- Config driving sandbox vs production: [config/mpesa.php](config/mpesa.php)

---

If you want, I can add an example payload and show the exact SQL/ORM changes that would occur for a sample `TransAmount` value (e.g., KSh 1,000).