2025-12-23 11:36:44 +09:00
# Customer Portal Flow Review: Shop, Eligibility, ID Verification & Opportunity Management
**Review Date:** December 23, 2025
2025-12-23 15:19:20 +09:00
**Last Updated:** December 23, 2025
2025-12-23 11:36:44 +09:00
**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
2025-12-23 15:19:20 +09:00
| 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 |
2025-12-23 11:36:44 +09:00
### Key Findings
2025-12-23 15:19:20 +09:00
1. **Internet Subscription Detection** - Now matches "SonixNet via NTT Optical Fiber" products in addition to "Internet" named products.
2025-12-23 11:36:44 +09:00
2025-12-23 15:19:20 +09:00
2. **ID Verification Integrated** - Upload functionality is now built into the Profile page (`/account/settings` ) rather than requiring a separate page.
2025-12-23 11:36:44 +09:00
2026-01-05 16:32:45 +09:00
3. **Opportunity Lifecycle Fields Exist** - `Opportunity_Source__c` (with portal picklist values) and `WHMCS_Service_ID__c` are in place and working.
2025-12-23 11:36:44 +09:00
2025-12-23 15:19:20 +09:00
4. **SIM vs Internet Flows Have Different Requirements** - SIM requires ID verification but not eligibility; Internet requires eligibility but not ID verification.
2025-12-23 11:36:44 +09:00
2025-12-23 15:19:20 +09:00
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.
2025-12-23 11:36:44 +09:00
---
## System Architecture Overview
### Three-Tier Integration
```
┌─────────────────────────────────────────────────────────────────────────────────┐
│ CUSTOMER PORTAL │
│ (Next.js Frontend) │
├─────────────────────────────────────────────────────────────────────────────────┤
│ Shop/Catalog │ Checkout │ Verification │ Dashboard │ Service Mgmt │
└───────┬────────┴─────┬──────┴───────┬────────┴──────┬──────┴─────────┬─────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│ BFF (NestJS) │
├─────────────────────────────────────────────────────────────────────────────────┤
│ CatalogService │ CheckoutService │ VerificationService │ OrderService │
│ │ │ │ │
2025-12-23 16:44:45 +09:00
│ Opportunity Resolution │ OrderOrchestrator │ FulfillmentOrchestrator │
2025-12-23 11:36:44 +09:00
└───────┬──────────────────────┴─────────┬──────────┴────────────┬───────────────┘
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌─────────────────┐ ┌──────────────┐
│ 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 |
2025-12-23 13:21:29 +09:00
| **SIM** | ❌ No | ✅ Yes (Residence Card) | Order Placement |
2025-12-23 11:36:44 +09:00
| **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"
2025-12-23 13:21:29 +09:00
└─► Redirect to login if /auth/login?returnTo=... (none - auth required)
unauthenticated Standard auth flow creates
SF Account + WHMCS Client
2025-12-23 11:36:44 +09:00
2025-12-23 13:21:29 +09:00
3. Configure & Checkout (Authenticated)
└─► AccountCheckoutContainer /account/shop/sim/configure (cart stored locally)
handles full flow /account/order
2025-12-23 11:36:44 +09:00
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
2025-12-23 16:44:45 +09:00
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.
2025-12-23 11:36:44 +09:00
**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
2025-12-23 13:21:29 +09:00
### User Registration Flow
**IMPORTANT: Guest checkout has been removed.** All checkout flows now require authentication first.
2025-12-23 11:36:44 +09:00
2025-12-23 13:21:29 +09:00
**Authentication-First Checkout Flow:**
2025-12-23 11:36:44 +09:00
2025-12-23 13:21:29 +09:00
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)
2025-12-23 11:36:44 +09:00
2025-12-23 13:21:29 +09:00
**Registration Location:** `apps/portal/src/app/auth/register/` (standard auth flow)
2025-12-23 11:36:44 +09:00
2025-12-23 13:21:29 +09:00
**Benefits of Auth-First Approach:**
2025-12-23 11:36:44 +09:00
2025-12-23 13:21:29 +09:00
- 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
2025-12-23 11:36:44 +09:00
### Opportunity Management Module
2025-12-23 16:44:45 +09:00
**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)
2025-12-23 11:36:44 +09:00
**Matching Rules:**
| Scenario | Action |
|----------|--------|
| Order has `opportunityId` | Use it directly |
| Internet order without Opp | Find Introduction/Ready stage or create new |
2025-12-23 16:44:45 +09:00
| SIM order without Opp | Find Introduction/Ready stage or create new |
2025-12-23 11:36:44 +09:00
| 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) |
2026-01-05 16:32:45 +09:00
| `Opportunity_Source__c` | Opportunity Source | Picklist | Portal | Creation |
2025-12-23 11:36:44 +09:00
| `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)
2025-12-23 16:44:45 +09:00
- Opportunity is created/reused at eligibility request (Stage = Introduction) and then reused at order placement when possible
2025-12-23 11:36:44 +09:00
---
### 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:
2026-01-05 16:32:45 +09:00
- `Opportunity_Source__c` - Picklist with portal values
2025-12-23 11:36:44 +09:00
- `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
2025-12-23 15:19:20 +09:00
This implementation provides a complete foundation for customer acquisition and service management flows.
2025-12-23 11:36:44 +09:00
2025-12-23 15:19:20 +09:00
### All Features Working
2025-12-23 11:36:44 +09:00
2025-12-23 13:21:29 +09:00
| 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 |
2025-12-23 15:19:20 +09:00
| 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
2025-12-23 11:36:44 +09:00
2025-12-23 15:19:20 +09:00
**WHMCS Cancellation is NOT Automated:**
When a customer requests cancellation, the portal:
2025-12-23 11:36:44 +09:00
2025-12-23 15:19:20 +09:00
- Updates Salesforce Opportunity (stage, scheduled date, etc.)
- Creates Salesforce Case for agent
- Does NOT automatically terminate WHMCS service
2025-12-23 11:36:44 +09:00
2025-12-23 15:19:20 +09:00
Agent must manually terminate WHMCS service on the scheduled date.
2025-12-23 11:36:44 +09:00
2025-12-23 15:19:20 +09:00
**Profile Data Field IDs:**
If customer number, DOB, or gender don't display, verify these env variables match your WHMCS:
2025-12-23 11:36:44 +09:00
```bash
2025-12-23 15:19:20 +09:00
WHMCS_CUSTOMER_NUMBER_FIELD_ID=198
WHMCS_DOB_FIELD_ID=201
WHMCS_GENDER_FIELD_ID=200
2025-12-23 11:36:44 +09:00
```