diff --git a/docs/architecture/PUBLIC-CATALOG-TASKS.md b/docs/architecture/PUBLIC-CATALOG-TASKS.md new file mode 100644 index 00000000..55cdb475 --- /dev/null +++ b/docs/architecture/PUBLIC-CATALOG-TASKS.md @@ -0,0 +1,419 @@ +# Public Catalog & Unified Checkout - Task Checklist + +> **Related**: See [PUBLIC-CATALOG-UNIFIED-CHECKOUT.md](./PUBLIC-CATALOG-UNIFIED-CHECKOUT.md) for full design document. + +## Quick Reference + +- **Total Effort**: ~7 weeks +- **Priority**: P0 (Critical), P1 (Important), P2 (Nice to have) +- **Status Legend**: ⬜ Todo, 🟡 In Progress, ✅ Done, ❌ Blocked +- **Branch**: `Homepage` + +### Current Progress + +``` +✅ Phase 0 (SF Permissions) → Phase 1 → Phase 2 → Phase 3 → Phase 4 → Phase 5 → Launch + ↑ + START HERE +``` + +### Critical Path + +Phase 4 (Salesforce Account Creation) remains the most complex phase, but **SF permissions are now verified** ✅ + +--- + +## Phase 0: Preparation (4 days) + +| # | Task | Priority | Effort | Status | Notes | +| --- | ---------------------------------------- | -------- | ------ | ------ | --------------------------- | +| 0.1 | Create feature branch `feature/homepage` | P0 | 1h | ✅ | Done - on Homepage branch | +| 0.2 | Document current catalog user flow | P0 | 2h | ⬜ | Screenshots + notes | +| 0.3 | Document current checkout user flow | P0 | 2h | ⬜ | Screenshots + notes | +| 0.4 | Set up feature flags infrastructure | P0 | 4h | ⬜ | Use env vars initially | +| 0.5 | Verify SF integration user permissions | P0 | 3h | ✅ | Done - permissions verified | +| 0.6 | Add "Portal Checkout" to SF picklist | P0 | 1h | ✅ | Done - picklist updated | +| 0.7 | Create test Salesforce sandbox accounts | P0 | 2h | ⬜ | For testing order flow | +| 0.8 | Create test WHMCS accounts | P0 | 2h | ⬜ | For payment testing | +| 0.9 | Document rollback/cleanup procedures | P0 | 2h | ⬜ | For orphaned records | + +### ✅ Salesforce Permissions - VERIFIED + +The following permissions have been confirmed: + +| Object | Permission | Status | +| ------------------------------- | ---------- | ------------------------------ | +| Account | CREATE | ✅ Verified | +| Account | UPDATE | ✅ Verified | +| Contact | CREATE | ✅ Verified | +| SF_Account_No\_\_c | READ/WRITE | ✅ Verified | +| WH_Account\_\_c | READ/WRITE | ✅ Verified | +| Portal_Status\_\_c | WRITE | ✅ Verified | +| Portal_Registration_Source\_\_c | WRITE | ✅ Verified (picklist updated) | + +--- + +## Phase 1: Public Catalog (5 days) + +### 1.1 Frontend - Routes & Layout + +| # | Task | Priority | Effort | Status | Notes | +| ------ | ----------------------------------------------------- | -------- | ------ | ------ | ----------------------------- | +| 1.1.1 | Create `CatalogLayout` component | P0 | 4h | ⬜ | Hybrid header (public + auth) | +| 1.1.2 | Create `CatalogHeader` component | P0 | 2h | ⬜ | Logo, nav, sign-in button | +| 1.1.3 | Create `(public)/catalog/layout.tsx` | P0 | 1h | ⬜ | Use CatalogLayout | +| 1.1.4 | Create `(public)/catalog/page.tsx` | P0 | 2h | ⬜ | Copy from authenticated | +| 1.1.5 | Create `(public)/catalog/internet/page.tsx` | P0 | 2h | ⬜ | Copy from authenticated | +| 1.1.6 | Create `(public)/catalog/internet/configure/page.tsx` | P0 | 2h | ⬜ | Copy from authenticated | +| 1.1.7 | Create `(public)/catalog/sim/page.tsx` | P0 | 2h | ⬜ | Copy from authenticated | +| 1.1.8 | Create `(public)/catalog/sim/configure/page.tsx` | P0 | 2h | ⬜ | Copy from authenticated | +| 1.1.9 | Create `(public)/catalog/vpn/page.tsx` | P0 | 1h | ⬜ | Copy from authenticated | +| 1.1.10 | Update internal links in catalog components | P0 | 2h | ⬜ | Ensure relative paths work | + +### 1.2 Backend - Public API + +| # | Task | Priority | Effort | Status | Notes | +| ----- | --------------------------------------------- | -------- | ------ | ------ | ----------------------- | +| 1.2.1 | Add `@Public()` to `CatalogController` | P0 | 0.5h | ⬜ | `catalog.controller.ts` | +| 1.2.2 | Update catalog services to handle null userId | P0 | 1h | ⬜ | Defensive coding | +| 1.2.3 | Add rate limiting to public catalog endpoints | P0 | 1h | ⬜ | Prevent abuse | +| 1.2.4 | Test catalog API without auth token | P0 | 1h | ⬜ | Manual + automated | + +### 1.3 Integration + +| # | Task | Priority | Effort | Status | Notes | +| ----- | -------------------------------------------- | -------- | ------ | ------ | ---------------------- | +| 1.3.1 | Update `catalogService` to work without auth | P0 | 1h | ⬜ | Frontend service | +| 1.3.2 | Test full catalog flow without login | P0 | 2h | ⬜ | All product types | +| 1.3.3 | Add "Proceed to Checkout" CTA | P0 | 2h | ⬜ | Redirects to /checkout | + +--- + +## Phase 2: Checkout Store (3 days) + +| # | Task | Priority | Effort | Status | Notes | +| --- | --------------------------------------- | -------- | ------ | ------ | ----------------------- | +| 2.1 | Define `CartItem` TypeScript interface | P0 | 1h | ⬜ | In domain package | +| 2.2 | Define `GuestInfo` TypeScript interface | P0 | 1h | ⬜ | In domain package | +| 2.3 | Create `checkout.store.ts` with Zustand | P0 | 2h | ⬜ | With persist middleware | +| 2.4 | Implement cart item serialization | P0 | 2h | ⬜ | Handle complex objects | +| 2.5 | Implement cart validation utilities | P0 | 2h | ⬜ | Zod schemas | +| 2.6 | Create `useCartRecovery` hook | P1 | 2h | ⬜ | Detect stale carts | +| 2.7 | Write unit tests for checkout store | P0 | 4h | ⬜ | Full coverage | +| 2.8 | Test localStorage persistence | P0 | 1h | ⬜ | Refresh, close browser | + +--- + +## Phase 3: Unified Checkout UI (8 days) + +### 3.1 Checkout Shell + +| # | Task | Priority | Effort | Status | Notes | +| ----- | ------------------------------------ | -------- | ------ | ------ | --------------- | +| 3.1.1 | Create `(public)/checkout/page.tsx` | P0 | 1h | ⬜ | Shell page | +| 3.1.2 | Create `CheckoutLayout` component | P0 | 2h | ⬜ | Minimal header | +| 3.1.3 | Create `CheckoutProgress` component | P0 | 2h | ⬜ | Step indicator | +| 3.1.4 | Create `CheckoutWizard` component | P0 | 3h | ⬜ | Step management | +| 3.1.5 | Create `OrderSummaryCard` component | P0 | 2h | ⬜ | Sidebar summary | +| 3.1.6 | Create `EmptyCartRedirect` component | P0 | 1h | ⬜ | Handle no cart | + +### 3.2 Account Step + +| # | Task | Priority | Effort | Status | Notes | +| ----- | ------------------------------------- | -------- | ------ | ------ | -------------------- | +| 3.2.1 | Create `AccountStep` component | P0 | 3h | ⬜ | Parent component | +| 3.2.2 | Create `GuestInfoForm` component | P0 | 3h | ⬜ | New customer form | +| 3.2.3 | Create `SignInForm` embedded variant | P0 | 2h | ⬜ | Reuse existing | +| 3.2.4 | Implement mode switching (new/signin) | P0 | 1h | ⬜ | Toggle between forms | +| 3.2.5 | Add form validation | P0 | 2h | ⬜ | Zod + useZodForm | + +### 3.3 Address Step + +| # | Task | Priority | Effort | Status | Notes | +| ----- | -------------------------------- | -------- | ------ | ------ | --------------- | +| 3.3.1 | Create `AddressStep` component | P0 | 2h | ⬜ | Wrapper | +| 3.3.2 | Integrate existing `AddressForm` | P0 | 2h | ⬜ | Reuse component | +| 3.3.3 | Add postal code auto-fill (JP) | P1 | 3h | ⬜ | External API | +| 3.3.4 | Add address validation | P0 | 2h | ⬜ | Required fields | + +### 3.4 Payment Step + +| # | Task | Priority | Effort | Status | Notes | +| ----- | --------------------------------------- | -------- | ------ | ------ | --------------- | +| 3.4.1 | Create `PaymentStep` component | P0 | 3h | ⬜ | Main component | +| 3.4.2 | Implement WHMCS SSO link opening | P0 | 2h | ⬜ | New tab | +| 3.4.3 | Create `usePaymentPolling` hook | P0 | 2h | ⬜ | Poll for method | +| 3.4.4 | Add focus listener for return detection | P0 | 1h | ⬜ | Window focus | +| 3.4.5 | Create `PaymentMethodDisplay` component | P0 | 1h | ⬜ | Show added card | +| 3.4.6 | Add loading/waiting states | P0 | 1h | ⬜ | UX polish | + +### 3.5 Review Step + +| # | Task | Priority | Effort | Status | Notes | +| ----- | ----------------------------------- | -------- | ------ | ------ | ------------ | +| 3.5.1 | Create `ReviewStep` component | P0 | 3h | ⬜ | Summary view | +| 3.5.2 | Create `OrderItemsReview` component | P0 | 2h | ⬜ | Cart items | +| 3.5.3 | Create `AddressReview` component | P0 | 1h | ⬜ | Show address | +| 3.5.4 | Create `PaymentReview` component | P0 | 1h | ⬜ | Show payment | +| 3.5.5 | Add terms acceptance checkbox | P0 | 0.5h | ⬜ | Required | +| 3.5.6 | Create submit order handler | P0 | 2h | ⬜ | API call | +| 3.5.7 | Add loading state during submission | P0 | 1h | ⬜ | UX | + +### 3.6 Confirmation + +| # | Task | Priority | Effort | Status | Notes | +| ----- | -------------------------------------------- | -------- | ------ | ------ | ------------- | +| 3.6.1 | Create `(public)/checkout/complete/page.tsx` | P0 | 2h | ⬜ | Success page | +| 3.6.2 | Create `OrderConfirmation` component | P0 | 2h | ⬜ | Order details | +| 3.6.3 | Add "Go to Dashboard" CTA | P0 | 0.5h | ⬜ | Navigation | +| 3.6.4 | Clear checkout store on completion | P0 | 0.5h | ⬜ | Cleanup | + +--- + +## Phase 4: Backend - Checkout Registration (7 days) + +> **CRITICAL**: Salesforce Account creation is SYNCHRONOUS and REQUIRED. +> Every new customer MUST have a Salesforce Account for business tracking. + +### 4.1 Domain Schema Changes + +| # | Task | Priority | Effort | Status | Notes | +| ----- | -------------------------------------------- | -------- | ------ | ------ | ---------------------- | +| 4.1.1 | Make `sfNumber` optional in signup schema | P0 | 1h | ⬜ | `packages/domain/auth` | +| 4.1.2 | Create `checkoutRegisterSchema` | P0 | 1h | ⬜ | New schema | +| 4.1.3 | Create `checkoutSubmitSchema` | P0 | 1h | ⬜ | Order from cart | +| 4.1.4 | Create `CreateSalesforceAccountRequest` type | P0 | 0.5h | ⬜ | SF Account payload | +| 4.1.5 | Create `CreateSalesforceContactRequest` type | P0 | 0.5h | ⬜ | SF Contact payload | +| 4.1.6 | Update type exports | P0 | 0.5h | ⬜ | Index files | +| 4.1.7 | Build domain package | P0 | 0.5h | ⬜ | Verify compilation | + +### 4.2 Salesforce Account Service Updates + +| # | Task | Priority | Effort | Status | Notes | +| ----- | ------------------------------------ | -------- | ------ | ------ | ---------------------- | +| 4.2.1 | Add `createAccount()` method | P0 | 3h | ⬜ | Creates SF Account | +| 4.2.2 | Add `createContact()` method | P0 | 2h | ⬜ | Creates SF Contact | +| 4.2.3 | Add `generateAccountNumber()` method | P0 | 2h | ⬜ | Auto-gen PNNNNNNN | +| 4.2.4 | Add `findByEmail()` method | P0 | 1h | ⬜ | Check for duplicates | +| 4.2.5 | Test SF Account creation in sandbox | P0 | 2h | ⬜ | Verify permissions | +| 4.2.6 | Test SF Contact creation in sandbox | P0 | 1h | ⬜ | Verify link to Account | +| 4.2.7 | Verify WH_Account\_\_c update works | P0 | 1h | ⬜ | Link SF → WHMCS | + +### 4.3 Checkout Service + +| # | Task | Priority | Effort | Status | Notes | +| ------ | ------------------------------------------ | -------- | ------ | ------ | ------------------- | +| 4.3.1 | Create `CheckoutService` class | P0 | 1h | ⬜ | Business logic | +| 4.3.2 | Implement `registerForCheckout()` - Step 1 | P0 | 1h | ⬜ | Create SF Account | +| 4.3.3 | Implement `registerForCheckout()` - Step 2 | P0 | 1h | ⬜ | Create SF Contact | +| 4.3.4 | Implement `registerForCheckout()` - Step 3 | P0 | 1h | ⬜ | Create WHMCS Client | +| 4.3.5 | Implement `registerForCheckout()` - Step 4 | P0 | 0.5h | ⬜ | Link SF to WHMCS | +| 4.3.6 | Implement `registerForCheckout()` - Step 5 | P0 | 1h | ⬜ | Create Portal User | +| 4.3.7 | Implement `registerForCheckout()` - Step 6 | P0 | 0.5h | ⬜ | Create ID Mapping | +| 4.3.8 | Implement `registerForCheckout()` - Step 7 | P0 | 0.5h | ⬜ | Generate tokens | +| 4.3.9 | Implement `rollbackRegistration()` method | P0 | 2h | ⬜ | Compensating txns | +| 4.3.10 | Implement `getPaymentStatus()` method | P0 | 1h | ⬜ | Check WHMCS | +| 4.3.11 | Add comprehensive logging | P0 | 1h | ⬜ | Debug each step | + +### 4.4 Checkout Controller + +| # | Task | Priority | Effort | Status | Notes | +| ----- | ------------------------------------------- | -------- | ------ | ------ | -------------- | +| 4.4.1 | Create `CheckoutController` class | P0 | 1h | ⬜ | New controller | +| 4.4.2 | Add `POST /checkout/register` endpoint | P0 | 1h | ⬜ | Public | +| 4.4.3 | Add `GET /checkout/payment-status` endpoint | P0 | 0.5h | ⬜ | Authenticated | +| 4.4.4 | Add `POST /checkout/submit` endpoint | P0 | 0.5h | ⬜ | Authenticated | +| 4.4.5 | Add rate limiting (5/min) | P0 | 0.5h | ⬜ | Prevent abuse | +| 4.4.6 | Create `CheckoutModule` | P0 | 0.5h | ⬜ | Nest module | +| 4.4.7 | Register in `AppModule` | P0 | 0.5h | ⬜ | Wire up | + +### 4.5 Testing + +| # | Task | Priority | Effort | Status | Notes | +| ----- | ------------------------------------ | -------- | ------ | ------ | -------------------- | +| 4.5.1 | Unit test: SF Account creation | P0 | 1h | ⬜ | Mock SF API | +| 4.5.2 | Unit test: SF Contact creation | P0 | 1h | ⬜ | Mock SF API | +| 4.5.3 | Unit test: Account number generation | P0 | 1h | ⬜ | Sequence logic | +| 4.5.4 | Unit test: Full registration flow | P0 | 2h | ⬜ | Happy path | +| 4.5.5 | Unit test: Rollback scenarios | P0 | 2h | ⬜ | Failure at each step | +| 4.5.6 | Integration test: SF sandbox | P0 | 2h | ⬜ | Real SF calls | +| 4.5.7 | Integration test: Full flow | P0 | 2h | ⬜ | SF + WHMCS + Portal | + +--- + +## Phase 5: Integration Testing (5 days) + +| # | Task | Priority | Effort | Status | Notes | +| ---- | ---------------------------------------- | -------- | ------ | ------ | ------------------- | +| 5.1 | Wire up checkout UI to register endpoint | P0 | 2h | ⬜ | API integration | +| 5.2 | Implement auto-login after registration | P0 | 2h | ⬜ | Token handling | +| 5.3 | Wire up order submission | P0 | 2h | ⬜ | API integration | +| 5.4 | E2E test: New customer full flow | P0 | 4h | ⬜ | Playwright | +| 5.5 | E2E test: Existing customer flow | P0 | 2h | ⬜ | Sign-in path | +| 5.6 | E2E test: Cart recovery | P1 | 2h | ⬜ | Abandon + resume | +| 5.7 | E2E test: Error scenarios | P0 | 3h | ⬜ | Network, validation | +| 5.8 | Performance test checkout | P1 | 2h | ⬜ | Load testing | +| 5.9 | Security review | P0 | 4h | ⬜ | Auth, CSRF, etc. | +| 5.10 | Fix bugs from testing | P0 | 8h | ⬜ | Buffer time | + +--- + +## Phase 6: Public Support (3 days) + +| # | Task | Priority | Effort | Status | Notes | +| --- | ------------------------------------------ | -------- | ------ | ------ | ------------ | +| 6.1 | Create `(public)/support/page.tsx` | P1 | 2h | ⬜ | FAQ landing | +| 6.2 | Create `PublicSupportView` component | P1 | 3h | ⬜ | Content | +| 6.3 | Create `(public)/support/contact/page.tsx` | P1 | 1h | ⬜ | Form page | +| 6.4 | Create `PublicContactForm` component | P1 | 3h | ⬜ | Form UI | +| 6.5 | Create `publicContactSchema` | P1 | 0.5h | ⬜ | Validation | +| 6.6 | Add `POST /support/contact` endpoint | P1 | 2h | ⬜ | Public API | +| 6.7 | Implement Salesforce Lead creation | P1 | 3h | ⬜ | Integration | +| 6.8 | Add rate limiting to contact endpoint | P1 | 0.5h | ⬜ | Prevent spam | +| 6.9 | Test public contact flow | P1 | 1h | ⬜ | Full flow | + +--- + +## Phase 7: Polish & Launch (4 days) + +### 7.1 Error Handling + +| # | Task | Priority | Effort | Status | Notes | +| ----- | ------------------------- | -------- | ------ | ------ | -------------------- | +| 7.1.1 | Review all error messages | P0 | 2h | ⬜ | User-friendly | +| 7.1.2 | Add error boundaries | P0 | 1h | ⬜ | React boundaries | +| 7.1.3 | Add retry mechanisms | P1 | 2h | ⬜ | Network errors | +| 7.1.4 | Add fallback UI states | P0 | 2h | ⬜ | Graceful degradation | + +### 7.2 UX Polish + +| # | Task | Priority | Effort | Status | Notes | +| ----- | ------------------------- | -------- | ------ | ------ | ----------------------- | +| 7.2.1 | Add loading skeletons | P0 | 2h | ⬜ | All async states | +| 7.2.2 | Add transition animations | P2 | 2h | ⬜ | Step transitions | +| 7.2.3 | Mobile responsive testing | P0 | 2h | ⬜ | All breakpoints | +| 7.2.4 | Accessibility audit | P1 | 2h | ⬜ | Screen reader, keyboard | + +### 7.3 Analytics & Monitoring + +| # | Task | Priority | Effort | Status | Notes | +| ----- | --------------------------- | -------- | ------ | ------ | ----------------- | +| 7.3.1 | Add funnel tracking events | P1 | 2h | ⬜ | Google Analytics | +| 7.3.2 | Add error tracking | P1 | 1h | ⬜ | Sentry or similar | +| 7.3.3 | Create conversion dashboard | P1 | 2h | ⬜ | Metrics | + +### 7.4 Documentation + +| # | Task | Priority | Effort | Status | Notes | +| ----- | ------------------------------ | -------- | ------ | ------ | ---------- | +| 7.4.1 | Update STRUCTURE.md | P1 | 1h | ⬜ | New routes | +| 7.4.2 | Update catalog-and-checkout.md | P1 | 2h | ⬜ | New flow | +| 7.4.3 | Create feature announcement | P1 | 1h | ⬜ | For users | + +### 7.5 Launch Prep + +| # | Task | Priority | Effort | Status | Notes | +| ----- | -------------------------------- | -------- | ------ | ------ | ---------------- | +| 7.5.1 | Configure feature flags for prod | P0 | 1h | ⬜ | Gradual rollout | +| 7.5.2 | Update production env vars | P0 | 0.5h | ⬜ | If needed | +| 7.5.3 | Create rollback procedure | P0 | 1h | ⬜ | Document steps | +| 7.5.4 | Prepare monitoring alerts | P0 | 1h | ⬜ | Error rate, etc. | +| 7.5.5 | Schedule launch window | P0 | 0.5h | ⬜ | With team | + +--- + +## Definition of Done + +Each task is considered **Done** when: + +- [ ] Code is written and follows project conventions +- [ ] Unit tests pass (if applicable) +- [ ] Code is reviewed and approved +- [ ] Feature works in staging environment +- [ ] No TypeScript errors +- [ ] No ESLint warnings +- [ ] Responsive design verified (if UI) + +--- + +## Dependencies Graph + +``` +Phase 0 ✅ (SF Permissions verified, branch created) + │ + ↓ +Phase 1 ──→ Phase 2 ──→ Phase 3 + │ + ↓ + Phase 4 (SF Account Creation) + │ + ↓ + Phase 5 (Integration Testing) + │ + ├──────────┬──────────┐ + ↓ │ ↓ + Phase 6 │ Phase 7 + │ │ │ + └──────────┴──────────┘ + ↓ + LAUNCH +``` + +### Phase Dependencies Detail + +| Phase | Depends On | Blocks | +| ------- | ------------------ | -------------------------------- | +| Phase 0 | - | All phases | +| Phase 1 | Phase 0 | Phase 3 (checkout needs catalog) | +| Phase 2 | Phase 0 | Phase 3 (checkout needs cart) | +| Phase 3 | Phase 1, 2 | Phase 5 | +| Phase 4 | Phase 0 (SF perms) | Phase 5 | +| Phase 5 | Phase 3, 4 | Phase 7 | +| Phase 6 | Phase 0 | Launch | +| Phase 7 | Phase 5 | Launch | + +--- + +## Risk Register + +| Risk | Likelihood | Impact | Mitigation | Status | +| ------------------------------------------- | ---------- | ------------ | ------------------------------------- | ------------ | +| **SF Account creation fails** | Medium | **Critical** | Retry logic, clear errors, monitoring | Open | +| ~~SF permission issues~~ | ~~Medium~~ | ~~Critical~~ | ~~Verify in Phase 0~~ | ✅ Mitigated | +| **Partial registration (orphaned records)** | Medium | High | Rollback logic, cleanup procedures | Open | +| SF API rate limits | Low | High | Monitor usage, batch if needed | Open | +| WHMCS SSO reliability | Medium | High | Add retry logic, clear error messages | Open | +| Performance regression | Low | Medium | Load test before launch | Open | +| Security vulnerability | Low | Critical | Security review, pen test | Open | +| Duplicate SF Accounts created | Low | Medium | Check by email before creating | Open | +| User confusion with new flow | Medium | Low | Clear UX, help text | Open | + +### Salesforce-Specific Risks + +| Risk | Mitigation | Status | +| -------------------------------------------- | ------------------------------------- | ----------- | +| ~~Integration user lacks CREATE on Account~~ | ~~Test in Phase 0~~ | ✅ Verified | +| Required fields missing in SF org | Document all fields, validate payload | Open | +| Customer number collision | Use SF auto-number or query for max | Open | +| SF sandbox differs from production | Test in production-like sandbox | Open | +| SF API timeout during registration | Add timeout handling, retry logic | Open | + +--- + +## Contacts + +| Role | Name | Responsibility | +| ------------ | ---- | ------------------------ | +| Tech Lead | TBD | Architecture decisions | +| Frontend Dev | TBD | UI implementation | +| Backend Dev | TBD | API implementation | +| QA | TBD | Testing | +| Product | TBD | Requirements, acceptance | + +--- + +## Changelog + +| Date | Change | Author | +| ---------- | ------------------------- | ------ | +| 2024-12-17 | Initial task list created | - | diff --git a/docs/architecture/PUBLIC-CATALOG-UNIFIED-CHECKOUT.md b/docs/architecture/PUBLIC-CATALOG-UNIFIED-CHECKOUT.md new file mode 100644 index 00000000..70d642fe --- /dev/null +++ b/docs/architecture/PUBLIC-CATALOG-UNIFIED-CHECKOUT.md @@ -0,0 +1,1421 @@ +# Public Catalog & Unified Checkout - Development Plan + +> **Status**: Planning +> **Created**: 2024-12-17 +> **Epic**: Transform Portal into Public-Facing Website with E-commerce Checkout + +## Executive Summary + +This document outlines the development plan to transform the customer portal from an authenticated-only application into a public-facing website where users can: + +1. **Browse catalog without authentication** +2. **Configure products without an account** +3. **Complete checkout with seamless account creation** +4. **Place orders in a single, unified flow** + +The goal is to eliminate friction in the customer acquisition funnel by making registration feel like a natural part of the ordering process, rather than a prerequisite. + +--- + +## Table of Contents + +1. [Current State Analysis](#current-state-analysis) +2. [Target Architecture](#target-architecture) +3. [User Journeys](#user-journeys) +4. [Technical Design](#technical-design) +5. [Development Phases](#development-phases) +6. [API Changes](#api-changes) +7. [Database Changes](#database-changes) +8. [Testing Strategy](#testing-strategy) +9. [Rollout Plan](#rollout-plan) +10. [Risks & Mitigations](#risks--mitigations) +11. [Success Metrics](#success-metrics) + +--- + +## Current State Analysis + +### Current Architecture + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ CURRENT FLOW │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. User visits portal │ +│ 2. Must sign up (requires sfNumber from Salesforce) │ +│ 3. Must add payment method (WHMCS SSO) │ +│ 4. Can browse catalog │ +│ 5. Can configure and order │ +│ │ +│ Problems: │ +│ • sfNumber requirement blocks new customers │ +│ • Registration is separate from ordering intent │ +│ • High friction = low conversion │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +### Critical Integration Requirements + +**Salesforce is the source of truth** for customer data and must be created for every new customer: + +| System | Role | Requirement | +| -------------- | ------------------------------ | ------------------------------------------------------- | +| **Salesforce** | CRM, customer tracking, orders | Account + Contact MUST be created for every customer | +| **WHMCS** | Billing, invoices, payments | Client MUST be linked to SF Account via `WH_Account__c` | +| **Portal DB** | Authentication, session | User record links all systems via `id_mappings` | + +The relationship chain: + +``` +Salesforce Account (has SF_Account_No__c) + └── WH_Account__c → WHMCS Client ID + └── Portal User → id_mappings → whmcsClientId + sfAccountId +``` + +### Current Route Structure + +``` +apps/portal/src/app/ +├── (public)/ # Only auth pages +│ ├── auth/ +│ │ ├── login/ +│ │ ├── signup/ # Requires sfNumber +│ │ └── ... +│ └── page.tsx # Landing page +│ +└── (authenticated)/ # Everything else requires auth + ├── catalog/ # ← Should be public! + ├── checkout/ + ├── dashboard/ + ├── billing/ + ├── subscriptions/ + ├── orders/ + └── support/ +``` + +### Current Dependencies + +| Component | Current Behavior | Issue | +| --------------- | ------------------------------------------ | ---------------------------- | +| Catalog API | Works without auth (returns generic plans) | ✅ Ready | +| Catalog UI | Requires authentication | ❌ Needs change | +| Checkout | Requires auth + payment method | ❌ Needs redesign | +| Signup | Requires sfNumber | ❌ Needs to be optional | +| Payment Methods | WHMCS SSO only | ⚠️ Constraint to work around | + +--- + +## Target Architecture + +### New Route Structure + +``` +apps/portal/src/app/ +├── (public)/ +│ ├── page.tsx # Homepage/Landing +│ ├── layout.tsx # PublicShell +│ │ +│ ├── catalog/ # ★ PUBLIC CATALOG +│ │ ├── page.tsx # Catalog home +│ │ ├── layout.tsx # CatalogLayout +│ │ ├── internet/ +│ │ │ ├── page.tsx # Internet plans +│ │ │ └── configure/page.tsx # Configure internet +│ │ ├── sim/ +│ │ │ ├── page.tsx # SIM plans +│ │ │ └── configure/page.tsx # Configure SIM +│ │ └── vpn/ +│ │ └── page.tsx # VPN plans +│ │ +│ ├── checkout/ # ★ UNIFIED CHECKOUT +│ │ ├── page.tsx # Multi-step checkout +│ │ └── complete/page.tsx # Order confirmation +│ │ +│ ├── support/ # ★ PUBLIC SUPPORT +│ │ ├── page.tsx # FAQ/Help center +│ │ └── contact/page.tsx # Contact form +│ │ +│ └── auth/ +│ ├── login/page.tsx # Simplified login +│ └── forgot-password/page.tsx +│ +├── (authenticated)/ +│ ├── layout.tsx # AppShell +│ ├── dashboard/ # Customer dashboard +│ ├── orders/ # Order history +│ ├── subscriptions/ # Manage subscriptions +│ ├── billing/ # Invoices & payments +│ └── support/ +│ └── cases/ # Support case management +``` + +### Unified Checkout Flow + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ NEW FLOW │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ PUBLIC CATALOG │ +│ ┌────────────────────────────────────────────────────────────┐ │ +│ │ Browse Plans → Configure → "Proceed to Checkout" │ │ +│ │ (saves to localStorage) │ │ +│ └────────────────────────────────────────────────────────────┘ │ +│ ↓ │ +│ UNIFIED CHECKOUT (/checkout) │ +│ ┌────────────────────────────────────────────────────────────┐ │ +│ │ Step 1: Account │ │ +│ │ • "Already have account? Sign in" OR │ │ +│ │ • Collect: email, name, phone, password │ │ +│ ├────────────────────────────────────────────────────────────┤ │ +│ │ Step 2: Address │ │ +│ │ • Collect service/shipping address │ │ +│ │ (Account created in background after this step) │ │ +│ ├────────────────────────────────────────────────────────────┤ │ +│ │ Step 3: Payment │ │ +│ │ • Open WHMCS to add payment method │ │ +│ │ • Poll for completion, show confirmation │ │ +│ ├────────────────────────────────────────────────────────────┤ │ +│ │ Step 4: Review & Submit │ │ +│ │ • Order summary, T&C acceptance, submit │ │ +│ └────────────────────────────────────────────────────────────┘ │ +│ ↓ │ +│ ORDER CONFIRMATION → Redirect to /orders/[id] │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## User Journeys + +### Journey 1: New Customer - Internet Order + +```mermaid +journey + title New Customer Orders Internet + section Browse + Visit homepage: 5: Customer + View internet plans: 5: Customer + Select 10Gbps plan: 5: Customer + Configure addons: 4: Customer + section Checkout + Enter email & name: 4: Customer + Enter address: 4: Customer + Add payment (WHMCS): 3: Customer + Review order: 5: Customer + Submit order: 5: Customer + section Post-Order + View confirmation: 5: Customer + Access dashboard: 5: Customer +``` + +### Journey 2: Existing Customer - Quick Order + +```mermaid +journey + title Existing Customer Quick Order + section Browse + Visit catalog: 5: Customer + Select SIM plan: 5: Customer + Configure: 5: Customer + section Checkout + Click "Sign In": 5: Customer + Login: 4: Customer + Verify address: 5: Customer + Confirm payment: 5: Customer + Submit: 5: Customer +``` + +### Journey 3: Abandoned Cart Recovery + +```mermaid +journey + title Cart Recovery + section Initial + Configure product: 5: Customer + Start checkout: 4: Customer + Enter email: 4: Customer + Leave site: 1: Customer + section Recovery + Receive email: 3: Customer + Click link: 4: Customer + Resume checkout: 5: Customer + Complete order: 5: Customer +``` + +--- + +## Technical Design + +### 1. Checkout State Store + +```typescript +// Location: apps/portal/src/features/checkout/stores/checkout.store.ts + +import { create } from "zustand"; +import { persist } from "zustand/middleware"; + +interface CartItem { + orderType: "INTERNET" | "SIM" | "VPN"; + planSku: string; + planName: string; + addonSkus: string[]; + configuration: { + installationType?: string; + simType?: string; + activationType?: string; + mnpDetails?: MnpDetails; + [key: string]: unknown; + }; + pricing: { + monthlyTotal: number; + oneTimeTotal: number; + breakdown: PriceBreakdownItem[]; + }; +} + +interface GuestInfo { + email: string; + firstName: string; + lastName: string; + phone: string; + phoneCountryCode: string; + dateOfBirth?: string; + gender?: "male" | "female" | "other"; + password: string; +} + +interface CheckoutState { + // Cart data + cartItem: CartItem | null; + + // Guest info (pre-registration) + guestInfo: Partial | null; + + // Address + address: Address | null; + + // Registration state + registrationComplete: boolean; + userId: string | null; + + // Payment state + paymentMethodVerified: boolean; + + // Checkout step + currentStep: "account" | "address" | "payment" | "review"; + + // Actions + setCartItem: (item: CartItem) => void; + updateGuestInfo: (info: Partial) => void; + setAddress: (address: Address) => void; + setRegistrationComplete: (userId: string) => void; + setPaymentVerified: (verified: boolean) => void; + setCurrentStep: (step: CheckoutState["currentStep"]) => void; + clear: () => void; +} + +export const useCheckoutStore = create()( + persist( + (set, get) => ({ + cartItem: null, + guestInfo: null, + address: null, + registrationComplete: false, + userId: null, + paymentMethodVerified: false, + currentStep: "account", + + setCartItem: item => set({ cartItem: item }), + updateGuestInfo: info => + set(state => ({ + guestInfo: { ...state.guestInfo, ...info }, + })), + setAddress: address => set({ address }), + setRegistrationComplete: userId => + set({ + registrationComplete: true, + userId, + }), + setPaymentVerified: verified => set({ paymentMethodVerified: verified }), + setCurrentStep: step => set({ currentStep: step }), + clear: () => + set({ + cartItem: null, + guestInfo: null, + address: null, + registrationComplete: false, + userId: null, + paymentMethodVerified: false, + currentStep: "account", + }), + }), + { + name: "checkout-store", + version: 1, + } + ) +); +``` + +### 2. Checkout Page Component Structure + +```typescript +// Location: apps/portal/src/app/(public)/checkout/page.tsx + +export default function CheckoutPage() { + return ( + + + + ); +} + +// Location: apps/portal/src/features/checkout/components/CheckoutWizard.tsx + +export function CheckoutWizard() { + const { currentStep, cartItem } = useCheckoutStore(); + const { isAuthenticated } = useAuthSession(); + + // Redirect if no cart + if (!cartItem) { + return ; + } + + // Skip account step if already authenticated + const effectiveStep = isAuthenticated && currentStep === 'account' + ? 'address' + : currentStep; + + return ( +
+ + +
+
+ {effectiveStep === 'account' && } + {effectiveStep === 'address' && } + {effectiveStep === 'payment' && } + {effectiveStep === 'review' && } +
+ +
+ +
+
+
+ ); +} +``` + +### 3. Account Step with Sign-In Option + +```typescript +// Location: apps/portal/src/features/checkout/components/steps/AccountStep.tsx + +export function AccountStep() { + const [mode, setMode] = useState<'new' | 'signin'>('new'); + const { updateGuestInfo, setCurrentStep } = useCheckoutStore(); + + const handleContinue = async (data: GuestFormData) => { + updateGuestInfo(data); + setCurrentStep('address'); + }; + + const handleSignInSuccess = () => { + // User is now authenticated, skip to address + setCurrentStep('address'); + }; + + return ( +
+ {/* Sign-in prompt */} +
+
+
+

