# Payment Visibility Fix

## Problem
Payments coming through `/payments/sasapay/ipn/` (e.g. FAITH's payment `UF1O16E915` for loan `BSH/202602/00173`) create repayment records successfully and send SMS confirmations, but don't show on the repayments page at `/loans/repayments/`.

## Root Causes

### 1. Branch Filter Hiding Repayments (MOST LIKELY)
**Issue:** The repayments view filters by `loan__borrower__branch_id=selected_branch_id` when a branch is selected. If the borrower has `branch=NULL` (not assigned), these repayments are silently excluded.

**When this happens:**
- User is admin/manager with a branch selected in session
- Borrower created via automatic IPN has no branch assigned
- Repayment exists but gets filtered out

**Fix Applied:**
```python
# BEFORE (loans/views.py line 1692)
repayments_list = repayments_list.filter(loan__borrower__branch_id=selected_branch_id)

# AFTER
repayments_list = repayments_list.filter(
    Q(loan__borrower__branch_id=selected_branch_id) |
    Q(loan__borrower__branch__isnull=True)  # Include borrowers with no branch
)
```

This ensures repayments whose borrowers have no branch assigned are always visible, regardless of branch filter.

### 2. Phone Number Matching Missing in MpesaTransaction
**Issue:** `MpesaTransaction.match_borrower()` only matches by ID number or loan number. SasaPay sends the customer's phone as `BillRefNumber` (e.g. `+254758587153`), so if the borrower doesn't have an ID number or loan number matching that phone, the transaction fails to match → no repayment is created.

**When this happens:**
- Payment comes through `/payments/mpesa/confirmation/` instead of `/payments/sasapay/ipn/`
- `BillRefNumber` is a phone number (not ID or loan number)
- Borrower has no ID registered or ID doesn't match phone

**Fix Applied:**
Added Strategy 3 to `match_borrower()` in `loans/models.py`:
```python
# Try all standard Kenyan phone format variants
# +2547xxxxxxxx, 2547xxxxxxxx, 07xxxxxxxx
borrower_by_phone = User.objects.filter(
    phone_number__in=variants,
    role='borrower',
    is_active=True,
).first()
```

This allows matching by phone when ID/loan number matching fails, matching the behavior of `sasapay_service.py`.

## Files Changed

1. **loans/views.py** (repayments view, line ~1690)
   - Added `Q(loan__borrower__branch__isnull=True)` to branch filters

2. **loans/models.py** (MpesaTransaction.match_borrower, line ~1983)
   - Added phone number matching as Strategy 3

## Testing on Server

Run the diagnostic script to check FAITH's payment:
```bash
python diagnose_faith_repayment.py
```

This will show:
- Whether the IPN was received
- Whether the repayment was created
- Whether borrower has a branch assigned
- Why it might be hidden from the repayments view

## Deployment

1. Deploy both file changes to production
2. Restart Django/gunicorn
3. Check `/loans/repayments/` — FAITH's payment should now be visible
4. For any historical repayments that were hidden, they'll automatically appear after deployment

## Prevention

To prevent this in the future:
1. Ensure all borrowers created via IPN have a default branch assigned
2. Or keep the `branch__isnull=True` filter in place (recommended)

## Alternative: Assign Default Branch in IPN Handler

If you want all IPN-created borrowers to have a branch, modify `sasapay_service.py` line ~280:
```python
# After matching borrower, assign default branch if none exists
if borrower and borrower.branch is None:
    from users.models import Branch
    default_branch = Branch.objects.first()  # or specific branch
    if default_branch:
        borrower.branch = default_branch
        borrower.save(update_fields=['branch'])
```

But keeping the filter inclusive (`branch__isnull=True`) is safer since it handles all edge cases.
