# Portal – Data Model & Mappings

This document consolidates all required/used fields across Salesforce and WHMCS, plus how the portal consumes them.

## Quick Reference: Required Fields Summary

### Product2 (7 core + service-specific fields)

**Core**: `StockKeepingUnit`, `Product2Categories1__c`, `Portal_Catalog__c`, `Portal_Accessible__c`, `Item_Class__c`, `Billing_Cycle__c`, `WH_Product_ID__c`, `WH_Product_Name__c`  
**Internet**: `Internet_Plan_Tier__c`, `Internet_Offering_Type__c`, `Internet_Monthly_Price__c`  
**SIM**: `SIM_Data_Size__c`, `SIM_Plan_Type__c`, `SIM_Has_Family_Discount__c`  
**Edge Cases**: `Is_Default__c`, `Display_Order__c`, `Auto_Add__c`, `VPN_Region__c`

_Note: For complete product setup details, see `SALESFORCE-PRODUCTS.md`_

### Order (6 core + service-specific independent fields)

**Core**: `AccountId`, `EffectiveDate`, `Status`, `Pricebook2Id`, `Order_Type__c`  
**Activation**: `Activation_Type__c`, `Activation_Scheduled_At__c`, `Activation_Status__c`  
**Internet**: `Internet_Plan_Tier__c`, `Installation_Type__c`, `Weekend_Install__c`, `Access_Mode__c`, `Hikari_Denwa__c`  
**VPN**: `VPN_Region__c`  
**SIM**: `SIM_Type__c`, `EID__c`, `SIM_Voice_Mail__c`, `SIM_Call_Waiting__c` + MNP fields  
**WHMCS**: `WHMCS_Order_ID__c`

### OrderItem (2 core + 2 integration fields)

**Core**: `OrderId`, `PricebookEntryId`, `Quantity`, `UnitPrice`  
**WHMCS**: `WHMCS_Service_ID__c`, `Billing_Cycle__c`

## Order with Add-ons Example

When a customer orders "Internet Gold + Hikari Denwa + Weekend Installation":

```sql
-- Order Header
Order_Type__c: "Internet"
Internet_Plan_Tier__c: "Gold"
Installation_Type__c: "Single"
Weekend_Install__c: true  -- Synced from add-on OrderItem

-- OrderItems (5 separate items)
1. Internet Gold Service        → SKU: "INTERNET-GOLD"              → WH: 188 (monthly)
2. Internet Installation        → SKU: "INTERNET-INSTALL-SINGLE"    → WH: 242 (one-time)
3. Weekend Installation Fee     → SKU: "INTERNET-INSTALL-WEEKEND"   → WH: 245 (one-time ¥3000)
4. Hikari Denwa Service        → SKU: "INTERNET-ADDON-HOME-PHONE"   → WH: 246 (monthly ¥450)
5. Hikari Denwa Installation   → SKU: "INTERNET-ADDON-DENWA-INSTALL" → WH: 247 (one-time)
```

**Complete Product Structure Needed:**

```sql
-- Main Services (Portal_Catalog = true)
"Internet Gold Plan"           → Item_Class: "Service"    → Monthly billing
"Single Installation"          → Item_Class: "Installation"    → One-time billing

-- Add-on Services (Portal_Catalog = false, shown as checkboxes)
"Hikari Denwa (Home Phone)"    → Item_Class: "Add-on"     → Monthly billing
"Weekend Installation"         → Item_Class: "Add-on"     → One-time billing
"Hikari Denwa Installation"    → Item_Class: "Add-on"     → One-time billing (auto-added when Denwa selected)
```

**Key Points:**

- **5 OrderItems total** - each service/fee is separate
- **Automatic dependencies** - selecting Hikari Denwa auto-adds its installation fee
- **Mixed billing cycles** - services are monthly, installations are one-time
- **Independent WHMCS products** - each OrderItem maps to different WHMCS Product ID

## Object inventory (what's standard vs custom)

- Salesforce standard
  - `Product2` (catalog)
  - `Pricebook2` / `PricebookEntry` (pricing)
  - `Order` (header)
  - `OrderItem` (line)
  - `Account` (customer profile and eligibility)

- WHMCS
  - Use native User, Client, Order, Invoice, Product (Service). No custom tables.

## Salesforce fields (by object)

