- Introduced new controllers for internet eligibility and service health checks to enhance backend functionality. - Created service modules for internet, SIM, and VPN offerings, improving organization and maintainability. - Developed various components for internet and SIM configuration, including forms and plan cards, to streamline user interactions. - Implemented hooks for managing service configurations and eligibility checks, enhancing frontend data handling. - Updated utility functions for pricing and catalog operations to support new service structures and improve performance.
640 lines
28 KiB
Markdown
640 lines
28 KiB
Markdown
# Customer Portal – Technical Operations Guide
|
||
|
||
A comprehensive guide for supervisors explaining how the Customer Portal integrates with WHMCS, Salesforce, and other systems.
|
||
|
||
---
|
||
|
||
## Contents
|
||
|
||
1. [System Architecture](#system-architecture)
|
||
2. [Data Ownership and Flow](#data-ownership-and-flow)
|
||
3. [Account Creation and Linking](#account-creation-and-linking)
|
||
4. [Profile and Address Management](#profile-and-address-management)
|
||
5. [Password Management](#password-management)
|
||
6. [Product Catalog and Eligibility](#product-catalog-and-eligibility)
|
||
7. [Order Creation](#order-creation)
|
||
8. [Order Fulfillment and Provisioning](#order-fulfillment-and-provisioning)
|
||
9. [Billing and Payments](#billing-and-payments)
|
||
10. [Subscriptions and Services](#subscriptions-and-services)
|
||
11. [SIM Management](#sim-management)
|
||
12. [Support Cases](#support-cases)
|
||
13. [Dashboard and Summary Data](#dashboard-and-summary-data)
|
||
14. [Realtime Events](#realtime-events)
|
||
15. [Caching Strategy](#caching-strategy)
|
||
16. [Error Handling](#error-handling)
|
||
17. [Rate Limiting and Security](#rate-limiting-and-security)
|
||
|
||
---
|
||
|
||
## System Architecture
|
||
|
||
The portal consists of two main components:
|
||
|
||
- **Frontend**: Next.js application serving the customer UI
|
||
- **BFF (Backend-for-Frontend)**: NestJS API that orchestrates calls to external systems
|
||
|
||
### Connected Systems
|
||
|
||
| System | Role | Integration Method |
|
||
| ---------------------- | --------------------------------------- | ------------------------------------ |
|
||
| **WHMCS** | Billing system of record | REST API (API actions) |
|
||
| **Salesforce** | CRM and order management | REST API + Change Data Capture (CDC) |
|
||
| **Freebit** | SIM/MVNO provisioning | REST API |
|
||
| **SFTP (fs.mvno.net)** | Call/SMS detail records | SFTP file download |
|
||
| **PostgreSQL** | Portal user accounts and ID mappings | Direct connection |
|
||
| **Redis** | Caching and pub/sub for realtime events | Direct connection |
|
||
|
||
### ID Mapping
|
||
|
||
The portal maintains a mapping table (`id_mappings` in PostgreSQL) linking:
|
||
|
||
- `user_id` (portal UUID) ↔ `whmcs_client_id` (integer) ↔ `sf_account_id` (Salesforce 18-char ID)
|
||
|
||
This mapping is validated on every cross-system operation. The mapping is cached in Redis and can be looked up by any of the three IDs.
|
||
|
||
---
|
||
|
||
## Data Ownership and Flow
|
||
|
||
| Data | System of Record | Portal Behavior |
|
||
| -------------------------- | ------------------------- | ------------------------------------------------------------------------- |
|
||
| User credentials | Portal (PostgreSQL) | Passwords hashed with Argon2; no WHMCS/SF credentials stored |
|
||
| Client profile & address | WHMCS | Portal reads/writes to WHMCS; clears cache on update |
|
||
| Product catalog & prices | Salesforce (Pricebook) | Portal reads from portal pricebook (configured via `PORTAL_PRICEBOOK_ID`) |
|
||
| Orders & order status | Salesforce (Order object) | Portal creates orders; Salesforce CDC triggers fulfillment |
|
||
| Invoices & payment methods | WHMCS | Portal reads only; payments via WHMCS SSO |
|
||
| Subscriptions/Services | WHMCS (tblhosting) | Portal reads only |
|
||
| Support cases | Salesforce (Case object) | Portal creates/reads cases with Origin = "Portal Website" |
|
||
| SIM details & usage | Freebit | Portal reads/writes via Freebit API |
|
||
|
||
---
|
||
|
||
## Account Creation and Linking
|
||
|
||
### Salesforce Account Lookup
|
||
|
||
The portal finds a Salesforce Account using the Customer Number:
|
||
|
||
```sql
|
||
SELECT Id, Name, WH_Account__c
|
||
FROM Account
|
||
WHERE SF_Account_No__c = '{customerNumber}'
|
||
```
|
||
|
||
| Field | Purpose |
|
||
| ------------------ | ----------------------------------------------------------- |
|
||
| `SF_Account_No__c` | Customer Number field used for lookup |
|
||
| `Id` | The Salesforce Account ID (18-char) stored in `id_mappings` |
|
||
| `WH_Account__c` | If populated, indicates account is already linked to WHMCS |
|
||
|
||
### New Customer Sign-Up
|
||
|
||
**Validation Steps:**
|
||
|
||
1. Check if email already exists in portal `users` table
|
||
- If exists with mapping → "You already have an account. Please sign in."
|
||
- If exists without mapping → "Please sign in to continue setup."
|
||
2. Query Salesforce Account by Customer Number (`SF_Account_No__c`)
|
||
- If not found → "Salesforce account not found for Customer Number"
|
||
3. Check if `WH_Account__c` field is already populated on Salesforce Account
|
||
- If populated → "You already have an account. Please use the login page."
|
||
4. Check if email exists in WHMCS via `GetClientsDetails`
|
||
- If found with portal mapping → "You already have an account. Please sign in."
|
||
- If found without mapping → "We found an existing billing account. Please link your account instead."
|
||
|
||
**Creation Steps (if validation passes):**
|
||
|
||
1. Create WHMCS client via `AddClient` API action with:
|
||
- Contact info: `firstname`, `lastname`, `email`, `phonenumber`
|
||
- Address fields: `address1`, `address2`, `city`, `state`, `postcode`, `country`
|
||
- Custom field 198 (configurable): Customer Number
|
||
- Optional custom fields: DOB, Gender, Nationality (field IDs configurable)
|
||
- Password (synced to WHMCS for SSO compatibility)
|
||
2. Create portal user record in PostgreSQL (password hashed with Argon2)
|
||
3. Create ID mapping in same transaction: `user_id` ↔ `whmcs_client_id` ↔ `sf_account_id`
|
||
4. Update Salesforce Account with portal fields (see below)
|
||
|
||
**Salesforce Account Fields Updated:**
|
||
|
||
| Field | Value | Notes |
|
||
| ------------------------------- | --------------- | ------------------------------------------------------ |
|
||
| `Portal_Status__c` | "Active" | Configurable via `ACCOUNT_PORTAL_STATUS_FIELD` |
|
||
| `Portal_Registration_Source__c` | "Portal" | Configurable via `ACCOUNT_PORTAL_STATUS_SOURCE_FIELD` |
|
||
| `Portal_Last_SignIn__c` | ISO timestamp | Configurable via `ACCOUNT_PORTAL_LAST_SIGNED_IN_FIELD` |
|
||
| `WH_Account__c` | WHMCS Client ID | Configurable via `ACCOUNT_WHMCS_FIELD` |
|
||
|
||
**Rollback Behavior:**
|
||
|
||
- If WHMCS client creation fails → no portal record created
|
||
- If portal DB transaction fails after WHMCS creation → WHMCS client marked `Inactive` for manual cleanup
|
||
|
||
### Linking Existing WHMCS Account
|
||
|
||
**Flow:**
|
||
|
||
1. Customer submits WHMCS email and password
|
||
2. Portal validates credentials via WHMCS `ValidateLogin` API action
|
||
3. Check if WHMCS client is already mapped → "This billing account is already linked. Please sign in."
|
||
4. Portal reads Customer Number from WHMCS custom field 198 via `GetClientsDetails`
|
||
5. Portal queries Salesforce Account by Customer Number (`SF_Account_No__c`)
|
||
6. Portal creates local user (without password – needs to be set)
|
||
7. Portal inserts ID mapping
|
||
8. Portal updates Salesforce Account with portal flags (`Portal_Status__c` = "Active", etc.)
|
||
9. Customer is prompted to set a portal password
|
||
|
||
---
|
||
|
||
## Profile and Address Management
|
||
|
||
### Data Source
|
||
|
||
- All profile/address data is read from WHMCS via `GetClientsDetails` API action
|
||
- Portal caches client profile for 30 minutes
|
||
|
||
### Updates
|
||
|
||
- Profile updates are written to WHMCS via `UpdateClient` API action
|
||
- Cache is invalidated immediately after successful update
|
||
- Salesforce only receives address snapshots at order creation time (not live synced)
|
||
|
||
### Available Fields
|
||
|
||
| Field | WHMCS Field | Notes |
|
||
| ---------------- | ------------- | -------------------- |
|
||
| First Name | `firstname` | |
|
||
| Last Name | `lastname` | |
|
||
| Email | `email` | |
|
||
| Phone | `phonenumber` | Format: +CC.NNNNNNNN |
|
||
| Address Line 1 | `address1` | |
|
||
| Address Line 2 | `address2` | Optional |
|
||
| City | `city` | |
|
||
| State/Prefecture | `state` | |
|
||
| Postcode | `postcode` | |
|
||
| Country | `country` | 2-letter ISO code |
|
||
|
||
---
|
||
|
||
## Password Management
|
||
|
||
### Portal Password
|
||
|
||
- Stored in PostgreSQL hashed with Argon2
|
||
- Completely separate from WHMCS credentials after initial sync
|
||
- WHMCS password is set during signup for SSO compatibility but not synced thereafter
|
||
|
||
### Password Reset Flow
|
||
|
||
1. Customer requests reset via email
|
||
2. Portal generates time-limited token (stored in DB)
|
||
3. Email sent with reset link
|
||
4. Customer submits new password with token
|
||
5. All existing sessions are invalidated (token blacklist)
|
||
6. Customer must log in again
|
||
|
||
### Rate Limiting
|
||
|
||
- Password reset requests: 5 per 15 minutes per IP
|
||
- Response always: "If an account exists, a reset email has been sent" (prevents enumeration)
|
||
|
||
---
|
||
|
||
## Product Catalog and Eligibility
|
||
|
||
### Catalog Source
|
||
|
||
- Products pulled from Salesforce Pricebook configured via `PORTAL_PRICEBOOK_ID`
|
||
- Only products marked for portal visibility are shown
|
||
- Categories: Internet, SIM/Mobile, VPN
|
||
|
||
### Caching
|
||
|
||
- Catalog cached in Redis without TTL
|
||
- Invalidated via Salesforce CDC when `PricebookEntry` records change
|
||
- Volatile/dynamic data uses 60-second TTL
|
||
|
||
### SIM Family Plans
|
||
|
||
- Portal checks WHMCS for existing active SIM subscriptions via `GetClientsProducts`
|
||
- Filter: `status = Active` AND product in SIM group
|
||
- If active SIM exists → family/discount SIM plans are shown
|
||
- If no active SIM → family plans are hidden
|
||
|
||
### Internet Eligibility
|
||
|
||
- Portal queries Salesforce for account-specific eligibility
|
||
- Eligibility result cached per `sf_account_id` (no TTL, invalidated on Salesforce change)
|
||
- Checks for duplicate active Internet services in WHMCS before allowing order
|
||
|
||
---
|
||
|
||
## Order Creation
|
||
|
||
### Pre-Checkout Validation
|
||
|
||
1. User must have valid ID mapping (`whmcs_client_id` exists)
|
||
2. User must have at least one payment method in WHMCS (via `GetPayMethods`)
|
||
3. For Internet orders: no existing active Internet service in WHMCS
|
||
|
||
### Salesforce Order Structure
|
||
|
||
**Order object fields:**
|
||
|
||
| Field | Value |
|
||
| ------------------------ | --------------------------------- |
|
||
| `AccountId` | From ID mapping (`sf_account_id`) |
|
||
| `EffectiveDate` | Today's date |
|
||
| `Status` | "Pending Review" |
|
||
| `Pricebook2Id` | Portal pricebook ID |
|
||
| `Type__c` | Internet / SIM / VPN |
|
||
| `Activation_Type__c` | Immediate / Scheduled |
|
||
| `Activation_Schedule__c` | Date if scheduled |
|
||
| Address fields | Snapshot from WHMCS profile |
|
||
|
||
**OrderItem records:**
|
||
|
||
- Created via Salesforce Composite API
|
||
- `PricebookEntryId` from catalog lookup by SKU
|
||
- `Quantity` and `UnitPrice` from pricebook
|
||
|
||
### No Payment Storage
|
||
|
||
- Portal verifies payment method exists but does not store or process card data
|
||
- Actual payment occurs in WHMCS after fulfillment creates invoice
|
||
|
||
---
|
||
|
||
## Order Fulfillment and Provisioning
|
||
|
||
### Trigger
|
||
|
||
- Salesforce CDC detects Order status change (e.g., to "Approved" or "Reactivate")
|
||
- Event is published and picked up by the portal's provisioning queue (BullMQ)
|
||
- Idempotency key prevents duplicate processing
|
||
|
||
### Provisioning Steps
|
||
|
||
1. Update Salesforce `Activation_Status__c` = "Activating"
|
||
2. Map Salesforce OrderItems to WHMCS products
|
||
3. Call WHMCS `AddOrder` API action:
|
||
- Creates WHMCS order, invoice, and hosting/subscription records
|
||
- `paymentmethod`: "stripe"
|
||
- `promocode`: applied if configured
|
||
- `noinvoiceemail`: true (portal handles notifications)
|
||
4. For SIM orders: call Freebit activation API
|
||
5. Update Salesforce Order with:
|
||
- `WHMCS_Order_ID__c`
|
||
- `Activation_Status__c` = "Active" or error status
|
||
- Error codes/messages if failed
|
||
6. Invalidate order cache
|
||
7. Publish realtime event for UI live update
|
||
|
||
### Distributed Transaction
|
||
|
||
The fulfillment uses a distributed transaction pattern with rollback:
|
||
|
||
- If WHMCS creation fails after Salesforce status update → rollback Salesforce status
|
||
- No partial orders are left in WHMCS
|
||
|
||
### Error Handling
|
||
|
||
| Scenario | Behavior |
|
||
| ---------------------- | ------------------------------------------------- |
|
||
| Missing payment method | Pause with `PAYMENT_METHOD_MISSING` in Salesforce |
|
||
| WHMCS API failure | Mark failed; rollback Salesforce status |
|
||
| Freebit failure | Mark failed; error written to Salesforce |
|
||
| Transient failure | Retry via BullMQ queue with backoff |
|
||
|
||
---
|
||
|
||
## Billing and Payments
|
||
|
||
### Invoice Data
|
||
|
||
- Fetched from WHMCS via `GetInvoices` and `GetInvoice` API actions
|
||
- List cached 90 seconds; individual invoice cached 5 minutes
|
||
- Invalidated by WHMCS webhooks and portal write operations
|
||
|
||
### Payment Methods
|
||
|
||
- Fetched from WHMCS via `GetPayMethods` API action
|
||
- Cached 15 minutes per user
|
||
- Portal transforms WHMCS response to normalized format
|
||
- First payment method marked as default
|
||
|
||
### Payment Gateways
|
||
|
||
- Fetched from WHMCS via `GetPaymentMethods` API action
|
||
- Cached 1 hour (rarely changes)
|
||
|
||
### SSO Links for Payment
|
||
|
||
- Portal generates WHMCS SSO token via `CreateSsoToken` API action
|
||
- Destination: `index.php?rp=/invoice/{id}/pay`
|
||
- Token valid ~60 seconds
|
||
- Payment method or gateway can be pre-selected via URL params
|
||
- Portal normalizes redirect URL to configured WHMCS base URL
|
||
|
||
---
|
||
|
||
## Subscriptions and Services
|
||
|
||
### Data Source
|
||
|
||
- WHMCS `GetClientsProducts` API action
|
||
- Returns hosting/subscription records from `tblhosting`
|
||
|
||
### Cached Fields
|
||
|
||
| Field | WHMCS Source |
|
||
| ----------------- | ------------------------------------------------------ |
|
||
| ID | `id` |
|
||
| Product Name | `name` / `groupname` |
|
||
| Status | `status` (Active, Pending, Suspended, Cancelled, etc.) |
|
||
| Registration Date | `regdate` |
|
||
| Next Due Date | `nextduedate` |
|
||
| Amount | `amount` |
|
||
| Billing Cycle | `billingcycle` |
|
||
|
||
### Caching
|
||
|
||
- List cached 5 minutes
|
||
- Individual subscription cached 10 minutes
|
||
- Invalidated on WHMCS webhooks or profile updates
|
||
|
||
---
|
||
|
||
## SIM Management
|
||
|
||
For subscriptions identified as SIM products, additional management is available via Freebit API.
|
||
|
||
### Identifying SIM Subscriptions
|
||
|
||
- Portal checks if product name contains "SIM" (case-insensitive)
|
||
- SIM management UI only shown for matching subscriptions
|
||
|
||
### Data Retrieval
|
||
|
||
**Account Details (PA05-01 getAcnt / master/getAcnt):**
|
||
|
||
| Portal Field | Freebit Field |
|
||
| --------------------- | --------------------------------------------- |
|
||
| MSISDN | `msisdn` |
|
||
| ICCID | `iccid` |
|
||
| IMSI | `imsi` |
|
||
| EID | `eid` |
|
||
| Plan Code | `planCode` |
|
||
| Status | `status` (active/suspended/cancelled/pending) |
|
||
| SIM Type | `simType` (physical/esim/standard/nano/micro) |
|
||
| Remaining Quota (KB) | `remainingQuotaKb` |
|
||
| Voice Mail | `voiceMailEnabled` |
|
||
| Call Waiting | `callWaitingEnabled` |
|
||
| International Roaming | `internationalRoamingEnabled` |
|
||
| Network Type | `networkType` (4G/5G) |
|
||
|
||
**Usage (getTrafficInfo):**
|
||
|
||
- Today's usage (MB)
|
||
- Monthly usage (MB)
|
||
- Recent daily breakdown
|
||
|
||
### Available Operations
|
||
|
||
| Operation | Freebit API | Effect Timing |
|
||
| --------------------- | --------------------------------- | ------------------------------------------------------- |
|
||
| Top-Up Data | `addSpec` / `eachQuota` | Immediate or scheduled |
|
||
| Change Plan | PA05-21 `changePlan` | 1st of following month (requires `runTime` in YYYYMMDD) |
|
||
| Update Voice Features | PA05-06 `talkoption/changeOrder` | Immediate |
|
||
| Update Network Type | PA05-38 `contractline/change` | Immediate |
|
||
| Cancel SIM Plan | PA05-04 `releasePlan` | Scheduled |
|
||
| Cancel SIM Account | PA02-04 `cnclAcnt` | Scheduled (requires `runDate`) |
|
||
| Reissue eSIM | `reissueEsim` / PA05-41 `addAcct` | As scheduled |
|
||
|
||
### Voice Feature Values
|
||
|
||
| Feature | Enable Value | Disable Value |
|
||
| -------------------- | ------------ | ------------- |
|
||
| Voice Mail | "10" | "20" |
|
||
| Call Waiting | "10" | "20" |
|
||
| World Wing (Roaming) | "10" | "20" |
|
||
|
||
When enabling World Wing, `worldWingCreditLimit` is set to "50000" (minimum permitted).
|
||
|
||
### Operation Timing Constraints
|
||
|
||
Critical rule: **30-minute minimum gap** between these operations:
|
||
|
||
- Voice feature changes (PA05-06)
|
||
- Network type changes (PA05-38)
|
||
- Plan changes (PA05-21)
|
||
|
||
Additional constraints:
|
||
|
||
- PA05-21 (plan change) and PA02-04 (cancellation) cannot coexist
|
||
- After PA02-04 cancellation is sent, any PA05-21 call will cancel the cancellation
|
||
- Voice/network changes should be made before 25th of month for billing cycle alignment
|
||
|
||
The portal enforces these constraints with in-memory operation timestamps per Freebit account. Stale entries are cleaned up periodically (entries older than 35 minutes, except cancellations which persist).
|
||
|
||
### Call/SMS History
|
||
|
||
- Retrieved via SFTP from `fs.mvno.net`
|
||
- Files: `PASI_talk-detail-YYYYMM.csv`, `PASI_sms-detail-YYYYMM.csv`
|
||
- Available 2 months behind current date (e.g., November can access September)
|
||
- Connection uses SSH key fingerprint verification in production
|
||
|
||
---
|
||
|
||
## Support Cases
|
||
|
||
### Salesforce Case Integration
|
||
|
||
**Create Case:**
|
||
|
||
| Field | Value |
|
||
| ------------- | --------------------------------- |
|
||
| `AccountId` | From ID mapping (`sf_account_id`) |
|
||
| `Origin` | "Portal Website" |
|
||
| `Subject` | Customer input (required) |
|
||
| `Description` | Customer input (required) |
|
||
| `Type` | Customer selection (optional) |
|
||
| `Priority` | Customer selection (optional) |
|
||
| `Status` | "New" |
|
||
|
||
**Read Cases:**
|
||
|
||
- Portal queries Cases where `AccountId` matches mapped account
|
||
- No caching – always live read from Salesforce
|
||
|
||
### Security
|
||
|
||
- Cases are strictly filtered to the customer's linked Account
|
||
- If case not found or belongs to different account → "case not found" response
|
||
|
||
---
|
||
|
||
## Dashboard and Summary Data
|
||
|
||
The dashboard aggregates data from multiple sources:
|
||
|
||
| Metric | Source | Query |
|
||
| ---------------- | ---------- | --------------------------------------- |
|
||
| Recent Orders | Salesforce | Orders for account, last 30 days |
|
||
| Pending Invoices | WHMCS | Invoices with status Unpaid/Overdue |
|
||
| Active Services | WHMCS | Subscriptions with status Active |
|
||
| Open Cases | Salesforce | Cases for account with Status ≠ Closed |
|
||
| Next Invoice | WHMCS | First unpaid invoice by due date |
|
||
| Activity Feed | Aggregated | Recent invoices, orders, cases combined |
|
||
|
||
---
|
||
|
||
## Realtime Events
|
||
|
||
### SSE Endpoint
|
||
|
||
- Single endpoint: `GET /api/events`
|
||
- Server-Sent Events (SSE) connection
|
||
- Requires authentication
|
||
- Backed by Redis pub/sub for multi-instance delivery
|
||
|
||
### Event Streams
|
||
|
||
| Channel | Events |
|
||
| ---------------------------- | ---------------------------------------- |
|
||
| `account:sf:{sf_account_id}` | Order status updates, fulfillment events |
|
||
| `global:services` | Services/pricebook invalidation |
|
||
|
||
### Connection Management
|
||
|
||
- Heartbeat every 30 seconds (`account.stream.heartbeat`)
|
||
- Ready event on connection (`account.stream.ready`)
|
||
- Per-user connection limit enforced
|
||
- Rate limit: 30 connection attempts per minute
|
||
- Connection limiter prevents resource exhaustion
|
||
|
||
---
|
||
|
||
## Caching Strategy
|
||
|
||
### Redis Key Scoping
|
||
|
||
All cache keys include user identifier to prevent data mix-ups between customers.
|
||
|
||
### TTL Summary
|
||
|
||
| Data | TTL | Invalidation Trigger |
|
||
| --------------------- | ------------------- | --------------------------------- |
|
||
| Catalog | None (event-driven) | Salesforce CDC on PricebookEntry |
|
||
| Eligibility | None | Salesforce CDC on Account changes |
|
||
| Orders | None (event-driven) | Salesforce CDC on Order/OrderItem |
|
||
| ID Mappings | None | Create/update/delete operations |
|
||
| Invoices (list) | 90 seconds | WHMCS webhook, write operations |
|
||
| Invoice (detail) | 5 minutes | WHMCS webhook, write operations |
|
||
| Subscriptions (list) | 5 minutes | WHMCS webhook, profile update |
|
||
| Subscription (single) | 10 minutes | WHMCS webhook, profile update |
|
||
| Payment Methods | 15 minutes | Add/remove operations |
|
||
| Payment Gateways | 1 hour | Rarely changes |
|
||
| Client Profile | 30 minutes | Profile/address update |
|
||
| Signup Account Lookup | 30 seconds | N/A |
|
||
| Support Cases | None (live) | N/A |
|
||
|
||
### Fallback Behavior
|
||
|
||
- If cache read fails → fall back to live API call
|
||
- Failures are not cached (prevents stale error states)
|
||
|
||
---
|
||
|
||
## Error Handling
|
||
|
||
### General Principles
|
||
|
||
- Fail safe with clear messages
|
||
- No partial writes – operations are atomic where possible
|
||
- Errors written back to Salesforce for visibility (fulfillment)
|
||
- Generic error messages to customers to avoid information leakage
|
||
|
||
### Error Responses by Area
|
||
|
||
| Area | Error Scenario | Response |
|
||
| ----------- | ---------------------------- | ------------------------------------------------------------------------- |
|
||
| Sign-up | Email exists with mapping | "You already have an account. Please sign in." |
|
||
| Sign-up | Email exists without mapping | "Please sign in to continue setup." |
|
||
| Sign-up | Customer Number not found | "Salesforce account not found for Customer Number" |
|
||
| Sign-up | `WH_Account__c` already set | "You already have an account. Please use the login page." |
|
||
| Sign-up | Email exists in WHMCS | "We found an existing billing account. Please link your account instead." |
|
||
| Sign-up | WHMCS client creation fails | "Failed to create billing account" (no portal record created) |
|
||
| Sign-up | DB transaction fails | WHMCS client marked Inactive for cleanup |
|
||
| Link | WHMCS client already mapped | "This billing account is already linked. Please sign in." |
|
||
| Checkout | No payment method | Block with "Add payment method" prompt |
|
||
| Checkout | Duplicate Internet service | Block with explanation |
|
||
| Fulfillment | Payment method missing | Pause, write `PAYMENT_METHOD_MISSING` to Salesforce |
|
||
| Billing | Invoice not found | "Invoice not found" (no data leakage) |
|
||
| Billing | WHMCS unavailable | "Billing system unavailable, try later" |
|
||
| SIM | 30-minute rule violation | "Please wait 30 minutes between changes" |
|
||
| SIM | Pending cancellation | "Plan changes not allowed after cancellation" |
|
||
| SIM | Not a SIM subscription | "This subscription is not a SIM service" |
|
||
| Support | Case not found/wrong account | "Case not found" |
|
||
|
||
---
|
||
|
||
## Rate Limiting and Security
|
||
|
||
### API Rate Limits
|
||
|
||
| Endpoint Category | Limit | Window |
|
||
| ----------------- | ------------ | ---------- |
|
||
| General API | 100 requests | 60 seconds |
|
||
| Login | 3 attempts | 15 minutes |
|
||
| Signup | 5 attempts | 15 minutes |
|
||
| Password Reset | 5 attempts | 15 minutes |
|
||
| Token Refresh | 10 attempts | 5 minutes |
|
||
| SSE Connect | 30 attempts | 60 seconds |
|
||
| Order Creation | 5 attempts | 60 seconds |
|
||
| Signup Validation | 20 attempts | 10 minutes |
|
||
|
||
### Rate Limit Key
|
||
|
||
- Composed of: IP address + User-Agent hash
|
||
- Prevents bypass via User-Agent rotation alone
|
||
- Uses Redis-backed `rate-limiter-flexible`
|
||
|
||
### Upstream Throttling
|
||
|
||
- WHMCS requests queued with concurrency limit
|
||
- Salesforce requests queued with concurrency limit
|
||
- Respects Salesforce daily API limits
|
||
- Graceful degradation when limits approached
|
||
|
||
### Security Features
|
||
|
||
- CAPTCHA integration available (Turnstile/hCaptcha)
|
||
- Configurable via `AUTH_CAPTCHA_PROVIDER`, `AUTH_CAPTCHA_SECRET`
|
||
- Can be enabled after threshold of failed auth attempts
|
||
- Password reset responses are generic (prevents account enumeration)
|
||
- Cross-account data access returns "not found" (prevents data leakage)
|
||
- Token blacklist for invalidated sessions
|
||
- CSRF protection on state-changing operations
|
||
|
||
---
|
||
|
||
## Environment Configuration
|
||
|
||
Key environment variables for system integration:
|
||
|
||
| Variable | Purpose | Default |
|
||
| -------------------------------- | -------------------------------- | -------------------- |
|
||
| `WHMCS_API_URL` | WHMCS API endpoint | - |
|
||
| `WHMCS_API_IDENTIFIER` | WHMCS API credentials | - |
|
||
| `WHMCS_API_SECRET` | WHMCS API credentials | - |
|
||
| `WHMCS_CUSTOMER_NUMBER_FIELD_ID` | Custom field for Customer Number | "198" |
|
||
| `SALESFORCE_LOGIN_URL` | Salesforce auth endpoint | - |
|
||
| `PORTAL_PRICEBOOK_ID` | Salesforce Pricebook for catalog | - |
|
||
| `ACCOUNT_PORTAL_STATUS_FIELD` | SF Account field for status | "Portal_Status\_\_c" |
|
||
| `ACCOUNT_WHMCS_FIELD` | SF Account field for WHMCS ID | "WH_Account\_\_c" |
|
||
| `FREEBIT_API_URL` | Freebit API endpoint | - |
|
||
| `SFTP_HOST` | MVNO SFTP server | "fs.mvno.net" |
|
||
|
||
---
|
||
|
||
_Last updated: December 2025_
|