# 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.

### Implementation Notes (Current Codebase)

- Public catalog routes are implemented under `/shop` (not `/catalog`).
- Unified checkout is implemented under `/order` (not `/checkout`).
- Internet orders require an availability/eligibility confirmation step (between Address and Payment).

---

## 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 (/order)                                          │
│   ┌────────────────────────────────────────────────────────────┐    │
│   │ 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 2.5: Availability (Internet only)                      │    │
│   │   • Request/confirm serviceability before payment           │    │
│   ├────────────────────────────────────────────────────────────┤    │
│   │ 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<GuestInfo> | 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<GuestInfo>) => void;
  setAddress: (address: Address) => void;
  setRegistrationComplete: (userId: string) => void;
  setPaymentVerified: (verified: boolean) => void;
  setCurrentStep: (step: CheckoutState["currentStep"]) => void;
  clear: () => void;
}

export const useCheckoutStore = create<CheckoutState>()(
  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 (
    <CheckoutLayout>
      <CheckoutWizard />
    </CheckoutLayout>
  );
}

// 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 <EmptyCartRedirect />;
  }

  // Skip account step if already authenticated
  const effectiveStep = isAuthenticated && currentStep === 'account'
    ? 'address'
    : currentStep;

  return (
    <div className="max-w-3xl mx-auto">
      <CheckoutProgress currentStep={effectiveStep} />

      <div className="mt-8 grid grid-cols-1 lg:grid-cols-3 gap-8">
        <div className="lg:col-span-2">
          {effectiveStep === 'account' && <AccountStep />}
          {effectiveStep === 'address' && <AddressStep />}
          {effectiveStep === 'payment' && <PaymentStep />}
          {effectiveStep === 'review' && <ReviewStep />}
        </div>

        <div className="lg:col-span-1">
          <OrderSummaryCard item={cartItem} />
        </div>
      </div>
    </div>
  );
}
```

### 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 (
    <div className="space-y-6">
      {/* Sign-in prompt */}
      <div className="bg-muted/50 rounded-xl p-6 border border-border">
        <div className="flex items-center justify-between">
          <div>
            <h3 className="font-semibold">Already have an account?</h3>
            <p className="text-sm text-muted-foreground">
              Sign in to use your saved information
            </p>
          </div>
          <Button
            variant="outline"
            onClick={() => setMode('signin')}
          >
            Sign In
          </Button>
        </div>
      </div>

      {mode === 'signin' ? (
        <SignInForm
          onSuccess={handleSignInSuccess}
          onCancel={() => setMode('new')}
          embedded
        />
      ) : (
        <>
          <div className="relative">
            <div className="absolute inset-0 flex items-center">
              <div className="w-full border-t border-border" />
            </div>
            <div className="relative flex justify-center text-sm">
              <span className="bg-background px-4 text-muted-foreground">
                Or continue as new customer
              </span>
            </div>
          </div>

          <GuestInfoForm onSubmit={handleContinue} />
        </>
      )}
    </div>
  );
}
```

### 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 (
    <div className="space-y-6">
      <div className="bg-card rounded-xl border border-border p-6">
        <div className="flex items-center gap-3 mb-4">
          <CreditCardIcon className="h-6 w-6 text-primary" />
          <h2 className="text-lg font-semibold">Payment Method</h2>
        </div>

        {hasPaymentMethod ? (
          <div className="space-y-4">
            <PaymentMethodCard
              method={paymentMethods.paymentMethods[0]}
            />
            <div className="flex items-center gap-2 text-sm text-success">
              <CheckCircleIcon className="h-5 w-5" />
              Payment method verified
            </div>
          </div>
        ) : (
          <div className="text-center py-8">
            <CreditCardIcon className="h-12 w-12 text-muted-foreground mx-auto mb-4" />

            {isWaiting ? (
              <>
                <h3 className="font-semibold mb-2">Waiting for payment method...</h3>
                <p className="text-sm text-muted-foreground mb-4">
                  Complete the payment setup in the new tab, then return here.
                </p>
                <Spinner className="mx-auto" />
              </>
            ) : (
              <>
                <h3 className="font-semibold mb-2">Add a payment method</h3>
                <p className="text-sm text-muted-foreground mb-4">
                  We'll open our secure payment portal to add your card.
                </p>
                <Button onClick={handleAddPayment}>
                  Add Payment Method
                </Button>
              </>
            )}
          </div>
        )}
      </div>

      <div className="flex justify-between">
        <Button
          variant="ghost"
          onClick={() => setCurrentStep('address')}
        >
          ← Back
        </Button>
        <Button
          onClick={() => setCurrentStep('review')}
          disabled={!hasPaymentMethod}
        >
          Continue to Review
        </Button>
      </div>
    </div>
  );
}
```

### 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<CheckoutRegisterResponse> {
    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<PaymentStatusResponse> {
    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<CheckoutCart> {
    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<OrderCreateResponse> {
    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<string> {
  // 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<CheckoutRegisterResponse> {
    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 <CatalogContent />;
}
```

---

## Document History

| Version | Date       | Author | Changes         |
| ------- | ---------- | ------ | --------------- |
| 1.0     | 2024-12-17 | -      | Initial version |