### Account (eligibility and mapping)

- Eligibility (used for personalized Internet catalog)
  - `Internet_Eligibility__c` (Picklist)
    - Values (Label = API Name):
      - `Home 1G`
      - `Home 10G`
      - `Apartment 1G`
      - `Apartment 100M`
    - Default behavior: if the field is missing/blank/invalid, the portal treats eligibility as `Home 1G`.
  - Environment variable mapping (so code doesn’t hardcode field API names):
    - `ELIGIBILITY_INTERNET_FIELD` (default: `Internet_Eligibility__c`)

- Mapping key
  - `SF_Account_No__c` (Text) used to link portal user ↔ Account (via WHMCS link/signup)

### Product2 Field Reference

_For complete Product2 field definitions, custom fields, and edge case handling, see `SALESFORCE-PRODUCTS.md`_

#### Core Fields Summary

- `StockKeepingUnit` - Product identifier (detailed format: `INTERNET-SILVER-APT-1G`)
- `Product2Categories1__c` - "Internet", "SIM", "VPN", "Other"
- `Portal_Catalog__c` - Show in main catalog (GET /catalog)
- `Portal_Accessible__c` - Can be used in orders/OrderItems
- `Item_Class__c` - "Service", "Installation", "Add-on", "Activation"
- `WH_Product_ID__c` - WHMCS Product ID

#### Pricing (from PricebookEntry)

```sql
-- Pricing comes from PricebookEntry.UnitPrice where Pricebook2Id = '01sTL000008eLVlYAM' (Portal Price Book)
-- Portal Price Book ID is configurable via PORTAL_PRICEBOOK_ID environment variable
UnitPrice                 Currency         -- Display price (¥4800, ¥4900, ¥5300)
```

## Add-on Products

Add-ons are separate products that customers can select alongside main services. They use the existing product structure with `Item_Class__c = "Add-on"`, `Portal_Catalog__c = false` (hidden from catalog), and `Portal_Accessible__c = true` (can be ordered).

### How Add-ons Work:

1. **Customer Selection**: Optional add-ons appear as checkboxes (e.g., Hikari Denwa)
2. **Frontend Logic**: Auto-add dependencies (e.g., Hikari Denwa adds installation fee)
3. **Salesforce Logic**: Auto-detect conditions and add OrderItems (e.g., weekend installation dates)
4. **Order Creation**: Each selected/detected add-on creates a separate OrderItem
5. **Provisioning**: Each add-on provisions as separate WHMCS product
6. **Billing**: Add-ons have their own pricing and billing cycles

### Internet Add-ons:

- **Weekend Installation**: Salesforce auto-adds (¥3000) - `INTERNET-INSTALL-WEEKEND` (when installation date is weekend)
- **Hikari Denwa Service**: Customer checkbox (¥450/month) - `INTERNET-ADDON-HOME-PHONE`
- **Hikari Denwa Installation**: Frontend auto-adds (one-time) - `INTERNET-ADDON-DENWA-INSTALL`

### SIM Add-ons:

- **Voice Mail**: Monthly service - `SIM-ADDON-VOICE-MAIL`
- **Call Waiting**: Monthly service - `SIM-ADDON-CALL-WAITING`
- **Unlimited Calling**: Monthly service - `SIM-ADDON-UNLIMITED-CALLING`

**Product Structure Examples:**
_For complete product definitions with all SKUs, fields, and WHMCS IDs, see `SALESFORCE-PRODUCTS.md`_

```sql
-- Main Service Plans (Portal_Catalog = true, Portal_Accessible = true)
"Internet Silver Plan (Apartment 1G)" → SKU: "INTERNET-SILVER-APT-1G" → WH_Product_ID__c: 184
"Internet Gold Plan (Apartment 1G)" → SKU: "INTERNET-GOLD-APT-1G" → WH_Product_ID__c: 185
"Internet Platinum Plan (Apartment 1G)" → SKU: "INTERNET-PLATINUM-APT-1G" → WH_Product_ID__c: 186

-- Add-on Services (Portal_Catalog = false, Portal_Accessible = true)
"Weekend Installation" → SKU: "INTERNET-INSTALL-WEEKEND" → WH_Product_ID__c: 245
"Hikari Denwa (Home Phone)" → SKU: "INTERNET-ADDON-HOME-PHONE" → WH_Product_ID__c: 246

-- Customer sees clear messaging:
Silver: Standard pricing, customer configures access mode
Gold: Premium pricing, operator reviews and may adjust configuration
Platinum: Premium pricing + additional fees, operator reviews and may adjust configuration
```

