Assist_Design/docs/_archive/reviews/SHOP-ELIGIBILITY-VERIFICATION-OPPORTUNITY-FLOW-REVIEW.md
barsa 6096c15659 Refactor Salesforce Opportunity Integration for SIM and Internet Cancellations
- Updated opportunity field mappings to replace deprecated fields with new ones for SIM and Internet cancellations, enhancing clarity and consistency.
- Introduced separate data structures for Internet and SIM cancellation data, improving type safety and validation.
- Refactored SalesforceOpportunityService to handle updates for both Internet and SIM cancellations, ensuring accurate data handling.
- Enhanced cancellation query fields to support new SIM cancellation requirements, improving the overall cancellation process.
- Cleaned up the portal integration to reflect changes in opportunity source fields, promoting better data integrity and tracking.
2026-01-05 17:08:33 +09:00

60 KiB
Raw Blame History

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
  2. System Architecture Overview
  3. Flow Diagrams
  4. Detailed Behavior Analysis
  5. Salesforce Field Changes Reference
  6. Agent Workflow & Checklist
  7. Critical Issues & Recommendations
  8. Customer Experience Analysis
  9. 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:

// 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):

// 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

Tier 1: Quick Wins (Days)

  1. Add Timeline Messaging

    // 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

    // 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

    // 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

    // 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

    // 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

    // 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

    // 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

    // 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

    // 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

    // 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"

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"

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

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

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

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

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

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:

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:

WHMCS_CUSTOMER_NUMBER_FIELD_ID=198
WHMCS_DOB_FIELD_ID=201
WHMCS_GENDER_FIELD_ID=200