# 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 Our team will verify NTT serviceability for your address. This usually takes 1-2 business days. We'll email you at {user.email} when complete. ``` 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

Last checked: {formatRelativeTime(lastCheckedAt)}

``` #### 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 ``` 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 { 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 ```