### Order (clean field structure)

#### Core Fields (Required for all orders)

```sql
-- Salesforce Standard Fields
AccountId                 Lookup(Account)  -- Customer account
EffectiveDate            Date             -- Order date
Status                   Picklist         -- "Pending Review", "Approved", "Completed", "Cancelled"
Pricebook2Id             Lookup(Pricebook2) -- Portal pricebook
OpportunityId            Lookup(Opportunity) -- Optional, created by BFF

-- Order Classification
Order_Type__c            Picklist         -- "Internet", "SIM", "VPN", "Other"
```

#### Activation Fields (All order types)

```sql
Activation_Type__c       Picklist         -- "Immediate", "Scheduled"
Activation_Scheduled_At__c Datetime       -- When Status = Scheduled
Activation_Status__c     Picklist         -- "Not Started", "Activating", "Activated", "Failed"
```

#### Internet Order Fields (Independent fields synced with OrderItems)

```sql
-- Customer/Operator Selections (Independent fields)
Internet_Plan_Tier__c       Picklist         -- "Silver", "Gold", "Platinum" (syncs with OrderItems)
Installation_Type__c        Picklist         -- "Single", "12-Month", "24-Month" (syncs with OrderItems)
Weekend_Install__c          Checkbox         -- Weekend installation add-on (syncs with OrderItems)
Access_Mode__c              Picklist         -- "IPoE-BYOR", "IPoE-HGW", "PPPoE" (operator configures)

-- Add-on Selections (Customer choices, syncs with OrderItems)
Hikari_Denwa__c             Checkbox         -- Customer selected Hikari Denwa service
```

#### VPN Order Fields (Customer selections)

```sql
-- Customer Selections
VPN_Region__c            Picklist         -- "USA-SF", "UK-London" (customer chooses region)
```

#### SIM Order Fields (Customer choices + MNP data)

```sql
-- SIM Configuration (plan type and data size come from selected Product2)
SIM_Type__c              Picklist         -- "eSIM", "Physical SIM" (customer choice)
EID__c                   Text(255)        -- Required when SIM_Type__c = "eSIM"

-- Add-on Selections (Customer choices, syncs with OrderItems)
SIM_Voice_Mail__c        Checkbox         -- Customer selected Voice Mail add-on
SIM_Call_Waiting__c      Checkbox         -- Customer selected Call Waiting add-on


-- MNP/Porting (when MNP_Application__c = true)
MNP_Application__c       Checkbox         -- Customer wants to port number
MNP_Reservation_Number__c Text(10)        -- 10 digits numeric
MNP_Expiry_Date__c       Date             -- MNP expiry date
MNP_Phone_Number__c      Text(11)         -- 11 digits, no hyphens
MVNO_Account_Number__c   Text(255)        -- Optional
Porting_LastName__c      Text(255)        -- Kanji/Alphabet
Porting_FirstName__c     Text(255)        -- Kanji/Alphabet
Porting_LastName_Katakana__c Text(255)    -- Katakana
Porting_FirstName_Katakana__c Text(255)   -- Katakana
Porting_Gender__c        Picklist         -- "Male", "Female", "Corporate/Other"
Porting_DateOfBirth__c   Date             -- YYYY/MM/DD
```

#### WHMCS Integration Fields

```sql
WHMCS_Order_ID__c        Number(18,0)     -- Set after provisioning
```

#### Billing/Shipping Snapshot (Set on create, not synced)

```sql
-- Billing Address
BillToContactId          Lookup(Contact)
BillToStreet             Text(255)
BillToCity               Text(40)
BillToState              Text(80)
BillToPostalCode         Text(20)
BillToCountry            Text(80)

-- Shipping Address
ShipToContactId          Lookup(Contact)
ShipToStreet             Text(255)
ShipToCity               Text(40)
ShipToState              Text(80)
ShipToPostalCode         Text(20)
ShipToCountry            Text(80)
```

### OrderItem (clean field structure)

#### Core Fields (Required for all order items)

