# Type Cleanup & Architecture Guide

## 🎯 Goal

Establish a single source of truth for every cross-layer contract so backend integrations, internal services, and the Portal all share identical type definitions and runtime validation.

**No business code should re-declare data shapes, and every external payload must be validated exactly once at the boundary.**

---

## 📐 Ideal State

### Layer Architecture

```
┌─────────────────────────────────────────────────┐
│  @customer-portal/contracts                    │
│  Pure TypeScript interfaces (no runtime deps)  │
│  → billing, subscriptions, payments, SIM, etc. │
└────────────────┬────────────────────────────────┘
                 │
                 ↓
┌─────────────────────────────────────────────────┐
│  @customer-portal/schemas                      │
│  Zod validators for each contract              │
│  → Billing, SIM, Payments, Integrations        │
└────────────────┬────────────────────────────────┘
                 │
                 ↓
┌─────────────────────────────────────────────────┐
│  Integration Packages                          │
│  → WHMCS mappers (billing, orders, payments)   │
│  → Freebit mappers (SIM operations)            │
│  Transform raw API data → contracts            │
└────────────────┬────────────────────────────────┘
                 │
                 ↓
┌─────────────────────────────────────────────────┐
│  Application Layers                            │
│  → BFF (NestJS): Orchestrates integrations     │
│  → Portal (Next.js): UI and client logic       │
│  Only import from contracts/schemas            │
└─────────────────────────────────────────────────┘
```

---

## 🗂️ Package Structure

### 1. Contracts (`packages/contracts/src/`)

**Purpose**: Pure TypeScript types - the single source of truth.

```
packages/contracts/src/
  ├── billing/
  │   ├── invoice.ts           # Invoice, InvoiceItem, InvoiceList
  │   └── index.ts
  ├── subscriptions/
  │   ├── subscription.ts      # Subscription, SubscriptionList
  │   └── index.ts
  ├── payments/
  │   ├── payment.ts           # PaymentMethod, PaymentGateway
  │   └── index.ts
  ├── sim/
  │   ├── sim-details.ts       # SimDetails, SimUsage, SimTopUpHistory
  │   └── index.ts
  ├── orders/
  │   ├── order.ts             # Order, OrderItem, FulfillmentOrderItem
  │   └── index.ts
  └── freebit/
      ├── requests.ts          # Freebit request payloads
      └── index.ts
```

**Usage**:
```typescript
import type { Invoice, InvoiceItem } from "@customer-portal/contracts/billing";
import type { SimDetails } from "@customer-portal/contracts/sim";
```

---

### 2. Schemas (`packages/schemas/src/`)

**Purpose**: Runtime validation using Zod schemas.

```
packages/schemas/src/
  ├── billing/
  │   ├── invoice.schema.ts    # invoiceSchema, invoiceListSchema
  │   └── index.ts
  ├── subscriptions/
  │   ├── subscription.schema.ts
  │   └── index.ts
  ├── payments/
  │   ├── payment.schema.ts
  │   └── index.ts
  ├── sim/
  │   ├── sim.schema.ts
  │   └── index.ts
  └── integrations/
      ├── whmcs/
      │   ├── invoice.schema.ts      # Raw WHMCS invoice schemas
      │   ├── payment.schema.ts
      │   ├── product.schema.ts
      │   ├── order.schema.ts         # NEW: WHMCS AddOrder schemas
      │   └── index.ts
      └── freebit/
          ├── account.schema.ts      # Raw Freebit response schemas
          ├── traffic.schema.ts
          ├── quota.schema.ts
          └── requests/
              ├── topup.schema.ts
              ├── plan-change.schema.ts
              ├── esim-activation.schema.ts  # NEW
              ├── features.schema.ts          # NEW
              └── index.ts
```

**Usage**:
```typescript
import { invoiceSchema } from "@customer-portal/schemas/billing";
import { whmcsAddOrderParamsSchema } from "@customer-portal/schemas/integrations/whmcs/order.schema";
import { freebitEsimActivationParamsSchema } from "@customer-portal/schemas/integrations/freebit/requests/esim-activation.schema";

// Validate at the boundary
const validated = invoiceSchema.parse(externalData);
```

---

### 3. Integration Packages

#### WHMCS Integration (`packages/integrations/whmcs/`)

```
packages/integrations/whmcs/src/
  ├── mappers/
  │   ├── invoice.mapper.ts        # transformWhmcsInvoice()
  │   ├── subscription.mapper.ts   # transformWhmcsSubscription()
  │   ├── payment.mapper.ts        # transformWhmcsPaymentMethod()
  │   ├── order.mapper.ts          # mapFulfillmentOrderItems(), buildWhmcsAddOrderPayload()
  │   └── index.ts
  ├── utils/
  │   └── index.ts
  └── index.ts
```

**Key Functions**:
- `transformWhmcsInvoice(raw)` → `Invoice`
- `transformWhmcsSubscription(raw)` → `Subscription`
- `mapFulfillmentOrderItems(items)` → `{ whmcsItems, summary }`
- `buildWhmcsAddOrderPayload(params)` → WHMCS API payload
- `createOrderNotes(sfOrderId, notes)` → formatted order notes

**Usage in BFF**:
```typescript
import { transformWhmcsInvoice, buildWhmcsAddOrderPayload } from "@customer-portal/integrations-whmcs/mappers";

// Transform WHMCS raw data
const invoice = transformWhmcsInvoice(whmcsResponse);

// Build order payload
const payload = buildWhmcsAddOrderPayload({
  clientId: 123,
  items: mappedItems,
  paymentMethod: "stripe",
});
```

#### Freebit Integration (`packages/integrations/freebit/`)

