# Feature: Members & KYC

> **Status:** Specified, not built · **Phase:** 2 · **Depends on:** tenancy (1.2), posting engine (2.1)
> **Regulatory basis:** Microfinance Regulations 2019 (GN 679) Reg 38; AML/CFT Act; PDPA 2022; Cooperative Societies Act (SACCOS)

---

## Purpose

The member record is the root of everything else. Savings, shares, loans,
guarantees, group membership and every regulatory return hang off it. Getting
the field set wrong here is the most expensive mistake in the project, because
correcting it later means a data migration across every tenant, applied to
records that are already evidence in a regulatory file.

So the field list comes from the regulation, not from what seems reasonable.

---

## Required fields

**Reg 38(1)** prescribes the minimum for a loan application, and since almost
every member eventually borrows, the member record captures all of it up front
rather than asking again later:

| Field | Reg 38 ref | Notes |
|---|---|---|
| Full name | (a) | First, middle, last — as printed on the NIDA card |
| Date of birth / registration | (b) | Entities register; individuals are born |
| Place of residence / business | (c) | Ward, district, region — Tanzanian administrative hierarchy |
| Occupation / type of business | (d) | |
| Economic sector | (f) | Drives BoT sector-exposure reporting |
| Marital status | (i) | |
| Number of dependents | (j) | Feeds capacity analysis |
| Place of employment | (k) | |
| Current income | (l) | Integer cents |
| Expenses and assets | (m) | Integer cents |
| Other loans outstanding | (n) | Cross-checked against the CRB report |

Plus, from AML/CFT and the SACCOS rules:

- **NIDA number** — the national ID; verified against the NIDA API where available
- **TIN** — for business members and tax reporting
- **Phone** — must be registered to the member; it is the second factor and the mobile-money destination
- **Source of income / funds** — AML requirement, not optional
- **Beneficial owners** — for entity members
- **PEP status** — politically exposed person screening
- **Sanctions screening** — UN, OFAC, local lists
- **Next of kin** — required on death or incapacity; also the first contact for collections
- **Passport photo, ID scan, signature specimen** — the signature is what a teller checks at the counter

### CRB consent — Reg 38(2)

The application form must carry a consent and declaration that the member's
credit information may be shared with the Credit Reference Bureaux.

Store the consent with **timestamp, IP address, and the exact wording that was
displayed**. A consent record that cannot reproduce what the member actually
agreed to is not evidence — and if the wording changes later, historic consents
must still show the old text.

---

## Customer due diligence

Risk-rated, per the AML regulations. The rating determines how much
verification is required and how closely the account is monitored afterwards.

| Risk | Trigger | Measures |
|---|---|---|
| **Low** | Small balances, salaried, local | Simplified CDD; identity may be verified shortly after onboarding |
| **Medium** | Standard membership | NIDA + photo ID + address proof + source of funds |
| **High** | PEP, high value, cash-intensive business, unusual pattern | Enhanced CDD: beneficial ownership, source of wealth, senior approval, ongoing monitoring |

Risk rating is **recomputed**, not set once — a member who becomes a PEP or
whose transaction pattern shifts must be re-rated automatically, with the
change logged.

---

## Data model

### `members`

```
tenant_id                uuid, indexed with everything
member_number            string   — MBR/YYYY/#####, UNIQUE (tenant_id, member_number)
type                     enum     — individual, group, entity
first_name, middle_name, last_name
date_of_birth            date
gender, marital_status   enum
dependents               int
nida_number              string   — ENCRYPTED at rest, unique per tenant
tin_number               string   — ENCRYPTED
phone, alt_phone         string   — E.164, +255
email                    string
occupation               string
economic_sector          enum     — BoT sector codes
employer                 string
monthly_income           bigint   — cents
monthly_expenses         bigint   — cents
total_assets             bigint   — cents
address, ward, district, region
kyc_status               enum     — pending, verified, rejected
kyc_verified_at/by
risk_rating              enum     — low, medium, high
is_pep                   bool
sanctions_checked_at     timestamp
status                   enum     — active, dormant, suspended, exited
joined_at, exited_at, exit_reason
```

Supporting: `member_documents`, `member_next_of_kin`, `member_consents`,
`member_risk_assessments`, `member_status_history`.

