UAT docs and bug fixes

This commit is contained in:
barsa 2026-02-24 11:09:35 +09:00
parent 5e5bff12da
commit 6e51012d21
39 changed files with 2961 additions and 406 deletions

View File

@ -0,0 +1,49 @@
#!/bin/bash
# PreToolUse hook: Block Bash commands that should use dedicated tools.
# Runs before every Bash call (main agent + subagents).
# Exit 0 = allow, Exit 2 = block with message.
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
# Strip leading whitespace and env vars (e.g. FOO=bar cat file)
CLEAN=$(echo "$COMMAND" | sed 's/^[[:space:]]*//' | sed 's/^[A-Za-z_][A-Za-z_0-9]*=[^ ]* *//')
# Extract the first word (the actual command)
FIRST=$(echo "$CLEAN" | awk '{print $1}' | sed 's|.*/||')
case "$FIRST" in
cat)
echo "Use the Read tool instead of cat." >&2
exit 2
;;
head|tail)
echo "Use the Read tool (with offset/limit) instead of $FIRST." >&2
exit 2
;;
ls)
echo "Use the Glob tool instead of ls." >&2
exit 2
;;
find)
echo "Use the Glob tool instead of find." >&2
exit 2
;;
grep|rg)
echo "Use the Grep tool instead of $FIRST." >&2
exit 2
;;
sed|awk)
echo "Use the Edit tool instead of $FIRST." >&2
exit 2
;;
echo)
# Block echo used for file writing (echo > file, echo >> file)
if echo "$COMMAND" | grep -qE '>\s*\S'; then
echo "Use the Write or Edit tool instead of echo redirection." >&2
exit 2
fi
;;
esac
exit 0

15
.claude/settings.json Normal file
View File

@ -0,0 +1,15 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/scripts/enforce-tools.sh"
}
]
}
]
}
}

194
CLAUDE.md
View File

@ -1,211 +1,57 @@
# CLAUDE.md # CLAUDE.md
Instructions for Claude Code working in this repository.
---
## Agent Behavior ## Agent Behavior
**Always use `pnpm`** — never use `npm`, `yarn`, or `npx`: **Always use `pnpm`** — never `npm`, `yarn`, or `npx`. Use `pnpm exec` for local binaries, `pnpm dlx` for one-off execution.
- Use `pnpm exec` to run local binaries (e.g., `pnpm exec prisma migrate status`) **Never run long-running processes** (dev servers, watchers, Docker) without explicit permission.
- Use `pnpm dlx` for one-off package execution (e.g., `pnpm dlx ts-prune`)
**Do NOT** run long-running processes without explicit permission: **Read relevant docs before implementing** — never guess endpoint behavior or payload shapes.
- `pnpm dev`, `pnpm dev:start`, or any dev server commands **Use dedicated tools, not Bash** — Read (not `cat`/`head`/`tail`), Glob (not `find`/`ls`), Grep (not `grep`/`rg`), Edit (not `sed`/`awk`). This applies to all agents and subagents.
- Any command that starts servers, watchers, or blocking processes
**Always ask first** before: ## Commands
- Starting development servers
- Running Docker containers
- Executing build watchers
- Any process that won't terminate on its own
**Before coding**: Read relevant docs. Never guess endpoint behavior or payload shapes.
---
## Quick Reference
```bash ```bash
# Build domain (required after domain changes) pnpm domain:build # Required after domain changes
pnpm domain:build
# Type check & lint
pnpm type-check pnpm type-check
pnpm lint pnpm lint
# Database
pnpm db:migrate pnpm db:migrate
pnpm db:generate pnpm db:generate
pnpm test # All tests
# Tests pnpm --filter @customer-portal/bff test # BFF tests only
pnpm test
pnpm --filter @customer-portal/bff test
``` ```
**Ports**: Frontend :3000 | Backend :4000/api | Prisma Studio :5555 Ports: Frontend :3000 | Backend :4000/api | Prisma Studio :5555
---
## Architecture ## Architecture
### Monorepo
``` ```
apps/ apps/
├── portal/ # Next.js 15 (React 19, Tailwind, shadcn/ui) ├── portal/ # Next.js 15 (React 19, Tailwind, shadcn/ui)
└── bff/ # NestJS 11 (Prisma, BullMQ, Zod) └── bff/ # NestJS 11 (Prisma, BullMQ, Zod)
packages/ packages/
└── domain/ # Shared contracts, schemas, provider mappers └── domain/ # Shared contracts, Zod schemas, provider mappers
``` ```
### Three-Layer Boundary **Systems of record**: WHMCS (billing, subscriptions, invoices, addresses) | Salesforce (CRM, orders) | Portal (UI + BFF orchestration)
| Layer | Location | Purpose | ## Key Conventions
| ------ | ------------------ | -------------------------------------------------- |
| Domain | `packages/domain/` | Contracts, Zod schemas, provider mappers |
| BFF | `apps/bff/` | HTTP boundary, orchestration, integrations |
| Portal | `apps/portal/` | UI layer, thin route wrappers over feature modules |
### Systems of Record **Imports** (ESLint enforced):
- **WHMCS**: Billing, subscriptions, invoices, authoritative addresses - Import from module index: `@customer-portal/domain/billing` — never root or deep paths
- **Salesforce**: CRM (Accounts, Contacts, Cases), order snapshots - Provider imports (`/providers`) are BFF-only, forbidden in Portal
- **Portal**: UI + BFF orchestration
--- **Domain**: Each module has `contract.ts`, `schema.ts`, `index.ts`, and `providers/` (BFF-only mappers). Map once in mappers, use domain types everywhere.
## Import Rules (ESLint Enforced) **Portal**: Pages in `app/` are thin shells — all logic lives in `features/` modules. No API calls in `app/`. Use `@/core/logger` not `console.log`.
```typescript **BFF**: Integration services fetch, transform via domain mappers, return domain types. Controllers are thin — use `createZodDto(schema)` + `ZodValidationPipe`. Use `nestjs-pino` logger not `console.log`.
// Allowed (Portal + BFF)
import type { Invoice } from "@customer-portal/domain/billing";
import { invoiceSchema } from "@customer-portal/domain/billing";
import { Formatting } from "@customer-portal/domain/toolkit";
// Allowed (BFF only) **Validation**: Zod-first. Schemas in domain, derive types with `z.infer`. Use `z.coerce.*` for query params.
import { Whmcs } from "@customer-portal/domain/billing/providers";
// Forbidden everywhere ## Docs
import { Billing } from "@customer-portal/domain"; // root import
import { Invoice } from "@customer-portal/domain/billing/contract"; // deep import
// Forbidden in Portal
import { Whmcs } from "@customer-portal/domain/billing/providers"; // provider adapters
```
---
## Domain Package
Each module follows:
```
packages/domain/<module>/
├── contract.ts # Normalized types (provider-agnostic)
├── schema.ts # Zod validation
├── index.ts # Public exports
└── providers/ # Provider-specific (BFF-only)
└── whmcs/
├── raw.types.ts # Raw API response types
└── mapper.ts # Transform raw → domain
```
**Key principle**: Map once in domain mappers, use domain types everywhere.
---
## Portal Structure
```
apps/portal/src/
├── app/ # Next.js App Router (thin shells, NO API calls)
├── components/ # Atomic: atoms/ molecules/ organisms/ templates/
├── core/ # Infrastructure: api/, logger/, providers/
├── features/ # Feature modules (api/, stores/, hooks/, views/)
└── shared/ # Cross-feature: hooks/, utils/, constants/
```
### Feature Module Pattern
```
features/<name>/
├── api/ # Data fetching (uses core/api/apiClient)
├── stores/ # Zustand state
├── hooks/ # React Query hooks
├── components/ # Feature UI
├── views/ # Page-level views
└── index.ts # Public exports (barrel)
```
**Rules**:
- Pages are thin wrappers importing views from features
- No API calls in `app/` directory
- No business logic in frontend; use services and APIs
---
## BFF Patterns
### Integration Layer
```
apps/bff/src/integrations/{provider}/
├── services/
│ ├── {provider}-connection.service.ts
│ └── {provider}-{entity}.service.ts
└── utils/
└── {entity}-query-builder.ts
```
### Data Flow
```
External API → Integration Service → Domain Mapper → Domain Type → Use Directly
(fetch + query) (transform once) (return)
```
**Integration services**:
1. Build queries (SOQL, API params)
2. Execute API calls
3. Use domain mappers to transform
4. Return domain types
5. NO additional mapping or business logic
### Controllers
- Thin: no business logic, no Zod imports
- Use `createZodDto(schema)` + global `ZodValidationPipe`
---
## Validation (Zod-First)
- Schemas in domain: `packages/domain/<module>/schema.ts`
- Derive types: `export type X = z.infer<typeof xSchema>`
- Query params: use `z.coerce.*` for URL strings
---
## Code Standards
- No `any` in public APIs
- No `console.log` (use logger: `nestjs-pino` for BFF, `@/core/logger` for Portal)
- Avoid `V2` suffix in service names
- No unsafe assertions
- Reuse existing types and functions; extend when needed
---
## Documentation
Read before implementing:
| Topic | Location | | Topic | Location |
| ------------------- | ---------------------------------------------- | | ------------------- | ---------------------------------------------- |

View File