Already have an account?

+

+ Sign in to use your saved information +

+
+ +
+
+ + {mode === 'signin' ? ( + setMode('new')} + embedded + /> + ) : ( + <> +
+
+
+
+
+ + Or continue as new customer + +
+
+ + + + )} +
+ ); +} +``` + +### 4. Payment Step with WHMCS Integration + +```typescript +// Location: apps/portal/src/features/checkout/components/steps/PaymentStep.tsx + +export function PaymentStep() { + const { setPaymentVerified, setCurrentStep, registrationComplete } = useCheckoutStore(); + const { data: paymentMethods, refetch } = usePaymentMethods(); + const createSsoLink = useCreatePaymentMethodsSsoLink(); + const [isWaiting, setIsWaiting] = useState(false); + + // Poll for payment method after opening WHMCS + useEffect(() => { + if (!isWaiting) return; + + const interval = setInterval(async () => { + const result = await refetch(); + if (result.data?.paymentMethods?.length > 0) { + setPaymentVerified(true); + setIsWaiting(false); + } + }, 3000); + + return () => clearInterval(interval); + }, [isWaiting, refetch, setPaymentVerified]); + + // Focus listener for when user returns + useEffect(() => { + const handleFocus = () => { + if (isWaiting) { + void refetch(); + } + }; + window.addEventListener('focus', handleFocus); + return () => window.removeEventListener('focus', handleFocus); + }, [isWaiting, refetch]); + + const handleAddPayment = async () => { + const { url } = await createSsoLink.mutateAsync(); + window.open(url, '_blank'); + setIsWaiting(true); + }; + + const hasPaymentMethod = paymentMethods?.paymentMethods?.length > 0; + + return ( +
+
+
+ +