### Uniqueness is always tenant-scoped

```sql
UNIQUE (tenant_id, member_number)   -- correct
UNIQUE (member_number)              -- wrong: blocks tenant B from using a number tenant A took
```

The same applies to `nida_number`. One person may legitimately be a member of
two different SACCOS.

### PII encryption

`nida_number` and `tin_number` are encrypted at rest via Laravel's encrypted
casts. This has a consequence worth stating plainly: **an encrypted column
cannot be searched with a `WHERE` clause**. Lookup by NIDA requires a blind
index — a deterministic HMAC of the normalised value, stored alongside and
indexed. Discovering this after building the search screen is a rewrite.

---

## Lifecycle

```
DRAFT ──▶ SUBMITTED ──▶ VERIFIED ──▶ APPROVED ──▶ ACTIVE
                            │            │           │
                            ▼            ▼           ▼
                        REJECTED     REJECTED    DORMANT ──▶ EXITED
                                                 SUSPENDED
```

Maker-checker: the person who captures the application is not the person who
verifies KYC. **Dormant** is reached automatically after a configurable period
with no transaction — it matters because dormant accounts are an AML risk and a
separate line in the returns.

**Exit** does not delete. Financial records are retained seven years minimum,
and share capital settlement on exit has its own rules under the Cooperative
Societies Act.

---

## Screens

| Screen | Notes |
|---|---|
| Member list | Search by name / number / NIDA / phone; filter by status, branch, officer, risk |
| Member profile | Summary, savings, shares, loans, documents, timeline, audit trail |
| Registration wizard | Multi-step: identity → contact → economic → next of kin → documents → consent → review |
| KYC verification queue | Side-by-side document and captured data, with an approve/reject decision and mandatory reason |
| Risk assessment | Rating, factors, override with justification |
| Document manager | Upload, version, expiry reminders |
| Bulk import | CSV for institutions migrating off Excel — with dry-run validation before commit |

**Bulk import matters more than it looks.** Most target institutions are coming
off spreadsheets and paper. If migrating their existing membership is painful,
they will not adopt the system at all. The dry run must report every rejected
row with a reason, and import must be all-or-nothing.

---

## Edge cases

- **NIDA API unavailable.** It frequently is. The workflow must complete with
  manual verification and flag the record for later automated re-check. A hard
  dependency on the API would stop onboarding entirely.
- **Duplicate NIDA within a tenant** → block, and show the existing member.
- **Same NIDA across tenants** → allowed. Different institutions, same person.
- **Member has no NIDA** (rural, elderly, refugee) → permitted with an
  alternative-identity workflow and enhanced CDD, because excluding them
  defeats the purpose of microfinance.
- **Name mismatch against NIDA** → flag for review, never auto-reject; married
  names and transliteration differences are routine.
- **Member dies** → estate workflow; loans against the estate, savings to next
  of kin, shares redeemed per bylaws.
- **Member exits with an outstanding loan** → exit blocked until settled or the
  loan is formally restructured against collateral.
- **Phone number reassigned** by the carrier → reverification before it can be
  used as a second factor.

---

## Test checklist

- [ ] All Reg 38 fields are present and required at submission
- [ ] CRB consent stores wording, timestamp and IP
- [ ] `nida_number` is encrypted at rest and searchable via blind index
- [ ] Duplicate NIDA blocked within a tenant, allowed across tenants
- [ ] Member numbers are unique per tenant and gapless within a year
- [ ] Verifier cannot be the same user as the capturer
- [ ] Risk rating recomputes when PEP status or income changes
- [ ] Dormancy applies automatically after the configured period
- [ ] Exit is blocked while a loan is outstanding
- [ ] Bulk import dry run reports every rejection with a reason
- [ ] Tenant isolation on every member endpoint (404, not 403)
- [ ] Every field change is audit-logged with before and after values

---

## Build order

1. Migration and model with `BelongsToTenant`, encryption, blind index
2. Member numbering series
3. Registration wizard with full Reg 38 validation
4. Document upload and storage
5. KYC verification queue with maker-checker
6. Risk assessment and PEP/sanctions screening
7. Member profile and search
8. Lifecycle transitions, dormancy job
9. Bulk import with dry run
10. NIDA API adapter, with the manual path as the fallback