```
packages/integrations/freebit/src/
  ├── mappers/
  │   ├── sim.mapper.ts           # transformFreebitAccountDetails(), transformFreebitTrafficInfo()
  │   └── index.ts
  ├── utils/
  │   ├── normalize.ts            # normalizeAccount()
  │   └── index.ts
  └── index.ts
```

**Key Functions**:
- `transformFreebitAccountDetails(raw)` → `SimDetails`
- `transformFreebitTrafficInfo(raw)` → `SimUsage`
- `transformFreebitQuotaHistory(raw)` → `SimTopUpHistory[]`
- `normalizeAccount(account)` → normalized MSISDN

**Usage in BFF**:
```typescript
import { transformFreebitAccountDetails } from "@customer-portal/integrations-freebit/mappers";
import { normalizeAccount } from "@customer-portal/integrations-freebit/utils";

const simDetails = transformFreebitAccountDetails(freebitResponse);
const account = normalizeAccount(msisdn);
```

---

## 🚫 Anti-Patterns to Avoid

### ❌ Don't re-declare types in application code
```typescript
// BAD - duplicating contract in BFF
export interface Invoice {
  id: string;
  amount: number;
  // ...
}
```

```typescript
// GOOD - import from contracts
import type { Invoice } from "@customer-portal/contracts/billing";
```

### ❌ Don't skip schema validation at boundaries
```typescript
// BAD - trusting external data
const invoice = whmcsResponse as Invoice;
```

```typescript
// GOOD - validate with schema
import { transformWhmcsInvoice } from "@customer-portal/integrations-whmcs/mappers";

const invoice = transformWhmcsInvoice(whmcsResponse); // Validates internally
```

### ❌ Don't use legacy domain imports
```typescript
// BAD - old path
import type { Invoice } from "@customer-portal/domain";
```

```typescript
// GOOD - new path
import type { Invoice } from "@customer-portal/contracts/billing";
```

---

## 🔧 Migration Checklist

### For WHMCS Order Workflows

- [x] Create `whmcs/order.schema.ts` with `WhmcsOrderItem`, `WhmcsAddOrderParams`, etc.
- [x] Move `buildWhmcsAddOrderPayload()` to `whmcs/mappers/order.mapper.ts`
- [x] Update `WhmcsOrderService` to use shared mapper
- [x] Update BFF order orchestrator to consume mapper outputs
- [ ] Add unit tests for mapper functions

### For Freebit Requests

- [x] Create `freebit/requests/esim-activation.schema.ts`
- [x] Create `freebit/requests/features.schema.ts`
- [x] Update `FreebitOperationsService` to validate requests through schemas
- [ ] Centralize options normalization in integration package
- [ ] Add regression tests for schema validation

### For Portal Alignment

- [x] Update SIM components to import from `@customer-portal/contracts/sim`
- [ ] Remove lingering `@customer-portal/domain` imports
- [ ] Update API client typings to use shared contracts

### For Governance

- [x] Document layer rules in `ARCHITECTURE.md`
- [x] Create this `TYPE-CLEANUP-GUIDE.md`
- [ ] Add ESLint rules preventing deep imports from legacy paths

---

## 📚 Import Examples

### BFF (NestJS)

```typescript
// Controllers
import type { Invoice, InvoiceList } from "@customer-portal/contracts/billing";
import { invoiceListSchema } from "@customer-portal/schemas/billing";

// Services
import { transformWhmcsInvoice } from "@customer-portal/integrations-whmcs/mappers";

// Operations
const invoices = rawInvoices.map(transformWhmcsInvoice);
const validated = invoiceListSchema.parse({ invoices, total });
```

### Portal (Next.js)

```typescript
// Components
import type { SimDetails } from "@customer-portal/contracts/sim";
import type { Invoice } from "@customer-portal/contracts/billing";

// API Client
export async function fetchInvoices(): Promise<InvoiceList> {
  const response = await api.get<InvoiceList>("/api/invoices");
  return response.data;
}
```

---

## 🎓 Best Practices

1. **Always validate at boundaries**: Use schemas when receiving data from external APIs
2. **Import from subpaths**: Use `@customer-portal/contracts/billing` not `@customer-portal/contracts`
3. **Use mappers in integrations**: Keep transformation logic in integration packages
4. **Don't export Zod schemas from contracts**: Contracts are type-only, schemas are runtime
5. **Keep integrations thin in BFF**: Let integration packages handle complex mapping

---

## 🔍 Finding the Right Import

| What you need | Where to import from |
|---------------|---------------------|
| TypeScript type definition | `@customer-portal/contracts/{domain}` |
| Runtime validation | `@customer-portal/schemas/{domain}` |
| External API validation | `@customer-portal/schemas/integrations/{provider}` |
| Transform external data | `@customer-portal/integrations-{provider}/mappers` |
| Normalize/format helpers | `@customer-portal/integrations-{provider}/utils` |

---

## 💡 Quick Reference

```typescript
// ✅ CORRECT Usage Pattern

// 1. Import types from contracts
import type { Invoice } from "@customer-portal/contracts/billing";

// 2. Import schema for validation
import { invoiceSchema } from "@customer-portal/schemas/billing";

// 3. Import mapper from integration
import { transformWhmcsInvoice } from "@customer-portal/integrations-whmcs/mappers";

// 4. Use in service
const invoice: Invoice = transformWhmcsInvoice(rawData); // Auto-validates
const validated = invoiceSchema.parse(invoice); // Optional explicit validation
```

---

**Last Updated**: 2025-10-03

For questions or clarifications, refer to `docs/ARCHITECTURE.md` or the package README files.