@ -693,7 +693,7 @@ export class GetStartedWorkflowService {
* 1. Validate session has WHMCS client ID * 1. Validate session has WHMCS client ID
* 2. Verify WHMCS client still exists * 2. Verify WHMCS client still exists
* 3. Find Salesforce account (by email or customer number) * 3. Find Salesforce account (by email or customer number)
* 4. Update WHMCS client with new password + DOB + gender * 4. Update WHMCS client with new password (DOB/gender only if provided)
* 5. Create portal user with hashed password * 5. Create portal user with hashed password
* 6. Create ID mapping * 6. Create ID mapping
* 7. Update Salesforce portal flags * 7. Update Salesforce portal flags
@ -757,7 +757,7 @@ export class GetStartedWorkflowService {
// Hash password for portal storage // Hash password for portal storage
const passwordHash = await argon2.hash(password); const passwordHash = await argon2.hash(password);
// Update WHMCS client with new password + DOB + gender // Update WHMCS client with new password (DOB/gender only if provided)
await this.updateWhmcsClientForMigration(whmcsClient.id, password, dateOfBirth, gender); await this.updateWhmcsClientForMigration(whmcsClient.id, password, dateOfBirth, gender);
// Create portal user and ID mapping // Create portal user and ID mapping
@ -875,15 +875,15 @@ export class GetStartedWorkflowService {
private async updateWhmcsClientForMigration( private async updateWhmcsClientForMigration(
clientId: number, clientId: number,
password: string, password: string,
dateOfBirth: string, dateOfBirth?: string,
gender: string gender?: string
): Promise<void> { ): Promise<void> {
const dobFieldId = this.config.get<string>("WHMCS_DOB_FIELD_ID"); const dobFieldId = this.config.get<string>("WHMCS_DOB_FIELD_ID");
const genderFieldId = this.config.get<string>("WHMCS_GENDER_FIELD_ID"); const genderFieldId = this.config.get<string>("WHMCS_GENDER_FIELD_ID");
const customfieldsMap: Record<string, string> = {}; const customfieldsMap: Record<string, string> = {};
if (dobFieldId) customfieldsMap[dobFieldId] = dateOfBirth; if (dobFieldId && dateOfBirth) customfieldsMap[dobFieldId] = dateOfBirth;
if (genderFieldId) customfieldsMap[genderFieldId] = gender; if (genderFieldId && gender) customfieldsMap[genderFieldId] = gender;
const updateData: Record<string, unknown> = { const updateData: Record<string, unknown> = {
password2: password, password2: password,

View File

@ -2,6 +2,7 @@ import { ConflictException, Inject, Injectable } from "@nestjs/common";
import { Logger } from "nestjs-pino"; import { Logger } from "nestjs-pino";
import * as argon2 from "argon2"; import * as argon2 from "argon2";
import type { Request } from "express"; import type { Request } from "express";
import { DistributedLockService } from "@bff/infra/cache/distributed-lock.service.js";
import { AuditService, AuditAction } from "@bff/infra/audit/audit.service.js"; import { AuditService, AuditAction } from "@bff/infra/audit/audit.service.js";
import { UsersService } from "@bff/modules/users/application/users.service.js"; import { UsersService } from "@bff/modules/users/application/users.service.js";
import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js"; import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js";
@ -50,6 +51,7 @@ export class SignupWorkflowService {
private readonly signupValidation: SignupValidationService, private readonly signupValidation: SignupValidationService,
private readonly whmcsSignup: SignupWhmcsService, private readonly whmcsSignup: SignupWhmcsService,
private readonly userCreation: SignupUserCreationService, private readonly userCreation: SignupUserCreationService,
private readonly lockService: DistributedLockService,
@Inject(Logger) private readonly logger: Logger @Inject(Logger) private readonly logger: Logger
) {} ) {}
@ -104,6 +106,10 @@ export class SignupWorkflowService {
const passwordHash = await argon2.hash(password); const passwordHash = await argon2.hash(password);
const lockKey = `signup:${email.toLowerCase().trim()}`;
return await this.lockService.withLock(
lockKey,
async () => {
try { try {
// Step 1: Check for existing WHMCS client before provisioning in Salesforce // Step 1: Check for existing WHMCS client before provisioning in Salesforce
await this.whmcsSignup.checkExistingClient(email.toLowerCase().trim()); await this.whmcsSignup.checkExistingClient(email.toLowerCase().trim());
@ -119,7 +125,9 @@ export class SignupWorkflowService {
signupData.sfNumber signupData.sfNumber
); );
if (normalizedCustomerNumber) { if (normalizedCustomerNumber) {
const existingMapping = await this.mappingsService.findBySfAccountId(accountSnapshot.id); const existingMapping = await this.mappingsService.findBySfAccountId(
accountSnapshot.id
);
if (existingMapping) { if (existingMapping) {
throw new ConflictException( throw new ConflictException(
"You already have an account. Please use the login page to access your existing account." "You already have an account. Please use the login page to access your existing account."
@ -202,6 +210,9 @@ export class SignupWorkflowService {
this.logger.error("Signup error", { error: extractErrorMessage(error) }); this.logger.error("Signup error", { error: extractErrorMessage(error) });
throw error; throw error;
} }
},
{ ttlMs: 60_000 }
);
} }
/** /**

View File

@ -2,7 +2,6 @@
import Link from "next/link"; import Link from "next/link";
import { ArrowLeftIcon } from "@heroicons/react/24/outline"; import { ArrowLeftIcon } from "@heroicons/react/24/outline";
import { Logo } from "@/components/atoms/logo";
export interface AuthLayoutProps { export interface AuthLayoutProps {
children: React.ReactNode; children: React.ReactNode;
@ -27,7 +26,7 @@ export function AuthLayout({
const maxWidth = wide ? "max-w-xl" : "max-w-md"; const maxWidth = wide ? "max-w-xl" : "max-w-md";
return ( return (
<div className="w-full flex flex-col items-center"> <div className="w-full flex flex-col items-center justify-center min-h-[calc(100dvh-4rem)] py-8 sm:py-12">
<div className={`w-full ${maxWidth}`}> <div className={`w-full ${maxWidth}`}>
{showBackButton && ( {showBackButton && (
<div className="mb-6"> <div className="mb-6">
@ -42,11 +41,6 @@ export function AuthLayout({
)} )}
<div className="text-center"> <div className="text-center">
<div className="flex justify-center mb-8">
<div className="h-16 w-16 rounded-xl bg-primary/5 flex items-center justify-center">
<Logo size={40} />
</div>
</div>
<h1 className="text-2xl font-bold tracking-tight text-foreground mb-2">{title}</h1> <h1 className="text-2xl font-bold tracking-tight text-foreground mb-2">{title}</h1>
{subtitle && ( {subtitle && (
<p className="text-sm text-muted-foreground leading-relaxed max-w-sm mx-auto"> <p className="text-sm text-muted-foreground leading-relaxed max-w-sm mx-auto">
@ -56,13 +50,13 @@ export function AuthLayout({
</div> </div>
</div> </div>
<div className={`mt-8 w-full ${maxWidth}`}> <div className={`mt-6 w-full ${maxWidth}`}>
<div className="bg-card text-card-foreground py-8 px-6 rounded-xl border border-border shadow-sm sm:px-10"> <div className="bg-card text-card-foreground py-8 px-6 rounded-xl border border-border shadow-sm sm:px-10">
{children} {children}
</div> </div>
{/* Trust indicator */} {/* Trust indicator */}
<div className="mt-8 text-center"> <div className="mt-6 text-center">
<p className="text-xs text-muted-foreground/60 flex items-center justify-center gap-1.5"> <p className="text-xs text-muted-foreground/60 flex items-center justify-center gap-1.5">
<span className="h-2 w-2 rounded-full bg-green-500/80" /> <span className="h-2 w-2 rounded-full bg-green-500/80" />
Secure login protected by SSL encryption Secure login protected by SSL encryption

View File

@ -30,7 +30,7 @@ export function PasswordRequirements({ checks, showHint = false }: PasswordRequi
if (showHint) { if (showHint) {
return ( return (
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
At least 8 characters with uppercase, lowercase, and numbers At least 8 characters with uppercase, lowercase, numbers, and a special character
</p> </p>
); );
} }
@ -41,6 +41,7 @@ export function PasswordRequirements({ checks, showHint = false }: PasswordRequi
<RequirementCheck met={checks.hasUppercase} label="Uppercase letter" /> <RequirementCheck met={checks.hasUppercase} label="Uppercase letter" />
<RequirementCheck met={checks.hasLowercase} label="Lowercase letter" /> <RequirementCheck met={checks.hasLowercase} label="Lowercase letter" />
<RequirementCheck met={checks.hasNumber} label="Number" /> <RequirementCheck met={checks.hasNumber} label="Number" />
<RequirementCheck met={checks.hasSpecialChar} label="Special character" />
</div> </div>
); );
} }

View File

@ -5,6 +5,7 @@ export interface PasswordChecks {
hasUppercase: boolean; hasUppercase: boolean;
hasLowercase: boolean; hasLowercase: boolean;
hasNumber: boolean; hasNumber: boolean;
hasSpecialChar: boolean;
} }
export interface PasswordValidation { export interface PasswordValidation {
@ -19,6 +20,7 @@ export function validatePasswordRules(password: string): string | undefined {
if (!/[A-Z]/.test(password)) return "Password must contain an uppercase letter"; if (!/[A-Z]/.test(password)) return "Password must contain an uppercase letter";
if (!/[a-z]/.test(password)) return "Password must contain a lowercase letter"; if (!/[a-z]/.test(password)) return "Password must contain a lowercase letter";
if (!/[0-9]/.test(password)) return "Password must contain a number"; if (!/[0-9]/.test(password)) return "Password must contain a number";
if (!/[^A-Za-z0-9]/.test(password)) return "Password must contain a special character";
return undefined; return undefined;
} }
@ -29,10 +31,15 @@ export function usePasswordValidation(password: string): PasswordValidation {
hasUppercase: /[A-Z]/.test(password), hasUppercase: /[A-Z]/.test(password),
hasLowercase: /[a-z]/.test(password), hasLowercase: /[a-z]/.test(password),
hasNumber: /[0-9]/.test(password), hasNumber: /[0-9]/.test(password),
hasSpecialChar: /[^A-Za-z0-9]/.test(password),
}; };
const isValid = const isValid =
checks.minLength && checks.hasUppercase && checks.hasLowercase && checks.hasNumber; checks.minLength &&
checks.hasUppercase &&
checks.hasLowercase &&
checks.hasNumber &&
checks.hasSpecialChar;
const error = validatePasswordRules(password); const error = validatePasswordRules(password);
return { checks, isValid, error }; return { checks, isValid, error };

View File

@ -147,10 +147,6 @@ export function AccountStatusStep() {
<div> <div>
<p className="text-xs font-medium text-muted-foreground mb-2">What you&apos;ll add:</p> <p className="text-xs font-medium text-muted-foreground mb-2">What you&apos;ll add:</p>
<ul className="space-y-1 text-sm text-muted-foreground"> <ul className="space-y-1 text-sm text-muted-foreground">
<li className="flex items-center gap-2">
<span className="w-4 text-center"></span>
<span>Date of birth</span>
</li>
<li className="flex items-center gap-2"> <li className="flex items-center gap-2">
<span className="w-4 text-center"></span> <span className="w-4 text-center"></span>
<span>New portal password</span> <span>New portal password</span>

View File

@ -16,6 +16,7 @@ import { useRouter } from "next/navigation";
import { import {
PrefilledUserInfo, PrefilledUserInfo,
NewCustomerFields, NewCustomerFields,
AddressFields,
PersonalInfoFields, PersonalInfoFields,
PasswordSection, PasswordSection,
useCompleteAccountForm, useCompleteAccountForm,
@ -45,6 +46,17 @@ export function CompleteAccountStep() {
const isNewCustomer = accountStatus === "new_customer"; const isNewCustomer = accountStatus === "new_customer";
const hasPrefill = !!(prefill?.firstName || prefill?.lastName); const hasPrefill = !!(prefill?.firstName || prefill?.lastName);
// Show address form if new customer OR if SF-unmapped with incomplete address
const prefillAddress = prefill?.address;
const hasCompleteAddress = !!(
prefillAddress?.address1 &&
prefillAddress?.city &&
prefillAddress?.state &&
prefillAddress?.postcode
);
const isSfUnmappedWithIncompleteAddress = accountStatus === "sf_unmapped" && !hasCompleteAddress;
const needsAddress = isNewCustomer || isSfUnmappedWithIncompleteAddress;
const form = useCompleteAccountForm({ const form = useCompleteAccountForm({
initialValues: { initialValues: {
firstName: formData.firstName || prefill?.firstName, firstName: formData.firstName || prefill?.firstName,
@ -56,6 +68,7 @@ export function CompleteAccountStep() {
marketingConsent: formData.marketingConsent, marketingConsent: formData.marketingConsent,
}, },
isNewCustomer, isNewCustomer,
needsAddress,
updateFormData, updateFormData,
}); });
@ -93,6 +106,14 @@ export function CompleteAccountStep() {
/> />
)} )}
{isSfUnmappedWithIncompleteAddress && (
<AddressFields
onAddressChange={form.handleAddressChange}
errors={form.errors}
loading={loading}
/>
)}
<PersonalInfoFields <PersonalInfoFields
phone={form.phone} phone={form.phone}
dateOfBirth={form.dateOfBirth} dateOfBirth={form.dateOfBirth}

View File

@ -3,7 +3,8 @@
* *
* For whmcs_unmapped users who have verified their email via OTP. * For whmcs_unmapped users who have verified their email via OTP.
* Email verification serves as identity proof - no legacy password needed. * Email verification serves as identity proof - no legacy password needed.
* Shows prefilled WHMCS data and collects password + required fields. * Shows prefilled WHMCS data and collects password + terms.
* DOB and gender are not collected here WHMCS already has them.
*/ */
"use client"; "use client";
@ -18,8 +19,6 @@ import { useGetStartedStore } from "../../../stores/get-started.store";
interface FormErrors { interface FormErrors {
password?: string | undefined; password?: string | undefined;
confirmPassword?: string | undefined; confirmPassword?: string | undefined;
dateOfBirth?: string | undefined;
gender?: string | undefined;
acceptTerms?: string | undefined; acceptTerms?: string | undefined;
} }
@ -46,8 +45,6 @@ export function MigrateAccountStep() {
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState("");
const [dateOfBirth, setDateOfBirth] = useState(formData.dateOfBirth);
const [gender, setGender] = useState<"male" | "female" | "other" | "">(formData.gender);
const [acceptTerms, setAcceptTerms] = useState(formData.acceptTerms); const [acceptTerms, setAcceptTerms] = useState(formData.acceptTerms);
const [marketingConsent, setMarketingConsent] = useState(formData.marketingConsent); const [marketingConsent, setMarketingConsent] = useState(formData.marketingConsent);
const [localErrors, setLocalErrors] = useState<FormErrors>({}); const [localErrors, setLocalErrors] = useState<FormErrors>({});
@ -73,14 +70,6 @@ export function MigrateAccountStep() {
errors.confirmPassword = "Passwords do not match"; errors.confirmPassword = "Passwords do not match";
} }
if (!dateOfBirth) {
errors.dateOfBirth = "Date of birth is required";
}
if (!gender) {
errors.gender = "Please select a gender";
}
if (!acceptTerms) { if (!acceptTerms) {
errors.acceptTerms = "You must accept the terms of service"; errors.acceptTerms = "You must accept the terms of service";
} }
@ -96,11 +85,7 @@ export function MigrateAccountStep() {
return; return;
} }
// Update form data (except password/confirmPassword - they're security-sensitive
// and should not be persisted in the store)
updateFormData({ updateFormData({
dateOfBirth,
gender: gender as "male" | "female" | "other",
acceptTerms, acceptTerms,
marketingConsent, marketingConsent,
}); });
@ -113,7 +98,7 @@ export function MigrateAccountStep() {
} }
}; };
const canSubmit = password && confirmPassword && dateOfBirth && gender && acceptTerms; const canSubmit = password && confirmPassword && acceptTerms;
return ( return (
<div className="space-y-6"> <div className="space-y-6">
@ -180,55 +165,6 @@ export function MigrateAccountStep() {
)} )}
</div> </div>
{/* Date of Birth */}
<div className="space-y-2">
<Label htmlFor="dateOfBirth">
Date of Birth <span className="text-danger">*</span>
</Label>
<Input
id="dateOfBirth"
type="date"
value={dateOfBirth}
onChange={e => {
setDateOfBirth(e.target.value);
setLocalErrors(prev => ({ ...prev, dateOfBirth: undefined }));
}}
disabled={loading}
error={localErrors.dateOfBirth}
max={new Date().toISOString().split("T")[0]}
/>
{localErrors.dateOfBirth && (
<p className="text-sm text-danger">{localErrors.dateOfBirth}</p>
)}
</div>
{/* Gender */}
<div className="space-y-2">
<Label>
Gender <span className="text-danger">*</span>
</Label>
<div className="flex gap-4">
{(["male", "female", "other"] as const).map(option => (
<label key={option} className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
name="gender"
value={option}
checked={gender === option}
onChange={() => {
setGender(option);
setLocalErrors(prev => ({ ...prev, gender: undefined }));
}}
disabled={loading}
className="h-4 w-4 text-primary focus:ring-primary"
/>
<span className="text-sm capitalize">{option}</span>
</label>
))}
</div>
{localErrors.gender && <p className="text-sm text-danger">{localErrors.gender}</p>}
</div>
{/* Password */} {/* Password */}
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="password"> <Label htmlFor="password">

View File

@ -29,11 +29,6 @@ export function VerificationStep() {
clearError(); clearError();
}; };
const handleCodeComplete = async (value: string) => {
clearError();
await verifyCode(value);
};
const handleVerify = async () => { const handleVerify = async () => {
if (code.length === 6) { if (code.length === 6) {
await verifyCode(code); await verifyCode(code);
@ -58,7 +53,6 @@ export function VerificationStep() {
<OtpInput <OtpInput
value={code} value={code}
onChange={handleCodeChange} onChange={handleCodeChange}
onComplete={handleCodeComplete}
disabled={loading} disabled={loading}
error={error ?? undefined} error={error ?? undefined}
autoFocus autoFocus

View File

@ -0,0 +1,35 @@
"use client";
import { Label } from "@/components/atoms";
import {
JapanAddressForm,
type JapanAddressFormData,
} from "@/features/address/components/JapanAddressForm";
import type { AccountFormErrors } from "./types";
interface AddressFieldsProps {
onAddressChange: (data: JapanAddressFormData, isComplete: boolean) => void;
errors: AccountFormErrors;
loading: boolean;
}
/**
* AddressFields - Standalone address form section.
*
* Used when an SF-unmapped user has incomplete prefilled address data
* and needs to provide a full address before account creation.
*/
export function AddressFields({ onAddressChange, errors, loading }: AddressFieldsProps) {
return (
<div className="space-y-2">
<Label>
Address <span className="text-danger">*</span>
</Label>
<p className="text-sm text-muted-foreground">
Your address information is incomplete. Please provide your full address.
</p>
<JapanAddressForm onChange={onAddressChange} disabled={loading} />
{errors.address && <p className="text-sm text-danger">{errors.address}</p>}
</div>
);
}

View File

@ -1,5 +1,6 @@
export { PrefilledUserInfo } from "./PrefilledUserInfo"; export { PrefilledUserInfo } from "./PrefilledUserInfo";
export { NewCustomerFields } from "./NewCustomerFields"; export { NewCustomerFields } from "./NewCustomerFields";
export { AddressFields } from "./AddressFields";
export { PersonalInfoFields } from "./PersonalInfoFields"; export { PersonalInfoFields } from "./PersonalInfoFields";
export { PasswordSection } from "./PasswordSection"; export { PasswordSection } from "./PasswordSection";
export { useCompleteAccountForm } from "./useCompleteAccountForm"; export { useCompleteAccountForm } from "./useCompleteAccountForm";

View File

@ -29,17 +29,20 @@ interface InitialValues {
interface UseCompleteAccountFormOptions { interface UseCompleteAccountFormOptions {
initialValues: InitialValues; initialValues: InitialValues;
isNewCustomer: boolean; isNewCustomer: boolean;
/** Whether the user needs to provide address data (new customer or SF-unmapped with incomplete address) */
needsAddress: boolean;
updateFormData: (data: Record<string, unknown>) => void; updateFormData: (data: Record<string, unknown>) => void;
} }
export function useCompleteAccountForm({ export function useCompleteAccountForm({
initialValues, initialValues,
isNewCustomer, isNewCustomer,
needsAddress,
updateFormData, updateFormData,
}: UseCompleteAccountFormOptions) { }: UseCompleteAccountFormOptions) {
const [firstName, setFirstName] = useState(initialValues.firstName || ""); const [firstName, setFirstName] = useState(initialValues.firstName || "");
const [lastName, setLastName] = useState(initialValues.lastName || ""); const [lastName, setLastName] = useState(initialValues.lastName || "");
const [isAddressComplete, setIsAddressComplete] = useState(!isNewCustomer); const [isAddressComplete, setIsAddressComplete] = useState(!needsAddress);
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState("");
const [phone, setPhone] = useState(initialValues.phone || ""); const [phone, setPhone] = useState(initialValues.phone || "");
@ -79,7 +82,9 @@ export function useCompleteAccountForm({
if (isNewCustomer) { if (isNewCustomer) {
if (!firstName.trim()) newErrors.firstName = "First name is required"; if (!firstName.trim()) newErrors.firstName = "First name is required";
if (!lastName.trim()) newErrors.lastName = "Last name is required"; if (!lastName.trim()) newErrors.lastName = "Last name is required";
if (!isAddressComplete) newErrors.address = "Please complete the address"; }
if (needsAddress && !isAddressComplete) {
newErrors.address = "Please complete the address";
} }
const passwordError = validatePasswordRules(password); const passwordError = validatePasswordRules(password);
@ -94,6 +99,7 @@ export function useCompleteAccountForm({
return Object.keys(newErrors).length === 0; return Object.keys(newErrors).length === 0;
}, [ }, [
isNewCustomer, isNewCustomer,
needsAddress,
firstName, firstName,
lastName, lastName,
isAddressComplete, isAddressComplete,
@ -137,7 +143,8 @@ export function useCompleteAccountForm({
dateOfBirth && dateOfBirth &&
gender && gender &&
acceptTerms && acceptTerms &&
(isNewCustomer ? firstName && lastName && isAddressComplete : true); (isNewCustomer ? firstName && lastName : true) &&
(needsAddress ? isAddressComplete : true);
return { return {
// Form values // Form values

View File

@ -7,7 +7,7 @@
import { create } from "zustand"; import { create } from "zustand";
import { logger } from "@/core/logger"; import { logger } from "@/core/logger";
import { getErrorMessage } from "@/shared/utils"; import { getErrorMessage, withRetry } from "@/shared/utils";
import type { AccountStatus, VerifyCodeResponse } from "@customer-portal/domain/get-started"; import type { AccountStatus, VerifyCodeResponse } from "@customer-portal/domain/get-started";
import type { AuthResponse } from "@customer-portal/domain/auth"; import type { AuthResponse } from "@customer-portal/domain/auth";
import * as api from "../api/get-started.api"; import * as api from "../api/get-started.api";
@ -159,7 +159,7 @@ export const useGetStartedStore = create<GetStartedState>()((set, get) => ({
set({ loading: true, error: null }); set({ loading: true, error: null });
try { try {
const result = await api.sendVerificationCode({ email }); const result = await withRetry(() => api.sendVerificationCode({ email }));
if (result.sent) { if (result.sent) {
set({ set({
@ -256,7 +256,8 @@ export const useGetStartedStore = create<GetStartedState>()((set, get) => ({
// For SF-only users, these come from session (via handoff token) // For SF-only users, these come from session (via handoff token)
const isNewCustomer = accountStatus === "new_customer"; const isNewCustomer = accountStatus === "new_customer";
const result = await api.completeAccount({ const result = await withRetry(() =>
api.completeAccount({
sessionToken, sessionToken,
password: formData.password, password: formData.password,
phone: formData.phone, phone: formData.phone,
@ -279,7 +280,8 @@ export const useGetStartedStore = create<GetStartedState>()((set, get) => ({
}, },
} }
: {}), : {}),
}); })
);
set({ loading: false, step: "success", sessionToken: null }); set({ loading: false, step: "success", sessionToken: null });
@ -307,14 +309,14 @@ export const useGetStartedStore = create<GetStartedState>()((set, get) => ({
set({ loading: true, error: null }); set({ loading: true, error: null });
try { try {
const result = await api.migrateWhmcsAccount({ const result = await withRetry(() =>
api.migrateWhmcsAccount({
sessionToken, sessionToken,
password: passwordToUse, password: passwordToUse,
dateOfBirth: formData.dateOfBirth,
gender: formData.gender as "male" | "female" | "other",
acceptTerms: formData.acceptTerms, acceptTerms: formData.acceptTerms,
marketingConsent: formData.marketingConsent, marketingConsent: formData.marketingConsent,
}); })
);
set({ loading: false, step: "success", sessionToken: null }); set({ loading: false, step: "success", sessionToken: null });

View File

@ -29,8 +29,8 @@ import {
} from "../stores/get-started.store"; } from "../stores/get-started.store";
import type { AccountStatus, VerifyCodeResponse } from "@customer-portal/domain/get-started"; import type { AccountStatus, VerifyCodeResponse } from "@customer-portal/domain/get-started";
// Session data staleness threshold (5 minutes) // Session data staleness threshold (15 minutes)
const SESSION_STALE_THRESHOLD_MS = 5 * 60 * 1000; const SESSION_STALE_THRESHOLD_MS = 15 * 60 * 1000;
// SessionStorage key constants // SessionStorage key constants
const SESSION_KEY_TOKEN = "get-started-session-token"; const SESSION_KEY_TOKEN = "get-started-session-token";

View File

@ -25,3 +25,4 @@ export {
getPaymentMethodCardDisplay, getPaymentMethodCardDisplay,
normalizeExpiryLabel, normalizeExpiryLabel,
} from "./payment-methods"; } from "./payment-methods";
export { withRetry } from "./retry";

View File

@ -0,0 +1,67 @@
import { logger } from "@/core/logger";
interface RetryOptions {
/** Maximum number of retry attempts (default: 2) */
maxRetries?: number;
/** Base delay in ms between retries (default: 1000) */
baseDelay?: number;
}
/**
* Retry a function on transient errors (network failures, 5xx responses).
* Does NOT retry on 4xx errors (validation, auth, etc.) since those won't succeed on retry.
*/
export async function withRetry<T>(fn: () => Promise<T>, options: RetryOptions = {}): Promise<T> {
const { maxRetries = 2, baseDelay = 1000 } = options;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
// eslint-disable-next-line no-await-in-loop -- Sequential retries are intentional
return await fn();
} catch (error) {
if (attempt === maxRetries || !isRetryable(error)) {
throw error;
}
const delay = baseDelay * Math.pow(2, attempt);
logger.warn(`Retrying after transient error (attempt ${attempt + 1}/${maxRetries})`, {
error: error instanceof Error ? error.message : String(error),
delay,
});
// eslint-disable-next-line no-await-in-loop -- Sequential delay between retries
await sleep(delay);
}
}
// Unreachable, but TypeScript needs it
throw new Error("Retry exhausted");
}
function isRetryable(error: unknown): boolean {
// Network errors (fetch failures, timeouts)
if (error instanceof TypeError && error.message.includes("fetch")) {
return true;
}
// Check for status code on API errors
if (error && typeof error === "object" && "status" in error) {
const status = (error as { status: number }).status;
// Retry on 5xx server errors, 408 timeout, 429 too many requests
return status >= 500 || status === 408 || status === 429;
}
// Network-related error messages
if (error instanceof Error) {
const msg = error.message.toLowerCase();
return msg.includes("network") || msg.includes("timeout") || msg.includes("econnrefused");
}
return false;
}
function sleep(ms: number): Promise<void> {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}

View File

@ -0,0 +1,57 @@
# UAT Guidebook Design
**Date:** 2026-02-24
**Status:** Approved
## Goal
Create a UAT reference guidebook that explains each user journey in the Customer Portal, including what happens in WHMCS and Salesforce, so testers can verify end-to-end behavior.
## Audience
QA testers who are familiar with WHMCS and Salesforce admin panels but do not have deep technical knowledge (no code, no API details).
## Format
- Markdown files in `docs/uat/`
- Text descriptions only (no screenshots)
- Organized by user journey
- Reference guide style (not test scripts)
## Structure
```
docs/uat/
├── README.md # Overview, how to use this guide
├── 01-signup-and-account-creation.md
├── 02-login-and-authentication.md
├── 03-dashboard-and-profile.md
├── 04-browsing-services.md
├── 05-ordering-a-service.md
├── 06-managing-sim-subscriptions.md
├── 07-managing-internet-subscriptions.md
├── 08-managing-vpn-subscriptions.md
├── 09-billing-and-invoices.md
├── 10-support-cases.md
├── 11-identity-verification.md
├── 12-notifications.md
├── 13-address-management.md
└── appendix-cross-reference.md # Portal <-> WHMCS <-> Salesforce mapping
```
## Journey File Template
Each journey file follows a consistent structure:
1. **Overview** - What this journey covers (2-3 sentences)
2. **Portal Flow** - Step-by-step what the user does, organized by sub-flows
3. **What Happens in WHMCS** - Records created/updated, where to find them
4. **What Happens in Salesforce** - Records created/updated, where to find them
5. **Key Things to Verify** - What testers should pay attention to
## Approach
- Explore the actual codebase (portal pages, BFF services, domain mappers, integration docs) to understand real behavior
- Write from the tester's perspective: "When you do X, you should see Y"
- Use plain language, avoid code references or technical jargon
- Mention specific WHMCS/Salesforce object names and fields testers can look up

View File

@ -0,0 +1,43 @@
# WHMCS Migration Form Simplification
## Problem
WHMCS migration users (account status `whmcs_unmapped`) are asked for date of birth and gender during the migrate-account step. However, WHMCS already has this data on file. Only a password is needed to create the portal account.
## Decision
Remove DOB and gender fields from the WHMCS migration form. Keep password, terms, and marketing consent. Continue showing pre-filled read-only info (name, email, phone, address).
## Changes
### 1. Domain Schema (`packages/domain/get-started/schema.ts`)
Make `dateOfBirth` and `gender` optional on `migrateWhmcsAccountRequestSchema`.
### 2. BFF Workflow (`apps/bff/src/modules/auth/infra/workflows/get-started-workflow.service.ts`)
- `migrateWhmcsAccount()`: destructure `dateOfBirth`/`gender` as optional
- `updateWhmcsClientForMigration()`: skip custom field updates when values not provided
- Method signature: make `dateOfBirth` and `gender` optional params
### 3. Frontend — MigrateAccountStep (`apps/portal/src/features/get-started/components/GetStartedForm/steps/MigrateAccountStep.tsx`)
- Remove DOB input field
- Remove gender radio buttons
- Remove local state for `dateOfBirth` and `gender`
- Remove DOB/gender from validation and `canSubmit`
- Remove DOB/gender from `updateFormData` call
### 4. Store (`apps/portal/src/features/get-started/stores/get-started.store.ts`)
- Stop sending `dateOfBirth`/`gender` in the `migrateWhmcsAccount` API call
### 5. Account Status Step (`apps/portal/src/features/get-started/components/GetStartedForm/steps/AccountStatusStep.tsx`)
- Update WHMCS summary card: remove "date of birth" from the "still needed" list
### 6. UAT Docs (`docs/uat/01-signup-and-account-creation.md`)
- Update Step 3 Outcome C: remove "date of birth" from needed items
- Update Step 4b: remove DOB and gender from the form fields list
- Update WHMCS verification: note DOB/gender are not updated during migration

View File

@ -0,0 +1,159 @@
# Signup and Account Creation
## Overview
This journey covers how a new user creates an account on the Customer Portal. The process starts with email verification, then branches depending on whether the user is completely new, already exists in Salesforce, or already has a WHMCS billing account. By the end, the user has a portal login, a linked WHMCS Client record, and an updated Salesforce Account.
## Portal Flow
### Step 1: Enter Your Email
1. Go to the Get Started page at `/auth/get-started`. You can reach it from the Login page by clicking "Get started" or "Transfer your account."
2. You will see a simple form titled **"Get Started"** with the subtitle "Enter your email to begin."
3. Enter your email address in the Email Address field and click **Send Verification Code**.
4. The system sends a 6-digit one-time code to that email address.
### Step 2: Verify Your Email
1. The page changes to **"Verify Your Email"** with the subtitle "Enter the code we sent to your email."
2. Your email address is shown on screen so you can confirm the right address was used.
3. Enter the 6-digit code. The code auto-submits once all 6 digits are entered, or you can click **Verify Code** manually.
4. The code expires after 10 minutes. If it expires, use the **Resend code** link to get a new one. Check your spam folder if it does not arrive.
5. If you entered the wrong email, click **Change email** to go back to the previous step.
6. You have a limited number of attempts. If you are running low, the portal shows a warning like "2 attempts remaining."
### Step 3: Account Status (What Happens After Verification)
After your code is verified, the system checks whether your email is already known. You will see one of four outcomes:
#### Outcome A: New Customer (No Existing Records)
- The page shows **"Email Verified!"** with a green checkmark.
- The message says: "Let's set up your account so you can access all our services."
- Click **Continue** to proceed to the full account creation form.
#### Outcome B: Salesforce-Only Customer (Known From a Previous Inquiry)
- The page shows **"Welcome back, [First Name]!"** and explains that information was found from a previous inquiry.
- A summary card shows what data is already on file (name, email, address from a previous inquiry) and what still needs to be provided (phone number, date of birth, password).
- If an eligibility status was already determined, it is shown as well.
- Click **Continue** to proceed to the account creation form, which will have some fields pre-filled.
#### Outcome C: Existing WHMCS Billing Account (Not Yet Linked to the Portal)
- The page shows **"Welcome Back!"** (or "Welcome back, [First Name]!") and explains that an existing billing account was found.
- A summary card shows what data is already on file from WHMCS (email verified, name, phone number, address) and what you will need to provide (new portal password).
- Click **Continue** to proceed to the migration form.
#### Outcome D: Portal Account Already Exists
- The page shows **"Account Found"** with a message saying you already have a portal account.
- You are given a **Go to Login** button that takes you to the login page with your email pre-filled.
### Step 4a: Complete Account Form (New Customer or Salesforce-Only)
This form collects the remaining information needed to create your account.
**For brand-new customers**, you fill in:
- First Name and Last Name
- Address (using a Japan-specific address form with postcode, prefecture/state, city, and street address fields)
- Phone Number
- Date of Birth (date picker)
- Gender (radio buttons: Male, Female, Other)
- Password and Confirm Password
- Accept Terms of Service and Privacy Policy checkbox (required)
- Marketing consent checkbox (optional)
**For Salesforce-only customers**, name and address are pre-filled from the Salesforce Account record. You see a summary of the pre-filled info at the top, and you fill in the remaining fields (phone, date of birth, gender, password, terms).
Password requirements are shown as you type, with real-time validation:
- At least 8 characters
- Contains an uppercase letter
- Contains a lowercase letter
- Contains a number
Click **Create Account** when everything is filled in.
### Step 4b: Migrate Account Form (Existing WHMCS Customer)
This form is simpler because your billing information is already on file in WHMCS.
- Your name, email, phone, and address are shown in disabled (greyed-out) fields so you can see what is on file but cannot change them here.
- Date of birth and gender are not collected during migration — WHMCS already has this information.
- You fill in:
- Password and Confirm Password (same requirements as above)
- Accept Terms of Service and Privacy Policy (required)
- Marketing consent (optional)
Click **Set Up Account** when ready.
### Step 5: Success
- The page shows **"Account Created!"** with a green checkmark.
- The message says: "Your account has been set up successfully. You can now access all our services."
- You are given two options:
- **Go to Dashboard** -- takes you to your new account dashboard.
- **Check Internet Availability** -- takes you to the internet service page to check eligibility at your address.
- You are now logged in automatically and can start using the portal.
## What Happens in WHMCS
After a successful signup, the following should be visible in WHMCS:
- **A new Client record is created** (for new and Salesforce-only customers) or the **existing Client record is linked** (for WHMCS migration).
- Navigate to **Clients > Client List** and search by the email address used during signup.
- Open the Client record and check:
- **First Name** and **Last Name** match what was entered during signup.
- **Email Address** matches the verified email.
- **Phone Number** matches what was entered.
- **Address fields** (Address 1, City, State, Postcode, Country) match what was entered.
- **Custom Fields** (under the client profile or a Custom Fields tab):
- **Customer Number** (custom field ID 198) -- this is the critical linking field. It stores the Salesforce Account Number (`SF_Account_No__c`) and is how the portal connects a WHMCS Client to a Salesforce Account.
- **Date of Birth** (custom field ID 201) -- matches what was entered during signup.
- **Gender** (custom field ID 200) -- matches what was selected during signup.
- For brand-new customers, the Client record should have an **Active** status.
- For WHMCS migration users, the existing Client record should now be linked -- no new Client should have been created. The Customer Number field (198) is populated with the Salesforce Account Number if it was not already set.
### How WHMCS Links to Salesforce
The link between WHMCS and Salesforce is established through the **Customer Number**:
1. The Salesforce Account has a field `SF_Account_No__c` (the Customer Number).
2. The WHMCS Client stores this same value in **custom field 198** (Customer Number).
3. The portal's internal database stores a mapping record linking: **Portal User ID****WHMCS Client ID****Salesforce Account ID**.
This three-way link is created during signup and is essential for all cross-system operations.
## What Happens in Salesforce
After a successful signup, the following should be visible in Salesforce:
- Search for the **Account** record using the email address or Customer Number (`SF_Account_No__c`).
- Check these fields on the Account:
| Field | API Name | Expected Value |
| -------------------------- | ------------------------------- | ---------------------------------------------------------------- |
| Customer Number | `SF_Account_No__c` | The unique customer identifier (matches WHMCS custom field 198) |
| Portal Status | `Portal_Status__c` | **Active** |
| Portal Registration Source | `Portal_Registration_Source__c` | **New Signup** (new customers) or **Migrated** (WHMCS migration) |
| WHMCS Account | `WH_Account__c` | Format: "#1234 - Customer Name" (contains the WHMCS Client ID) |
| Portal Last Sign-In | `Portal_Last_SignIn__c` | Timestamp of the signup |
| Email | `PersonEmail` | The verified email address |
| Phone | `Phone` | The phone number entered |
- The associated **Contact** record should have the correct email address and name.
## Key Things to Verify
- Try signing up with an email that already has a portal account. You should see the "Account Found" message and be directed to log in.
- Try signing up with an email that exists in WHMCS but not the portal. You should see the WHMCS migration flow with pre-filled data.
- Try signing up with an email that exists only in Salesforce (from a previous inquiry). You should see pre-filled name and address data.
- Try signing up with a completely new email. You should get the full account creation form with no pre-filled data.
- Verify that password validation works: try a password shorter than 8 characters, one without uppercase, one without a number. Each should show an error.
- Verify the Terms of Service checkbox is required -- the form should not submit without it.
- Check that the verification code expires after 10 minutes and that the "Resend code" option works.
- After signup, check WHMCS and Salesforce to confirm the records were created or updated correctly.
- For WHMCS migration, confirm that no duplicate Client was created in WHMCS.
- Verify that after successful account creation, you are automatically logged in and can access the dashboard without needing to log in again.

View File

@ -0,0 +1,111 @@
# Login and Authentication
## Overview
This journey covers how users sign in to the Customer Portal, how the two-step login flow works (password followed by a one-time code), what happens when a password is forgotten, and how session timeouts are handled. The portal uses email-plus-password credentials with a mandatory OTP (one-time password) verification step for security.
## Portal Flow
### Signing In
1. Go to the Login page at `/auth/login`. The page is titled **"Welcome back"** with the subtitle "Sign in to your Assist Solutions account."
2. You will see a form with the following fields:
- **Email Address** (required)
- **Password** (required)
- **Remember me** checkbox
3. Below the form, there are links:
- **Forgot your password?** -- takes you to the password reset flow.
- **Don't have an account? Get started** -- takes you to the signup flow.
- **Existing customer? Transfer your account** -- also takes you to the signup/migration flow.
4. Enter your email and password, then click **Sign in**.
### OTP Verification (Second Step)
After entering valid credentials, the portal does not log you in immediately. Instead:
1. The page changes to show **"Check your email"** with a mail icon.
2. A message says: "We sent a verification code to [masked email]" (for example, `t***@example.com`).
3. A 6-digit OTP input field appears.
4. A countdown timer shows how long until the code expires (displayed as MM:SS).
5. Enter the 6-digit code. It auto-submits when you enter all 6 digits, or you can click **Verify and Sign In** manually.
6. Below the input, there is a **"Remember this device for 7 days"** checkbox. If checked, you will not need to enter an OTP again on this device for 7 days.
7. If the code expires, a message appears: "Code expired. Please go back and try again."
8. A **Back to login** link lets you return to the credentials step to start over.
9. A help message at the bottom says: "Didn't receive the code? Check your spam folder or go back to try again."
### After Successful Login
- Once the OTP is verified, you see a brief loading overlay saying **"Redirecting to dashboard..."** with the subtitle "Please wait while we load your account."
- You are redirected to your account dashboard at `/account`.
- If you originally tried to access a specific page before being redirected to login, you will be taken back to that page instead of the dashboard.
### Login Error Messages
- If you enter incorrect credentials, an error message appears on the form.
- If your account is locked or there is a server issue, an appropriate error message is shown.
- If you land on the login page after being logged out, a banner appears at the top explaining why:
- **Session Expired** (yellow warning): "For your security, your session expired. Please sign in again to continue."
- **Signed Out For Your Safety** (red error): "We detected a security change and signed you out. Please sign in again to verify your session."
- **Signed Out** (blue info): "You have been signed out. Sign in again whenever you're ready."
### Forgot Password Flow
1. From the login page, click **Forgot your password?**
2. You land on the **"Forgot password"** page with the subtitle "Enter your email address and we'll send you a reset link."
3. Enter your email address and click **Send reset link**.
4. The system sends a password reset email to that address.
5. A **Back to login** link is available at the bottom.
### Reset Password Flow
1. Open the reset link from your email. This takes you to `/auth/reset-password?token=...`.
2. The page is titled **"Reset your password"** with the subtitle "Set a new password for your account."
3. You will see two fields:
- **New password** (required)
- **Confirm password** (required)
4. Enter and confirm your new password, then click **Update password**.
5. If the reset link is missing or expired, you see a message: "The password reset link is missing or has expired. Please request a new link to continue." A button lets you **Request new reset link**.
6. A **Back to login** link is available at the bottom.
### Session Behavior
The portal manages your login session automatically:
- **Automatic refresh**: The system checks your session every 4 minutes and refreshes it if it is about to expire. This happens silently in the background.
- **Tab focus refresh**: When you switch back to the portal tab after being away, the system checks and refreshes your session if needed.
- **Session timeout warning**: About 5 minutes before your session expires, a popup dialog appears with the title **"Session Expiring Soon"**. It shows how many minutes are left and offers two buttons:
- **Extend Session** -- refreshes your session so you stay logged in.
- **Logout Now** -- logs you out immediately.
- **Session expired**: If you do not respond to the warning and the session expires, you are automatically logged out and redirected to the login page with the "Session Expired" banner.
### Logging Out
- You can log out at any time from the account menu.
- After logging out, you are redirected to the login page with the "Signed Out" info banner.
## What Happens in WHMCS
Login and authentication are handled entirely by the Customer Portal. WHMCS is not involved in the login process.
- No records are created or updated in WHMCS when a user logs in, logs out, or resets their password.
- Portal passwords are stored separately from WHMCS passwords. Changing a portal password does not affect the user's WHMCS password (if they had one from before migration).
## What Happens in Salesforce
Login and authentication are handled entirely by the Customer Portal. Salesforce is not involved in the login process.
- No records are created or updated in Salesforce when a user logs in, logs out, or resets their password.
## Key Things to Verify
- **Happy path login**: Enter valid email and password, receive OTP, enter OTP, confirm you land on the dashboard.
- **Wrong password**: Enter an incorrect password and confirm an error message appears. The OTP step should not be reached.
- **Wrong OTP**: Enter the correct password but then an incorrect OTP code. Confirm an error message appears.
- **Expired OTP**: Wait for the OTP countdown to reach zero. Confirm the "Code expired" message appears and you must go back to try again.
- **Remember this device**: Check the "Remember this device for 7 days" box during OTP. Log out and log back in. Confirm you are not asked for OTP again on the same browser.
- **Forgot password flow**: Click "Forgot your password?", enter email, confirm the reset email is sent. Open the reset link, set a new password, confirm you can log in with the new password.
- **Expired reset link**: Try using an old or invalid reset link. Confirm the "link is missing or has expired" message appears.
- **Session timeout**: Stay logged in without activity until the session timeout warning appears. Click "Extend Session" and confirm the session continues. Alternatively, let it expire and confirm you are redirected to the login page.
- **Redirect after login**: Navigate directly to a protected page (for example `/account/services`) while logged out. Confirm you are redirected to login, and after logging in you are taken back to `/account/services` (not the default dashboard).
- **Already logged in**: Navigate to `/auth/login` while already logged in. Confirm you are automatically redirected away from the login page to your dashboard.
- **Logout banner messages**: Log out manually and confirm the "Signed Out" info banner. Let a session expire and confirm the "Session Expired" warning banner.

View File

@ -0,0 +1,225 @@
# Dashboard and Profile
## Overview
This journey covers the main dashboard that users see after logging in, the profile page where they can view and edit their personal information and address, and the identity verification section. The dashboard provides an at-a-glance summary of the account, while the profile/settings page lets users manage their details.
## Portal Flow
### The Dashboard
After logging in, you land on the dashboard at `/account`. The page title is **"Dashboard"** with the description "Overview of your account."
#### Greeting Section
At the top of the page, you see a personalized greeting:
- **"Welcome back"** in small text above your name.
- Your **first name** displayed in large bold text (or your email username if no first name is on file).
- Below that, a status badge shows the number of tasks that need your attention:
- If there are urgent tasks, the badge is red and shows an exclamation icon (for example, "1 task needs attention").
- If there are non-urgent tasks, the badge is yellow.
- If everything is up to date, you see the message "Everything is up to date."
#### Tasks Section
Below the greeting, the dashboard shows up to 4 task cards. These are action items that need your attention. Each task card has:
- A colored left border indicating priority (red for critical, yellow for warning, blue for info, or neutral).
- An icon representing the task type.
- A title and description explaining what needs to be done.
- An action button or link to resolve the task.
The following task types can appear (each only shows when its condition is met):
- **Overdue or upcoming invoice** -- Reminds you to pay an invoice that is due soon or overdue. Links to the invoices page.
- **Add a payment method** -- Prompts you to add a payment method if none is on file. Links to the payments page.
- **Order in progress** -- Shows that an order is being processed. Links to the orders page.
- **Internet eligibility pending** -- Indicates that your address eligibility check is still in progress.
- **ID verification needed** -- Prompts you to upload or re-upload your residence card for identity verification. Links to the verification page.
- **Onboarding** -- Suggests next steps for new accounts.
#### Account Overview (Quick Stats)
In the bottom-left area, an **"Account Overview"** section shows three clickable stat cards:
- **Active Services** -- Shows the number of active subscriptions. Clicking takes you to `/account/services`.
- **Open Support Cases** -- Shows the number of open support tickets. Clicking takes you to `/account/support`. If there are open cases, the card is highlighted in yellow.
- **Recent Orders** -- Shows the number of recent orders (if any). Clicking takes you to `/account/orders`.
Each card shows the count in large bold text, or a message like "No active services" if the count is zero.
#### Recent Activity
In the bottom-right area, a **"Recent Activity"** timeline shows the most recent account events (up to 5). Each activity item has:
- A colored icon indicating the activity type.
- A title describing what happened.
- A relative timestamp (for example, "2 hours ago" or "Yesterday").
Activity types include:
- **Invoice created** (blue icon) -- A new invoice was generated.
- **Invoice paid** (green icon) -- An invoice was paid.
- **Service activated** (purple/primary icon) -- A service was activated.
- **Case created** (yellow icon) -- A support case was opened.
- **Case closed** (green icon) -- A support case was resolved.
Clicking on a clickable activity item takes you to the relevant page (for example, clicking an invoice activity takes you to that invoice).
If there is no recent activity, you see a placeholder message: "No recent activity. Your account activity will appear here as you use our services."
#### Internet Eligibility Toast
If your internet eligibility check was previously pending and has now been completed, a toast notification appears at the top of the dashboard saying: "We've finished reviewing your address -- you can now choose personalized internet plans."
### The Profile / Settings Page
Navigate to `/account/settings` (accessible from the account navigation). The page is titled **"Profile"** with the description "Manage your account information."
The profile page has three main sections:
#### Personal Information Card
This card shows your personal details with a pencil **Edit** button in the top-right corner.
**Read-only fields** (cannot be changed from the portal):
- **First Name** -- Shows your first name, with a note: "Name cannot be changed from the portal."
- **Last Name** -- Shows your last name, with the same note.
- **Customer Number** -- Your Salesforce Customer Number, with a note: "Customer number is read-only."
- **Date of Birth** -- Your date of birth, with a note: "Date of birth is stored in billing profile."
- **Gender** -- Your gender selection, with a note: "Gender is stored in billing profile."
**Editable fields** (can be changed when you click Edit):
- **Email Address** -- Your email. In view mode, it shows a note: "Email can be updated from the portal." In edit mode, it becomes a text input you can modify.
- **Phone Number** -- Your phone number. In edit mode, it becomes a text input.
When you click **Edit**, the editable fields switch to input fields. Two buttons appear:
- **Cancel** -- Discards changes and returns to view mode.
- **Save Changes** -- Saves your updates. While saving, the button shows "Saving..."
#### Address Information Card
This card shows your mailing address with an **Edit** button.
**In view mode**, the address is displayed in a formatted block showing:
- Street address (Address 1 and Address 2)
- City, State/Prefecture, Postcode
- Country
If no address is on file, you see a placeholder message "No address on file" with an **Add Address** button.
**In edit mode**, an address form appears where you can update:
- Address 1 (street)
- Address 2 (apartment, suite, etc.)
- City
- State/Prefecture
- Postcode
- Country
- Phone Number and Phone Country Code (for the address)
Two buttons appear at the bottom:
- **Cancel** -- Discards changes.
- **Save Address** -- Saves the updated address. While saving, the button shows "Saving..."
If there is an error saving the address, a red error banner appears below the form.
#### Identity Verification Card
This card shows the status of your residence card verification with a status pill in the top-right corner.
The status pill shows one of:
- **Verified** (green) -- Your identity has been verified. The card shows "Your identity has been verified. No further action is needed." along with the verification date.
- **Under Review** (blue) -- Your document has been submitted and is being reviewed. The card shows "Your residence card has been submitted. We'll verify it before activating SIM service." along with the submission date.
- **Action Needed** (yellow) -- Your submission was rejected. The card shows the rejection reason (if provided by the reviewer) and asks you to upload a new document. Tips are provided: make sure all text is readable, avoid glare, and keep file size under 5 MB.
- **Required for SIM** (yellow) -- No document has been submitted yet. The card explains that uploading a residence card is required to activate SIM services.
When upload is available (status is "Action Needed" or "Required for SIM"), you see:
- A file input accepting images (JPG, PNG) or PDF, up to 5 MB.
- After selecting a file, the file name is shown with a **Change** button.
- A **Submit Document** button to upload the file. While uploading, the button shows "Uploading..."
- Accepted formats note: "Accepted formats: JPG, PNG, or PDF (max 5MB). Make sure all text is readable."
## What Happens in WHMCS
### Dashboard
- The dashboard data is read from multiple sources. The active services count, open cases count, and recent orders come from the portal's aggregated status endpoint, which pulls data from WHMCS and Salesforce.
- No records are created or modified in WHMCS when viewing the dashboard.
### Profile and Address Changes
- When you edit your **email** or **phone number** from the Personal Information card, the change is written to the WHMCS Client record.
- In WHMCS, go to **Clients > Client List**, find the client, and confirm the email or phone number was updated.
- When you edit your **address**, the change is written directly to the WHMCS Client record.
- In WHMCS, open the Client record and check the address fields (Address 1, Address 2, City, State, Postcode, Country) to confirm they match.
- Changes appear immediately in WHMCS because the portal clears the WHMCS cache for that user after saving.
- **Name, date of birth, and gender** cannot be changed from the portal. To change these, an administrator must update them directly in WHMCS.
### Identity Verification
- The residence card verification status is stored in Salesforce, not WHMCS. No WHMCS records are involved.
## What Happens in Salesforce
### Dashboard
- The dashboard reads some data from Salesforce (through the portal backend) such as order information and eligibility status. No Salesforce records are created or modified when viewing the dashboard.
### Profile Changes
- Email and phone changes made through the portal are written to WHMCS only. Salesforce Address data is only updated as a snapshot when orders are created, not from the profile page.
### Identity Verification
- When you upload a residence card, the file and verification status are managed through Salesforce.
- To check the verification status in Salesforce:
- Find the customer's **Account** record.
- Look for the **Id Verification Status** field (or related verification object).
- Status values: Not Submitted, Submitted (pending review), Verified, Rejected.
- When verification status changes in Salesforce (to Verified or Rejected), a Platform Event is fired which triggers an in-app notification in the portal.
## Key Things to Verify
### Dashboard
- After logging in, confirm the dashboard loads with your correct first name in the greeting.
- If you have an unpaid invoice, confirm a task card appears for it.
- If you have no payment method, confirm a task card prompts you to add one.
- If you recently placed an order, confirm it appears in the Recent Activity timeline.
- Click the "Active Services" stat card and confirm it navigates to `/account/services`.
- Click the "Open Support Cases" stat card and confirm it navigates to `/account/support`.
- Confirm the Recent Activity section shows relevant events with correct timestamps.
- For a brand-new account with no services or activity, confirm the dashboard shows "Everything is up to date" and "No recent activity."
### Profile / Personal Information
- Navigate to `/account/settings` and confirm your name, email, phone, customer number, date of birth, and gender are displayed correctly.
- Click **Edit**, change the email or phone number, click **Save Changes**, and confirm the change is saved.
- After saving, check the WHMCS Client record to confirm the email or phone was updated.
- Confirm that First Name, Last Name, Customer Number, Date of Birth, and Gender are not editable (they should be displayed as read-only with explanatory notes).
- Click **Edit** and then **Cancel** -- confirm no changes are saved.
### Address
- Navigate to `/account/settings` and check the Address Information card.
- If an address is on file, confirm it displays correctly.
- Click **Edit**, change the address, click **Save Address**, and confirm the change is saved.
- After saving, check the WHMCS Client record to confirm the address fields were updated.
- If no address is on file, confirm the "No address on file" message appears with an **Add Address** button.
### Identity Verification
- If no residence card has been submitted, confirm the status shows "Required for SIM" and the upload form is visible.
- Upload a test document (JPG, PNG, or PDF under 5 MB) and confirm it submits successfully. The status should change to "Under Review."
- Try uploading a file larger than 5 MB or an unsupported format and confirm an error is shown.
- If the verification was rejected, confirm the rejection reason is displayed and you can re-upload a new document.
- If the verification is complete, confirm the status shows "Verified" and no upload option is shown.

View File

@ -0,0 +1,123 @@
# Browsing Services
## Overview
This journey covers how users browse the service catalog on the Customer Portal. The portal offers three main orderable service types -- Internet (fiber optic), SIM/eSIM (mobile data and voice), and VPN Router (streaming access) -- plus informational pages for Business and Onsite Support. Services are sourced from the Salesforce product catalog and displayed differently depending on whether the user is logged in or visiting as a guest.
## Portal Flow
### Services Landing Page
1. **As a guest**, go to `/services`. You see the public services overview page with a hero section titled "Our Services," value proposition badges (Full English Support, One provider all services, English support, Fast activation), and a grid of service cards.
2. **As a logged-in user**, go to `/account/services`. You see the same service cards (without the large hero section), wrapped in the standard account page layout with the title "Services" and description "Browse and order connectivity services."
3. The page shows five service cards in two rows:
- **Top row (larger cards):** Internet (Fiber Optic) and SIM & eSIM (Mobile Data). These are the primary services. The SIM card has a badge that says "1st month free."
- **Bottom row (smaller cards):** VPN Router (Streaming Access), Business (Enterprise IT), and Onsite Support (Tech Assistance).
4. Each card shows the service name, a subtitle, a short description, key feature pills, and a "View Plans" link.
5. Clicking a card takes you to that service's plans page. Internet and SIM link to either `/services/{type}` (public) or `/account/services/{type}` (logged in). Business and Onsite Support always link to their respective public pages.
6. At the bottom of the public page, there is a "Need help choosing?" section with a "Get in Touch" button and a toll-free phone number.
### Browsing Internet Plans
#### Public View (Not Logged In)
1. Go to `/services/internet`. You see a marketing-style page with speed offerings (Hikari Cross for houses, Hikari for apartments), plan tier comparisons, and a "How It Works" section.
2. Plans are grouped by offering type (e.g., Hikari Cross for standalone homes, Hikari for apartment buildings).
3. Each offering shows speed tiers (Standard, Pro, Platinum) with monthly prices starting from a given amount, plus a setup fee.
4. There is a call-to-action button to check availability. Clicking it either takes you to the eligibility check flow (if not logged in) or to the account-side plans page.
#### Account View (Logged In)
1. Go to `/account/services/internet`. What you see depends on your **internet eligibility status**:
**Not Requested:** You see the same public marketing content with a "Check Availability" button. Clicking it takes you to `/account/services/internet/request` to submit an eligibility check.
**Pending:** You see a "Your Internet Options" heading with a status badge showing "Checking Availability." A message explains that the team is reviewing your address and you will be contacted within 1-2 business days. The date your request was submitted is shown.
**Eligible:** You see a status badge showing your eligible speed (e.g., "Cross 10G"). Below that, a plan comparison guide appears, followed by offering cards for the speed options available at your address. Each offering card shows the available tiers with pricing. You can expand a card to see tier details and click to configure.
**Ineligible:** You see a message explaining that internet service is not available at your address, along with any notes from the review team.
2. If you already have an active internet subscription, a warning banner appears saying "Active subscription found" with a link to contact support for additional lines.
### Browsing SIM & eSIM Plans
1. Go to `/services/sim` (public) or `/account/services/sim` (logged in).
2. Plans are organized into three tabs at the top:
- **Data + Voice** (default tab): Plans that include mobile data, SMS, and voice calling. These run on the NTT Docomo network.
- **Data Only**: Plans with data and SMS but no voice calling.
- **Voice Only**: Plans for voice service only.
3. Each plan card shows:
- Plan name and data allowance (e.g., "3GB", "20GB")
- Monthly price
- Whether the plan includes voice calling
- Whether a family discount is available
4. The page also shows informational sections such as device compatibility guidance, calling rates, SIM fees, and a "How It Works" section explaining the order process.
5. Clicking a plan's "Select Plan" button takes you to the SIM configuration page at `/account/services/sim/configure?planSku={sku}`.
### Browsing VPN Router Plans
1. Go to `/services/vpn` (public) or `/account/services/vpn` (logged in).
2. The page shows VPN router plans with two region options: US (San Francisco) and UK (London).
3. Each plan card shows the region, monthly price, and key features (streaming access to region-specific content, pre-configured plug-and-play router).
4. The page includes highlight features, a "How It Works" section (Sign Up, Choose Region, Place Order, Connect & Stream), and an FAQ section covering topics like supported streaming services, connection speed, and multi-device use.
5. Clicking a plan takes you to the VPN configuration/checkout flow.
### Internet Eligibility Check (Public Flow)
1. If a guest user wants to check internet availability, they reach the eligibility check flow. This is a multi-step process:
- **Step 1 - Enter Details**: The user provides their email address and optionally an address.
- **Step 2 - Verify Email**: A one-time code is sent to the email. The user enters it to verify.
- **Step 3 - Complete Account** (if needed): If the user does not have an account, they are prompted to create one so the eligibility result can be saved.
- **Step 4 - Success**: The request is submitted. The user sees a confirmation message.
2. This flow creates a new Salesforce Case for the eligibility check request (a new Case is created each time, even if one was submitted before).
## What Happens in Salesforce
- **Product Catalog**: All service plans, add-ons, installation options, and activation fees are stored as **Product2** records in Salesforce. Key fields include:
- `SKU__c` (unique product identifier)
- `Portal_Category__c` (Internet, SIM, VPN)
- `Portal_Catalog__c` (plan, installation, addon, activation)
- `Portal_Accessible__c` (whether the product appears in the portal catalog)
- `Item_Class__c` (Service, Installation, Add-on, Activation)
- `Billing_Cycle__c` (Monthly, Onetime)
- `Internet_Plan_Tier__c` (Standard, Pro, Platinum -- for internet plans)
- `Internet_Offering_Type__c` (Hikari, Hikari Cross -- for internet plans)
- `SIM_Data_Size__c` and `SIM_Plan_Type__c` (for SIM plans)
- `VPN_Region__c` (US, UK -- for VPN plans)
- `Display_Order__c` (controls sort order in the catalog)
- **Pricebook Entries**: Each Product2 has associated **PricebookEntry** records that define the unit price. The portal reads the standard pricebook to display prices.
- **Internet Eligibility**: The eligibility status is stored on the Salesforce **Account** record in these fields:
| Field | API Name | What It Stores |
| ------------------ | ------------------------------------------- | ---------------------------------------------------------------------------- |
| Eligibility Value | `Internet_Eligibility__c` | The speed/type (e.g., "Cross 10G", "Hikari 1G"), or empty if not yet checked |
| Eligibility Status | `Internet_Eligibility_Status__c` | Status of the check |
| Request Date | `Internet_Eligibility_Request_Date_Time__c` | When the customer requested the check |
| Checked Date | `Internet_Eligibility_Checked_Date_Time__c` | When the agent completed the review |
The portal determines the customer's eligibility state based on these fields: if `Internet_Eligibility__c` has a value, the customer is eligible for that speed; if it's blank with a pending request, the check is in progress; if blank with no request, no check has been done.
- **Eligibility Requests**: Submitting an eligibility check always creates a **new Case** in Salesforce (with Origin "Portal Notification") linked to the user's Account. A new Case is created each time, even if the customer has submitted a request before. A support agent then manually checks NTT fiber availability at the customer's address and updates the Account's eligibility field accordingly. This is not an automated check -- a person reviews it.
- **Opportunities**: When a user requests an eligibility check, the system looks for an existing open Opportunity for internet on the Account. If one exists, it reuses it. If none exists, it creates a new Opportunity at the "Introduction" stage. This Opportunity tracks the customer's journey from initial interest through to ordering.
## What Happens in WHMCS
- WHMCS is not directly involved in the service browsing flow. The catalog is served entirely from Salesforce.
- Each Salesforce Product2 record has a `WH_Product_ID__c` field that maps it to a corresponding WHMCS product ID. This mapping is used later during order fulfillment to create the correct WHMCS service (covered in the next journey).
## Key Things to Verify
1. **Catalog completeness**: Confirm that all expected Internet, SIM, and VPN plans appear on their respective pages. Check that plans marked as `Portal_Accessible__c = true` in Salesforce show up, and plans marked `false` do not.
2. **Pricing accuracy**: Verify that the monthly and one-time prices shown on plan cards match the PricebookEntry unit prices in Salesforce.
3. **Plan grouping**: For Internet, verify plans are correctly grouped by offering type (Hikari vs. Hikari Cross). For SIM, verify plans appear under the correct tab (Data+Voice, Data Only, Voice Only) based on the `SIM_Plan_Type__c` field.
4. **Display order**: Check that plans appear in the correct sort order based on `Display_Order__c`.
5. **Internet eligibility flow**: Test each eligibility status:
- Create a test user with no eligibility value -- they should see "Check Availability."
- Set the Account's eligibility field to blank with a pending Case -- they should see "Checking Availability."
- Set the Account's eligibility field to a value like "Cross 10G" -- they should see eligible plans for that speed.
- Set the eligibility status to ineligible -- they should see the ineligible message.
6. **Active subscription warning**: If a user already has an active internet subscription in WHMCS, the warning banner should appear on the internet plans page.
7. **Public vs. account views**: Confirm that public pages at `/services/...` show the same catalog as account pages at `/account/services/...`. The account view should show personalized eligibility for internet; SIM and VPN catalogs should be identical.
8. **Navigation**: Verify all "Back to Services" links work correctly and that clicking plan cards navigates to the correct configuration page.
9. **Responsive design**: Browse the service pages on mobile and tablet sizes to confirm layouts adapt correctly.

View File

@ -0,0 +1,351 @@
# Ordering a Service
## Overview
This journey covers the complete order lifecycle -- from configuring a service and proceeding through checkout, to order submission, fulfillment, and real-time status tracking. This is one of the most critical flows in the portal. An order starts in the portal, creates records in Salesforce (Order, Opportunity), and ultimately provisions a service in WHMCS. All orders require manual approval by support agents in Salesforce -- there is no automatic provisioning. Agents verify the customer's identity, double-check the information, and for internet orders, coordinate with NTT for availability and installation.
## Portal Flow
### Configuring a Service
Before reaching checkout, the user must configure their chosen service. Each service type has its own multi-step configuration wizard.
#### Internet Configuration
1. From the internet plans page, click a plan tier (Standard, Pro, or Platinum) within an offering card to start configuration at `/account/services/internet/configure`.
2. **Step 1 -- Service Configuration**: Review your selected plan details. Choose a connection access mode (IPoE with your own router, IPoE with a Home Gateway, or PPPoE). The Platinum tier shows an important notice about router requirements.
3. **Step 2 -- Installation**: Choose an installation option. Options vary by term length (one-time, 12-month, or 24-month contract) and affect whether the installation fee is spread monthly or paid upfront.
4. **Step 3 -- Add-ons**: Optionally select add-ons for your internet service. Some add-ons are bundled (included automatically with certain plans) and will be pre-selected.
5. **Step 4 -- Review Order**: See a full summary of your plan, installation option, add-ons, monthly total, and one-time total. Prices exclude 10% consumption tax. Click **Proceed to Checkout** to continue.
#### SIM / eSIM Configuration
1. From the SIM plans page, click **Select Plan** on a plan card. This navigates to `/account/services/sim/configure?planSku={sku}`.
2. **Step 1 -- Select SIM Type**: Choose between **eSIM** and **Physical SIM**. If you choose eSIM, you must enter your device's EID (a 32-digit identifier found in your phone's settings).
3. **Step 2 -- Activation**: Choose when to activate: **Immediate** (activate as soon as possible) or **Scheduled** (pick a future date). An activation fee is shown.
4. **Step 3 -- Add-ons**: Optionally select add-ons such as additional data or voice features.
5. **Step 4 -- Number Porting (MNP)**: Choose whether to port an existing phone number. If yes, provide the MNP reservation number, expiry date, carrier account number, and personal information (name, name in katakana, gender, date of birth) as required by the porting process.
6. **Step 5 -- Review Order**: See a full summary including plan, SIM type, EID (for eSIM), activation type, number porting status, add-ons, monthly total, and one-time total. An info box explains next steps (order review and ID verification within 1-2 business days). Click **Proceed to Checkout**.
#### VPN Router Configuration
1. From the VPN plans page, click a plan card for your chosen region (US or UK).
2. The configuration is simpler -- select the plan, optionally choose any add-ons or activation options, review, and proceed to checkout.
### Checkout
1. After clicking "Proceed to Checkout" from any configuration wizard, the portal builds a **checkout session**. This session stores the selected plan, add-ons, installation options, and all configuration details server-side.
2. If you are not logged in, you are redirected to the login page. Your cart data is preserved so you can continue after signing in.
3. The checkout page at `/account/order` (or `/account/checkout`) shows the title **"Checkout"** with the description "Verify your address, review totals, and submit your order."
4. For internet orders, the checkout page also checks your eligibility status. If eligibility has not been requested yet, or is pending/ineligible, a banner explains the situation and lets you request an eligibility check directly from the checkout page.
#### Checkout Requirements
The checkout page presents three requirements that must all be satisfied before you can submit:
**1. Service Address Confirmation**
- Your current address on file is displayed. You must confirm it is correct by clicking a confirmation button.
- If your address is incomplete or incorrect, you can update it before confirming.
- The address is used for service delivery (especially important for internet installation).
**2. Payment Method**
- The portal checks whether you have a payment method on file in WHMCS.
- If you do, your default payment method is shown (e.g., credit card ending in a given number).
- If you do not have a payment method, a button lets you open the WHMCS payment portal in a new tab to add one. After adding a card, click the refresh button on the checkout page to update the status.
**3. Identity Verification (Residence Card)**
- Japanese telecommunications regulations require identity verification.
- If you have already submitted a residence card image and it is verified or pending review, a status indicator shows this.
- If you have not submitted one, a file upload area lets you upload a photo of your residence card directly from the checkout page.
- Status values: "Not submitted", "Pending review", "Verified", or "Rejected" (with reviewer notes explaining why).
#### Order Summary and Submission
1. Below the requirements section, the **Review & Submit** section shows:
- An estimated monthly total and one-time total.
- A "What to expect" list: team review, possible contact for details, residence card verification before activation, charges only after approval, and confirmation by email.
2. The **Submit order** button is disabled until all three requirements are satisfied (address confirmed, payment method on file, residence card submitted).
3. Click **Submit order**. A loading state shows "Submitting..." while the order is being created.
4. If submission is successful, you are redirected to the order detail page with a success banner.
5. If submission fails (for example, due to a missing residence card or expired checkout session), an error message appears. If the failure is due to a missing residence card, you are redirected to the verification page to upload one.
### Order Confirmation and Tracking
1. After successful submission, you land on the order detail page at `/account/orders/{salesforceOrderId}?status=success`.
2. A green **"Order submitted"** banner appears at the top with these details:
- "We've received your order and started the review process."
- "We'll confirm everything within 1 business day."
- "You'll get an email as soon as the order is approved."
- "Installation scheduling happens right after approval."
3. The checkout cart is automatically cleared at this point.
#### Order Detail Page
The order detail page shows comprehensive information about your order:
- **Stats Cards**: Four cards across the top showing Order Status (with a colored status pill), Monthly cost, One-time cost, and Order Date.
- **Order Progress Timeline**: A visual step-by-step progress bar showing where the order stands. The steps vary by service type:
- **Internet**: Submitted -> Under Review -> Scheduled -> Active
- **SIM**: Submitted -> Processing -> Activating -> Active
- **VPN**: Submitted -> Processing -> Active
- **Order Details Card**: Shows the service label (e.g., "Internet Service"), the full date the order was placed, and monthly/one-time pricing in large text. Below that, a list of order items with icons indicating their category (Service, Installation, Add-on, Activation) and their respective prices.
- **Next Steps**: A blue info box showing what happens next based on the current status (e.g., "We will contact you within 1 business day with next steps").
- **Installation Fee Notice**: If the order includes installation or activation items, a yellow warning box explains that standard installation is included but additional charges may apply for weekend scheduling or specialized equipment.
#### Real-Time Order Updates
- The order detail page establishes a **live connection** to the server (using Server-Sent Events). When the order status changes in Salesforce, the page automatically refreshes to show the updated status without requiring the user to reload.
- This means testers will see status changes reflected in near real-time as the backend team processes orders.
#### Order Statuses and Their Meaning
| Portal Status Label | What It Means | Timeline Indicator |
| -------------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------- |
| **Under Review** | The order has been submitted and the team is reviewing it. | Highlighted on "Under Review" step |
| **Installation Scheduled** | For internet orders: an installation date has been set. | Highlighted on "Scheduled" step |
| **Setting Up Service** | The backend is actively provisioning the service (creating WHMCS records, activating SIM, etc.). | Highlighted on "Activating" step |
| **Service Active** | The service is fully provisioned and active. The order is complete. | Green checkmarks on all steps |
| **Processing** | A general processing state for orders that do not match the above categories. | Highlighted on "Processing" step |
### Orders List Page
1. Go to `/account/orders` to see all your orders.
2. The page shows summary statistics at the top: Total orders, Pending, Active, and Processing counts.
3. You can search orders by order number and filter by status (All, Pending, Active, Processing, Cancelled) and type (All, Internet, SIM, VPN).
4. Each order card in the list shows the order number, service type icon, status pill, items summary, and creation date. Click a card to go to its detail page.
## What Happens in Salesforce
### Order Creation
When the user clicks "Submit order," the BFF performs these steps in Salesforce:
1. **Validates the order**: Checks the user's account mapping (portal user ID to Salesforce Account ID), verifies the SKUs exist in the product catalog, and validates business rules (e.g., internet orders need at least one main service SKU, SIM orders must specify SIM type, eSIM orders must include an EID).
2. **Links to an Opportunity**: The system looks for an existing open Opportunity on the user's Account for this product type. If one exists (for example, from a previous eligibility check), the order is linked to it. If none exists, a new Opportunity is created. Either way, the Opportunity's stage is set to **"Post Processing"** (75% probability), meaning an order has been placed.
3. **Creates the Order**: A new **Order** record is created on the Account with:
- `Status` = "Draft"
- `Type` = the order type (Internet, SIM, VPN)
- `OpportunityId` linking to the resolved Opportunity
- `EffectiveDate` set to today
- Configuration fields like `SIM_Type__c`, `Access_Mode__c`, `Activation_Type__c`, and MNP-related fields are populated from the checkout session.
4. **Creates Order Items**: For each SKU in the order, an **OrderItem** record is created, linked to the Order and the corresponding **Product2** and **PricebookEntry** records. Each item includes the unit price, quantity, and billing cycle.
5. **Notifies the support team**: A new internal Case is created in Salesforce (with Origin "Portal Notification") to alert the customer service team that a new order has been placed. This Case is for the support team only -- it does not appear in the customer's support cases list.
### Salesforce Order Fields to Check
After an order is submitted, go to the Account's related Orders list and open the new Order. Check these fields:
**Core Order Fields:**
| Field | API Name | Expected Value |
| -------------- | ------------------- | --------------------------------------------- |
| Status | `Status` | "Draft" immediately after submission |
| Type | `Type` | "Internet", "SIM", or "VPN" |
| Opportunity | `OpportunityId` | Links to the associated Opportunity |
| Effective Date | `EffectiveDate` | Date the order was placed |
| Account | `AccountId` | The customer's SF Account |
| WHMCS Order ID | `WHMCS_Order_ID__c` | Populated after provisioning (links to WHMCS) |
**Activation Fields (updated during provisioning):**
| Field | API Name | Expected Value |
| ------------------------ | ------------------------------- | -------------------------------------------------------------------------- |
| Activation Type | `Activation_Type__c` | "Immediate" or "Scheduled" |
| Activation Status | `Activation_Status__c` | Progresses: empty → "Activating" → "Scheduled" → "Activated" |
| Activation Scheduled At | `Activation_Scheduled_At__c` | Date/time if scheduled activation |
| Activation Error Code | `Activation_Error_Code__c` | Empty if successful; error code if failed (e.g., "PAYMENT_METHOD_MISSING") |
| Activation Error Message | `Activation_Error_Message__c` | Empty if successful; error details if failed |
| Activation Last Attempt | `Activation_Last_Attempt_At__c` | Timestamp of the last provisioning attempt |
**Internet-Specific Order Fields:**
| Field | API Name | Expected Value |
| ------------------ | ----------------------- | ----------------------------------- |
| Access Mode | `Access_Mode__c` | "IPoE-BYOR", "IPoE-HGW", or "PPPoE" |
| Internet Plan Tier | `Internet_Plan_Tier__c` | The selected plan tier |
| Installment Plan | `Installment_Plan__c` | "Single", "12-Month", or "24-Month" |
| Weekend Install | `Weekend_Install__c` | true/false |
| Hikari Denwa | `Hikari_Denwa__c` | true/false (home phone add-on) |
**SIM-Specific Order Fields:**
| Field | API Name | Expected Value |
| ----------------------- | ------------------------ | -------------------------------------------------------------------------- |
| SIM Type | `SIM_Type__c` | "eSIM" or "Physical SIM" |
| EID | `EID__c` | 32-digit device identifier (eSIM only) |
| Voice Mail | `SIM_Voice_Mail__c` | true/false |
| Call Waiting | `SIM_Call_Waiting__c` | true/false |
| Physical SIM Assignment | `Assign_Physical_SIM__c` | Links to SIM_Inventory\_\_c record (Physical SIM only, populated by agent) |
**MNP (Number Porting) Fields (SIM orders with MNP only):**
| Field | API Name | Expected Value |
| ----------------------------- | ------------------------------- | ---------------------------------- |
| MNP Application | `MNP_Application__c` | MNP status |
| MNP Reservation Number | `MNP_Reservation_Number__c` | The porting reservation number |
| MNP Expiry Date | `MNP_Expiry_Date__c` | Reservation expiry date |
| MNP Phone Number | `MNP_Phone_Number__c` | The number being ported |
| MVNO Account Number | `MVNO_Account_Number__c` | Customer's current carrier account |
| Porting Last Name | `Porting_LastName__c` | Porting person's last name |
| Porting First Name | `Porting_FirstName__c` | Porting person's first name |
| Porting Last Name (Katakana) | `Porting_LastName_Katakana__c` | Last name in katakana |
| Porting First Name (Katakana) | `Porting_FirstName_Katakana__c` | First name in katakana |
| Porting Gender | `Porting_Gender__c` | Gender |
| Porting Date of Birth | `Porting_DateOfBirth__c` | Date of birth |
**VPN-Specific Order Fields:**
| Field | API Name | Expected Value |
| ---------- | --------------- | -------------- |
| VPN Region | `VPN_Region__c` | "US" or "UK" |
**Address Snapshot (captured at order time):**
| Field | API Name | Expected Value |
| ------------------- | -------------------- | ------------------------------------------- |
| Billing Street | `BillingStreet` | Customer's address at time of order |
| Billing City | `BillingCity` | City |
| Billing State | `BillingState` | Prefecture |
| Billing Postal Code | `BillingPostalCode` | Postal code |
| Billing Country | `BillingCountry` | Country |
| Address Changed | `Address_Changed__c` | true if address was updated during checkout |
### Salesforce OrderItem Fields to Check
Each line item in the order (plan, installation, add-ons, activation fee) becomes an OrderItem. Check:
| Field | API Name | Expected Value |
| ---------------- | --------------------------------- | ------------------------------------------------------------------- |
| Product | `Product2Id` / `PricebookEntryId` | Links to the correct catalog product |
| Unit Price | `UnitPrice` | Matches the price shown during checkout |
| Quantity | `Quantity` | Usually 1 |
| Billing Cycle | `Billing_Cycle__c` | "Monthly" or "One-time" |
| WHMCS Service ID | `WHMCS_Service_ID__c` | Populated after provisioning (links each item to its WHMCS service) |
### Salesforce Opportunity Fields to Check
After order placement, find the linked Opportunity on the Account:
| Field | API Name | Expected Value |
| ------------------ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Stage | `StageName` | "Post Processing" after order submission |
| Commodity Type | `CommodityType__c` | "Personal SonixNet Home Internet", "SIM", or "VPN" |
| Opportunity Source | `Opportunity_Source__c` | Contains "Portal" (e.g., "Portal - Internet Eligibility Request", "Portal - SIM Checkout Registration") |
| WHMCS Service ID | `WHMCS_Service_ID__c` | Populated after provisioning -- this is the critical field that links the Opportunity to the WHMCS subscription for lifecycle tracking (plan changes, cancellation, etc.) |
| Close Date | `CloseDate` | Set during order creation |
**Opportunity Stage Lifecycle:**
| Stage | Meaning |
| --------------- | ---------------------------------------------------- |
| Introduction | Initial interest (e.g., eligibility check requested) |
| Ready | Customer is eligible and ready to order |
| Post Processing | Order has been placed, being processed |
| Active | Service is live and active |
| △Cancelling | Cancellation has been requested |
| Cancelled | Service has been cancelled |
| Void | Lost or not eligible |
## What Happens in WHMCS
### Order Fulfillment (Provisioning)
After the Salesforce Order is created, all orders require manual review and approval by the support team in Salesforce before provisioning begins. There is no automatic provisioning -- agents verify the customer's identity, check the submitted information, and approve the order.
1. **For Internet orders**: The support team reviews the order, verifies the address and eligibility, and coordinates with NTT for availability and installation scheduling. When ready, they change the order's `Activation_Status__c` to "Activating" or "Scheduled." This triggers the provisioning process. Internet orders take longer than SIM or VPN because support agents need to coordinate with NTT for availability and installation scheduling.
2. **For SIM orders (eSIM)**: After review and residence card verification, the order's activation status is set to "Activating." The provisioning system then:
- Creates a **Product/Service** in WHMCS linked to the user's WHMCS Client record.
- Creates an associated WHMCS **Order** for the service.
- Activates the SIM through the mobile network provisioning system.
3. **For SIM orders (Physical SIM)**: The flow is similar, but the order waits in "Approved" status until a physical SIM card is assigned (`Assign_Physical_SIM__c` field is populated). Once assigned, the provisioning process creates the WHMCS records and activates the service.
4. **For VPN orders**: After review, the provisioning creates a WHMCS Product/Service and Order, and the router is shipped to the customer.
### WHMCS Records Created
- **Product/Service**: A new service record is created under the user's WHMCS Client. The product is determined by the `WH_Product_ID__c` field on the Salesforce Product2 record. The service status is set to Active once provisioning succeeds. The service also gets a custom field **OpportunityId** that stores the Salesforce Opportunity ID -- this is how WHMCS links back to Salesforce for lifecycle tracking.
- **Order**: A WHMCS Order record is created that groups the service and any add-on products. The WHMCS Order ID is written back to the Salesforce Order's `WHMCS_Order_ID__c` field.
- **Invoice**: WHMCS automatically generates an invoice for the new service based on its billing cycle and pricing.
### How WHMCS and Salesforce Stay Linked After Provisioning
After provisioning, the systems are linked bidirectionally:
| Direction | Field | Location | Value |
| ---------------------- | ------------------------------ | ---------------------- | ------------------------------------------------- |
| SF Order → WHMCS | `WHMCS_Order_ID__c` | Salesforce Order | The WHMCS Order ID |
| SF OrderItem → WHMCS | `WHMCS_Service_ID__c` | Salesforce OrderItem | The WHMCS Service/Product ID for each line item |
| SF Opportunity → WHMCS | `WHMCS_Service_ID__c` | Salesforce Opportunity | The main WHMCS Service ID (used for cancellation) |
| WHMCS Service → SF | **OpportunityId** custom field | WHMCS Product/Service | The Salesforce Opportunity ID |
This bidirectional linking is what makes cancellation work: the portal looks up the Opportunity by the WHMCS Service ID and updates its stage.
### Where to Find Records in WHMCS
- Go to the **Client** record (search by email or client ID).
- Under **Products/Services**, look for the newly created service. It should match the plan name from the Salesforce order.
- Check the service's **Custom Fields** -- the **OpportunityId** field should contain the Salesforce Opportunity ID.
- Under **Orders**, find the order associated with the new service.
- Check that the billing cycle and pricing match what was shown during checkout.
## What Happens If Something Goes Wrong
### Payment Method Missing
If the user does not have a valid payment method in WHMCS at the time of provisioning, the order's `Activation_Error_Code__c` is set to "PAYMENT_METHOD_MISSING." The provisioning system will not retry until the error condition is resolved. The user needs to add a payment method through the portal's billing section, and the support team can then re-trigger provisioning.
### Provisioning Failure
If the WHMCS service creation or SIM activation fails, the order's `Activation_Error_Code__c` and `Activation_Error_Message__c` fields are populated with details about the failure. The support team can investigate and manually retry or resolve the issue.
### Order Validation Failure
If an order fails validation (e.g., invalid SKUs, missing required configuration), the submission fails at checkout time and the user sees an error message. No Salesforce Order is created.
### Checkout Session Expiry
Checkout sessions have an expiration time. If a user takes too long to complete checkout, the session expires and they will need to restart the configuration process.
## Key Things to Verify
### Order Placement
1. **End-to-end flow**: For each service type (Internet, SIM eSIM, SIM Physical, VPN), go through the full flow: browse plans, configure, checkout, submit. Verify the order is created successfully.
2. **Checkout requirements**: Verify the Submit button stays disabled until all three requirements are met (address confirmed, payment method on file, residence card submitted/pending).
3. **Address confirmation**: Test with a complete address and with an incomplete address. The incomplete case should prompt the user to update their address.
4. **Payment method check**: Test with a user who has a payment method and one who does not. The "no payment method" case should show instructions to add one.
5. **Residence card check**: Test with each status -- not submitted (shows upload), pending (shows waiting), verified (shows green check), rejected (shows message with reviewer notes).
### Salesforce Verification
6. **Order record**: After submitting, find the Order in Salesforce. Verify all fields: Status = "Draft", correct order type, correct Account, and correct configuration fields (SIM type, access mode, etc.).
7. **Order Items**: Verify each SKU from the checkout cart appears as an OrderItem with the correct Product2, unit price, and quantity.
8. **Opportunity**: Verify an Opportunity is linked to the Order and its stage is "Post Processing."
9. **MNP data**: For SIM orders with number porting, verify all MNP fields are populated on the Order (reservation number, expiry, porting name, etc.).
### Order Tracking
10. **Real-time updates**: With the order detail page open, have someone change the order's activation status in Salesforce. The page should update automatically within a few seconds without a manual refresh.
11. **Progress timeline**: Verify the timeline matches the order's current status. Test each status transition and confirm the correct step is highlighted.
12. **Status labels**: Confirm that Draft/Pending Review shows "Under Review," Scheduled shows "Installation Scheduled," Activating shows "Setting Up Service," and Activated shows "Service Active."
### WHMCS Provisioning
13. **Service creation**: After the order is activated/provisioned, verify a matching Product/Service exists in WHMCS under the correct Client record, with the correct product, billing cycle, and price.
14. **Order creation**: Verify a WHMCS Order was created and an invoice was generated.
15. **Error handling**: Test the "payment method missing" scenario -- remove the user's payment method, trigger provisioning, and verify the error code appears on the Salesforce Order. Then add a payment method and re-trigger to verify recovery.
### Orders List
16. **Filtering**: Submit several orders of different types and statuses. Verify the search, status filter, and type filter all work correctly on the My Orders page.
17. **Summary stats**: Verify the counts for Total, Pending, Active, and Processing match the actual orders.
18. **Navigation**: Click an order card and verify it navigates to the correct order detail page.

View File

@ -0,0 +1,267 @@
# Managing SIM Subscriptions
## Overview
This journey covers everything a customer can do with an active SIM subscription in the portal. It includes viewing SIM details and data usage, changing plans, topping up data, reissuing a SIM card, managing voice features, viewing call and SMS history, and cancelling the service. SIM operations flow through the Freebit MVNO platform and are tracked in WHMCS for billing.
## Portal Flow
### Viewing a SIM Subscription
1. Go to **Subscriptions** from the sidebar.
2. The subscriptions list shows all services with their name, status, billing amount, cycle, and next due date.
3. Click on a SIM subscription (product name contains "SIM") to open the detail page.
4. The detail page has two tabs at the top: **Overview & Billing** and **SIM Management**.
5. The **Overview & Billing** tab shows four stat cards: Service Status, Billing Amount (with cycle), Next Due Date, and Registration Date. Below that is a Billing History table showing related invoices.
6. Click **SIM Management** to see the full SIM dashboard.
### SIM Management Dashboard
The SIM Management tab is the main hub. It shows:
**Header area:**
- The phone number (MSISDN) as the heading.
- A prominent **Top Up Data** button at the top right.
**Left column (larger):**
- **Action tiles** in a 2x2 grid: Call History, Change Plan, Reissue SIM, and Cancel SIM (shown in red).
- **Voice/Network Status** section with toggle switches for Voice Mail, Network Type (4G/5G), Call Waiting, and International Roaming. If the SIM is a data-only plan, voice features are hidden and a note says "Voice features are not available on data-only plans."
**Right column (smaller):**
- A **circular data usage chart** showing remaining data in MB and how much has been used.
- **Billing summary** rows: Monthly Cost, Next Bill on, and Registered date.
- **Important Notes** box explaining that changes take about 30 minutes, may require a device restart, voice/network/plan changes must be at least 30 minutes apart, and Voice Mail / Call Waiting changes must be requested before the 25th of the month.
### Topping Up Data
1. From the SIM Management dashboard, click the **Top Up Data** button (top right) or click the action tile.
2. This navigates to a dedicated **Top Up Data** page.
3. Enter the amount of data in whole GB (minimum 1 GB, maximum 50 GB).
4. The page shows a real-time cost calculation: 1 GB = 500 JPY. For example, entering "5" shows 5000 MB and a cost of 2,500 JPY.
5. Click **Submit Top Up**.
6. Behind the scenes, the system creates a WHMCS invoice for the calculated amount, captures payment using the customer's stored payment method, and then adds the data quota via Freebit.
7. On success, a green banner confirms the top-up with the amount and cost.
8. If payment fails, no data is added and an error message is shown.
### Changing the SIM Plan
1. From the SIM Management dashboard, click **Change Plan**.
2. This navigates to a dedicated **Change Plan** page.
3. The page shows the **Current Plan** at the top (name, data size, monthly price).
4. Below that, a list of **available plans** is displayed as radio-button cards. Only plans other than the current one are shown. The available plans are:
- SIM Data-only 5GB (Freebit code: PASI_5G)
- SIM Data-only 10GB (Freebit code: PASI_10G)
- SIM Data-only 25GB (Freebit code: PASI_25G)
- SIM Data-only 50GB (Freebit code: PASI_50G)
5. Each plan card shows the plan name, data size, plan type, and monthly price.
6. Select a plan and click **Confirm Plan Change**.
7. Important rules displayed on the page:
- Plan changes take effect on the 1st of the following month.
- Requests must be made before the 25th of the current month.
- The current data balance will be reset when the new plan activates.
8. On success, a green banner shows a message like "Plan change scheduled for the 1st of next month."
### Reissuing a SIM (Physical or eSIM)
1. From the SIM Management dashboard, click **Reissue SIM**.
2. This navigates to a dedicated **Reissue SIM** page.
3. The page shows the current SIM information: phone number, current SIM type (physical or eSIM), ICCID (for physical), and current EID (for eSIM).
4. Choose a replacement SIM type by clicking one of two large cards:
- **Physical SIM** -- a new SIM card will be shipped (typically 3-5 business days). The old SIM is deactivated once the new one is activated.
- **eSIM** -- download a new eSIM profile instantly. Requires entering the new device's 32-digit EID.
5. If eSIM is selected, an EID input field appears. The EID must be exactly 32 digits. A character counter shows progress (e.g., "28/32").
6. Click **Submit Reissue Request**.
7. On success:
- For eSIM: "You will receive instructions via email to download your new eSIM profile."
- For Physical SIM: "You will be contacted shortly with shipping details (typically 3-5 business days)."
### Managing Voice Features and Network Type
1. On the SIM Management dashboard, the **Voice/Network Status** section has toggle switches.
2. Available toggles (for voice-enabled plans):
- **Voice Mail** -- toggle on/off (300 JPY/month when enabled).
- **Call Waiting** -- toggle on/off (300 JPY/month when enabled).
- **International Roaming** -- toggle on/off.
- **Network Type** -- toggle between 4G and 5G.
3. Flip any toggle and the change is sent immediately.
4. Rules and constraints:
- Changes take effect in approximately 30 minutes.
- Voice, network, and plan changes must be at least 30 minutes apart. If you try to change too quickly, the portal shows: "Please wait 30 minutes between voice/network/plan changes before trying again."
- Voice Mail and Call Waiting changes must be requested before the 25th of the month.
- A device restart is recommended after changes are applied to ensure they take effect.
5. If the SIM is a data-only plan, voice toggles (Voice Mail, Call Waiting, International Roaming) are hidden. Only the Network Type toggle is shown.
### Viewing Call and SMS History
1. From the SIM Management dashboard, click the **Call History** tile.
2. This navigates to a dedicated **Call & SMS History** page.
3. At the top, a banner shows which month's data is displayed (e.g., "Showing data for: September 2025").
4. There are three tabs:
- **Domestic Calls** -- shows Date, Time, Called To, Call Length, and Call Charge (in JPY).
- **International Calls** -- shows Date, Start Time, Stop Time, Country, Called To, and Call Charge.
- **SMS History** -- shows Date, Time, Sent To, and Type (Domestic SMS or International SMS).
5. Each tab shows the total record count in parentheses.
6. Tables are paginated (50 records per page).
7. Important notes at the bottom:
- History is updated approximately 2 months after calls/messages are made.
- Approximately 3 months of records are available.
- Charges are based on carrier billing data.
### MNP -- Mobile Number Portability
MNP allows a customer to bring their phone number from another carrier when activating a new SIM. This happens during the **order activation** flow (not as a standalone action on an existing subscription).
During SIM order activation, the customer can choose "I want to port my number" which reveals the MNP form. The MNP form collects:
- **MNP Reservation Number** -- obtained from the customer's current carrier.
- **Reservation Expiry Date** -- the date by which the port must be completed.
- **Phone Number** -- the number being ported.
- **Current Carrier Account Number** (optional).
- **Porting Person Details** (optional): Last Name, First Name, Last Name (Katakana), First Name (Katakana), Gender, Date of Birth.
These details are sent to Freebit during SIM provisioning. The ported number replaces the default assigned number.
### Cancelling a SIM Subscription
1. From the SIM Management dashboard, click the **Cancel SIM** tile (shown in red).
2. This navigates to the **Cancel SIM Service** page, which uses a multi-step cancellation flow shared with Internet services.
3. If the cancellation is already in progress (Salesforce Opportunity is in "Cancelling" stage), the page shows a "Cancellation In Progress" status with the scheduled end date.
4. Otherwise, the cancellation flow has three steps:
**Step 1 -- Review Service Information:**
- Shows service details: product name, phone number, plan, start date, billing amount.
- Displays cancellation terms and conditions.
- If the subscription is within its minimum contract period, a warning banner appears with the minimum contract end date.
**Step 2 -- Select Cancellation Month:**
- The customer selects from available cancellation months (dropdown).
- Default is the 1st of next month.
- Must check two confirmation boxes: "I have read the cancellation terms" and "I confirm I want to cancel."
- Optional comments field.
**Step 3 -- Confirm and Submit:**
- Summary of service info and selected cancellation month.
- Any additional notices (e.g., voice SIM billing notes).
- Click **Submit Cancellation**.
5. On success: "Cancellation request submitted. You will receive a confirmation email." The page redirects to the subscription detail after 2 seconds.
## What Happens in WHMCS
| Action | WHMCS Impact |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **View subscription** | Subscription data (product name, status, amount, cycle, dates) is read from WHMCS via the client mapping. Cached for 5-10 minutes. |
| **Top up data** | A new Invoice is created with the top-up amount (e.g., "SIM Data Top-Up 5GB" for 2,500 JPY). Payment is captured immediately using the customer's stored payment method. |
| **Change plan** | The plan change is scheduled in Freebit. The WHMCS product/service record is updated to reflect the new plan once the change takes effect on the 1st of next month. |
| **Reissue SIM** | No direct WHMCS changes for the reissue itself. The service continues under the same WHMCS service ID. |
| **Voice features** | Voice option state is tracked in the portal's database. Freebit is called to apply the changes. WHMCS add-on services for Voice Mail (pid 119) and Call Waiting (pid 123) are updated to reflect the new state. |
| **Call history** | Read-only. History data comes from Freebit SFTP files, not WHMCS. |
| **Cancel SIM** | The service continues until the end of the current billing cycle (or the selected cancellation date). No pro-rated refund is issued. The cancellation is scheduled in Freebit, and the WHMCS service status changes to Cancelled when the billing period ends. |
**Where to find SIM services in WHMCS:**
- Go to **Clients > [Client] > Products/Services**.
- SIM services have product names like "SonixNet SIM Service Data-only/5GB (Monthly)" or "SonixNet SIM Service Data+Voice/10GB (Monthly)."
- The Domain field on the WHMCS service often stores the phone number (MSISDN).
- Invoices for top-ups appear under the client's invoice list.
## What Happens in Salesforce
| Action | Salesforce Impact |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **View subscription** | No Salesforce read for basic subscription display (comes from WHMCS). |
| **Top up data** | No Salesforce record is created for individual top-ups. |
| **Change plan** | The plan change is scheduled in Freebit. No direct Salesforce update is triggered from the portal for plan changes. |
| **Reissue SIM** | The reissue request is sent to Freebit. The new SIM identifiers (ICCID, EID) are stored by Freebit. |
| **Cancel SIM** | The linked Salesforce Opportunity stage changes from "Active" to "△Cancelling" with a scheduled end date. When cancellation completes, it moves to "Cancelled". A new internal Case (Origin: "Portal Notification") is also created to notify the support team. |
| **MNP (during activation)** | MNP details are stored on the Salesforce Order record's custom fields (see doc 05 for the full MNP field list). |
### How Cancellation Links WHMCS to Salesforce
The portal knows which Salesforce Opportunity to cancel by using the **WHMCS Service ID**:
1. The portal reads the WHMCS Service ID from the customer's subscription.
2. It searches Salesforce for the Opportunity where `WHMCS_Service_ID__c` matches that WHMCS Service ID.
3. It updates that Opportunity's stage and cancellation fields.
**Salesforce Opportunity fields updated during SIM cancellation:**
| Field | API Name | Value |
| -------------------------- | ---------------------------------------- | -------------------------------------------- |
| Stage | `StageName` | Changes to "△Cancelling", then "Cancelled" |
| SIM Cancellation Notice | `SIMCancellationNotice__c` | Updated with cancellation status |
| SIM Scheduled Cancellation | `SIMScheduledCancellationDateAndTime__c` | The selected cancellation date |
**Where to find SIM data in Salesforce:**
- **Opportunity**: Look for Opportunities on the Account where `CommodityType__c` = "SIM". Check `WHMCS_Service_ID__c` to verify it matches the WHMCS service. The `StageName` shows the lifecycle state.
- **Order**: SIM orders have `Type` = "SIM". Check the SIM-specific fields listed in doc 05.
- **Product2**: SIM products have `SIM_Data_Size__c` and `SIM_Plan_Type__c` fields. `WH_Product_ID__c` maps to the WHMCS product.
## Key Things to Verify
### Subscription Detail Page
- [ ] SIM subscriptions show both "Overview & Billing" and "SIM Management" tabs.
- [ ] Non-SIM subscriptions (Internet, VPN) do not show the SIM Management tab.
- [ ] The four stat cards display correct values matching WHMCS.
- [ ] Billing history shows invoices related to this subscription.
### SIM Management Dashboard
- [ ] Phone number (MSISDN) is displayed correctly as the heading.
- [ ] The circular data usage chart shows correct remaining and used values from Freebit.
- [ ] Monthly cost, next bill date, and registration date match WHMCS data.
- [ ] All four action tiles (Call History, Change Plan, Reissue SIM, Cancel SIM) are present and clickable.
- [ ] For data-only plans, voice toggles are hidden and the note about data-only plans appears.
- [ ] For voice-enabled plans, all four toggles (Voice Mail, Call Waiting, International Roaming, Network Type) are visible.
### Top Up Data
- [ ] The cost calculator updates in real time as the GB amount is typed.
- [ ] Minimum 1 GB, maximum 50 GB, whole numbers only.
- [ ] On success, a WHMCS invoice is created and paid.
- [ ] Data is added to the SIM (takes a few minutes to reflect in the usage display).
- [ ] If payment fails, no data is added and the user sees an error.
### Change Plan
- [ ] Current plan is highlighted at the top with correct details.
- [ ] Only plans other than the current plan are listed.
- [ ] The important notice about the 25th deadline and 1st-of-month activation is visible.
- [ ] After submitting, the success message mentions the scheduled date.
### Reissue SIM
- [ ] Current SIM details (number, type, ICCID/EID) are shown.
- [ ] Physical SIM option shows shipping info (3-5 business days).
- [ ] eSIM option requires a valid 32-digit EID.
- [ ] The submit button is disabled until a SIM type is selected (and EID entered for eSIM).
### Voice Features
- [ ] Toggling a feature sends the change and the toggle updates optimistically.
- [ ] If a change is rejected (e.g., too soon after a previous change), the toggle reverts and an error is displayed.
- [ ] The 30-minute spacing rule between changes is enforced.
### Call and SMS History
- [ ] All three tabs load their respective data.
- [ ] Tables show correct columns (domestic: Date, Time, Called To, Call Length, Charge; international: Date, Start/Stop Time, Country, Called To, Charge; SMS: Date, Time, Sent To, Type).
- [ ] Pagination works when there are more than 50 records.
- [ ] The "2 months delay" note is displayed.
### Cancellation
- [ ] If already cancelling, the "Cancellation In Progress" status page appears.
- [ ] The cancellation month dropdown offers future months.
- [ ] Both confirmation checkboxes must be ticked before the submit button is enabled.
- [ ] Minimum contract warning appears when applicable.
- [ ] After submission, the Salesforce Opportunity moves to "Cancelling" stage.
- [ ] The confirmation email is sent to the customer's email address.

View File

@ -0,0 +1,190 @@
# Managing Internet Subscriptions
## Overview
This journey covers how a customer views and manages their Internet subscription in the portal. Internet services are NTT fiber connections sold under the "SonixNet via NTT Optical Fiber" brand. The portal shows subscription details and billing history, and provides a cancellation flow. Unlike SIM subscriptions, Internet subscriptions do not have a self-service management dashboard for plan changes or configuration -- those changes are handled through customer support.
## Portal Flow
### Viewing an Internet Subscription
1. Go to **Subscriptions** from the sidebar.
2. The subscriptions list shows all services. Internet subscriptions have product names like "SonixNet via NTT Optical Fiber (Home 1G) [GOLD]" or "SonixNet via NTT Optical Fiber (APT 1G) [SILVER]."
3. Click on an Internet subscription to open the detail page.
4. The detail page shows four stat cards across the top:
- **Service Status** -- Active, Pending, Suspended, etc.
- **Billing Amount** -- the monthly fee with the billing cycle label (e.g., "6,500 JPY / monthly").
- **Next Due Date** -- when the next invoice is due.
- **Registration Date** -- when the service was first set up.
5. Below the stat cards is a **Billing History** section showing all invoices related to this subscription.
6. If the service is Active, a **Cancel Service** button appears in the top-right header area.
Internet subscriptions do **not** have a SIM Management tab. The detail page only shows the Overview & Billing view.
### Understanding Internet Plans
Internet plans come from the Salesforce product catalog. They are organized by:
**Offering Type** (determines the physical connection):
- **Home 1G** -- for detached houses, up to 1 Gbps.
- **Apartment 1G** -- for apartments with 1 Gbps fiber to the building.
- **Apartment 100M** -- for apartments with shared 100 Mbps fiber.
- **Flets X (Home 10G)** -- for detached houses, up to 10 Gbps (premium tier).
**Plan Tier** (determines the service level and price):
- **Silver** -- basic tier.
- **Gold** -- mid-tier with enhanced support.
- **Platinum** -- premium tier with priority support.
Each combination creates a specific WHMCS product. For example, "Internet Gold (Apartment 1G)" maps to WHMCS product ID 185 with a monthly price of 5,300 JPY.
### Internet Eligibility
Before ordering Internet, customers must go through an eligibility check (covered in the ordering journey). This is relevant to subscription management because:
- The eligibility status is stored on the Salesforce Account record (Internet_Eligibility\_\_c field).
- If a customer already has an active Internet service in WHMCS, the portal blocks ordering a second one (duplicate detection in production mode).
- Eligibility requests create a Salesforce Case. A support agent then manually checks NTT fiber availability at the customer's address and updates the eligibility field -- this is not an automated check.
### Installation Options
Internet orders include an installation component. While this is part of the ordering flow, it shows up on the subscription as a separate WHMCS service:
- **Single Installation** (one-time payment) -- WHMCS product ID 242.
- **12-Month Installation** (monthly installments) -- WHMCS product ID 243.
- **24-Month Installation** (monthly installments) -- WHMCS product ID 244.
- **Weekend Installation Fee** (add-on, one-time) -- WHMCS product ID 245.
These appear as separate line items on the WHMCS order and may show as separate subscriptions in the portal.
### Add-on Services
Internet subscriptions can have add-ons:
- **Hikari Denwa (Home Phone)** -- a VoIP home phone service, billed monthly (WHMCS product ID 246).
- **Hikari Denwa Installation Fee** -- one-time setup fee (WHMCS product ID 247).
These also appear as separate WHMCS services linked to the same order.
### Cancelling an Internet Subscription
1. From the Internet subscription detail page, click the **Cancel Service** button (red, in the top-right corner). This button only appears when the service status is Active.
2. The cancellation page loads, showing the service name (e.g., "Cancel Internet Service - SonixNet via NTT Optical Fiber (APT 1G) [GOLD]").
3. If a cancellation is already in progress (Salesforce Opportunity stage is "Cancelling"), the page shows a **Cancellation In Progress** status view with:
- The service name.
- The scheduled end date.
- An **Equipment Return Status** section showing the rental return status from Salesforce (shown when the customer has rented equipment such as a router or ONU).
- A note saying the customer will receive an email when cancellation is complete.
4. If no cancellation is pending, the full cancellation flow is displayed in three steps:
**Step 1 -- Review Service Information:**
- Shows service details (product name, billing amount, registration date, next due date).
- Displays cancellation terms and conditions specific to Internet services.
- Shows any warnings (e.g., minimum contract period).
**Step 2 -- Select Cancellation Month:**
- Choose from a list of available cancellation months.
- Check two confirmation boxes: "I have read the terms" and "I confirm I want to cancel."
- Optionally add comments.
**Step 3 -- Confirm and Submit:**
- Summary of service information and selected month.
- Click **Submit Cancellation**.
5. On success: "Cancellation request submitted. You will receive a confirmation email." The page redirects to the subscription detail after 2 seconds.
## What Happens in WHMCS
| Action | WHMCS Impact |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **View subscription** | The subscription data comes from WHMCS (product name, status, amount, billing cycle, next due date, registration date). Data is cached for 5-10 minutes. |
| **Cancel Internet** | The service continues until the end of the current billing cycle (or the selected cancellation month). No pro-rated refund is issued. The WHMCS service status changes to Cancelled at the end of the period. Related services (installation installments, add-ons like Hikari Denwa) are also cancelled as part of this process. |
**Where to find Internet services in WHMCS:**
- Go to **Clients > [Client] > Products/Services**.
- Internet services have product names starting with "SonixNet via NTT Optical Fiber" followed by the offering type and tier in brackets.
- Example product names:
- "SonixNet via NTT Optical Fiber (Home 1G) [SILVER]"
- "SonixNet via NTT Optical Fiber (APT 1G) [GOLD]"
- "SonixNet via NTT Optical Fiber (Flets X) [PLATINUM - Base Plan]"
- Installation services appear as separate products (e.g., "NTT Installation Fee (Single Installment)").
- Add-ons appear as separate products (e.g., "Hikari Denwa Monthly Service").
- All products in the same order share the same WHMCS Order ID.
**WHMCS Product IDs for Internet:**
| Plan | Home 1G | Apt 1G | Apt 100M | Flets X (10G) |
| -------- | ------- | ------ | -------- | ------------- |
| Silver | 181 | 184 | 187 | 239 |
| Gold | 182 | 185 | 188 | 214 |
| Platinum | 183 | 186 | 189 | 213 |
## What Happens in Salesforce
| Action | Salesforce Impact |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **View subscription** | No Salesforce read for basic subscription display (comes from WHMCS). |
| **Cancel Internet** | The linked Salesforce Opportunity stage changes from "Active" to "△Cancelling" with a scheduled end date. A new internal Case (Origin: "Portal Notification") is created to notify the support team. When cancellation is complete, the Opportunity moves to "Cancelled." |
### How Cancellation Links WHMCS to Salesforce
Same as SIM cancellation -- the portal finds the Opportunity by matching the WHMCS Service ID:
1. The portal reads the WHMCS Service ID from the customer's internet subscription.
2. It searches Salesforce for the Opportunity where `WHMCS_Service_ID__c` matches.
3. It updates that Opportunity's stage and cancellation fields.
**Salesforce Opportunity fields updated during Internet cancellation:**
| Field | API Name | Value |
| ----------------------- | ------------------------------------- | ------------------------------------------------------------------------- |
| Stage | `StageName` | Changes to "△Cancelling", then "Cancelled" |
| Scheduled Cancellation | `ScheduledCancellationDateAndTime__c` | The selected cancellation month |
| Cancellation Notice | `CancellationNotice__c` | Status: 有 (Received), 未 (Not Yet), 不要 (Not Required), 移転 (Transfer) |
| Line Return (Equipment) | `LineReturn__c` | Equipment return status: NotYet, SentKit, Returned1, Returned2, N/A, etc. |
**Where to find Internet data in Salesforce:**
- **Opportunity**: Look for Opportunities on the Account where `CommodityType__c` contains "Internet". Check `WHMCS_Service_ID__c` to verify it matches the WHMCS service. The `StageName` shows the lifecycle state. The `LineReturn__c` field tracks equipment returns.
- **Order**: Internet orders have `Type` = "Internet". The Order stores `WHMCS_Order_ID__c` and individual `WHMCS_Service_ID__c` on OrderItems.
- **Product2**: Internet products have `Internet_Plan_Tier__c` (Silver/Gold/Platinum), `Internet_Offering_Type__c` (Home 1G, Apartment 1G, etc.), and `WH_Product_ID__c` mapping to WHMCS.
- **Account**: The `Internet_Eligibility__c` field stores the eligibility value (e.g., "Cross 10G"). Related fields: `Internet_Eligibility_Status__c`, `Internet_Eligibility_Request_Date_Time__c`, `Internet_Eligibility_Checked_Date_Time__c`. A Case with Origin "Portal Notification" is created each time the customer requests an eligibility check.
## Key Things to Verify
### Subscription Detail Page
- [ ] Internet subscriptions do NOT show a SIM Management tab.
- [ ] The four stat cards show correct values matching WHMCS.
- [ ] The billing amount reflects the correct plan tier pricing.
- [ ] Billing history shows all invoices for this subscription (monthly recurring and any one-time installation charges).
- [ ] The Cancel Service button appears only when the status is Active.
- [ ] The Cancel Service button does not appear for non-Active statuses (Pending, Suspended, Cancelled).
### Product Name Display
- [ ] The product name matches the WHMCS product name format (e.g., includes the offering type and tier).
- [ ] Installation and add-on services appear as separate subscriptions if they were part of the same order.
### Cancellation Flow
- [ ] Clicking Cancel Service loads the cancellation page with correct service details.
- [ ] If already cancelling, the "Cancellation In Progress" view appears with the scheduled end date.
- [ ] For Internet cancellations, the Equipment Return Status section appears when applicable.
- [ ] Available cancellation months are in the future.
- [ ] Both confirmation checkboxes must be checked before submission is allowed.
- [ ] After submission, the Salesforce Opportunity moves to "Cancelling."
- [ ] The customer receives a confirmation email.
- [ ] After cancellation completes, the WHMCS service status changes to Cancelled.
### Cross-Service Verification
- [ ] If an Internet service has related installation or add-on services, check that all are visible in the subscriptions list.
- [ ] Verify that the portal blocks ordering a second Internet service when one is already active (production behavior).

View File

@ -0,0 +1,112 @@
# Managing VPN Subscriptions
## Overview
This journey covers how a customer views and manages their VPN subscription in the portal. VPN is a router-based service that provides a pre-configured VPN connection to either the United States (San Francisco) or the United Kingdom (London), primarily for accessing region-specific streaming content. The subscription is a monthly rental with no long-term contract. Management is straightforward -- the portal shows subscription details and billing history.
## Portal Flow
### Viewing a VPN Subscription
1. Go to **Subscriptions** from the sidebar.
2. The subscriptions list shows all services. VPN subscriptions have product names like "SonixNet USA Remote Access VPN Service (Monthly)" or "SonixNet UK Remote Access VPN Service (Monthly)."
3. Click on a VPN subscription to open the detail page.
4. The detail page shows four stat cards across the top:
- **Service Status** -- Active, Pending, Suspended, etc.
- **Billing Amount** -- the monthly rental fee with the billing cycle label.
- **Next Due Date** -- when the next invoice is due.
- **Registration Date** -- when the VPN service was first activated.
5. Below the stat cards is a **Billing History** section showing all invoices related to this VPN subscription.
VPN subscriptions do **not** have a SIM Management tab or any special management dashboard. The detail page only shows the Overview & Billing view.
### Understanding VPN Plans
VPN plans are simple. There are two region options:
- **VPN USA (San Francisco)** -- routes traffic through a US server. WHMCS product ID 33, billed monthly.
- **VPN UK (London)** -- routes traffic through a UK server. WHMCS product ID 54, billed monthly.
Each VPN order also includes a one-time **VPN Activation Fee** (WHMCS product ID 37, approximately 3,000 JPY). This activation fee covers the initial setup and router preparation.
Key points about the VPN service:
- It is a **router rental** -- a physical pre-configured VPN router is shipped to the customer.
- No technical setup is required by the customer (plug in and connect).
- Any device connected to the VPN router's WiFi is routed through the VPN.
- Content subscriptions (Netflix, Hulu, BBC iPlayer, etc.) are NOT included.
- The service does not guarantee access to any specific streaming platform.
- Monthly rental with no long-term contract -- the customer can cancel at any time.
- Prices exclude 10% consumption tax.
### VPN Subscription Billing
A typical VPN subscription consists of two WHMCS services from the same order:
1. **Monthly VPN Service** -- the recurring monthly charge (e.g., "SonixNet USA Remote Access VPN Service (Monthly)").
2. **VPN Activation Fee** -- a one-time charge (e.g., "SonixNet VPN Services (Account Activation Fee)").
Both appear in the subscriptions list. The activation fee will show a status of "Completed" after it has been paid, while the monthly service shows "Active" during the rental period.
### Cancelling a VPN Subscription
VPN cancellation is not currently handled through a self-service portal flow in the same way as SIM or Internet cancellations. To cancel a VPN subscription, the customer should contact customer support through the support case system.
If a self-service cancellation flow is added in the future, it would follow a similar pattern to the Internet cancellation flow (select a cancellation month, confirm, submit).
## What Happens in WHMCS
| Action | WHMCS Impact |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **View subscription** | The subscription data comes from WHMCS (product name, status, amount, billing cycle, next due date, registration date). Data is cached for 5-10 minutes. |
**Where to find VPN services in WHMCS:**
- Go to **Clients > [Client] > Products/Services**.
- VPN services have product names:
- "SonixNet USA Remote Access VPN Service (Monthly)" (WHMCS product ID 33)
- "SonixNet UK Remote Access VPN Service (Monthly)" (WHMCS product ID 54)
- The activation fee appears as: "SonixNet VPN Services (Account Activation Fee)" (WHMCS product ID 37).
- Both the monthly service and the activation fee are part of the same WHMCS Order.
## What Happens in Salesforce
| Action | Salesforce Impact |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **View subscription** | No Salesforce read for basic subscription display (comes from WHMCS). |
| **Order VPN** | A Salesforce Order is created with Order_Type\_\_c referencing VPN. Order items link to VPN Product2 records. The Order tracks the WHMCS Order ID and service IDs. |
**Where to find VPN data in Salesforce:**
- **Order**: VPN orders contain OrderItems linked to VPN Product2 records. Check for items with SKUs like "VPN-REMOTE-ACCESS-USA-SF" or "VPN-REMOTE-ACCESS-UK-LONDON."
- **Product2**: VPN products have Product2Categories1**c = "VPN". The VPN_Region**c field stores the region (USA-SF or UK-London). WH_Product_ID\_\_c maps to the WHMCS product.
- **Opportunity**: A related Opportunity may exist depending on the sales workflow, tracking the VPN service lifecycle.
## Key Things to Verify
### Subscription Detail Page
- [ ] VPN subscriptions do NOT show a SIM Management tab.
- [ ] The four stat cards show correct values matching WHMCS.
- [ ] The billing amount matches the expected monthly VPN fee.
- [ ] Billing history shows both the recurring monthly charges and the one-time activation fee invoice.
- [ ] The product name clearly identifies the VPN region (USA or UK).
### Subscriptions List
- [ ] VPN subscriptions appear in the subscriptions list alongside SIM and Internet subscriptions.
- [ ] The VPN activation fee appears as a separate subscription entry with status "Completed" (after payment).
- [ ] The monthly VPN service shows status "Active" during the rental period.
- [ ] Search and status filters work correctly for VPN subscriptions.
### Billing Verification
- [ ] The first billing cycle includes both the activation fee and the first month's rental.
- [ ] Subsequent months only show the monthly rental charge.
- [ ] The billing cycle is shown as "Monthly" for the service and "One-time" for the activation fee.
### Cross-Service Verification
- [ ] A customer can have VPN, SIM, and Internet subscriptions simultaneously.
- [ ] VPN subscriptions are counted in the dashboard subscription statistics (Active, Completed counts).
- [ ] The subscription stats on the subscriptions list page correctly include VPN services in the totals.

View File

@ -0,0 +1,101 @@
# Billing and Invoices
## Overview
This journey covers how customers view and manage their billing information in the portal. All billing data (invoices, payment methods, billing summary) comes from WHMCS -- the portal reads it but does not store it locally. Customers can browse invoices, filter and search them, view full invoice details, download PDF copies, and pay unpaid invoices through a secure WHMCS link.
## Portal Flow
### Viewing the Invoice List
1. Navigate to **Account > Billing > Invoices** (URL: `/account/billing/invoices`).
2. The page loads with the title "Invoices" and a subtitle "Manage and view your billing invoices."
3. At the top, a **summary statistics bar** shows counts for:
- **Total** invoices
- **Paid** invoices (green)
- **Unpaid** invoices (amber) -- only shown when count is greater than zero
- **Overdue** invoices (amber) -- only shown when count is greater than zero
4. Below the stats bar, a **search and filter bar** lets you:
- **Search** by invoice number or description (free-text search box)
- **Filter by status**: All Statuses, Paid, Unpaid, Cancelled, Overdue, or Collections
- A **Clear Filters** button appears when any filter is active
5. The invoice table shows each invoice in a row with: invoice number, description, status badge, total amount, due date, and issued date.
6. **Pagination** controls appear at the bottom of the table when there are more invoices than fit on one page (default 10 per page).
### Invoice Status Badges
Invoices display color-coded status badges:
| Status | Color | Meaning |
| ----------- | ----- | ------------------------------------ |
| Paid | Green | Invoice has been fully paid |
| Unpaid | Amber | Payment is due but not yet overdue |
| Overdue | Red | Payment is past the due date |
| Draft | Grey | Invoice is in draft, not yet issued |
| Pending | Amber | Invoice is pending |
| Cancelled | Grey | Invoice was cancelled |
| Refunded | Blue | Invoice was refunded |
| Collections | Red | Invoice has been sent to collections |
### Viewing Invoice Details
1. Click any invoice row in the list to open the invoice detail page.
2. The detail page shows:
- **Summary bar** at the top with: total amount (large, bold), currency code, status badge, due date, days overdue (shown only for overdue invoices), invoice number, and issued date.
- **Download PDF** button -- generates a secure WHMCS link and downloads the invoice as a PDF.
- **Pay Now** button -- appears only for Unpaid or Overdue invoices. Opens the WHMCS payment page in a new browser tab.
- If the invoice is **Paid**, a green "Payment received" banner appears showing the paid date.
3. Below the summary bar, the **line items** section lists each item on the invoice: description, item type, and amount.
4. At the bottom, the **totals section** shows subtotal, tax, and total.
### Downloading an Invoice PDF
1. From the invoice detail page, click **Download PDF**.
2. The portal generates a secure single-sign-on (SSO) link to the WHMCS invoice page.
3. The browser starts downloading the PDF. The customer does not need to log in to WHMCS separately.
### Paying an Invoice
1. From the invoice detail page for an Unpaid or Overdue invoice, click **Pay Now**.
2. The portal generates a secure SSO link to the WHMCS payment page.
3. A new browser tab opens with the WHMCS payment page, where the customer can complete payment using their saved payment methods.
4. For overdue invoices, a warning message reads: "This invoice is overdue. Please pay as soon as possible to avoid service interruption."
### Managing Payment Methods
1. Payment methods are stored in WHMCS (typically via Stripe).
2. From the invoice detail page, a **Manage Payment Methods** button opens the WHMCS payment methods page via SSO link.
3. Customers can add, update, or remove payment methods directly in WHMCS.
### Billing Summary on Dashboard
1. The customer dashboard shows a **Billing Summary** card with:
- **Outstanding** amount and count of unpaid invoices
- **Overdue** amount and count of overdue invoices
- **Paid This Period** amount and count of paid invoices
- A **View All** link to the full invoices page.
## What Happens in WHMCS
- **Invoices**: All invoices are stored in WHMCS under the Client's account. Navigate to **Clients > [Client Name] > Invoices** in WHMCS admin.
- **Invoice statuses**: WHMCS manages the status lifecycle (Draft, Unpaid, Paid, Overdue, Cancelled, Refunded, Collections). The portal reads these statuses directly.
- **Transactions**: When a customer pays an invoice, a Transaction record is created in WHMCS. You can find these under **Billing > Transactions** or under the specific Invoice in WHMCS admin.
- **Payment Methods**: Stored under **Clients > [Client Name] > Pay Methods** in WHMCS admin. These are typically Stripe-linked card details.
- **SSO Links**: The portal generates temporary WHMCS SSO links so the customer can view/download/pay invoices without needing separate WHMCS login credentials. These links expire after a short time.
## What Happens in Salesforce
- Salesforce is **not** the system of record for billing data. Invoices and payments live entirely in WHMCS.
- Salesforce receives **address snapshots** only when orders are created, so order records show the billing address used at checkout time.
## Key Things to Verify
- **Invoice list loads correctly**: Invoices appear sorted by date. Summary stats (total, paid, unpaid, overdue counts) match what you see in WHMCS admin.
- **Filtering works**: Selecting a status filter (e.g., "Unpaid") shows only invoices with that status. Search by invoice number returns the correct invoice.
- **Invoice detail matches WHMCS**: The total amount, line items, tax, due date, and status on the detail page all match the invoice in WHMCS admin.
- **PDF download works**: Clicking "Download PDF" downloads a valid PDF of the invoice.
- **Pay Now opens WHMCS**: Clicking "Pay Now" opens a new tab with the WHMCS payment page. The customer is already logged in (SSO). After payment, returning to the portal and refreshing shows the invoice as Paid.
- **Status badges are accurate**: A Paid invoice shows green, Unpaid shows amber, Overdue shows red.
- **Pagination**: If the customer has more than 10 invoices, pagination controls appear and navigate correctly.
- **Error handling**: If WHMCS is unavailable, the portal shows a friendly error message rather than broken or partial data.
- **Dashboard billing summary**: The billing summary card on the dashboard matches the overall totals from the invoices page.

View File

@ -0,0 +1,133 @@
# Support Cases
## Overview
This journey covers how customers create and manage support tickets through the portal. Cases are stored in Salesforce as Case records with the origin "Portal Support." Customers can create new cases, view their case list with filters, read the full conversation history for each case, and add replies. The portal shows only customer-friendly statuses (New, In Progress, Awaiting Customer, Closed) -- internal Salesforce workflow statuses are hidden.
## Portal Flow
### Viewing the Support Cases List
1. Navigate to **Account > Support** (URL: `/account/support`).
2. The page loads with the title "Support Cases" and the subtitle "Track and manage your support requests."
3. A **New Case** button appears in the top-right corner.
4. At the top, **summary statistics** show:
- **Total** cases
- **Open** cases (cases that are not Closed)
- **High Priority** (shown only when count is greater than zero)
- **Resolved** (Closed cases)
5. Below the stats, a **search and filter bar** provides:
- **Search** by case number or subject (free-text)
- **Filter by status**: All Statuses, New, In Progress, Awaiting Customer, Closed
- **Filter by priority**: All Priorities, Low, Medium, High
- A **Clear Filters** button when any filter is active
6. Each case in the list shows:
- A status icon (colored by status)
- The **case number** (e.g., #00001234) and **subject**
- A **status badge** (color-coded) and **priority badge** (color-coded)
- The **category** if one was assigned
- A **last updated** timestamp (relative, e.g., "2 hours ago")
7. Clicking a case row navigates to the case detail page.
8. If no cases exist, an empty state message reads "No support cases found" with a prompt to create one.
### Creating a New Support Case
1. Click the **New Case** button or navigate to `/account/support/new`.
2. The page shows:
- An **AI Assistant suggestion** banner at the top suggesting the customer try the AI chat first for instant answers.
- A form with three fields:
- **Subject** (required): Brief description of the issue. Maximum 255 characters.
- **Priority** (optional, defaults to Medium): A dropdown with three options:
- Low -- General question
- Medium -- Issue affecting work
- High -- Urgent / Service disruption
- **Description** (required): Detailed description of the issue. The helper text reads: "The more details you provide, the faster we can help you."
3. Click **Create Case** to submit.
4. On success, the customer is redirected to the support cases list with a success indicator.
5. At the bottom, a **contact options** section shows:
- **Phone Support**: 0120-660-470 (Mon-Fri, 9:30-18:00 JST)
- **AI Chat Assistant**: Available 24/7
### Viewing a Case Detail
1. From the case list, click a case to open its detail page (URL: `/account/support/{caseId}`).
2. The page header shows:
- The case subject and case number
- **Status badge** (New, In Progress, Awaiting Customer, or Closed)
- **Priority badge** (Low, Medium, or High)
- **Created** date and **Updated** timestamp
- **Category** if one was assigned
- **Closed** date if the case is closed
3. Below the header, the **Conversation** section shows all messages in a chat-like format:
- The customer's original description appears as the first message.
- **Messages are grouped by date** with separators (Today, Yesterday, or the specific date like "Mon, Dec 30").
- **Customer messages** appear on the right side with a colored bubble.
- **Support team messages** appear on the left side.
- Each message shows the author name, timestamp, and whether it came via email or as a comment.
- Messages with **attachments** show a paperclip icon.
- **New messages** (since the customer last viewed the case) show a "New" badge.
- Consecutive messages from the same person within 5 minutes are grouped together for a cleaner look.
4. A **Refresh** button at the top of the conversation allows manually refreshing messages. Messages also auto-refresh every 30 seconds.
### Adding a Reply to a Case
1. At the bottom of the case detail page, a **reply form** appears (for open cases only).
2. Type a message in the text area and click **Send Reply**.
3. The message appears immediately in the conversation (optimistic update) with a "Sending..." indicator.
4. Once confirmed, a brief "Sent" confirmation appears.
5. If sending fails, the message text is restored to the input field so the customer can try again.
6. **Closed cases**: The reply form is replaced with a notice: "This case is closed. If you need further assistance, please create a new case."
### Status Meanings
The portal simplifies Salesforce's internal statuses into four customer-friendly values:
| Portal Status | What It Means |
| ----------------- | ---------------------------------------------- |
| New | Case was just created, not yet reviewed |
| In Progress | Support team is working on it |
| Awaiting Customer | Support has replied, waiting for your response |
| Closed | Case is resolved and closed |
### Priority Meanings
| Priority | Color | When to Use |
| -------- | ----- | ---------------------------------- |
| Low | Green | General questions, no urgency |
| Medium | Amber | Issue affecting your work |
| High | Red | Urgent issue or service disruption |
## What Happens in Salesforce
- **Case creation**: A new Case record is always created in Salesforce (never updates an existing one). Find it under **Cases** in Salesforce, filtered by Origin = "Portal Support."
- **Key fields to check on the Case**:
| Field | API Name | Expected Value |
| ----------- | ---------------- | ------------------------------------------------------------------ |
| Subject | `Subject` | Matches what the customer entered |
| Description | `Description` | Matches the customer's description |
| Status | `Status` | "新規" (New) -- initial status |
| Priority | `Priority` | "低" (Low), "中" (Medium), or "高" (High) |
| Origin | `Origin` | "Portal Support" (customer-visible cases) |
| Type | `Type` | Category selected: Technical, Billing, General, or Feature Request |
| Account | `AccountId` | The customer's Salesforce Account ID |
| Contact | `ContactId` | The customer's Salesforce Contact ID |
| Opportunity | `Opportunity__c` | Linked Opportunity (if case relates to a specific service) |
- **Case comments**: When the customer adds a reply, a **CaseComment** record is created on the Case. Check under the Case's "Case Comments" related list.
- **Email messages**: If support replies via email, an **EmailMessage** record is attached to the Case. These appear in the portal conversation alongside comments.
- **Status changes**: When an agent changes the Case status in Salesforce, the portal automatically picks up the change. A Platform Event (`Case_Status_Update__e`) is fired, which clears the portal's cache and sends a real-time update.
- **Categories**: Map to the Salesforce Case **Type** field. Possible values: Technical, Billing, General, Feature Request.
## Key Things to Verify
- **Case creation**: Create a new case and verify it appears in Salesforce with the correct Subject, Description, Priority, Status (New), and Origin ("Portal Support").
- **Case list accuracy**: The list matches the cases shown in Salesforce for the customer's Account. Status and priority badges show correctly.
- **Filtering**: Filter by status (e.g., "In Progress") and verify only matching cases appear. Search by case number returns the correct case.
- **Conversation timeline**: The case detail shows the description as the first message, followed by all CaseComments and EmailMessages in chronological order. Messages are grouped by date correctly.
- **Adding a reply**: Post a reply and verify a CaseComment record is created in Salesforce on the Case. The comment body matches what was typed.
- **Real-time status updates**: Change the Case status in Salesforce (e.g., from New to In Progress). The portal should reflect the change shortly without a manual page refresh.
- **Closed case behavior**: When a case has status Closed in Salesforce, the reply form in the portal is hidden and replaced with a "case is closed" notice.
- **Status mapping**: Verify that internal Salesforce statuses are correctly simplified. For example, a Case with status "Awaiting Approval" or "VPN Pending" in Salesforce should show as "In Progress" in the portal.
- **Error handling**: If Salesforce is unavailable, the portal shows "support system unavailable, please try again later" rather than broken data.
- **Case not found**: Trying to access a case ID that belongs to another customer shows "Case not found" without leaking information.

View File

@ -0,0 +1,122 @@
# Identity Verification
## Overview
This journey covers the residence card (ID) verification process that customers must complete before they can place certain orders (such as SIM activation). Customers upload a photo or scan of their residence card, which is then reviewed by a support agent. Verification status is tracked on the Salesforce Account record. The portal shows the current status and allows re-upload if the document is rejected.
## Portal Flow
### Where to Find ID Verification
ID verification is available in two locations:
1. **Profile / Settings page** (`/account/settings`): The verification card is integrated into the settings page alongside other profile information.
2. **Standalone verification page** (`/account/settings/verification`): This page is used when the customer is redirected from the checkout flow. It accepts a `returnTo` parameter so the customer can be sent back to checkout after uploading.
### Status: Not Submitted
1. When a customer has not yet submitted their residence card, the page shows:
- A status pill labeled **"Not Submitted"** (amber)
- An informational banner titled "Upload required" explaining what to do
- Instructions listing:
- Upload a clear photo or scan of your residence card (JPG, PNG, or PDF)
- Make sure all text is readable and the full card is visible
- Avoid glare/reflections and blurry photos
- Maximum file size: 5 MB
2. A **file picker** lets the customer select a file from their device.
- Accepted formats: JPG, PNG, PDF, and other image formats
3. After selecting a file, a preview appears showing the file name, with a **Change** button to select a different file.
4. Click **Submit Document** to upload the file.
5. A loading state shows "Uploading..." while the file is being sent.
6. If the upload came from a checkout redirect (with a `returnTo` parameter), a **Back to checkout** button also appears.
### Status: Under Review (Pending)
1. After successful upload, the status changes to **"Under Review"** (blue).
2. The page shows:
- A status pill labeled **"Under Review"**
- An informational banner: "We'll verify your residence card before activating SIM service."
- A **submission timestamp** showing when the document was submitted
3. The customer cannot upload a new document while the current one is under review.
4. If arriving from checkout, a **Continue to Checkout** button appears so the customer can proceed (orders can be submitted while verification is pending, though they will not be fulfilled until verification completes).
### Status: Verified
1. When the agent approves the document, the status changes to **"Verified"** (green).
2. The page shows:
- A status pill labeled **"Verified"**
- A success banner: "Your residence card is on file and approved. No further action is required."
3. The customer cannot upload a new document (the upload form is hidden).
4. If arriving from checkout, a **Continue to Checkout** button appears.
### Status: Rejected
1. If the agent rejects the document, the status changes to **"Action Needed"** (amber).
2. The page shows:
- A status pill labeled **"Action Needed"**
- A warning banner titled "Verification rejected" with the agent's rejection reason (reviewer notes)
- Instructions for re-uploading
- The **submission and review timestamps** showing when the document was submitted and reviewed
3. The **file picker** reappears so the customer can upload a corrected document.
4. After re-uploading, the status returns to "Under Review."
### Accepted File Types and Limits
| Property | Value |
| ------------ | ------------------------------------------- |
| File types | JPG, JPEG, PNG, PDF, other images |
| Maximum size | 5 MB |
| Tip | Higher resolution photos make review faster |
## What Happens in Salesforce
- **File storage**: The uploaded document is stored as a **ContentVersion** record in Salesforce, linked to the customer's Account.
- **Account fields updated on upload**:
- `Id_Verification_Status__c` is set to **"Submitted"**
- `Id_Verification_Submitted_Date_Time__c` is set to the upload timestamp
- **Agent review**: The agent reviews the document in Salesforce and updates the Account:
- **Approved**: Sets `Id_Verification_Status__c` to **"Verified"** and `Id_Verification_Verified_Date_Time__c` to the review time.
- **Rejected**: Sets `Id_Verification_Status__c` to **"Rejected"**, fills `Id_Verification_Rejection_Message__c` with the reason, and optionally `Id_Verification_Note__c` with internal notes.
- **Platform Event**: When the verification status changes, Salesforce fires a Platform Event (`Account_Update__e`). The portal receives this event and automatically refreshes the verification status in real time -- the customer sees the update without needing to refresh the page.
- **Notification**: When the status changes to Verified or Rejected, the portal creates an in-app notification for the customer:
- Verified: "ID verification complete" with a link to continue checkout
- Rejected: "ID verification requires attention" with a link to resubmit
### Salesforce Account Fields Reference
| Field | Type | Set By | When |
| ---------------------------------------- | -------- | ------------ | --------------- |
| `Id_Verification_Status__c` | Picklist | Portal/Agent | Upload / Review |
| `Id_Verification_Submitted_Date_Time__c` | DateTime | Portal | On upload |
| `Id_Verification_Verified_Date_Time__c` | DateTime | Agent | After approval |
| `Id_Verification_Note__c` | Text | Agent | After review |
| `Id_Verification_Rejection_Message__c` | Text | Agent | If rejected |
### Status Values in Salesforce
| Salesforce Value | Portal Display | Can Submit Orders? |
| ---------------- | -------------- | ------------------ |
| Not Submitted | Not Submitted | No |
| Submitted | Under Review | Yes |
| Verified | Verified | Yes |
| Rejected | Action Needed | No |
## Key Things to Verify
- **Upload flow**: Select a file (JPG, PNG, or PDF under 5 MB), click Submit Document, and verify:
- The file appears as a ContentVersion record in Salesforce linked to the customer's Account
- `Id_Verification_Status__c` changes to "Submitted"
- `Id_Verification_Submitted_Date_Time__c` is set
- The portal shows "Under Review" status
- **Rejection flow**: Have an agent reject the verification in Salesforce with a rejection message. Verify:
- The portal updates to show "Action Needed" with the rejection reason displayed
- The upload form reappears so the customer can re-upload
- An in-app notification is created: "ID verification requires attention"
- **Approval flow**: Have an agent approve the verification in Salesforce. Verify:
- The portal updates to show "Verified" with a green status badge
- The upload form is hidden
- An in-app notification is created: "ID verification complete"
- **Real-time updates**: Status changes in Salesforce should appear in the portal within a few seconds (via Platform Events) without manual page refresh.
- **Checkout redirect**: Navigate to `/account/settings/verification?returnTo=/account/checkout`. After upload, the "Continue to Checkout" button should correctly navigate back to the checkout page.
- **File validation**: Try uploading a file larger than 5 MB or an unsupported format and verify the portal shows an appropriate error.
- **Error handling**: If the upload fails (e.g., network issue), an error message appears and the customer can try again.

View File

@ -0,0 +1,107 @@
# Notifications
## Overview
This journey covers the in-app notification system that keeps customers informed about important events related to their account, orders, billing, and services. Notifications are stored in the portal's own database and are triggered by events from Salesforce (via Platform Events), WHMCS (via the fulfillment process), and the portal itself. Customers access notifications through a bell icon in the navigation bar.
## Portal Flow
### The Notification Bell
1. The **bell icon** appears in the top navigation bar on every page when the customer is logged in.
2. When there are **unread notifications**, a badge appears on the bell showing the count (e.g., "3"). If the count exceeds 9, it shows "9+."
3. The unread count refreshes automatically every 60 seconds.
4. Clicking the bell opens a **dropdown panel**.
### Notification Dropdown
1. Clicking the bell opens a dropdown showing the most recent 10 notifications (both read and unread).
2. Each notification shows:
- An **icon** indicating the type (green checkmark for positive events, amber warning for issues, blue info for general updates)
- The notification **title** (bold for unread, normal weight for read)
- A short **message** with details (up to 2 lines)
- A **relative timestamp** (e.g., "2 hours ago")
- An **unread indicator** (small blue dot) for notifications that haven't been read
3. At the top of the dropdown, a **"Mark all read"** button appears when there are unread notifications.
4. Each notification can be individually **dismissed** using an X button that appears on hover.
5. Clicking a notification:
- Marks it as read
- If the notification has an action link, navigates to the relevant page
6. At the bottom of the dropdown, a **"View all notifications"** link navigates to the full notifications page (`/account/notifications`).
7. The dropdown closes when clicking outside it or pressing the Escape key.
### Notification Types
The following notification types can appear in the portal:
| Type | Title | Triggered By | Links To |
| ---------------------- | ------------------------------------------ | ------------------------------ | -------------------------- |
| Internet Eligible | "Good news! Internet service is available" | Eligibility check completed | Internet services page |
| Internet Ineligible | "Internet service not available" | Eligibility check completed | Support page |
| ID Verified | "ID verification complete" | Agent approves residence card | Continue checkout |
| ID Rejected | "ID verification requires attention" | Agent rejects residence card | Verification resubmit page |
| Order Approved | "Order approved" | Order approved in Salesforce | Orders page |
| Service Activated | "Service activated" | WHMCS provisioning complete | Services page |
| Order Failed | "Order requires attention" | Fulfillment error | Support page |
| Cancellation Scheduled | "Cancellation scheduled" | Customer requests cancellation | Services page |
| Invoice Due | "Invoice due" | Invoice due within 7 days | Invoices page |
### How Notifications Are Triggered
**From Salesforce (via Platform Events):**
- When a Salesforce agent updates the `Internet_Eligibility_Status__c` field on an Account to "Eligible" or "Ineligible," a Platform Event fires. The portal receives this event and creates a notification.
- When a Salesforce agent updates the `Id_Verification_Status__c` field to "Verified" or "Rejected," the same mechanism creates a notification.
- Notifications are only created for **final states** (Eligible/Ineligible, Verified/Rejected) -- intermediate states like "Pending" or "Submitted" do not generate notifications.
**From the fulfillment process:**
- When an order is approved, activated, or fails during the fulfillment workflow, the portal creates a notification.
- When a customer requests a cancellation (internet or SIM), a "Cancellation scheduled" notification is created.
**From the dashboard check:**
- When the customer loads their dashboard, the portal checks if any invoices are due within 7 days or overdue. If so, an "Invoice due" notification is created.
### Deduplication
The notification system prevents duplicate notifications:
- Most notification types use a 1-hour deduplication window -- the same type and source combination will not create a second notification within 1 hour.
- Invoice due, payment method expiring, and system announcements use a 24-hour deduplication window.
### Reading and Dismissing Notifications
- **Mark as read**: Click any notification to mark it as read. The bold text becomes normal and the blue dot disappears.
- **Mark all as read**: Click "Mark all read" at the top of the dropdown to mark all notifications as read at once.
- **Dismiss**: Hover over a notification and click the X button to dismiss it. Dismissed notifications no longer appear in the list.
## What Happens in WHMCS
- WHMCS does not store portal notifications directly.
- However, WHMCS events can **indirectly trigger** notifications. For example:
- When the fulfillment process provisions a service in WHMCS and succeeds, a "Service activated" notification is created.
- When a WHMCS invoice is approaching its due date, the dashboard check creates an "Invoice due" notification.
## What Happens in Salesforce
- Salesforce events trigger notifications via **Platform Events**:
- The `Account_Update__e` Platform Event fires when eligibility or verification fields change on the Account.
- The portal's event subscriber receives this, checks which fields changed, and creates the appropriate notification.
- Salesforce does **not** store portal notification records. Notifications live in the portal's own database.
- To verify Salesforce-triggered notifications, change the relevant field on the Account in Salesforce and check that the notification appears in the portal.
## Key Things to Verify
- **Bell icon and count**: Verify the bell icon shows the correct unread count. Create a trigger event (e.g., approve an ID verification in Salesforce) and verify the count increments.
- **Dropdown displays correctly**: Click the bell and verify notifications appear with correct titles, messages, icons, and timestamps.
- **Mark as read**: Click a notification and verify the unread indicator disappears and the text style changes from bold to normal.
- **Mark all as read**: Click "Mark all read" and verify all unread indicators disappear and the badge count resets to zero.
- **Dismiss**: Hover over a notification, click X, and verify it disappears from the list.
- **Navigation**: Click a notification with an action link (e.g., "ID verification complete") and verify it navigates to the correct page (e.g., the verification page).
- **Eligibility notifications**: Change `Internet_Eligibility_Status__c` to "Eligible" in Salesforce and verify a "Good news! Internet service is available" notification appears in the portal.
- **Verification notifications**: Change `Id_Verification_Status__c` to "Rejected" in Salesforce and verify a "ID verification requires attention" notification appears.
- **Order notifications**: Process an order through fulfillment and verify "Order approved" and "Service activated" notifications appear at the appropriate stages.
- **Invoice due notification**: Ensure a customer has an invoice due within 7 days, load the dashboard, and verify an "Invoice due" notification is created.
- **Deduplication**: Trigger the same event twice within an hour and verify only one notification is created.
- **Real-time delivery**: When a Salesforce event triggers a notification, it should appear in the portal within a few seconds without needing a page refresh (via server-sent events).

View File

@ -0,0 +1,129 @@
# Address Management
## Overview
This journey covers how customers view and edit their address in the portal. Addresses use a Japan-specific format with bilingual support -- the address is stored in both Japanese (for Salesforce) and romanized English (for WHMCS). The portal integrates with the Japan Post API to look up addresses from postal codes, ensuring accuracy. When a customer updates their address, the change is written to WHMCS (the billing system of record) and the portal cache is cleared so the new address appears immediately.
## Portal Flow
### Viewing Your Current Address
1. Navigate to **Account > Settings** (URL: `/account/settings`).
2. The **Address Information** card shows the current address on file:
- Primary line (street/building details)
- Secondary line (building name or room number, shown when provided)
- City, prefecture (state), and postal code
- Country (displayed as the full country name, e.g., "Japan")
3. An **Edit** button appears in the top-right corner of the address card.
4. If no address is on file, an empty state shows "No address on file" with an **Add Address** button.
### Editing Your Address
1. Click **Edit** on the address card. The display switches to a step-by-step address form.
2. The form uses **progressive disclosure** -- each step appears after the previous one is completed, with smooth animations. A progress indicator at the top shows how far along you are.
#### Step 1: Enter ZIP Code
1. Type a 7-digit Japanese postal code (e.g., "100-0001" or "1000001").
2. As soon as a valid 7-digit code is entered, the portal automatically looks up the address using the Japan Post API.
3. If the postal code is found, a **"Verified" badge** appears and the looked-up address is displayed in a green-bordered card showing:
- **Prefecture** (in both romanized English and Japanese, e.g., "Tokyo / 東京都")
- **City / Ward** (in both languages, e.g., "Minato-ku / 港区")
- **Town** (in both languages, e.g., "Higashiazabu / 東麻布")
4. These fields are read-only -- they are automatically filled from the postal code lookup and cannot be manually edited. This ensures accuracy.
5. If the postal code is not found, an error is displayed.
#### Step 2: Street Address
1. After the postal code is verified, a **Street Address** field appears.
2. Enter the chome-banchi-go number in hyphenated format (e.g., "1-5-3" or "1-5").
3. The field validates the format: it must be numbers separated by hyphens (chome-banchi-go or chome-banchi).
4. Helper text guides the customer: "Enter chome-banchi-go (e.g., 1-5-3)"
#### Step 3: Residence Type
1. After entering a valid street address, a **Residence Type** selector appears.
2. Choose between two options:
- **House** (with a house icon)
- **Apartment** (with a building icon)
3. The selected option highlights with a colored border.
#### Step 4: Building Details
1. After selecting the residence type, **Building Details** fields appear.
2. **Building Name** (required): Enter the building or residence name.
- For apartments: e.g., "Sunshine Mansion"
- For houses: e.g., "Tanaka Residence"
3. **Room Number** (required for apartments only): Enter the room/unit number (e.g., "201"). This field only appears when "Apartment" is selected.
#### Completion
1. When all fields are filled, a green **"Address Complete"** success message appears.
2. Click **Save Address** to submit the changes.
3. A loading state shows "Saving..." while the address is being updated.
4. On success, the form closes and the address card shows the updated address.
5. Click **Cancel** at any time to discard changes and return to the address display.
### Address Form Summary
| Step | Field | Required | Format | Source |
| ---- | -------------- | -------- | ------------------------- | -------------- |
| 1 | ZIP Code | Yes | 7 digits (e.g., 100-0001) | Customer input |
| 1 | Prefecture | Auto | Read-only from lookup | Japan Post API |
| 1 | City / Ward | Auto | Read-only from lookup | Japan Post API |
| 1 | Town | Auto | Read-only from lookup | Japan Post API |
| 2 | Street Address | Yes | Hyphenated (e.g., 1-5-3) | Customer input |
| 3 | Residence Type | Yes | House or Apartment | Customer input |
| 4 | Building Name | Yes | Free text | Customer input |
| 4 | Room Number | Yes\* | Free text | Customer input |
\*Room number is required only when "Apartment" is selected.
## What Happens in WHMCS
- WHMCS is the **system of record** for the customer's billing address.
- When the customer saves their address, the portal writes the update to WHMCS and clears the cached address data so the change appears immediately.
- The address is mapped to WHMCS fields as follows:
| WHMCS Field | Content | Example |
| ----------- | ---------------------------------------- | ---------------------- |
| address1 | Building name + Room number (apartments) | "Sunshine Mansion 201" |
| address2 | Town + Street address (romanized) | "Higashiazabu 1-5-3" |
| city | City (romanized) | "Minato-ku" |
| state | Prefecture (romanized) | "Tokyo" |
| postcode | ZIP code | "1060044" |
| country | Country code | "JP" |
- To verify the address update in WHMCS admin, navigate to **Clients > [Client Name] > Profile** and check the address fields.
- The updated address will appear on all future invoices generated by WHMCS.
## What Happens in Salesforce
- Salesforce receives address updates on the **Contact** record when the customer saves their address:
| Salesforce Field | Content | Example |
| ----------------- | -------------------------------- | ------------------ |
| MailingStreet | Town + Street address (Japanese) | "東麻布1-5-3" |
| MailingCity | City (Japanese) | "港区" |
| MailingState | Prefecture (Japanese) | "東京都" |
| MailingPostalCode | ZIP code | "1060044" |
| MailingCountry | Country | "Japan" |
| BuildingName\_\_c | Building name | "Sunshine Mansion" |
| RoomNumber\_\_c | Room number | "201" |
- Note that Salesforce stores the **Japanese** version of the address, while WHMCS stores the **romanized English** version. Both are derived from the same Japan Post lookup data.
- Address updates on the Salesforce Contact happen alongside the WHMCS update.
## Key Things to Verify
- **Postal code lookup**: Enter a valid Japanese postal code and verify the prefecture, city, and town auto-fill correctly in both Japanese and English. Try known postal codes (e.g., 1000001 for Chiyoda-ku, Tokyo).
- **Invalid postal code**: Enter an invalid or nonexistent postal code and verify an appropriate error message appears.
- **Progressive disclosure**: Verify that each step only appears after the previous step is completed (ZIP verified before street address appears, street address filled before residence type appears, etc.).
- **Apartment vs. House**: Select "Apartment" and verify the Room Number field appears. Select "House" and verify the Room Number field is hidden.
- **Address save to WHMCS**: After saving, check the WHMCS admin Client profile and verify address1, address2, city, state, and postcode are correctly populated with romanized English values.
- **Address save to Salesforce**: After saving, check the Salesforce Contact record and verify MailingStreet, MailingCity, MailingState, and MailingPostalCode contain the Japanese values.
- **Immediate reflection**: After saving, the address card in the portal immediately shows the updated address without needing a page refresh.
- **Invoice address**: After updating the address, generate a new invoice in WHMCS and verify it uses the updated address.
- **Cancel editing**: Click Edit, make changes, then click Cancel. Verify the address reverts to the previously saved values.
- **Validation**: Try submitting with an invalid street address format (e.g., "abc" instead of "1-5-3") and verify the form shows a validation error.
- **Error handling**: If the Japan Post lookup service is unavailable, verify the portal handles the error gracefully.

108
docs/uat/README.md Normal file
View File

@ -0,0 +1,108 @@
# UAT Guidebook -- Customer Portal
## About This Guide
This guidebook is a reference for UAT (User Acceptance Testing) of the Customer Portal. It explains what each part of the portal does, how it works from the user's perspective, and what you should see in WHMCS and Salesforce when actions are taken.
**This guide is for you if:** You are a QA tester who knows how to navigate the WHMCS admin panel and Salesforce, and you need to understand what the Customer Portal does so you can verify it works correctly.
**This guide is NOT:** A test script with pass/fail checklists. It is a reference that explains functionality so you can design your own test scenarios and know what to look for.
## How the Systems Connect
The Customer Portal is the front-end that customers use. Behind the scenes, it connects to two main back-office systems:
- **WHMCS** -- The billing system. Manages client accounts, invoices, payments, subscriptions, and product records. When a customer signs up, pays a bill, or changes a plan, WHMCS is where the billing side of things happens.
- **Salesforce** -- The CRM and order management system. Manages customer accounts, orders, opportunities, support cases, and the product catalog. When a customer places an order, creates a support ticket, or goes through identity verification, Salesforce is where those records live.
There is also a third system, **Freebit**, which handles the actual SIM/MVNO operations (activating SIMs, changing plans, porting numbers). Testers generally won't interact with Freebit directly, but it's mentioned in the SIM guides so you understand why certain operations take time to process.
### Typical Data Flow
```
Customer does something in Portal
|
v
Portal backend processes the request
|
+---> Creates/updates records in WHMCS (billing)
|
+---> Creates/updates records in Salesforce (CRM/orders)
|
+---> (For SIM operations) Sends commands to Freebit
```
## How to Use This Guide
Each journey file covers one area of the portal. Every file is structured the same way:
1. **Overview** -- A quick summary of what the journey covers.
2. **Portal Flow** -- Step-by-step description of what the user sees and does in the portal.
3. **What Happens in WHMCS** -- What records to check in the WHMCS admin panel, and what field values to expect.
4. **What Happens in Salesforce** -- What records to check in Salesforce, and what field values to expect.
5. **Key Things to Verify** -- A list of important things to pay attention to during testing.
**Recommended approach:** Read through a journey file to understand the flow, then go through it yourself in the portal while checking WHMCS and Salesforce to confirm everything matches.
## Journey Index
### Getting Started
| # | Journey | What It Covers |
| --- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| 01 | [Signup and Account Creation](./01-signup-and-account-creation.md) | Creating a new account, email verification, WHMCS client creation, Salesforce account linking, migrating existing WHMCS accounts |
| 02 | [Login and Authentication](./02-login-and-authentication.md) | Two-step login (password + OTP), forgot/reset password, session timeout, logout |
| 03 | [Dashboard and Profile](./03-dashboard-and-profile.md) | Dashboard overview page, task cards, activity feed, profile editing, address editing, identity verification upload |
### Browsing and Ordering
| # | Journey | What It Covers |
| --- | ------------------------------------------------ | -------------------------------------------------------------------------------------------- |
| 04 | [Browsing Services](./04-browsing-services.md) | Service catalog (SIM, Internet, VPN), plan details, eligibility checks |
| 05 | [Ordering a Service](./05-ordering-a-service.md) | Service configuration, checkout, order submission, real-time tracking, provisioning in WHMCS |
### Managing Subscriptions
| # | Journey | What It Covers |
| --- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| 06 | [Managing SIM Subscriptions](./06-managing-sim-subscriptions.md) | SIM details, plan changes, data top-ups, SIM reissue, MNP (number porting), call/SMS history, voice features, cancellation |
| 07 | [Managing Internet Subscriptions](./07-managing-internet-subscriptions.md) | Internet plan details, access modes, add-ons, cancellation |
| 08 | [Managing VPN Subscriptions](./08-managing-vpn-subscriptions.md) | VPN plan details, regions, billing |
### Billing and Support
| # | Journey | What It Covers |
| --- | ---------------------------------------------------- | -------------------------------------------------------------------- |
| 09 | [Billing and Invoices](./09-billing-and-invoices.md) | Invoice list, invoice detail, PDF download, payment, payment methods |
| 10 | [Support Cases](./10-support-cases.md) | Creating tickets, viewing cases, adding replies, status tracking |
### Account Management
| # | Journey | What It Covers |
| --- | ------------------------------------------------------ | ------------------------------------------------------------------- |
| 11 | [Identity Verification](./11-identity-verification.md) | Residence card upload, verification status, approval/rejection flow |
| 12 | [Notifications](./12-notifications.md) | In-app notifications, notification types, preferences |
| 13 | [Address Management](./13-address-management.md) | Address viewing/editing, Japan Post postal code lookup, WHMCS sync |
### Reference
| Document | What It Covers |
| --------------------------------------------------------- | ----------------------------------------------------------------------------- |
| [Cross-Reference Appendix](./appendix-cross-reference.md) | Quick-reference table mapping Portal features to WHMCS and Salesforce objects |
## Key Terminology
| Term | Meaning |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| **Customer Number** | A unique identifier assigned to each customer in Salesforce. Used to link the portal account to the Salesforce Account record. |
| **WHMCS Client** | A customer record in WHMCS. Each portal user has a corresponding WHMCS Client. |
| **Salesforce Account** | A customer record in Salesforce. Linked to the portal via Customer Number. |
| **Subscription** | An active service (SIM, Internet, or VPN) that a customer is paying for. Stored as a Product/Service in WHMCS. |
| **Opportunity** | A Salesforce record that represents a potential or in-progress order. Created when a customer submits an order. |
| **MNP** | Mobile Number Portability -- the process of transferring a phone number from one carrier to another. |
| **eSIM** | An embedded SIM that is provisioned digitally (no physical card). Requires a 32-digit EID from the device. |
| **EID** | A 32-digit identifier for an eSIM-capable device, needed to provision an eSIM. |
| **OTP** | One-Time Password -- a 6-digit code sent by email during login for two-factor authentication. |
| **Platform Event** | A Salesforce mechanism that sends real-time notifications to the portal (e.g., when an order status changes). |
| **Freebit** | The MVNO platform that handles SIM operations (activation, plan changes, number porting). |
| **IPoE / PPPoE** | Internet connection access modes. IPoE is simpler (plug and play), PPPoE requires manual configuration with credentials. |

View File

@ -0,0 +1,236 @@
# Cross-Reference Appendix
Quick-reference tables mapping Customer Portal features to their corresponding records in WHMCS and Salesforce. Use this when you need to quickly look up where to verify something.
## How the Systems Link Together
Understanding how WHMCS and Salesforce are connected is essential for UAT. Here is the linking chain:
### Customer Linking (established during signup)
```
Portal User ID ←→ WHMCS Client ID ←→ Salesforce Account ID
(portal database stores this three-way mapping)
```
| System | Field | What It Stores |
| ------------------ | ------------------------------------- | ----------------------------------------------------- |
| WHMCS Client | Custom Field #198 ("Customer Number") | The Salesforce Account Number (`SF_Account_No__c`) |
| Salesforce Account | `WH_Account__c` | The WHMCS Client ID in format "#1234 - Customer Name" |
| Salesforce Account | `SF_Account_No__c` | The Customer Number (same value as WHMCS field #198) |
**To verify the link:** Open the WHMCS Client's custom fields and find the Customer Number. Then search Salesforce for an Account with that same `SF_Account_No__c`. The Account's `WH_Account__c` field should contain the WHMCS Client ID.
### Subscription / Opportunity Linking (established during provisioning)
```
WHMCS Service ←→ Salesforce Opportunity
```
| System | Field | What It Stores |
| ---------------------- | ---------------------------- | ----------------------------- |
| WHMCS Service | Custom Field "OpportunityId" | The Salesforce Opportunity ID |
| Salesforce Opportunity | `WHMCS_Service_ID__c` | The WHMCS Service/Product ID |
**To verify the link:** Open a WHMCS Service's custom fields and find the OpportunityId. Then open that Opportunity in Salesforce and check `WHMCS_Service_ID__c` matches the WHMCS Service ID.
This bidirectional link is what makes cancellation work -- the portal uses it to find the right Opportunity to update.
### Order Linking (established during provisioning)
```
Salesforce Order → WHMCS Order
Salesforce OrderItem → WHMCS Service
```
| System | Field | What It Stores |
| -------------------- | --------------------- | --------------------------------------- |
| Salesforce Order | `WHMCS_Order_ID__c` | The WHMCS Order ID |
| Salesforce OrderItem | `WHMCS_Service_ID__c` | The WHMCS Service ID for each line item |
---
## Salesforce Account Fields Reference
These are the key fields on the Salesforce Account record that the portal reads and writes:
### Portal-Specific Fields
| Field Label | API Name | Set By | When | Values |
| -------------------------- | ------------------------------- | ---------- | ---------------- | -------------------------------------------- |
| Customer Number | `SF_Account_No__c` | Salesforce | Account creation | Unique identifier (matches WHMCS field #198) |
| Portal Status | `Portal_Status__c` | Portal | Signup | "Active" |
| Portal Registration Source | `Portal_Registration_Source__c` | Portal | Signup | "New Signup" or "Migrated" |
| Portal Last Sign-In | `Portal_Last_SignIn__c` | Portal | Each login | Timestamp |
| WHMCS Account | `WH_Account__c` | Portal | Signup | "#1234 - Customer Name" format |
### Internet Eligibility Fields
| Field Label | API Name | Set By | When | Values |
| -------------------- | ------------------------------------------- | -------- | ---------------------------- | ----------------------------------------------------- |
| Internet Eligibility | `Internet_Eligibility__c` | SF Agent | After manual NTT check | Speed value (e.g., "Cross 10G", "Hikari 1G") or empty |
| Eligibility Status | `Internet_Eligibility_Status__c` | SF Agent | After review | Status of the check |
| Request Date | `Internet_Eligibility_Request_Date_Time__c` | Portal | When customer requests check | Timestamp |
| Checked Date | `Internet_Eligibility_Checked_Date_Time__c` | SF Agent | When agent completes review | Timestamp |
### ID Verification Fields
| Field Label | API Name | Set By | When | Values |
| ------------------- | ---------------------------------------- | ----------------- | ------------------ | ---------------------------------------------------- |
| Verification Status | `Id_Verification_Status__c` | Portal / SF Agent | Upload / Review | "Not Submitted", "Submitted", "Verified", "Rejected" |
| Submitted Date | `Id_Verification_Submitted_Date_Time__c` | Portal | On document upload | Timestamp |
| Verified Date | `Id_Verification_Verified_Date_Time__c` | SF Agent | After approval | Timestamp |
| Note | `Id_Verification_Note__c` | SF Agent | After review | Internal notes |
| Rejection Message | `Id_Verification_Rejection_Message__c` | SF Agent | If rejected | Reason shown to customer |
### Standard Fields
| Field Label | API Name | Description |
| --------------- | ------------------------------------------------------------------------------------- | ----------------------------------------- |
| Email | `PersonEmail` | Customer email address |
| Phone | `Phone` | Customer phone number |
| Billing Address | `BillingStreet`, `BillingCity`, `BillingState`, `BillingPostalCode`, `BillingCountry` | Customer address (snapshot at order time) |
---
## WHMCS Client Custom Fields Reference
| Field ID | Field Name | What It Stores | Where to Find It |
| -------- | --------------- | -------------------------------------------------------------------------------- | -------------------------------- |
| **198** | Customer Number | Salesforce Account Number (`SF_Account_No__c`) -- the critical cross-system link | Client > Profile > Custom Fields |
| **200** | Gender | Customer gender (Male, Female, Other) | Client > Profile > Custom Fields |
| **201** | Date of Birth | Customer DOB | Client > Profile > Custom Fields |
---
## Salesforce Opportunity Stage Reference
| Stage | Display | Meaning |
| --------------- | --------------- | ------------------------------------------------------------------------ |
| Introduction | Introduction | Initial interest (e.g., eligibility check requested) |
| WIKI | WIKI | Low-priority lead |
| Ready | Ready | Customer is eligible and ready to order |
| Post Processing | Post Processing | Order has been placed, being reviewed |
| Active | Active | Service is live |
| △Cancelling | Cancelling | Cancellation requested, service still running until end of billing cycle |
| Cancelled | Cancelled | Service has been cancelled |
| Completed | Completed | Service lifecycle complete |
| Void | Void | Lost or not eligible |
| Pending | Pending | On hold |
---
## Salesforce Case Origin Reference
| Origin Value | Created By | Visible to Customer? | When Created |
| ------------------- | --------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------- |
| Portal Support | Customer (via support page) | Yes -- appears in customer's cases list | Customer creates a support ticket |
| Portal Notification | Portal (automatic) | No -- internal only | Order placed, eligibility requested, cancellation requested, ID verification uploaded |
| Web | Public contact form | No -- unlinked to account | Guest submits contact form |
---
## Portal Action → WHMCS Records
| Portal Action | WHMCS Object | Where to Find It | Key Fields to Check |
| ----------------------------------- | --------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| **Sign up (new customer)** | Client | Clients > Client List > search by email | First Name, Last Name, Email, Phone, Address, Custom Fields (#198 Customer Number, #200 Gender, #201 DOB) |
| **Sign up (migrate WHMCS account)** | Client (existing) | Clients > Client List > search by email | Confirm no duplicate created; Custom Field #198 now has Salesforce Account Number |
| **Edit profile (email/phone)** | Client | Clients > Client List > open client | Email Address, Phone Number updated |
| **Edit address** | Client | Clients > Client List > open client | Address 1, Address 2, City, State, Postcode, Country |
| **Order provisioned** | Product/Service | Client > Products/Services tab | Product name, Status (Active), Billing Cycle, Amount, Custom Field "OpportunityId" = SF Opportunity ID |
| **Order provisioned** | Order | Client > Orders tab | WHMCS Order created with line items |
| **SIM plan change** | Product/Service | Client > Products/Services tab | Product name updated to new plan, Amount updated (takes effect 1st of next month) |
| **SIM data top-up** | Invoice | Billing > Invoices > search by client | New invoice for top-up amount (500 JPY per GB), Status Paid |
| **SIM reissue** | Product/Service | Client > Products/Services tab | Same WHMCS service ID -- no new service created |
| **Voice feature toggle** | Add-on Services | Client > Products/Services tab | Voice Mail (pid 119), Call Waiting (pid 123) updated |
| **View invoices** | Invoices | Billing > Invoices > search by client | Invoice Number, Status, Amount, Due Date match portal |
| **Pay invoice** | Invoice + Transaction | Billing > Invoices > open invoice | Status changes to Paid; Transaction record created |
| **Cancel subscription** | Product/Service | Client > Products/Services tab | Status changes to Cancelled at end of billing cycle |
## Portal Action → Salesforce Records
| Portal Action | Salesforce Object | Where to Find It | Key Fields to Check |
| -------------------------------- | ----------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| **Sign up** | Account | Search by email or `SF_Account_No__c` | `Portal_Status__c` = Active, `Portal_Registration_Source__c`, `WH_Account__c` contains WHMCS Client ID |
| **Sign up** | Contact | Under the Account record | Email, First Name, Last Name |
| **Place an order** | Order | Account > Related > Orders | `Status`, `Type`, `OpportunityId`, service-specific fields (see doc 05 for full field list) |
| **Place an order** | Opportunity | Account > Related > Opportunities | `StageName` = "Post Processing", `CommodityType__c`, `Opportunity_Source__c` contains "Portal" |
| **Place an order** | OrderItem | Under the Order | `Product2Id`, `UnitPrice`, `Quantity`, `Billing_Cycle__c` |
| **Place an order** | Case | Cases tab (not visible to customer) | Origin = "Portal Notification", Subject contains order info |
| **Order provisioned** | Order | Same Order | `WHMCS_Order_ID__c` populated, `Activation_Status__c` = "Activated" |
| **Order provisioned** | OrderItem | Under the Order | `WHMCS_Service_ID__c` populated for each item |
| **Order provisioned** | Opportunity | Same Opportunity | `WHMCS_Service_ID__c` populated, `StageName` = "Active" |
| **Create support case** | Case | Cases tab > search by Account | Origin = "Portal Support", `Subject`, `Priority`, `Status` |
| **Reply to support case** | CaseComment | Under the Case | Comment body, Created Date |
| **Upload residence card** | Account | Search by `SF_Account_No__c` | `Id_Verification_Status__c` = "Submitted" |
| **Upload residence card** | ContentVersion | Account > Related > Files | Uploaded document file |
| **ID approved by agent** | Account | Same Account | `Id_Verification_Status__c` = "Verified", `Id_Verification_Verified_Date_Time__c` set |
| **ID rejected by agent** | Account | Same Account | `Id_Verification_Status__c` = "Rejected", `Id_Verification_Rejection_Message__c` has reason |
| **Internet eligibility request** | Account | Search by `SF_Account_No__c` | `Internet_Eligibility_Request_Date_Time__c` set |
| **Internet eligibility request** | Case | Cases tab | Origin = "Portal Notification", new Case created each time |
| **Eligibility checked by agent** | Account | Same Account | `Internet_Eligibility__c` populated with speed value, `Internet_Eligibility_Checked_Date_Time__c` set |
| **Cancel SIM** | Opportunity | Account > Opportunities | `StageName` = "△Cancelling", `SIMScheduledCancellationDateAndTime__c` set, `WHMCS_Service_ID__c` matches WHMCS service |
| **Cancel SIM** | Case | Cases tab (not visible to customer) | Origin = "Portal Notification" |
| **Cancel Internet** | Opportunity | Account > Opportunities | `StageName` = "△Cancelling", `ScheduledCancellationDateAndTime__c` set, `CancellationNotice__c`, `LineReturn__c` for equipment |
| **Cancel Internet** | Case | Cases tab (not visible to customer) | Origin = "Portal Notification" |
## Portal Feature → Data Source
This table shows where each portal feature gets its data from. Useful for understanding which system to check when something looks wrong.
| Portal Feature | Primary Data Source | Secondary Source |
| --------------------------------- | --------------------------------------- | --------------------------------------- |
| Dashboard - Active Services count | WHMCS (subscriptions) | -- |
| Dashboard - Open Cases count | Salesforce (cases) | -- |
| Dashboard - Recent Orders | Salesforce (orders) | -- |
| Dashboard - Recent Activity | Portal database (aggregated) | WHMCS + Salesforce |
| Dashboard - Task Cards | Portal (computed from multiple sources) | WHMCS + Salesforce |
| Service Catalog / Plans | Salesforce (Product2, PricebookEntry) | -- |
| Eligibility Status | Salesforce (Account fields) | -- |
| Checkout / Order Submission | Salesforce (Order, Opportunity) | -- |
| Order Real-time Tracking | Salesforce Platform Events → Portal SSE | -- |
| Subscription List | WHMCS (Products/Services) | -- |
| SIM Management (usage, voice) | Freebit (via portal backend) | WHMCS for billing |
| SIM Plan Change | Freebit (execution) | WHMCS (billing update) |
| SIM Data Top-up | Freebit (execution) | WHMCS (invoice creation) |
| Invoices | WHMCS (Invoices) | -- |
| Payments | WHMCS (Transactions) | -- |
| Support Cases | Salesforce (Cases) | -- |
| Identity Verification | Salesforce (Account + ContentVersion) | -- |
| Notifications | Portal database | Triggered by Salesforce Platform Events |
| Address | WHMCS (Client record) | Japan Post API (for lookup) |
| Profile (name, DOB, gender) | WHMCS (Client record) | -- |
| Profile (Customer Number) | Salesforce (Account `SF_Account_No__c`) | -- |
## WHMCS Navigation Quick Reference
| What You're Looking For | Where to Go in WHMCS |
| --------------------------------------- | --------------------------------------------------------------- |
| A customer's record | Clients > Client List > search by name or email |
| A customer's services | Open Client > Products/Services tab |
| Service custom fields (OpportunityId) | Open Client > Products/Services > click service > Custom Fields |
| A customer's invoices | Open Client > Invoices tab, or Billing > Invoices |
| A specific invoice | Billing > Invoices > search by invoice number |
| Payment transactions | Open an Invoice > Transactions section |
| Client custom fields (#198, #200, #201) | Open Client > Profile > Custom Fields section |
| Payment methods | Open Client > Pay Methods tab |
## Salesforce Navigation Quick Reference
| What You're Looking For | Where to Go in Salesforce |
| -------------------------------- | ----------------------------------------------------------------------------------------- |
| A customer's account | Search globally by email or Customer Number (`SF_Account_No__c`), or Accounts tab |
| Portal status fields | Open Account > check `Portal_Status__c`, `Portal_Registration_Source__c`, `WH_Account__c` |
| WHMCS link verification | Open Account > check `WH_Account__c` contains WHMCS Client ID |
| Internet eligibility | Open Account > check `Internet_Eligibility__c` and related date fields |
| ID verification status | Open Account > check `Id_Verification_Status__c` and related fields |
| Orders for a customer | Open Account > Related > Orders |
| Opportunities for a customer | Open Account > Related > Opportunities |
| Opportunity → WHMCS link | Open Opportunity > check `WHMCS_Service_ID__c` |
| Cancellation status | Open Opportunity > check `StageName` for "△Cancelling" or "Cancelled" |
| Support cases (customer-visible) | Cases tab > filter Origin = "Portal Support" |
| Internal notification cases | Cases tab > filter Origin = "Portal Notification" |
| Case comments/replies | Open a Case > Related > Case Comments or Email Messages |
| Uploaded documents | Open Account > Related > Files (ContentVersion) |
| Product catalog | Products tab (Product2 records) > check `Portal_Accessible__c` = true |

View File

@ -297,10 +297,10 @@ export const migrateWhmcsAccountRequestSchema = z.object({
sessionToken: z.string().min(1, SESSION_TOKEN_REQUIRED), sessionToken: z.string().min(1, SESSION_TOKEN_REQUIRED),
/** Password for the new portal account (will also sync to WHMCS) */ /** Password for the new portal account (will also sync to WHMCS) */
password: passwordSchema, password: passwordSchema,
/** Date of birth */ /** Date of birth (optional — WHMCS already has this for existing customers) */
dateOfBirth: isoDateOnlySchema, dateOfBirth: isoDateOnlySchema.optional(),
/** Gender */ /** Gender (optional — WHMCS already has this for existing customers) */
gender: genderEnum, gender: genderEnum.optional(),
/** Accept terms of service */ /** Accept terms of service */
acceptTerms: z.boolean().refine(val => val === true, { acceptTerms: z.boolean().refine(val => val === true, {
message: ACCEPT_TERMS_REQUIRED, message: ACCEPT_TERMS_REQUIRED,

View File

@ -1 +0,0 @@
e35685ef6ccb89f970ef919f003aa30b5738452dec6cb9ccdeb82d08dba02b3c /home/barsa/projects/customer_portal/customer-portal/portal-backend.latest.tar

View File

@ -1 +0,0 @@
9f24a647acd92c8e9300eed9eb1a73aceb75713a769e380be02a413fb551e916 /home/barsa/projects/customer_portal/customer-portal/portal-frontend.latest.tar