```sql
-- Salesforce Standard Fields
OrderId                  Lookup(Order)    -- Parent order
PricebookEntryId         Lookup(PricebookEntry) -- Product + pricing
Quantity                 Number(18,2)     -- Usually 1
UnitPrice                Currency         -- Price at time of order
```

#### WHMCS Integration Fields

```sql
-- Provisioning
WHMCS_Service_ID__c      Number(18,0)     -- Set after provisioning
Billing_Cycle__c         Picklist         -- "Monthly", "Annually", "One-time"
```

**Note**: `WH_Product_ID__c` is stored on Product2, but operator can override it on OrderItem during review for Gold/Platinum plans.

## Salesforce Setup Checklist

### Custom Fields to Create

#### Product2 Object

```sql
-- Core Fields (All Products)
StockKeepingUnit          Text(255)        External ID, Unique (standard Salesforce field)
Product2Categories1__c    Picklist         Values: Internet, SIM, VPN, Other
Portal_Catalog__c         Checkbox         Default: false
Portal_Accessible__c      Checkbox         Default: true
Item_Class__c             Picklist         Values: Service, Installation, Add-on, Activation
WH_Product_ID__c          Number(18,0)

-- Internet Fields
Internet_Plan_Tier__c     Picklist         Values: Silver, Gold, Platinum
Internet_Offering_Type__c Picklist         Values: Home 1G, Home 10G, Apartment 1G, Apartment 100M
Internet_Monthly_Price__c Currency


-- SIM Fields
SIM_Data_Size__c          Text(50)
SIM_Plan_Type__c          Picklist         Values: DataOnly, DataSmsVoice, VoiceOnly
SIM_Has_Family_Discount__c Checkbox        Default: false

-- VPN Fields

```

#### Order Object

```sql
-- Core Fields
Order_Type__c             Picklist         Values: Internet, SIM, VPN, Other

-- Activation Fields
Activation_Type__c        Picklist         Values: Immediate, Scheduled
Activation_Scheduled_At__c Datetime
Activation_Status__c      Picklist         Values: Not Started, Activating, Activated, Failed

-- Internet Fields (Independent fields synced with OrderItems)
Internet_Plan_Tier__c     Picklist         Values: Silver, Gold, Platinum
Installation_Type__c      Picklist         Values: Single, 12-Month, 24-Month
Weekend_Install__c        Checkbox         Weekend installation add-on
Access_Mode__c            Picklist         Values: IPoE-BYOR, IPoE-HGW, PPPoE (operator sets)

-- VPN Fields (Independent fields)
VPN_Region__c             Picklist         Values: USA-SF, UK-London (customer chooses)

-- SIM Fields (Customer choices only - plan details come from Product2)
SIM_Type__c               Picklist         Values: eSIM, Physical SIM (customer chooses format)
EID__c                    Text(255)        Required for eSIM activation

-- MNP Fields (when applicable)
MNP_Application__c        Checkbox
MNP_Reservation_Number__c Text(10)
MNP_Expiry_Date__c        Date
MNP_Phone_Number__c       Text(11)
MVNO_Account_Number__c    Text(255)
Porting_LastName__c       Text(255)
Porting_FirstName__c      Text(255)
Porting_LastName_Katakana__c Text(255)
Porting_FirstName_Katakana__c Text(255)
Porting_Gender__c         Picklist         Values: Male, Female, Corporate/Other
Porting_DateOfBirth__c    Date

-- WHMCS Integration
WHMCS_Order_ID__c         Number(18,0)


```

#### OrderItem Object

```sql
-- WHMCS Integration Fields
WHMCS_Service_ID__c       Number(18,0)     Set after provisioning
Billing_Cycle__c          Picklist         Values: Monthly, Annually, One-time
```

#### Account Object

```sql
-- Portal Integration Fields
Internet_Eligibility__c   Picklist         Values: Home 1G, Home 10G, Apartment 1G, Apartment 100M
SF_Account_No__c          Text(255)        External ID, Unique (Customer Number)
```

## Salesforce Sync Flow: Order Fields ↔ OrderItems

### Overview

Independent fields on Order are synced with OrderItems using Salesforce Flow. When operators change Order fields, Flow automatically adds/removes/updates OrderItems. When OrderItems change, Flow updates Order fields.

### Weekend Installation Auto-Detection