Payment Method

+
+ + {hasPaymentMethod ? ( +
+ +
+ + Payment method verified +
+
+ ) : ( +
+ + + {isWaiting ? ( + <> +

Waiting for payment method...

+

+ Complete the payment setup in the new tab, then return here. +

+ + + ) : ( + <> +

Add a payment method

+

+ We'll open our secure payment portal to add your card. +

+ + + )} +
+ )} +
+ +
+ + +
+
+ ); +} +``` + +### 5. New BFF Endpoints + +```typescript +// Location: apps/bff/src/modules/checkout/checkout.controller.ts + +@Controller("checkout") +export class CheckoutController { + constructor( + private readonly checkoutService: CheckoutService, + private readonly logger: Logger + ) {} + + /** + * Register a new user during checkout + * + * IMPORTANT: This creates accounts in ALL systems synchronously: + * 1. Salesforce Account + Contact (for CRM tracking) + * 2. WHMCS Client (for billing) + * 3. Portal User (for authentication) + * + * Returns auth tokens so user is immediately logged in + */ + @Post("register") + @Public() + @RateLimit({ limit: 5, ttl: 60 }) + @UsePipes(new ZodValidationPipe(checkoutRegisterSchema)) + async register(@Body() body: CheckoutRegisterRequest): Promise { + this.logger.log("Checkout registration request", { email: body.email }); + return this.checkoutService.registerForCheckout(body); + } + + /** + * Check if current user has valid payment method + * Used by checkout to gate the review step + */ + @Get("payment-status") + async getPaymentStatus(@Request() req: RequestWithUser): Promise { + return this.checkoutService.getPaymentStatus(req.user.id); + } + + /** + * Build cart preview from configuration + * Works for both authenticated and anonymous users + */ + @Post("cart") + @Public() + @UsePipes(new ZodValidationPipe(checkoutBuildCartRequestSchema)) + async buildCart( + @Request() req: RequestWithUser, + @Body() body: CheckoutBuildCartRequest + ): Promise { + const userId = req.user?.id; + return this.checkoutService.buildCart(body, userId); + } + + /** + * Submit order - requires authentication + */ + @Post("submit") + @UsePipes(new ZodValidationPipe(checkoutSubmitSchema)) + async submitOrder( + @Request() req: RequestWithUser, + @Body() body: CheckoutSubmitRequest + ): Promise { + return this.checkoutService.submitOrder(req.user.id, body); + } +} +``` + +### 6. Salesforce Account Creation Service + +**CRITICAL**: Every new customer MUST have a Salesforce Account for business tracking. + +```typescript +// Location: apps/bff/src/integrations/salesforce/services/salesforce-account.service.ts +// ADD these new methods to the existing service + +/** + * Create a new Salesforce Account for a new customer + * This is used when customer signs up through checkout (no existing sfNumber) + */ +async createAccount(data: CreateSalesforceAccountRequest): Promise<{ + accountId: string; + accountNumber: string; +}> { + this.logger.log('Creating new Salesforce Account', { email: data.email }); + + // Generate unique account number (SF_Account_No__c) + const accountNumber = await this.generateAccountNumber(); + + const accountPayload = { + Name: `${data.firstName} ${data.lastName}`, + SF_Account_No__c: accountNumber, + BillingStreet: data.address.address1, + BillingCity: data.address.city, + BillingState: data.address.state, + BillingPostalCode: data.address.postcode, + BillingCountry: data.address.country, + Phone: data.phone, + // Portal tracking fields + [this.portalStatusField]: 'Active', + [this.portalSourceField]: 'Portal Checkout', + // Record type for individual customers + RecordTypeId: this.configService.get('SF_PERSON_ACCOUNT_RECORD_TYPE_ID'), + }; + + try { + const result = await this.connection.sobject('Account').create(accountPayload); + + if (!result.id) { + throw new Error('Salesforce Account creation failed - no ID returned'); + } + + this.logger.log('Salesforce Account created', { + accountId: result.id, + accountNumber + }); + + return { + accountId: result.id, + accountNumber + }; + } catch (error) { + this.logger.error('Failed to create Salesforce Account', { + error: getErrorMessage(error), + email: data.email, + }); + throw new Error('Failed to create customer account in CRM'); + } +} + +/** + * Create a Contact associated with an Account + */ +async createContact(data: CreateSalesforceContactRequest): Promise<{ contactId: string }> { + this.logger.log('Creating Salesforce Contact', { + accountId: data.accountId, + email: data.email + }); + + const contactPayload = { + AccountId: data.accountId, + FirstName: data.firstName, + LastName: data.lastName, + Email: data.email, + Phone: data.phone, + MailingStreet: data.address.address1, + MailingCity: data.address.city, + MailingState: data.address.state, + MailingPostalCode: data.address.postcode, + MailingCountry: data.address.country, + }; + + try { + const result = await this.connection.sobject('Contact').create(contactPayload); + + if (!result.id) { + throw new Error('Salesforce Contact creation failed - no ID returned'); + } + + this.logger.log('Salesforce Contact created', { contactId: result.id }); + return { contactId: result.id }; + } catch (error) { + this.logger.error('Failed to create Salesforce Contact', { + error: getErrorMessage(error), + accountId: data.accountId, + }); + throw new Error('Failed to create customer contact in CRM'); + } +} + +/** + * Generate a unique customer number for new accounts + * Format: PNNNNNNN (P = Portal, 7 digits) + */ +private async generateAccountNumber(): Promise { + // Query for max existing portal account number + const result = await this.connection.query( + `SELECT SF_Account_No__c FROM Account + WHERE SF_Account_No__c LIKE 'P%' + ORDER BY SF_Account_No__c DESC LIMIT 1`, + { label: 'auth:getMaxAccountNumber' } + ); + + let nextNumber = 1000001; // Start from P1000001 + + if (result.totalSize > 0) { + const lastNumber = result.records[0]?.SF_Account_No__c; + if (lastNumber) { + const numPart = parseInt(lastNumber.substring(1), 10); + if (!isNaN(numPart)) { + nextNumber = numPart + 1; + } + } + } + + return `P${nextNumber}`; +} +``` + +### 7. Checkout Registration Service - Full Flow + +```typescript +// Location: apps/bff/src/modules/checkout/checkout.service.ts + +@Injectable() +export class CheckoutService { + constructor( + private readonly salesforceAccountService: SalesforceAccountService, + private readonly whmcsService: WhmcsService, + private readonly usersFacade: UsersFacade, + private readonly mappingsService: MappingsService, + private readonly tokenService: AuthTokenService, + private readonly prisma: PrismaService, + @Inject(Logger) private readonly logger: Logger + ) {} + + /** + * Register a new customer during checkout + * + * This is a critical workflow that creates accounts in ALL systems: + * + * 1. Create Salesforce Account (generates SF_Account_No__c) + * 2. Create Salesforce Contact (linked to Account) + * 3. Create WHMCS Client (for billing) + * 4. Update SF Account with WH_Account__c + * 5. Create Portal User + * 6. Create ID Mapping (links all systems) + * 7. Generate auth tokens + * + * If any step fails, we attempt rollback of previous steps. + */ + async registerForCheckout(data: CheckoutRegisterRequest): Promise { + this.logger.log("Starting checkout registration", { email: data.email }); + + // Track created resources for rollback + let sfAccountId: string | null = null; + let sfContactId: string | null = null; + let sfAccountNumber: string | null = null; + let whmcsClientId: number | null = null; + let portalUserId: string | null = null; + + try { + // Step 1: Create Salesforce Account + this.logger.log("Step 1: Creating Salesforce Account"); + const sfAccount = await this.salesforceAccountService.createAccount({ + firstName: data.firstName, + lastName: data.lastName, + email: data.email, + phone: this.formatPhone(data.phoneCountryCode, data.phone), + address: data.address, + }); + sfAccountId = sfAccount.accountId; + sfAccountNumber = sfAccount.accountNumber; + + // Step 2: Create Salesforce Contact + this.logger.log("Step 2: Creating Salesforce Contact"); + const sfContact = await this.salesforceAccountService.createContact({ + accountId: sfAccountId, + firstName: data.firstName, + lastName: data.lastName, + email: data.email, + phone: this.formatPhone(data.phoneCountryCode, data.phone), + address: data.address, + }); + sfContactId = sfContact.contactId; + + // Step 3: Create WHMCS Client + this.logger.log("Step 3: Creating WHMCS Client"); + const whmcsClient = await this.whmcsService.addClient({ + firstname: data.firstName, + lastname: data.lastName, + email: data.email, + phonenumber: this.formatPhone(data.phoneCountryCode, data.phone), + address1: data.address.address1, + address2: data.address.address2 || "", + city: data.address.city, + state: data.address.state, + postcode: data.address.postcode, + country: data.address.country, + password2: data.password, + customfields: this.buildWhmcsCustomFields(sfAccountNumber), + }); + whmcsClientId = whmcsClient.clientId; + + // Step 4: Update Salesforce Account with WHMCS ID + this.logger.log("Step 4: Linking Salesforce to WHMCS"); + await this.salesforceAccountService.updatePortalFields(sfAccountId, { + whmcsAccountId: whmcsClientId, + status: "Active", + source: "Portal Checkout", + }); + + // Step 5: Create Portal User (in transaction) + this.logger.log("Step 5: Creating Portal User"); + const user = await this.prisma.$transaction(async tx => { + const passwordHash = await argon2.hash(data.password); + + const newUser = await tx.user.create({ + data: { + email: data.email, + passwordHash, + isActive: true, + emailVerified: false, + }, + }); + + // Step 6: Create ID Mapping + await tx.idMapping.create({ + data: { + userId: newUser.id, + whmcsClientId: whmcsClientId!, + sfAccountId: sfAccountId!, + sfContactId: sfContactId!, + }, + }); + + return newUser; + }); + portalUserId = user.id; + + // Step 7: Generate auth tokens + this.logger.log("Step 6: Generating auth tokens"); + const tokens = await this.tokenService.generateTokenPair({ + sub: user.id, + email: user.email, + }); + + this.logger.log("Checkout registration completed successfully", { + userId: user.id, + sfAccountId, + sfAccountNumber, + whmcsClientId, + }); + + return { + success: true, + user: { + id: user.id, + email: user.email, + firstname: data.firstName, + lastname: data.lastName, + }, + session: { + expiresAt: tokens.accessTokenExpiresAt, + refreshExpiresAt: tokens.refreshTokenExpiresAt, + }, + sfAccountNumber, // Return so it can be shown to user + }; + } catch (error) { + this.logger.error("Checkout registration failed, initiating rollback", { + error: getErrorMessage(error), + sfAccountId, + whmcsClientId, + portalUserId, + }); + + // Rollback in reverse order + await this.rollbackRegistration({ + portalUserId, + whmcsClientId, + sfAccountId, + }); + + throw new BadRequestException("Registration failed. Please try again or contact support."); + } + } + + private async rollbackRegistration(resources: { + portalUserId: string | null; + whmcsClientId: number | null; + sfAccountId: string | null; + }) { + // Best-effort rollback - log failures but don't throw + + if (resources.portalUserId) { + try { + await this.prisma.user.delete({ where: { id: resources.portalUserId } }); + this.logger.log("Rollback: Deleted portal user", { userId: resources.portalUserId }); + } catch (e) { + this.logger.error("Rollback failed: Portal user", { error: getErrorMessage(e) }); + } + } + + if (resources.whmcsClientId) { + try { + await this.whmcsService.deleteClient(resources.whmcsClientId); + this.logger.log("Rollback: Deleted WHMCS client", { clientId: resources.whmcsClientId }); + } catch (e) { + this.logger.error("Rollback failed: WHMCS client", { error: getErrorMessage(e) }); + } + } + + // Note: We intentionally do NOT delete the Salesforce Account + // It's better to have an orphaned SF Account that can be cleaned up + // than to lose potential customer data + if (resources.sfAccountId) { + this.logger.warn("Salesforce Account not rolled back (intentional)", { + sfAccountId: resources.sfAccountId, + action: "Manual cleanup may be required", + }); + } + } + + private formatPhone(countryCode: string, phone: string): string { + const cc = countryCode.replace(/\D/g, ""); + const num = phone.replace(/\D/g, ""); + return `+${cc}.${num}`; + } + + private buildWhmcsCustomFields(sfAccountNumber: string): string { + const customerNumberFieldId = this.configService.get("WHMCS_CUSTOMER_NUMBER_FIELD_ID"); + if (!customerNumberFieldId) return ""; + + return `${customerNumberFieldId}|${sfAccountNumber}`; + } +} +``` + +--- + +## Development Phases + +### Phase 0: Preparation (3 days) + +| Task | Owner | Effort | Dependencies | +| ---------------------------------------- | ----- | ------ | ------------ | +| Create feature branch | Dev | 0.5d | - | +| Document current catalog/checkout flows | Dev | 0.5d | - | +| Set up feature flags for gradual rollout | Dev | 1d | - | +| Create test accounts for each scenario | QA | 1d | - | + +### Phase 1: Public Catalog (5 days) + +| Task | Description | Effort | Priority | +| ---------------------------------------------------------- | ------------------------------------ | ------ | -------- | +| **1.1** Create `CatalogLayout` component | Hybrid header for public/auth states | 1d | P0 | +| **1.2** Move catalog routes to `(public)/catalog/` | Copy and adapt existing pages | 1d | P0 | +| **1.3** Add `@Public()` decorator to BFF catalog endpoints | Make API publicly accessible | 0.5d | P0 | +| **1.4** Update catalog service for anonymous users | Handle missing userId gracefully | 0.5d | P0 | +| **1.5** Create "Proceed to Checkout" flow | Save config to store, redirect | 1d | P0 | +| **1.6** Test all catalog pages without auth | E2E testing | 1d | P0 | + +**Deliverable**: Users can browse and configure products without logging in. + +### Phase 2: Checkout Store & Cart (3 days) + +| Task | Description | Effort | Priority | +| ----------------------------------------- | ------------------------------------ | ------ | -------- | +| **2.1** Create checkout Zustand store | With localStorage persistence | 0.5d | P0 | +| **2.2** Implement cart item serialization | Save/restore configuration | 0.5d | P0 | +| **2.3** Add cart validation utilities | Ensure cart is valid before checkout | 0.5d | P0 | +| **2.4** Create cart recovery hook | Detect and restore abandoned carts | 0.5d | P1 | +| **2.5** Unit tests for store | Full coverage | 1d | P0 | + +**Deliverable**: Cart state persists across page reloads and sessions. + +### Phase 3: Unified Checkout UI (8 days) + +| Task | Description | Effort | Priority | +| ------------------------------------------ | --------------------------- | ------ | -------- | +| **3.1** Create `/checkout` page shell | Layout, progress indicator | 0.5d | P0 | +| **3.2** Build `CheckoutWizard` component | Step management, navigation | 1d | P0 | +| **3.3** Build `AccountStep` component | Sign-in + new account form | 1.5d | P0 | +| **3.4** Build `AddressStep` component | Reuse existing address form | 1d | P0 | +| **3.5** Build `PaymentStep` component | WHMCS SSO + polling | 1.5d | P0 | +| **3.6** Build `ReviewStep` component | Summary + terms + submit | 1d | P0 | +| **3.7** Build `OrderSummaryCard` component | Sidebar cart display | 0.5d | P0 | +| **3.8** Build order confirmation page | Success state, next steps | 1d | P0 | + +**Deliverable**: Complete checkout UI with all steps functional. + +### Phase 4: Backend - Checkout Registration (7 days) + +| Task | Description | Effort | Priority | +| --------------------------------------------------------- | -------------------------------------------------- | ------ | -------- | +| **4.1** Update domain schema - make sfNumber optional | Schema changes | 0.5d | P0 | +| **4.2** Add `createAccount()` to SalesforceAccountService | Create SF Account for new customers | 1d | P0 | +| **4.3** Add `createContact()` to SalesforceAccountService | Create SF Contact linked to Account | 0.5d | P0 | +| **4.4** Add `generateAccountNumber()` method | Auto-generate SF_Account_No\_\_c (PNNNNNNN format) | 0.5d | P0 | +| **4.5** Create `CheckoutService` in BFF | Main registration orchestration | 1d | P0 | +| **4.6** Implement `registerForCheckout()` method | Full 7-step registration flow | 1.5d | P0 | +| **4.7** Create `POST /checkout/register` endpoint | Public registration endpoint | 0.5d | P0 | +| **4.8** Implement rollback logic | Compensating transactions on failure | 1d | P0 | +| **4.9** Add comprehensive logging | Track each step for debugging | 0.5d | P0 | + +**Deliverable**: Users can create accounts during checkout with **synchronous** Salesforce Account + Contact + WHMCS Client creation. + +> **IMPORTANT**: Salesforce Account is created FIRST to ensure CRM tracking. The flow is: +> +> 1. Create SF Account (generates customer number) +> 2. Create SF Contact +> 3. Create WHMCS Client +> 4. Link SF Account to WHMCS (update WH_Account\_\_c) +> 5. Create Portal User + ID Mapping +> 6. Return auth tokens + +### Phase 5: Integration & Testing (5 days) + +| Task | Description | Effort | Priority | +| ------------------------------------------------------- | ------------------------- | ------ | -------- | +| **5.1** Integrate checkout UI with BFF endpoints | Wire up API calls | 1d | P0 | +| **5.2** Implement auth token handling post-registration | Auto-login after signup | 0.5d | P0 | +| **5.3** E2E tests - new customer flow | Full journey testing | 1d | P0 | +| **5.4** E2E tests - existing customer flow | Sign-in during checkout | 0.5d | P0 | +| **5.5** E2E tests - cart recovery | Abandoned cart scenarios | 0.5d | P1 | +| **5.6** Performance testing | Load testing checkout | 0.5d | P1 | +| **5.7** Security review | Auth, CSRF, rate limiting | 1d | P0 | + +**Deliverable**: Fully tested, production-ready checkout flow. + +### Phase 6: Public Support (3 days) + +| Task | Description | Effort | Priority | +| --------------------------------------------------- | -------------------------- | ------ | -------- | +| **6.1** Create public support landing page | FAQ, contact info | 1d | P1 | +| **6.2** Create public contact form | No auth required | 1d | P1 | +| **6.3** Add `POST /support/contact` public endpoint | Creates Lead in Salesforce | 1d | P1 | + +**Deliverable**: Non-authenticated users can get help and contact support. + +### Phase 7: Polish & Launch Prep (4 days) + +| Task | Description | Effort | Priority | +| ------------------------------------- | ---------------------------- | ------ | -------- | +| **7.1** Error handling polish | User-friendly error messages | 1d | P0 | +| **7.2** Loading states and skeletons | Smooth UX during async ops | 0.5d | P0 | +| **7.3** Mobile responsiveness testing | All breakpoints | 0.5d | P0 | +| **7.4** Analytics integration | Track funnel, conversions | 0.5d | P1 | +| **7.5** Documentation update | README, guides | 0.5d | P1 | +| **7.6** Feature flag configuration | Gradual rollout setup | 0.5d | P0 | +| **7.7** Production deployment prep | Env configs, monitoring | 0.5d | P0 | + +**Deliverable**: Production-ready feature. + +--- + +## Timeline Summary + +``` +Week 1: Phase 0 (Prep) + Phase 1 (Public Catalog) +Week 2: Phase 2 (Cart) + Phase 3 Start (Checkout UI) +Week 3: Phase 3 Complete (Checkout UI) +Week 4: Phase 4 (Backend - SF Account Creation) +Week 5: Phase 4 Complete + Phase 5 (Integration Testing) +Week 6: Phase 6 (Public Support) + Phase 7 (Polish) +Week 7: Buffer + Launch + +Total: ~7 weeks +``` + +### Critical Path + +The **Salesforce Account creation** (Phase 4) is on the critical path because: + +1. Orders require a valid SF Account ID +2. WHMCS must be linked to SF Account +3. All tracking/reporting depends on SF data + +Ensure SF integration user has proper permissions before starting Phase 4. + +--- + +## API Changes + +### New Endpoints + +| Method | Path | Auth | Description | +| ------ | ------------------------------ | -------- | --------------------------- | +| `POST` | `/api/checkout/register` | Public | Create user during checkout | +| `GET` | `/api/checkout/payment-status` | Required | Check payment method status | +| `POST` | `/api/checkout/cart` | Public | Build cart from config | +| `POST` | `/api/checkout/submit` | Required | Submit order | +| `POST` | `/api/support/contact` | Public | Public contact form | + +### Modified Endpoints + +| Method | Path | Change | +| ------ | ------------------ | ------------------------- | +| `GET` | `/api/catalog/*` | Add `@Public()` decorator | +| `POST` | `/api/auth/signup` | Make sfNumber optional | + +### New Schemas + +```typescript +// packages/domain/checkout/schema.ts + +export const checkoutRegisterSchema = z.object({ + email: z.string().email(), + firstName: z.string().min(1), + lastName: z.string().min(1), + phone: z.string().min(1), + phoneCountryCode: z.string().regex(/^\+\d{1,4}$/), + password: z.string().min(8), + address: addressFormSchema, + acceptTerms: z.literal(true), + marketingConsent: z.boolean().optional(), +}); + +export const checkoutSubmitSchema = z.object({ + orderType: orderTypeSchema, + skus: z.array(z.string()).min(1), + configuration: z.record(z.unknown()).optional(), +}); + +export const publicContactSchema = z.object({ + email: z.string().email(), + name: z.string().min(1), + subject: z.string().min(1), + message: z.string().min(10), + phone: z.string().optional(), +}); +``` + +--- + +## Database Changes + +### Portal Database - No Schema Changes Required + +The current database schema supports this feature: + +- `User` table already stores auth credentials +- `IdMapping` table already has `sfAccountId` and `sfContactId` fields +- No new tables needed + +### Salesforce - New Field Requirements + +Ensure these fields exist and are writable by the integration user: + +| Object | Field | Type | Purpose | +| ------- | ------------------------------- | -------- | ---------------------------------- | +| Account | `SF_Account_No__c` | Text | Unique customer number | +| Account | `WH_Account__c` | Text | WHMCS Client ID link | +| Account | `Portal_Status__c` | Picklist | Active/Inactive | +| Account | `Portal_Registration_Source__c` | Picklist | Add "Portal Checkout" value | +| Contact | Standard fields | - | First, Last, Email, Phone, Address | + +### Salesforce - Customer Number Generation + +New accounts created through checkout will have auto-generated customer numbers: + +- Format: `PNNNNNNN` (P prefix + 7 digits) +- Example: `P1000001`, `P1000002` +- The "P" prefix distinguishes portal-originated customers from legacy customers + +### WHMCS - No Changes Required + +WHMCS client creation already works; just need to: + +- Set the customer number custom field with the generated SF_Account_No\_\_c +- Ensure the client is created with proper address data + +--- + +## Testing Strategy + +### Unit Tests + +| Component | Coverage Target | +| ------------------------- | --------------- | +| CheckoutStore | 100% | +| Cart validation utilities | 100% | +| Checkout steps | 90% | +| BFF CheckoutService | 90% | + +### Integration Tests + +| Flow | Scenarios | +| -------------------------- | ---------------------------------------------- | +| New customer checkout | Happy path, validation errors, payment failure | +| Existing customer checkout | Sign-in flow, pre-filled data | +| Cart persistence | Page refresh, browser close, storage limits | + +### E2E Tests + +| Test | Description | +| ------------------------------------ | ------------------------------------------------ | +| `checkout-new-customer.spec.ts` | Full flow: browse → configure → checkout → order | +| `checkout-existing-customer.spec.ts` | Sign-in during checkout | +| `checkout-cart-recovery.spec.ts` | Abandoned cart scenarios | +| `public-catalog.spec.ts` | Catalog access without auth | + +### Manual Testing Checklist + +- [ ] Mobile responsive design +- [ ] Screen reader accessibility +- [ ] Keyboard navigation +- [ ] WHMCS payment flow in different browsers +- [ ] Error recovery scenarios +- [ ] Rate limiting behavior +- [ ] Session timeout during checkout + +--- + +## Rollout Plan + +### Stage 1: Internal Testing (1 week) + +- Deploy to staging +- Internal team testing +- Bug fixes + +### Stage 2: Beta (1 week) + +- Enable for 10% of traffic via feature flag +- Monitor error rates, conversion +- Gather feedback + +### Stage 3: Gradual Rollout (2 weeks) + +- 25% → 50% → 75% → 100% +- Monitor metrics at each stage +- Rollback plan ready + +### Stage 4: Full Launch + +- Remove feature flag +- Update marketing materials +- Announce to existing customers + +--- + +## Risks & Mitigations + +| Risk | Likelihood | Impact | Mitigation | +| ---------------------------------------------- | ---------- | ------------ | --------------------------------------------------------------- | +| **Salesforce Account creation fails** | Medium | **Critical** | Robust error handling, retry logic, clear user messaging | +| **Salesforce API rate limits** | Medium | High | Implement queuing if needed, monitor API usage | +| Salesforce permission issues | Medium | High | Pre-validate permissions in Phase 0, test with integration user | +| WHMCS SSO breaks checkout flow | Medium | High | Implement iframe fallback, clear error messaging | +| Partial registration (SF created, WHMCS fails) | Medium | High | Rollback logic, manual cleanup process documented | +| Cart data corruption | Low | Medium | Robust validation, version migration logic | +| Rate limiting blocks legitimate users | Low | Medium | Tune limits, add captcha fallback | +| Performance degradation (SF calls slow) | Medium | Medium | Add timeouts, show progress indicators | +| Duplicate SF Account creation | Low | Medium | Check for existing account by email before creating | + +### Salesforce-Specific Risks + +| Risk | Mitigation | +| ---------------------------------------- | ------------------------------------------------------ | +| Integration user lacks create permission | Test all CRUD operations in sandbox first | +| Required fields missing in payload | Document all required fields, validate before API call | +| Customer number collision | Use database sequence or SF auto-number field | +| SF sandbox vs production differences | Test in production-like sandbox with same config | + +--- + +## Success Metrics + +### Primary Metrics + +| Metric | Current | Target | Measurement | +| ------------------------ | -------- | ------ | ------------------------- | +| Catalog page views | N/A | +200% | Analytics | +| Checkout start rate | ~20% | 40% | Cart creation / plan view | +| Checkout completion rate | ~30% | 50% | Orders / checkout starts | +| New customer acquisition | Baseline | +50% | New accounts | + +### Secondary Metrics + +| Metric | Target | +| -------------------------- | ------------ | +| Time to first order | < 10 minutes | +| Cart abandonment rate | < 60% | +| Payment step completion | > 70% | +| Error rate during checkout | < 2% | + +--- + +## Appendix A: Component File Structure + +``` +apps/portal/src/features/checkout/ +├── components/ +│ ├── CheckoutLayout.tsx +│ ├── CheckoutProgress.tsx +│ ├── CheckoutWizard.tsx +│ ├── OrderSummaryCard.tsx +│ ├── EmptyCartRedirect.tsx +│ └── steps/ +│ ├── AccountStep.tsx +│ ├── AddressStep.tsx +│ ├── PaymentStep.tsx +│ └── ReviewStep.tsx +├── hooks/ +│ ├── useCheckout.ts # Main checkout logic +│ ├── useCheckoutNavigation.ts # Step navigation +│ ├── usePaymentPolling.ts # WHMCS polling +│ └── useCartRecovery.ts # Abandoned cart +├── services/ +│ ├── checkout.service.ts # API calls +│ └── cart.service.ts # Cart utilities +├── stores/ +│ └── checkout.store.ts # Zustand store +├── utils/ +│ ├── cart-validation.ts +│ └── checkout-analytics.ts +└── index.ts +``` + +--- + +## Appendix B: Feature Flag Configuration + +```typescript +// Feature flags for gradual rollout +export const FEATURE_FLAGS = { + PUBLIC_CATALOG: 'public-catalog-enabled', + UNIFIED_CHECKOUT: 'unified-checkout-enabled', + CHECKOUT_REGISTRATION: 'checkout-registration-enabled', + PUBLIC_SUPPORT: 'public-support-enabled', +}; + +// Usage in components +function CatalogPage() { + const isPublicCatalogEnabled = useFeatureFlag(FEATURE_FLAGS.PUBLIC_CATALOG); + + if (!isPublicCatalogEnabled && !isAuthenticated) { + redirect('/auth/login'); + } + + return ; +} +``` + +--- + +## Document History + +| Version | Date | Author | Changes | +| ------- | ---------- | ------ | --------------- | +| 1.0 | 2024-12-17 | - | Initial version |