# Customer Portal Flow Review: Shop, Eligibility, ID Verification & Opportunity Management

**Review Date:** December 23, 2025  
**Last Updated:** December 23, 2025  
**Scope:** Complete end-to-end analysis of customer acquisition flows  
**Priority Focus:** Customer Experience (CX)

---

## Table of Contents

1. [Executive Summary](#executive-summary)
2. [System Architecture Overview](#system-architecture-overview)
3. [Flow Diagrams](#flow-diagrams)
4. [Detailed Behavior Analysis](#detailed-behavior-analysis)
5. [Salesforce Field Changes Reference](#salesforce-field-changes-reference)
6. [Agent Workflow & Checklist](#agent-workflow--checklist)
7. [Critical Issues & Recommendations](#critical-issues--recommendations)
8. [Customer Experience Analysis](#customer-experience-analysis)
9. [Implementation Improvements](#implementation-improvements)

---

## Executive Summary

### Current State Assessment

| Area                   | Status   | CX Impact | Notes                                    |
| ---------------------- | -------- | --------- | ---------------------------------------- |
| Shop/Catalog           | ✅ Good  | Low Risk  | Well-structured, cached                  |
| Internet Eligibility   | ✅ Good  | Low Risk  | Manual NTT check, email notifications    |
| ID Verification        | ✅ Good  | Low Risk  | Integrated into Profile page             |
| Opportunity Management | ✅ Good  | Low Risk  | Fields exist, WHMCS linking works        |
| Order Fulfillment      | ✅ Good  | Low Risk  | Distributed transaction support          |
| Profile Data           | ✅ Fixed | Low Risk  | Customer number, DOB, gender now display |

### Key Findings

1. **Internet Subscription Detection** - Now matches "SonixNet via NTT Optical Fiber" products in addition to "Internet" named products.

2. **ID Verification Integrated** - Upload functionality is now built into the Profile page (`/account/settings`) rather than requiring a separate page.

3. **Opportunity Lifecycle Fields Exist** - `Opportunity_Source__c` (with portal picklist values) and `WHMCS_Service_ID__c` are in place and working.

4. **SIM vs Internet Flows Have Different Requirements** - SIM requires ID verification but not eligibility; Internet requires eligibility but not ID verification.

5. **Opportunity ↔ WHMCS Linking** - After provisioning, Opportunity is linked via `WHMCS_Service_ID__c` to enable cancellation workflows.

6. **Cancellation is NOT Automated to WHMCS** - Portal updates Salesforce Opportunity and creates Case, but WHMCS service termination requires agent action.

---

## System Architecture Overview

### Three-Tier Integration

```
┌─────────────────────────────────────────────────────────────────────────────────┐
│                                CUSTOMER PORTAL                                   │
│                              (Next.js Frontend)                                  │
├─────────────────────────────────────────────────────────────────────────────────┤
│  Shop/Catalog  │  Checkout  │  Verification  │  Dashboard  │  Service Mgmt     │
└───────┬────────┴─────┬──────┴───────┬────────┴──────┬──────┴─────────┬─────────┘
        │              │              │               │                │
        ▼              ▼              ▼               ▼                ▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│                                  BFF (NestJS)                                    │
├─────────────────────────────────────────────────────────────────────────────────┤
│  CatalogService  │  CheckoutService  │  VerificationService  │  OrderService   │
│                  │                   │                       │                  │
│  Opportunity Resolution      │  OrderOrchestrator  │  FulfillmentOrchestrator  │
└───────┬──────────────────────┴─────────┬──────────┴────────────┬───────────────┘
        │                                │                       │
        ▼                                ▼                       ▼
┌───────────────┐              ┌─────────────────┐       ┌──────────────┐
│  SALESFORCE   │              │     WHMCS       │       │   PRISMA     │
│  (CRM/Orders) │              │   (Billing)     │       │ (Portal DB)  │
└───────────────┘              └─────────────────┘       └──────────────┘
```

### Data Flow by Product Type

| Product      | Eligibility Required      | ID Verification Required | Opportunity Created At |
| ------------ | ------------------------- | ------------------------ | ---------------------- |
| **Internet** | ✅ Yes (Manual NTT check) | ❌ Not enforced          | Eligibility Request    |
| **SIM**      | ❌ No                     | ✅ Yes (Residence Card)  | Order Placement        |
| **VPN**      | ❌ No                     | ❌ No                    | Order Placement        |

---

## Flow Diagrams

### 1. Internet Customer Journey

```
┌─────────────────────────────────────────────────────────────────────────────────┐
│                        INTERNET CUSTOMER JOURNEY                                 │
└─────────────────────────────────────────────────────────────────────────────────┘

CUSTOMER ACTION                 SYSTEM BEHAVIOR                 SALESFORCE CHANGES
═══════════════                 ═══════════════                 ══════════════════

1. Browse Internet Plans
   └─► Catalog loads from SF    GET /api/catalog/internet       (none - read only)
       (cached 5 min)

2. Click "Select Plan"
   └─► Redirect to checkout     User must be authenticated      (none)

3. Enter Service Address
   └─► Address validation       Frontend validation             (none)

4. Submit Eligibility Request
   └─► API call triggered       POST /eligibility-request       ┌─────────────────────┐
                                                                │ CASE:               │
                                                                │ • Created           │
                                                                │ • Subject=Internet  │
                                                                │   availability check│
                                                                │ • Origin=Portal     │
                                                                ├─────────────────────┤
                                                                │ ACCOUNT:            │
                                                                │ • Internet_         │
                                                                │   Eligibility_      │
                                                                │   Status__c=Pending │
                                                                │ • Internet_         │
                                                                │   Eligibility_      │
                                                                │   Request_Date_     │
                                                                │   Time__c=NOW()     │
                                                                │ • Internet_         │
                                                                │   Eligibility_      │
                                                                │   Case_Id__c=       │
                                                                │   (Case ID)         │
                                                                ├─────────────────────┤
                                                                │ NOTE: Opportunity   │
                                                                │ is NOT created at   │
                                                                │ this step - only    │
                                                                │ at order placement  │
                                                                └─────────────────────┘

   ⏳ CUSTOMER WAITS (no SLA shown)
   ════════════════════════════════

5. Agent Processes Case         (MANUAL AGENT WORK)             ┌─────────────────────┐
   └─► NTT Check                Checks serviceability           │ ACCOUNT:            │
   └─► Update Account                                           │ • Internet_         │
   └─► SF Flow sends email                                      │   Eligibility__c=   │
                                                                │   "Home 1G" (or     │
                                                                │   other result)     │
                                                                │ • Internet_         │
                                                                │   Eligibility_      │
                                                                │   Status__c=        │
                                                                │   Eligible/         │
                                                                │   Ineligible        │
                                                                │ • Internet_         │
                                                                │   Eligibility_      │
                                                                │   Checked_Date_     │
                                                                │   Time__c=NOW()     │
                                                                ├─────────────────────┤
                                                                │ SALESFORCE FLOW:    │
                                                                │ • Sends email to    │
                                                                │   customer with     │
                                                                │   eligibility result│
                                                                └─────────────────────┘

6. Customer Returns/Refreshes
   └─► Sees eligible plans      GET /api/catalog/internet       (read only)
       filtered by eligibility  /eligibility

7. Proceed to Payment
   └─► Add payment method       Stripe integration              (none)

8. Place Order
   └─► Order created            POST /api/orders                ┌─────────────────────┐
                                                                │ ORDER:              │
                                                                │ • Created           │
                                                                │ • OpportunityId=    │
                                                                │   (linked)          │
                                                                │ • Status=Created    │
                                                                ├─────────────────────┤
                                                                │ OPPORTUNITY:        │
                                                                │ • Stage=Post        │
                                                                │   Processing        │
                                                                └─────────────────────┘

9. Agent Approves Order
   └─► Fulfillment triggered    Order.Status='Approved'         ┌─────────────────────┐
                                triggers CDC event              │ ORDER:              │
                                                                │ • Activation_       │
                                                                │   Status__c=        │
                                                                │   Activating        │
                                                                └─────────────────────┘

10. WHMCS Provisioning
    └─► Service created         addOrder → acceptOrder          ┌─────────────────────┐
                                                                │ ORDER:              │
                                                                │ • Status=Completed  │
                                                                │ • Activation_       │
                                                                │   Status__c=        │
                                                                │   Activated         │
                                                                │ • WHMCS_Order_ID__c │
                                                                │   =(WHMCS ID)       │
                                                                ├─────────────────────┤
                                                                │ OPPORTUNITY:        │
                                                                │ • Stage=Active      │
                                                                │ • WHMCS_Service_    │
                                                                │   ID__c=(service ID)│
                                                                └─────────────────────┘

11. Customer Has Active Service
    └─► Service visible in      GET /subscriptions              (read only)
        portal dashboard
```

### 2. SIM Customer Journey

```
┌─────────────────────────────────────────────────────────────────────────────────┐
│                           SIM CUSTOMER JOURNEY                                   │
└─────────────────────────────────────────────────────────────────────────────────┘

CUSTOMER ACTION                 SYSTEM BEHAVIOR                 SALESFORCE CHANGES
═══════════════                 ═══════════════                 ══════════════════

1. Browse SIM Plans
   └─► Catalog loads            GET /api/catalog/sim            (none - read only)
       Family plans shown if
       user has existing SIM

2. Click "Select Plan"
   └─► Redirect to login if     /auth/login?returnTo=...        (none - auth required)
       unauthenticated          Standard auth flow creates
                                SF Account + WHMCS Client

3. Configure & Checkout (Authenticated)
   └─► AccountCheckoutContainer  /account/shop/sim/configure    (cart stored locally)
       handles full flow         /account/order

4. Upload Residence Card (ID Verification)
   └─► File upload              POST /verification/             ┌─────────────────────┐
                                residence-card                  │ ACCOUNT:            │
                                                                │ • Id_Verification_  │
                                                                │   Status__c=        │
                                                                │   Submitted         │
                                                                │ • Id_Verification_  │
                                                                │   Submitted_Date_   │
                                                                │   Time__c=NOW()     │
                                                                ├─────────────────────┤
                                                                │ CONTENT VERSION:    │
                                                                │ • File uploaded     │
                                                                │ • FirstPublish      │
                                                                │   LocationId=       │
                                                                │   Account           │
                                                                └─────────────────────┘

   ⏳ CUSTOMER WAITS (manual review)
   ═════════════════════════════════

5. Agent Reviews ID             (MANUAL AGENT WORK)             ┌─────────────────────┐
   └─► Verify document                                          │ ACCOUNT:            │
   └─► Update status                                            │ • Id_Verification_  │
                                                                │   Status__c=        │
                                                                │   Verified/Rejected │
                                                                │ • Id_Verification_  │
                                                                │   Verified_Date_    │
                                                                │   Time__c=NOW()     │
                                                                │ • Id_Verification_  │
                                                                │   Rejection_        │
                                                                │   Message__c=       │
                                                                │   (if rejected)     │
                                                                └─────────────────────┘

6. Customer Returns (If Verified)
   └─► Can proceed to payment   Verification status checked     (none)

7-11. (Same as Internet steps 7-11)
```

### 3. Opportunity Lifecycle State Machine

```
┌─────────────────────────────────────────────────────────────────────────────────┐
│                     OPPORTUNITY STAGE TRANSITIONS                                │
└─────────────────────────────────────────────────────────────────────────────────┘

                    ┌───────────────────┐
                    │  POST PROCESSING  │ ◄─── Created at ORDER PLACEMENT
                    │     (75% prob)    │      (Not at eligibility request)
                    └─────────┬─────────┘
                              │
              ┌───────────────┼───────────────┐
              │               │               │
              ▼               ▼               ▼
      ┌───────────┐   ┌───────────────┐   ┌─────────────┐
      │   VOID    │   │    ACTIVE     │   │   PENDING   │
      │ (Closed,  │   │   (90% prob)  │   │  (On hold)  │
      │  0% prob) │   └───────┬───────┘   └─────────────┘
      └───────────┘           │
       Failed                 │ Set when:
       provisioning           │ • Service provisioned in WHMCS
                              │ • WHMCS_Service_ID__c populated
                              │
                              ▼
                    ┌───────────────┐
                    │  △CANCELLING  │ ◄─── Set when:
                    │  (100% prob)  │      • Customer submits cancellation
                    └───────┬───────┘      • ScheduledCancellationDateAndTime__c set
                            │              • CancellationNotice__c = 有
                            │              • LineReturn__c = NotYet
                            ▼
                    ┌───────────────┐
                    │  〇CANCELLED  │ ◄─── Set when:
                    │  (Closed,Won) │      • Service terminated
                    └───────────────┘      • Equipment returned (if applicable)

NOTE: Introduction/Ready stages may be used by agents for pre-order tracking,
      but portal creates Opportunities starting at Post Processing.
```

---

## Detailed Behavior Analysis

### Shop/Catalog Module

**Location:** `apps/bff/src/modules/catalog/`

**Key Services:**

- `InternetCatalogService` - Plans, installations, addons
- `SimCatalogService` - Plans, activation fees, addons
- `VpnCatalogService` - Plans

**Caching Strategy:**

- Catalog items cached via `CatalogCacheService`
- Cache keys built by product type + item type
- Cache invalidation on product updates

**Eligibility-Filtered Plans:**

```typescript
// InternetCatalogService.getPlansForUser()
// Filters plans based on Account.Internet_Eligibility__c value
// e.g., if eligibility = "Home 1G", only Home 1G plans shown
```

**Family Discount Logic (SIM):**

```typescript
// SimCatalogService.getPlansForUser()
// Checks WHMCS for existing active SIM services
// Shows family discount plans only if user has existing SIM
```

### Eligibility Validation Module

**Location:** `apps/bff/src/modules/catalog/services/internet-catalog.service.ts`

**Flow Summary:**

1. Customer requests eligibility check
2. System creates Case for agent to process
3. System updates Account eligibility fields to "Pending"
4. **Manual Agent Work Required** - Agent checks NTT serviceability
5. Agent updates Account with eligibility result
6. Salesforce Flow sends email notification to customer
7. **Note:** Opportunity **is created/reused during eligibility** (Stage = Introduction) so the Case can link to it (`Case.OpportunityId`) and the journey can be reused at order placement.

**Account Fields Updated:**
| Field | Value Set | When |
|-------|-----------|------|
| `Internet_Eligibility_Status__c` | "Pending" | On request |
| `Internet_Eligibility_Request_Date_Time__c` | NOW() | On request |
| `Internet_Eligibility_Case_Id__c` | Case ID | On request |
| `Internet_Eligibility__c` | Result value | By agent |
| `Internet_Eligibility_Checked_Date_Time__c` | NOW() | By agent |

### ID Verification Module

**Location:** `apps/bff/src/modules/verification/residence-card.service.ts`

**Flow Summary:**

1. Customer uploads residence card image
2. File stored as Salesforce ContentVersion (linked to Account)
3. Account verification status updated to "Submitted"
4. **Manual Agent Work Required**
5. Agent reviews document and updates status

**Account Fields Updated:**
| Field | Value Set | When |
|-------|-----------|------|
| `Id_Verification_Status__c` | "Submitted" | On upload |
| `Id_Verification_Submitted_Date_Time__c` | NOW() | On upload |
| `Id_Verification_Rejection_Message__c` | null | Cleared on resubmit |
| `Id_Verification_Note__c` | null | Cleared on resubmit |
| `Id_Verification_Status__c` | "Verified"/"Rejected" | By agent |
| `Id_Verification_Verified_Date_Time__c` | NOW() | By agent |

**Supported File Types:**

- PDF
- PNG
- JPG/JPEG

### User Registration Flow

**IMPORTANT: Guest checkout has been removed.** All checkout flows now require authentication first.

**Authentication-First Checkout Flow:**

1. User browses public catalog at `/shop`
2. When proceeding to checkout, unauthenticated users are redirected to `/auth/login`
3. After login/registration, users continue checkout at `/account/order`
4. Checkout is handled by `AccountCheckoutContainer` (single-page flow)

**Registration Location:** `apps/portal/src/app/auth/register/` (standard auth flow)

**Benefits of Auth-First Approach:**

- Simpler code: Removed `checkout-registration` module entirely
- Better UX: Users complete registration once, then shop freely
- Cleaner architecture: No multi-step guest registration with partial rollback
- Consistent: All users have accounts before interacting with Salesforce/WHMCS

### Opportunity Management Module

**Location (current code paths):**

- `apps/bff/src/modules/catalog/services/internet-catalog.service.ts` (eligibility request creates/reuses Opportunity + Case)
- `apps/bff/src/modules/orders/services/order-orchestrator.service.ts` (order placement resolves Opportunity)
- `apps/bff/src/integrations/salesforce/services/salesforce-opportunity.service.ts` (Salesforce query/create/update)

**Matching Rules:**
| Scenario | Action |
|----------|--------|
| Order has `opportunityId` | Use it directly |
| Internet order without Opp | Find Introduction/Ready stage or create new |
| SIM order without Opp | Find Introduction/Ready stage or create new |
| VPN order | Always create new Opportunity |

**Stage Transitions by Trigger:**
| Trigger | From Stage(s) | To Stage |
|---------|---------------|----------|
| Eligibility Request | (new) | Introduction |
| Eligibility Confirmed | Introduction | Ready |
| Eligibility Denied | Introduction | Void |
| Order Placed | Introduction, Ready | Post Processing |
| Service Provisioned | Post Processing | Active |
| Cancellation Requested | Active | △Cancelling |
| Cancellation Complete | △Cancelling | 〇Cancelled |

---

## Salesforce Field Changes Reference

### Complete Field Map by Object

#### Account Object

| Field API Name                              | Label                 | Type     | Who Updates  | When          |
| ------------------------------------------- | --------------------- | -------- | ------------ | ------------- |
| `SF_Account_No__c`                          | Customer Number       | Text     | Portal       | Registration  |
| `Portal_Status__c`                          | Portal Status         | Picklist | Portal       | Registration  |
| `Portal_Registration_Source__c`             | Registration Source   | Text     | Portal       | Registration  |
| `Portal_Last_SignIn__c`                     | Last Sign In          | DateTime | Portal       | Each login    |
| `WH_Account__c`                             | WHMCS Account         | Text     | Portal       | Registration  |
| `Internet_Eligibility__c`                   | Internet Eligibility  | Text     | **Agent**    | After check   |
| `Internet_Eligibility_Status__c`            | Eligibility Status    | Picklist | Portal/Agent | Request/Check |
| `Internet_Eligibility_Request_Date_Time__c` | Eligibility Requested | DateTime | Portal       | Request       |
| `Internet_Eligibility_Checked_Date_Time__c` | Eligibility Checked   | DateTime | **Agent**    | After check   |
| `Internet_Eligibility_Notes__c`             | Eligibility Notes     | Text     | **Agent**    | After check   |
| `Internet_Eligibility_Case_Id__c`           | Eligibility Case      | Lookup   | Portal       | Request       |
| `Id_Verification_Status__c`                 | ID Status             | Picklist | Portal/Agent | Upload/Review |
| `Id_Verification_Submitted_Date_Time__c`    | ID Submitted          | DateTime | Portal       | Upload        |
| `Id_Verification_Verified_Date_Time__c`     | ID Verified           | DateTime | **Agent**    | Review        |
| `Id_Verification_Note__c`                   | ID Notes              | Text     | **Agent**    | Review        |
| `Id_Verification_Rejection_Message__c`      | Rejection Reason      | Text     | **Agent**    | If rejected   |

#### Opportunity Object

| Field API Name                        | Label                  | Type     | Who Updates  | When                 |
| ------------------------------------- | ---------------------- | -------- | ------------ | -------------------- |
| `StageName`                           | Stage                  | Picklist | Portal/Agent | Throughout lifecycle |
| `CommodityType`                       | Commodity Type         | Picklist | Portal       | Creation             |
| `Application_Stage__c`                | Application Stage      | Picklist | Portal       | Creation (INTRO-1)   |
| `Opportunity_Source__c`               | Opportunity Source     | Picklist | Portal       | Creation             |
| `WHMCS_Service_ID__c`                 | WHMCS Service ID       | Number   | Portal       | After provisioning   |
| `CancellationNotice__c`               | Cancellation Notice    | Picklist | Portal       | Cancellation request |
| `ScheduledCancellationDateAndTime__c` | Scheduled Cancellation | DateTime | Portal       | Cancellation request |
| `LineReturn__c`                       | Line Return Status     | Picklist | **Agent**    | Equipment tracking   |

#### Order Object

| Field API Name                | Label             | Type     | Who Updates  | When               |
| ----------------------------- | ----------------- | -------- | ------------ | ------------------ |
| `OpportunityId`               | Opportunity       | Lookup   | Portal       | Order creation     |
| `Status`                      | Status            | Picklist | Portal/Agent | Throughout         |
| `Activation_Status__c`        | Activation Status | Picklist | Portal       | Fulfillment        |
| `Activation_Error_Code__c`    | Error Code        | Text     | Portal       | If failed          |
| `Activation_Error_Message__c` | Error Message     | Text     | Portal       | If failed          |
| `WHMCS_Order_ID__c`           | WHMCS Order ID    | Text     | Portal       | After provisioning |

#### Case Object

| Field API Name  | Label       | Type     | Who Updates | When               |
| --------------- | ----------- | -------- | ----------- | ------------------ |
| `OpportunityId` | Opportunity | Lookup   | Portal      | Eligibility/Cancel |
| `Origin`        | Origin      | Picklist | Portal      | "Portal"           |
| `Status`        | Status      | Picklist | **Agent**   | Processing         |
| `Priority`      | Priority    | Picklist | Portal      | Creation           |

---

## Agent Workflow & Checklist

### Internet Eligibility Processing

**Queue:** Cases with Subject = "Internet availability check request (Portal)"

**Agent Checklist:**

- [ ] **1. Open the Case**
  - Note the OpportunityId (related Opportunity)
  - Review Description for address details
- [ ] **2. Perform NTT Serviceability Check**
  - Check FLET'S光 availability for the address
  - Determine offering type (Home 1G, Mansion 1G, etc.)
- [ ] **3. Update Account Fields**

  ```
  Internet_Eligibility__c = "[Offering Type]" (e.g., "Home 1G", "Mansion 1G")
  Internet_Eligibility_Status__c = "Eligible" or "Ineligible"
  Internet_Eligibility_Checked_Date_Time__c = NOW()
  Internet_Eligibility_Notes__c = [Any relevant notes]
  ```

- [ ] **4. Close the Case**
  - Status = "Closed"
  - Resolution notes

**ℹ️ Automatic Actions:**

- Salesforce Flow automatically sends email to customer when eligibility fields are updated
- Portal polls Account for eligibility changes (customer sees result on next visit)
- Opportunity is created/reused at eligibility request (Stage = Introduction) and then reused at order placement when possible

---

### ID Verification Processing

**Queue:** Accounts with `Id_Verification_Status__c = "Submitted"`

**Agent Checklist:**

- [ ] **1. Find the Account**
  - Filter by `Id_Verification_Status__c = "Submitted"`
- [ ] **2. Download & Review Document**
  - Go to Account → Files → Most Recent
  - Verify: Name matches account, document is valid, not expired
- [ ] **3. Update Account (Approved)**

  ```
  Id_Verification_Status__c = "Verified"
  Id_Verification_Verified_Date_Time__c = NOW()
  Id_Verification_Note__c = "Verified by [Agent Name]"
  ```

- [ ] **3. Update Account (Rejected)**
  ```
  Id_Verification_Status__c = "Rejected"
  Id_Verification_Verified_Date_Time__c = NOW()
  Id_Verification_Rejection_Message__c = "[Specific reason - customer will see this]"
  ```

**⚠️ Critical:** Rejection message is shown to customer. Be specific but professional.

---

### Order Approval Processing

**Queue:** Orders with `Status = "Pending Review"` or triggered via automation

**Agent Checklist:**

- [ ] **1. Verify Customer Prerequisites**
  - Internet: `Account.Internet_Eligibility__c` is set
  - SIM: `Account.Id_Verification_Status__c = "Verified"`
- [ ] **2. Review Order Details**
  - Check order items are correct
  - Verify pricing
- [ ] **3. Approve Order**
  - Set `Order.Status = "Approved"`
  - This triggers CDC event → BFF provisioning

- [ ] **4. Monitor Activation**
  - Check `Activation_Status__c` updates to "Activated"
  - If "Failed", check error code/message

---

### Cancellation Processing

**Queue:** Cases with Subject = "Cancellation Request - ..."

**Agent Checklist:**

- [ ] **1. Open the Case**
  - Note the OpportunityId
  - Review cancellation month and service details
- [ ] **2. Verify the 25th Rule**
  - If requested before 25th: Can cancel THIS month
  - If requested on/after 25th: Must be NEXT month
- [ ] **3. Check Opportunity Fields** (portal sets these)
  - `ScheduledCancellationDateAndTime__c` = End of cancellation month
  - `CancellationNotice__c` = "有"
  - `LineReturn__c` = "NotYet"
- [ ] **4. Process Equipment Return** (if Internet)
  - Send return kit
  - Update `LineReturn__c` = "SentKit"
  - Track return: "Returned1", "Returned2"
- [ ] **5. Terminate Service in WHMCS**
  - On the scheduled date
- [ ] **6. Complete Cancellation**
  - `Opportunity.Stage = "〇Cancelled"`

---

## Critical Issues & Recommendations

### 🔴 Critical Issues

#### 1. Internet Eligibility Has No SLA Visibility

**Problem:** Customers submit eligibility requests and see "Pending" indefinitely. No estimated time, no progress indicator.

**Impact:** High abandonment rate, customer frustration, support inquiries.

**Recommendation:**

```
SHORT TERM:
- Add expected timeframe messaging: "Usually completed within 2-3 business days"
- Send email when eligibility is updated

LONG TERM:
- Implement automated NTT API check where possible
- Add SLA tracking on Cases with escalation rules
```

#### 2. ID Verification Rejection Lacks Guidance

**Problem:** When rejected, customers see the rejection message but no clear next steps.

**Impact:** Customers don't know how to fix the issue, leading to repeated failures.

**Recommendation:**

```
- Create structured rejection reasons with remediation steps
- Add "Re-submit" button that clears previous submission
- Show example of correct document format
```

#### 3. ~~Salesforce Fields Not Created~~ (RESOLVED)

**Status:** ✅ Confirmed fields exist:

- `Opportunity_Source__c` - Picklist with portal values
- `WHMCS_Service_ID__c` - Number field for WHMCS linking

**Note:** Emails for eligibility and ID verification status changes are sent automatically from Salesforce (via Flow/Process Builder).

### 🟡 Medium Issues

#### 4. No Internet ID Verification Requirement

**Problem:** Internet orders don't require ID verification, but SIM orders do. This may be intentional but creates inconsistency.

**Recommendation:**

```
- Confirm business requirement
- If ID needed for Internet, add gating step in checkout
- Document the decision either way
```

#### 5. WHMCS Rollback is Manual

**Problem:** If checkout registration fails after WHMCS client creation, the WHMCS client cannot be auto-deleted.

**Impact:** Orphaned WHMCS accounts require manual cleanup.

**Recommendation:**

```
- Add monitoring/alerting for failed registrations
- Create weekly cleanup process for orphaned WHMCS clients
- Consider delayed WHMCS creation (after all validations pass)
```

#### 6. Opportunity Matching Could Create Duplicates

**Problem:** The matching query only looks for open Opportunities. If two eligibility requests happen quickly, two Opportunities could be created.

**Recommendation:**

```
- Add optimistic locking or unique constraint
- Consider using Salesforce duplicate rules
```

### 🟢 Minor Issues

#### 7. Cache Invalidation on Eligibility Update

**Problem:** After agent updates eligibility, customer might see cached "Pending" status.

**Current:** Cache key includes account ID, TTL-based expiry.

**Recommendation:**

```
- Reduce eligibility cache TTL during "Pending" status
- Consider CDC-triggered cache invalidation
```

---

## Customer Experience Analysis

### Current CX Pain Points (Ranked)

| Rank | Issue                            | User Impact      | Frequency               |
| ---- | -------------------------------- | ---------------- | ----------------------- |
| 1    | No eligibility timeline          | High frustration | Every Internet customer |
| 2    | Unclear ID rejection reasons     | High frustration | ~10% of SIM customers   |
| 3    | No email notifications           | Medium annoyance | All customers           |
| 4    | Must return to check status      | Medium annoyance | All customers           |
| 5    | Address entry before eligibility | Low friction     | Internet customers      |

### Recommended CX Improvements

#### Tier 1: Quick Wins (Days)

1. **Add Timeline Messaging**

   ```tsx
   // AvailabilityStep.tsx
   <AlertBanner variant="info">
     Our team will verify NTT serviceability for your address.
     <strong>This usually takes 1-2 business days.</strong>
     We'll email you at {user.email} when complete.
   </AlertBanner>
   ```

2. **Improve Rejection Messages**

   ```typescript
   // Structured rejection reasons
   const REJECTION_REASONS = {
     EXPIRED: "Document has expired. Please upload a valid, unexpired residence card.",
     BLURRY: "Image is not clear enough. Please retake the photo in good lighting.",
     WRONG_TYPE:
       "This document type is not accepted. Please upload your residence card (在留カード).",
     NAME_MISMATCH: "Name on document doesn't match account. Please contact support.",
   };
   ```

3. **Add Status Polling with User Feedback**
   ```tsx
   // Show last checked time
   <p className="text-sm text-muted-foreground">
     Last checked: {formatRelativeTime(lastCheckedAt)}
     <button onClick={refetch}>Check now</button>
   </p>
   ```

#### Tier 2: Medium Effort (Weeks)

1. **Email Notifications**
   - Eligibility confirmed/denied
   - ID verification approved/rejected
   - Order status changes

2. **Progress Indicators**

   ```tsx
   // Visual progress for Internet checkout
   <StepIndicator
     steps={[
       { label: "Address", status: "complete" },
       { label: "Eligibility Check", status: "in_progress" },
       { label: "Payment", status: "pending" },
       { label: "Order", status: "pending" },
     ]}
   />
   ```

3. **Save & Resume Checkout**
   - Store checkout session in database
   - Allow customers to return and complete after eligibility

#### Tier 3: Strategic (Months)

1. **Automated NTT Check**
   - Integrate with NTT API (where available)
   - Instant eligibility for covered areas
   - Fallback to manual for edge cases

2. **Real-time Status Updates**
   - WebSocket notifications when status changes
   - Push notifications for mobile

---

## Implementation Improvements

### Code Quality Observations

#### ✅ What's Working Well

1. **Domain-Driven Design**
   - Clear separation in `packages/domain/`
   - Zod schemas for validation
   - Type-safe contracts

2. **Error Handling** [[memory:6689308]]
   - Production-ready error messages
   - No sensitive data exposure
   - Structured logging

3. **Distributed Transactions**
   - `DistributedTransactionService` for fulfillment
   - Step tracking with rollback support
   - Idempotency keys for retry safety

4. **Caching Strategy**
   - Catalog caching with cache keys
   - Eligibility caching per account
   - Invalidation on updates

#### ✅ Areas for Improvement (Resolved)

1. **Opportunity Matching Race Condition** → ✅ FIXED

   ```typescript
   // Now uses distributed lock via DistributedLockService
   // Located: apps/bff/src/infra/cache/distributed-lock.service.ts
   return this.lockService.withLock(
     `opportunity:${accountId}:${productType}`,
     async () => {
       const existingOppId = await this.opportunityService.findOpenOpportunityForAccount(...);
       if (!existingOppId) return this.createNewOpportunity(...);
       return this.useExistingOpportunity(existingOppId);
     },
     { ttlMs: 10_000 }
   );
   ```

2. **Hardcoded Eligibility Field Names** → ✅ FIXED

   ```typescript
   // Created centralized field maps in domain package
   // Located: packages/domain/salesforce/field-maps.ts
   import { ACCOUNT_FIELDS } from "@customer-portal/domain/salesforce";

   const eligibilityValue = account[ACCOUNT_FIELDS.eligibility.value];
   const eligibilityStatus = account[ACCOUNT_FIELDS.eligibility.status];
   ```

3. **Missing Opportunity Creation for SIM** → ✅ FIXED
   ```typescript
   // CheckoutRegistrationService now creates Opportunity for SIM orders
   // Added orderType parameter to registration request
   if (data.orderType === "SIM" && this.opportunityMatchingService) {
     opportunityId =
       await this.opportunityMatchingService.createOpportunityForCheckoutRegistration(sfAccountId);
   }
   ```

### Suggested Refactors (Partially Implemented)

1. **Create Unified Status Service** → 📋 FUTURE

   ```typescript
   // CustomerStatusService - not yet implemented
   // Consider creating for dashboard aggregation
   class CustomerStatusService {
     async getCustomerStatus(userId: string): Promise<CustomerStatus> {
       return {
         eligibility: await this.getEligibilityStatus(userId),
         verification: await this.getVerificationStatus(userId),
         pendingOrders: await this.getPendingOrders(userId),
         activeServices: await this.getActiveServices(userId),
       };
     }
   }
   ```

2. **Add Notification Service** → ✅ IMPLEMENTED

   ```typescript
   // Located: apps/bff/src/modules/notifications/notifications.service.ts
   // Creates in-app notifications from Platform Events
   // Emails handled by Salesforce Flows
   ```

3. **Centralize Field Maps** → ✅ IMPLEMENTED

   ```typescript
   // Located: packages/domain/salesforce/field-maps.ts
   import { ACCOUNT_FIELDS, SALESFORCE_FIELDS } from "@customer-portal/domain/salesforce";

   // Full field maps for Account, Opportunity, Order, and Case objects
   ```

---

## Salesforce Email Configuration (Required)

### Overview

Salesforce sends emails automatically when eligibility and ID verification statuses change. The Portal creates matching in-app notifications via Platform Events.

### Flow Triggers to Create

#### 1. Internet Eligibility Status Change Flow

**Trigger:** Record-Triggered Flow on Account  
**When:** `Internet_Eligibility_Status__c` changes AND is not null AND is not "Pending"

```yaml
Flow Name: Portal - Eligibility Status Email
Object: Account
Trigger: When a record is updated
Entry Conditions:
  - Internet_Eligibility_Status__c IS CHANGED
  - Internet_Eligibility_Status__c NOT EQUALS "Pending"
  - Internet_Eligibility_Status__c IS NOT NULL

Actions:
  1. Decision: Check Status Value
     - If "Eligible" → Send Eligible Email
     - If "Ineligible" → Send Ineligible Email

  2. Send Email (Eligible):
     Template: Portal_Eligibility_Eligible
     To: {!$Record.PersonEmail} OR Contact.Email
     Subject: "Good news! Internet service is available at your address"

  3. Send Email (Ineligible):
     Template: Portal_Eligibility_Ineligible
     To: {!$Record.PersonEmail} OR Contact.Email
     Subject: "Update on your internet availability request"
```

#### 2. ID Verification Status Change Flow

**Trigger:** Record-Triggered Flow on Account  
**When:** `Id_Verification_Status__c` changes to "Verified" or "Rejected"

```yaml
Flow Name: Portal - ID Verification Status Email
Object: Account
Trigger: When a record is updated
Entry Conditions:
  - Id_Verification_Status__c IS CHANGED
  - Id_Verification_Status__c IN ("Verified", "Rejected")

Actions:
  1. Decision: Check Status Value
     - If "Verified" → Send Verified Email
     - If "Rejected" → Send Rejected Email

  2. Send Email (Verified):
     Template: Portal_ID_Verified
     To: {!$Record.PersonEmail} OR Contact.Email
     Subject: "Your identity has been verified"

  3. Send Email (Rejected):
     Template: Portal_ID_Rejected
     To: {!$Record.PersonEmail} OR Contact.Email
     Subject: "Action required: ID verification needs attention"
     Body should include: {!$Record.Id_Verification_Rejection_Message__c}
```

### Email Templates to Create

#### Template: Portal_Eligibility_Eligible

```html
Subject: Good news! Internet service is available at your address Hi {{{Recipient.FirstName}}},
Great news! We've confirmed that internet service is available at your address. Your eligible
offering: {{{Account.Internet_Eligibility__c}}} You can now complete your order: [Complete Your
Order] → https://portal.example.com/shop/internet If you have any questions, please contact our
support team. Best regards, The SonixNet Team
```

#### Template: Portal_Eligibility_Ineligible

```html
Subject: Update on your internet availability request Hi {{{Recipient.FirstName}}}, Thank you for
your interest in our internet service. Unfortunately, after checking with NTT, we've determined that
internet service is not currently available at your address. {{{#if
Account.Internet_Eligibility_Notes__c}}} Notes: {{{Account.Internet_Eligibility_Notes__c}}}
{{{/if}}} If you believe this is an error or your situation changes, please contact our support
team. Best regards, The SonixNet Team
```

#### Template: Portal_ID_Verified

```html
Subject: Your identity has been verified Hi {{{Recipient.FirstName}}}, Your identity verification is
complete! You can now proceed with your order. [Continue to Checkout] →
https://portal.example.com/checkout Best regards, The SonixNet Team
```

#### Template: Portal_ID_Rejected

```html
Subject: Action required: ID verification needs attention Hi {{{Recipient.FirstName}}}, We were
unable to verify your identity based on the document you submitted. Reason:
{{{Account.Id_Verification_Rejection_Message__c}}} Please resubmit a clearer image of your residence
card: [Resubmit Document] → https://portal.example.com/account/verification Tips for a successful
submission: - Ensure the entire card is visible - Take the photo in good lighting - Make sure the
image is not blurry - The document must not be expired If you need assistance, please contact our
support team. Best regards, The SonixNet Team
```

### Platform Event Configuration

Portal uses Platform Events to receive real-time updates and create in-app notifications.

**Existing Platform Event:** `Account_Internet_Eligibility_Update__e`

**Environment Variable:** `SF_ACCOUNT_EVENT_CHANNEL=/event/Account_Internet_Eligibility_Update__e`

#### Platform Event Fields (Required)

| Field API Name                              | Type     | Description                               |
| ------------------------------------------- | -------- | ----------------------------------------- |
| `AccountId__c`                              | Text     | Salesforce Account ID                     |
| `Internet_Eligibility__c`                   | Text     | Eligibility value (e.g., "Home 1G")       |
| `Internet_Eligibility_Status__c`            | Text     | Status (Pending, Eligible, Ineligible)    |
| `Internet_Eligibility_Request_Date_Time__c` | DateTime | When requested                            |
| `Internet_Eligibility_Checked_Date_Time__c` | DateTime | When checked                              |
| `Internet_Eligibility_Notes__c`             | Text     | Agent notes                               |
| `Internet_Eligibility_Case_Id__c`           | Text     | Related Case ID                           |
| `Id_Verification_Status__c`                 | Text     | ID status (Submitted, Verified, Rejected) |
| `Id_Verification_Rejection_Message__c`      | Text     | Rejection reason                          |

#### Flow to Publish Platform Event

**Trigger:** Record-Triggered Flow on Account  
**When:** Any of the eligibility or verification fields change

```yaml
Flow Name: Portal - Publish Account Status Update
Object: Account
Trigger: When a record is updated
Entry Conditions (OR):
  - Internet_Eligibility_Status__c IS CHANGED
  - Id_Verification_Status__c IS CHANGED

Actions:
  1. Create Platform Event Record:
     Object: Account_Internet_Eligibility_Update__e
     Fields:
       - AccountId__c = {!$Record.Id}
       - Internet_Eligibility__c = {!$Record.Internet_Eligibility__c}
       - Internet_Eligibility_Status__c = {!$Record.Internet_Eligibility_Status__c}
       - Id_Verification_Status__c = {!$Record.Id_Verification_Status__c}
       - Id_Verification_Rejection_Message__c = {!$Record.Id_Verification_Rejection_Message__c}
       ... (other fields as needed)
```

---

## Notification Architecture (Implemented)

### Design Principle: Email + In-App Notifications

Salesforce sends emails automatically, and the Portal displays in-app notifications for the same events. This provides:

- **Push notification** via email (customer's inbox)
- **Pull notification** via in-app (when customer logs in)

### Implementation Details

- **Detection Method:** Platform Events (`Account_Internet_Eligibility_Update__e`)
- **Storage:** Portal PostgreSQL database (30-day expiry)
- **Duplication:** Same notification content in email AND in-app

### Architecture Diagram

```
┌─────────────────────────────────────────────────────────────────────────────────┐
│                     NOTIFICATION ARCHITECTURE                                    │
└─────────────────────────────────────────────────────────────────────────────────┘

                              SALESFORCE
                    ┌──────────────────────────────┐
                    │  Account Field Change        │
                    │  (Eligibility, Verification) │
                    └─────────────┬────────────────┘
                                  │
                    ┌─────────────┴─────────────┐
                    │                           │
                    ▼                           ▼
          ┌─────────────────┐         ┌─────────────────┐
          │   SF FLOW       │         │  PLATFORM EVENT │
          │   Sends Email   │         │  (Pub/Sub API)  │
          └────────┬────────┘         └────────┬────────┘
                   │                           │
                   ▼                           ▼
          ┌─────────────────┐         ┌─────────────────┐
          │  Customer's     │         │   Portal BFF    │
          │  Email Inbox    │         │  (Subscriber)   │
          └─────────────────┘         │                 │
                                      │  Creates notif. │
                                      └────────┬────────┘
                                               │
                                               ▼
                                      ┌─────────────────┐
                                      │  NOTIFICATION   │
                                      │  TABLE (Prisma) │
                                      └────────┬────────┘
                                               │
                                               ▼
                                      ┌─────────────────┐
                                      │  Portal UI      │
                                      │  🔔 (2) Badge   │
                                      └─────────────────┘
```

### Notification Types

| Type                     | Trigger                  | Email? | In-App?   | Action            |
| ------------------------ | ------------------------ | ------ | --------- | ----------------- |
| `eligibility_eligible`   | Eligibility = Eligible   | ✅ SF  | ✅ Portal | View Plans        |
| `eligibility_ineligible` | Eligibility = Ineligible | ✅ SF  | ✅ Portal | Contact Support   |
| `verification_verified`  | ID Status = Verified     | ✅ SF  | ✅ Portal | Continue Checkout |
| `verification_rejected`  | ID Status = Rejected     | ✅ SF  | ✅ Portal | Resubmit          |
| `order_activated`        | Order Activated          | ✅ SF  | ✅ Portal | View Service      |
| `cancellation_scheduled` | Cancellation Requested   | ✅ SF  | ✅ Portal | View Status       |

### Database Schema (Implemented)

Located in `apps/bff/prisma/schema.prisma`:

```prisma
model Notification {
  id          String             @id @default(uuid())
  userId      String             @map("user_id")

  // Notification content
  type        NotificationType
  title       String
  message     String?

  // Action (optional CTA button)
  actionUrl   String?            @map("action_url")
  actionLabel String?            @map("action_label")

  // Source tracking for deduplication
  source      NotificationSource @default(SALESFORCE)
  sourceId    String?            @map("source_id") // SF Account ID, etc.

  // Status
  read        Boolean            @default(false)
  readAt      DateTime?          @map("read_at")
  dismissed   Boolean            @default(false)

  // Timestamps
  createdAt   DateTime           @default(now()) @map("created_at")
  expiresAt   DateTime           @map("expires_at") // 30 days from creation

  user        User               @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@index([userId, read, dismissed])
  @@index([userId, createdAt])
  @@index([expiresAt])
  @@map("notifications")
}

enum NotificationType {
  ELIGIBILITY_ELIGIBLE
  ELIGIBILITY_INELIGIBLE
  VERIFICATION_VERIFIED
  VERIFICATION_REJECTED
  ORDER_APPROVED
  ORDER_ACTIVATED
  ORDER_FAILED
  CANCELLATION_SCHEDULED
  CANCELLATION_COMPLETE
  PAYMENT_METHOD_EXPIRING
  INVOICE_DUE
  SYSTEM_ANNOUNCEMENT
}

enum NotificationSource {
  SALESFORCE
  WHMCS
  PORTAL
  SYSTEM
}
```

### Case Management Strategy

| Case Type                                     | Customer Visible? | Show In Portal?                     |
| --------------------------------------------- | ----------------- | ----------------------------------- |
| **Support Tickets** (Origin="Portal Website") | ✅ Yes            | Show in Support section             |
| **Eligibility Check** (Origin="Portal")       | ❌ No             | Internal workflow only              |
| **ID Verification** (Origin="Portal")         | ❌ No             | Internal workflow only              |
| **Cancellation Request** (Origin="Portal")    | Status only       | Show via Opportunity/Service status |

---

## Summary

This implementation provides a complete foundation for customer acquisition and service management flows.

### All Features Working

| Feature                       | Status  | Location                                                         |
| ----------------------------- | ------- | ---------------------------------------------------------------- |
| Notification Database Schema  | ✅ Done | `apps/bff/prisma/schema.prisma`                                  |
| NotificationService           | ✅ Done | `apps/bff/src/modules/notifications/notifications.service.ts`    |
| Notification API              | ✅ Done | `apps/bff/src/modules/notifications/notifications.controller.ts` |
| Platform Event Integration    | ✅ Done | Extended `CatalogCdcSubscriber` + `AccountNotificationHandler`   |
| Cleanup Scheduler             | ✅ Done | `notification-cleanup.service.ts` (30 day expiry)                |
| Frontend Bell Icon            | ✅ Done | `apps/portal/src/features/notifications/components/`             |
| Frontend Hooks                | ✅ Done | `apps/portal/src/features/notifications/hooks/`                  |
| Distributed Lock Service      | ✅ Done | `apps/bff/src/infra/cache/distributed-lock.service.ts`           |
| Centralized SF Field Maps     | ✅ Done | `packages/domain/salesforce/field-maps.ts`                       |
| Guest Checkout Removal        | ✅ Done | Removed `checkout-registration` module, redirect to login        |
| Checkout Store Simplification | ✅ Done | `apps/portal/src/features/checkout/stores/checkout.store.ts`     |
| OrderType Standardization     | ✅ Done | PascalCase ("Internet", "SIM", "VPN") across all layers          |
| Internet Subscription Match   | ✅ Done | Matches "SonixNet via NTT Optical Fiber" products                |
| ID Verification in Profile    | ✅ Done | `apps/portal/src/features/account/views/ProfileContainer.tsx`    |
| Profile Data Display          | ✅ Done | Customer number, DOB, gender now load correctly                  |
| Opportunity ↔ WHMCS Linking   | ✅ Done | `WHMCS_Service_ID__c` field links after provisioning             |

### Key Architecture Points

1. **Subscription Type Detection** - Uses product name matching:
   - Internet: matches "internet", "sonixnet", or "ntt" + "fiber"
   - SIM: matches "sim" in product/group name

2. **ID Verification** - Integrated into Profile page (`/account/settings`), standalone page kept for checkout redirects

3. **Opportunity ↔ WHMCS Link** - `WHMCS_Service_ID__c` stores WHMCS service ID after provisioning

4. **Cancellation Flow** - Portal updates Salesforce, WHMCS termination is manual (agent action)

5. **Profile Data** - Fetched from WHMCS custom fields (IDs: 198, 200, 201 by default)

### Important Notes

**WHMCS Cancellation is NOT Automated:**
When a customer requests cancellation, the portal:

- Updates Salesforce Opportunity (stage, scheduled date, etc.)
- Creates Salesforce Case for agent
- Does NOT automatically terminate WHMCS service

Agent must manually terminate WHMCS service on the scheduled date.

**Profile Data Field IDs:**
If customer number, DOB, or gender don't display, verify these env variables match your WHMCS:

```bash
WHMCS_CUSTOMER_NUMBER_FIELD_ID=198
WHMCS_DOB_FIELD_ID=201
WHMCS_GENDER_FIELD_ID=200
```