```apex
// Trigger: After Insert, After Update on Order
// When: Installation_Scheduled_Date__c changes

Date installDate = order.Installation_Scheduled_Date__c;
String dayOfWeek = installDate.format('E'); // 'Sat' or 'Sun' for weekends

if (dayOfWeek == 'Sat' || dayOfWeek == 'Sun') {
    // Add weekend installation fee if not exists
    List<OrderItem> existing = [SELECT Id FROM OrderItem
                               WHERE OrderId = :order.Id
                               AND Product2.SKU__c = 'INTERNET-INSTALL-WEEKEND'];

    if (existing.isEmpty()) {
        Product2 product = [SELECT Id FROM Product2 WHERE SKU__c = 'INTERNET-INSTALL-WEEKEND'];
        PricebookEntry pbe = [SELECT Id, UnitPrice FROM PricebookEntry
                             WHERE Product2Id = :product.Id AND Pricebook2.Name = 'Portal'];

        INSERT new OrderItem(
            OrderId = order.Id,
            PricebookEntryId = pbe.Id,
            Quantity = 1,
            UnitPrice = pbe.UnitPrice
        );

        order.Weekend_Install__c = true;
        UPDATE order;
    }
} else {
    // Remove weekend fee if date changed to weekday
    DELETE [SELECT Id FROM OrderItem WHERE OrderId = :order.Id
            AND Product2.SKU__c = 'INTERNET-INSTALL-WEEKEND'];
    order.Weekend_Install__c = false;
    UPDATE order;
}
```

### Flow 1: Order Fields → OrderItems (When operator changes Order fields)

#### Internet Plan Tier Change

```apex
// Trigger: After Update on Order
// When: Internet_Plan_Tier__c changes

// 1. Remove old Internet service OrderItem
DELETE [SELECT Id FROM OrderItem
        WHERE OrderId = :order.Id
        AND Product2.Product2Categories1__c = 'Internet'
        AND Product2.Item_Class__c = 'Service'];

// 2. Add new Internet service OrderItem
String newSku = 'INTERNET-' + order.Internet_Plan_Tier__c.toUpperCase();
Product2 newProduct = [SELECT Id FROM Product2 WHERE SKU__c = :newSku LIMIT 1];
PricebookEntry pbe = [SELECT Id, UnitPrice FROM PricebookEntry
                      WHERE Product2Id = :newProduct.Id AND Pricebook2.Name = 'Portal' LIMIT 1];

OrderItem newItem = new OrderItem(
    OrderId = order.Id,
    PricebookEntryId = pbe.Id,
    Quantity = 1,
    UnitPrice = pbe.UnitPrice
);
INSERT newItem;
```

#### Installation Type Change

```apex
// When: Installation_Type__c changes

// 1. Remove old installation OrderItem
DELETE [SELECT Id FROM OrderItem
        WHERE OrderId = :order.Id
        AND Product2.Product2Categories1__c = 'Internet'
        AND Product2.Item_Class__c = 'Install'];

// 2. Add new installation OrderItem if not null
if (order.Installation_Type__c != null) {
    String installSku = 'INTERNET-INSTALL-' +
        (order.Installation_Type__c == 'Single' ? 'SINGLE' :
         order.Installation_Type__c == '12-Month' ? '12M' : '24M');

    // Create new installation OrderItem with installSku
}
```

#### Weekend Install Toggle

```apex
// When: Weekend_Install__c changes

if (order.Weekend_Install__c == true && oldOrder.Weekend_Install__c == false) {
    // Add weekend installation OrderItem
    // SKU: 'INTERNET-INSTALL-WEEKEND'
} else if (order.Weekend_Install__c == false && oldOrder.Weekend_Install__c == true) {
    // Remove weekend installation OrderItem
    DELETE [SELECT Id FROM OrderItem
            WHERE OrderId = :order.Id
            AND Product2.SKU__c = 'INTERNET-INSTALL-WEEKEND'];
}
```

#### Add-on Field Changes

```apex
// When: Hikari_Denwa__c changes
if (order.Hikari_Denwa__c == true && oldOrder.Hikari_Denwa__c == false) {
    // Add Hikari Denwa service and installation OrderItems
    INSERT new OrderItem(Product2.SKU__c = 'INTERNET-ADDON-HOME-PHONE');
    INSERT new OrderItem(Product2.SKU__c = 'INTERNET-ADDON-DENWA-INSTALL');
} else if (order.Hikari_Denwa__c == false && oldOrder.Hikari_Denwa__c == true) {
    // Remove Hikari Denwa OrderItems
    DELETE [SELECT Id FROM OrderItem WHERE OrderId = :order.Id
            AND Product2.SKU__c IN ('INTERNET-ADDON-HOME-PHONE', 'INTERNET-ADDON-DENWA-INSTALL')];
}

// When: SIM add-on fields change
if (order.SIM_Voice_Mail__c != oldOrder.SIM_Voice_Mail__c) {
    if (order.SIM_Voice_Mail__c) {
        INSERT new OrderItem(Product2.SKU__c = 'SIM-ADDON-VOICE-MAIL');
    } else {
        DELETE [SELECT Id FROM OrderItem WHERE OrderId = :order.Id
                AND Product2.SKU__c = 'SIM-ADDON-VOICE-MAIL'];
    }
}

// Similar logic for SIM_Call_Waiting__c
```

### Flow 2: OrderItems → Order Fields (When OrderItems change)

#### Sync Order Fields from OrderItems

```apex
// Trigger: After Insert, After Update, After Delete on OrderItem
// Purpose: Keep Order fields in sync with OrderItems

// Get all OrderItems for this Order
List<OrderItem> items = [SELECT Product2.Internet_Plan_Tier__c, Product2.SKU__c,
                                Product2.Item_Class__c, Product2.Product2Categories1__c
                         FROM OrderItem WHERE OrderId = :orderId];

Order orderToUpdate = new Order(Id = orderId);

// Sync Internet fields
for (OrderItem item : items) {
    if (item.Product2.Product2Categories1__c == 'Internet' && item.Product2.Item_Class__c == 'Service') {
        orderToUpdate.Internet_Plan_Tier__c = item.Product2.Internet_Plan_Tier__c;
    }
    if (item.Product2.Product2Categories1__c == 'Internet' && item.Product2.Item_Class__c == 'Installation') {
        if (item.Product2.SKU__c.contains('SINGLE')) orderToUpdate.Installation_Type__c = 'Single';
        else if (item.Product2.SKU__c.contains('12M')) orderToUpdate.Installation_Type__c = '12-Month';
        else if (item.Product2.SKU__c.contains('24M')) orderToUpdate.Installation_Type__c = '24-Month';
    }
    if (item.Product2.SKU__c == 'INTERNET-INSTALL-WEEKEND') {
        orderToUpdate.Weekend_Install__c = true;
    }
    if (item.Product2.SKU__c == 'INTERNET-ADDON-HOME-PHONE') {
        orderToUpdate.Hikari_Denwa__c = true;
    }

    // SIM add-on sync
    if (item.Product2.SKU__c == 'SIM-ADDON-VOICE-MAIL') {
        orderToUpdate.SIM_Voice_Mail__c = true;
    }
    if (item.Product2.SKU__c == 'SIM-ADDON-CALL-WAITING') {
        orderToUpdate.SIM_Call_Waiting__c = true;
    }


    // VPN fields: Only VPN_Region__c is stored on Order (customer selection)
}

// Set defaults if no items found
if (noInternetService) orderToUpdate.Internet_Plan_Tier__c = null;
if (noInstallation) orderToUpdate.Installation_Type__c = null;
if (noWeekendInstall) orderToUpdate.Weekend_Install__c = false;
if (noHikariDenwa) orderToUpdate.Hikari_Denwa__c = false;
if (noSIMVoiceMail) orderToUpdate.SIM_Voice_Mail__c = false;
if (noSIMCallWaiting) orderToUpdate.SIM_Call_Waiting__c = false;


UPDATE orderToUpdate;
```

## WHMCS fields/IDs used

- Users & Clients (created/linked at signup)
  - User: `firstname`, `lastname`, `email`, `password`
  - Client: `firstname`, `lastname`, `email`, optional `phonenumber`, `companyname`
  - Address: `address1`, `address2`, `city`, `state`, `postcode`, `country`
  - Custom field: `CustomerNumber` (maps to Salesforce Account `SF_Account_No__c`)
  - Link: User ↔ Client recorded in portal mapping table

- Invoices & PayMethods
  - GetInvoices (by `clientid`) surfaced in portal
  - GetPayMethods used to gate checkout (must have a method on file)
  - SSO links used to open invoice/payment-methods UI in WHMCS

- Client linking in portal
  - Portal mapping stores: `userId`, `whmcsClientId`, `sfAccountId`
- Product IDs (pid) used in AddOrder:
  - Internet (by dwelling/speed): Home 1G SILV=181, GOLD=182, PLAT=183; APT 1G SILV=184, GOLD=185, PLAT=186; APT 100M SILV=187, GOLD=188, PLAT=189
  - Installation: Single=242, 12M=243, 24M=244
  - VPN: USA=33, UK=54, Activation=37
  - SIM/eSIM: see mapping table above (e.g., Data-only 5GB=97, Data+Voice 10GB=216, Voice-only=142)

### WHMCS AddOrder API (Official Format)

**Required Parameters:**

- `clientid` (int) – WHMCS client ID resolved from `Order.AccountId` via portal mapping
- `paymentmethod` (string) – Payment method (e.g., "mailin", "paypal") - **Required by WHMCS API**

**Product Arrays (Official WHMCS Format):**

- `pid[]` (string[]) – Array of WHMCS product IDs from `Product2.WH_Product_ID__c`
- `billingcycle[]` (string[]) – Array of billing cycles from `Product2.Billing_Cycle__c`
- `qty[]` (int[]) – Array of quantities from `OrderItem.Quantity`

**Optional Parameters:**

- `promocode` (string) – Promotion code
- `noinvoice` (bool) – Suppress invoice creation (true for provisioning)
- `noemail` (bool) – Suppress order confirmation email (true for provisioning)
- `configoptions[]` (string[]) – Base64 encoded serialized arrays (if needed)
- `customfields[]` (string[]) – Base64 encoded serialized arrays (if needed)

**Example Request:**

```json
{
  "clientid": 1,
  "paymentmethod": "mailin",
  "pid": ["185", "242", "246", "247"],
  "billingcycle": ["monthly", "onetime", "monthly", "onetime"],
  "qty": [1, 1, 1, 1],
  "noinvoice": true,
  "noemail": true
}
```

**Response:**

```json
{
  "result": "success",
  "orderid": 12345,
  "serviceids": "67890,67891,67892,67893",
  "invoiceid": 0
}
```

## Catalog requirements

_For complete catalog architecture, API endpoints, and frontend implementation, see `PRODUCT-CATALOG-ARCHITECTURE.md`_

- Pricebook: create a `Portal` price book; ensure active `PricebookEntry` for each visible Product2
- API dependencies: `GET /catalog` queries `Product2` fields: Id, Name, StockKeepingUnit, Product2Categories1**c, Portal_Catalog**c, Portal_Accessible\_\_c
- New endpoints: `/catalog/sim/activation-fees`, `/catalog/vpn/activation-fees`, `/catalog/sim/addons`, etc.
- Personalized catalog: `GET /catalog/personalized`
  - Filters by consolidated offering: include Product2 where `Internet_Offering_Type__c` equals `Account.Internet_Eligibility__c` (fallback to Home 1G when Account field missing/invalid)

## Enablement & permissions (Salesforce)

- Enable Products and Price Books in the org (Setup → Product Settings)
- Integration user (Named Credential/Connected App user) minimum permissions:
  - Objects (Read): `Product2`, `Pricebook2`, `PricebookEntry`, `Order`, `OrderItem`, `Account`
  - Fields (Readable):
    - `Product2.StockKeepingUnit`, `Product2.Portal_Catalog__c`, `Product2.Portal_Accessible__c`, `Product2.Product2Categories1__c`
    - Optional display: `Product2.Portal_Description__c`, `Product2.Portal_Feature_Bullets__c`, `Product2.Portal_Hero_Image_URL__c`, `Product2.Portal_Tags__c`, `Product2.Portal_Sort_Order__c`, `Product2.Portal_Valid_From__c`, `Product2.Portal_Valid_Until__c`
    - Optional eligibility: `Product2.Portal_Eligibility_Dwelling__c`, `Product2.Portal_Eligibility_Speed__c`, `Product2.Portal_Eligibility_Region__c`
  - Account eligibility fields per env: dwelling/speed
  - Order custom fields listed above (Type/Activation/Internet/SIM/MNP)
  - OrderItem custom fields: `ConfigOptions_JSON__c`

## Portal field mapping reference (source-of-truth → target)

Activation (Portal → Salesforce)

- `activationType` (Immediate | Scheduled) → `Order.Activation_Type__c`
- `activationScheduledAt` (Datetime) → `Order.Activation_Scheduled_At__c`

Address (Portal → WHMCS/Order snapshot)

- `street` → `WHMCS.Client.address1`; `Order.BillToStreet`
- `addressLine2` → `WHMCS.Client.address2`
- `city` → `WHMCS.Client.city`; `Order.BillToCity`
- `state` → `WHMCS.Client.state`; `Order.BillToState`
- `postalCode` → `WHMCS.Client.postcode`; `Order.BillToPostalCode`
- `country` (ISO 2) → `WHMCS.Client.country`; `Order.BillToCountry`

Checkout (Portal → Salesforce/WHMCS)

- `billingCycle` (UI concept only) → derived server-side from SKU class; WHMCS `billingcycle`
- `productSKU` (Text) → `Product2.StockKeepingUnit` (PBE lookup)
- `promoCode` (Text) → WHMCS `promocode`
- `quantity` (Number) → `OrderItem.Quantity` (validated against `Product2.Portal_Max_Quantity__c`)

Internet (Portal → Salesforce)

- `accessMode` (IPoE‑HGW | IPoE‑BYOR | PPPoE) → `Order.Access_Mode__c`
- `installmentPlan` (One-time | 12-Month | 24-Month) → `Order.Installment_Plan__c`
- `planTier` (Platinum | Gold | Silver) → `Order.Internet_Plan_Tier__c` (derived from SKU)
- `serviceSpeed` (Text) → `Order.Service_Speed__c`
- `weekendInstall` (Checkbox) → `Order.Weekend_Install__c`
- `internetOffering` (Picklist) → `Order.Internet_Offering_Type__c` (defaults from Account when not provided)
  - Also set from product on selection: `Order.Internet_Offering_Type__c = Product2.Portal_Internet_Offering_Type__c`

VPN (Portal → Salesforce)

- `vpnRegion` (Picklist) → `Order.VPN_Region__c`
- (optional) `vpnType` (Picklist) → `Order.VPN_Type__c`

SIM / eSIM (Portal → Salesforce)

- `simType` (eSIM | Physical SIM) → `Order.SIM_Type__c`
- `eid` (Text) → `Order.EID__c` (required when `SIM_Type__c = eSIM`)
- Porting (all optional unless flow requires):
  - `mnpApplication` → `Order.MNP_Application__c`
  - `mnpReservationNumber` → `Order.MNP_Reservation_Number__c`
  - `mnpExpiryDate` → `Order.MNP_Expiry_Date__c`
  - `mnpPhoneNumber` → `Order.MNP_Phone_Number__c`
  - `mvnoAccountNumber` → `Order.MVNO_Account_Number__c`
  - `portingDob` → `Order.Porting_DateOfBirth__c`
  - `portingFirstNameKanji` → `Order.Porting_FirstName_Kanji__c`
  - `portingLastNameKanji` → `Order.Porting_LastName_Kanji__c`
  - `portingFirstNameKatakana` → `Order.Porting_FirstName_Katakana__c`
  - `portingLastNameKatakana` → `Order.Porting_LastName_Katakana__c`
  - `portingGender` (Male | Female | Corporate/Other) → `Order.Porting_Gender__c`

Signup (Portal → WHMCS; plus SF mapping)

- `email`, `firstName`, `lastName`, `password` → WHMCS User + Client
- `company` → `WHMCS.Client.companyname` (optional)
- `phone` → `WHMCS.Client.phonenumber` (optional)
- `customerNumber` (Salesforce Account number) → `WHMCS.Client.customfields.CustomerNumber` → BFF links to Salesforce `Account.SF_Account_No__c`

## Notes

- BFF resolves pricing by `Product2.StockKeepingUnit` → `PricebookEntry` and provisioning by `Product2.WH_Product_ID__c` → WHMCS `pid[]`
- Use `SIM` as the sole Product2 category for both eSIM and Physical SIM; `Order_Type__c` retains SIM vs eSIM
- All Product2 visible in portal must have `StockKeepingUnit`, `Portal_Catalog__c = true`, and a `PricebookEntry`
