# Order Fulfillment - Complete Implementation Guide *This document provides the complete, up-to-date specification for order creation and fulfillment workflow.* ## 🏗️ Architecture Overview ### System Components - **Portal Frontend**: Next.js customer interface - **Portal BFF**: NestJS backend orchestrating all integrations - **Salesforce**: Order management, catalog, CS review/approval - **WHMCS**: Billing, payment methods, service provisioning ### Data Flow ``` Customer → Portal → BFF → Salesforce (Order Creation) CS Team → Salesforce (Platform Event) → BFF (Subscriber) → WHMCS (Order Fulfillment) ``` ## 🛍️ Complete Customer Journey ### Phase 1: Order Creation #### 1. Customer Signup ```typescript // Required fields { email: "customer@example.com", password: "secure_password", firstName: "John", lastName: "Doe", customerNumber: "SF123456" // Salesforce Account Number } // Portal creates: ├── WHMCS Client (with Customer Number in custom field) ├── Portal User account └── Mapping: userId ↔ whmcsClientId ↔ sfAccountId ``` #### 2. Payment Method Setup (Required Gate) ```typescript // Portal checks payment method before checkout GET /billing/payment-methods/summary Response: { hasPaymentMethod: true/false } // If false, redirect to WHMCS SSO POST /auth/sso-link Response: { ssoUrl: "https://whmcs.com/index.php?rp=/account/paymentmethods&token=..." } ``` #### 3. Browse Catalog ```typescript // Personalized catalog based on eligibility GET /catalog/personalized Headers: { Authorization: "Bearer jwt_token" } // BFF queries Salesforce SELECT Id, Name, StockKeepingUnit, WH_Product_ID__c, Billing_Cycle__c FROM Product2 WHERE Portal_Catalog__c = true AND Internet_Offering_Type__c = :accountEligibility ``` #### 4. Place Order ```typescript // Customer checkout POST /orders { "items": [ { "sku": "INTERNET-GOLD-APT-1G", "quantity": 1 }, { "sku": "INTERNET-INSTALL-SINGLE", "quantity": 1 }, { "sku": "INTERNET-ADDON-HOME-PHONE", "quantity": 1 } ], "activationType": "Scheduled", "activationScheduledAt": "2024-01-20T09:00:00Z" } // BFF creates in Salesforce: Order { AccountId: "001xx000004TmiQAAS", Status: "Pending Review", Order_Type__c: "Internet", Activation_Type__c: "Scheduled", Activation_Scheduled_At__c: "2024-01-20T09:00:00Z" } OrderItems [ { Product2.SKU: "INTERNET-GOLD-APT-1G", Quantity: 1 }, { Product2.SKU: "INTERNET-INSTALL-SINGLE", Quantity: 1 }, { Product2.SKU: "INTERNET-ADDON-HOME-PHONE", Quantity: 1 } ] ``` ### Phase 2: CS Review & Approval #### 5. Order Review (Salesforce) ```sql -- CS Team views order with all details SELECT Id, OrderNumber, Status, TotalAmount, Account.Name, Order_Type__c, Activation_Type__c, Activation_Scheduled_At__c, (SELECT Product2.Name, Product2.WH_Product_ID__c, Quantity, UnitPrice FROM OrderItems) FROM Order WHERE Id = '8014x000000ABCDXYZ' ``` #### 6. Provision Trigger (Platform Events) ```text Salesforce Record‑Triggered Flow publishes Platform Event: OrderProvisionRequested__e Fields: - OrderId__c (Text 18) - IdemKey__c (Text 80, optional) ``` The portal subscribes to this event, enqueues a job, and performs provisioning. ### Phase 3: Order Fulfillment #### 7. Order Fulfillment Service (Modular Architecture) ##### OrderFulfillmentValidator ```typescript class OrderFulfillmentValidator { async validateFulfillmentRequest(sfOrderId: string, idempotencyKey: string) { // 1. Validate Salesforce order exists const sfOrder = await this.salesforceService.getOrder(sfOrderId); // 2. Check idempotency (already provisioned?) if (sfOrder.WHMCS_Order_ID__c) { return { isAlreadyProvisioned: true, whmcsOrderId: sfOrder.WHMCS_Order_ID__c }; } // 3. Get WHMCS client ID from mapping const clientId = await this.mappingsService.findBySfAccountId(sfOrder.Account.Id); // 4. Validate payment method exists const hasPaymentMethod = await this.whmcsOrderService.hasPaymentMethod(clientId); if (!hasPaymentMethod) { throw new ConflictException('Payment method missing - client must add payment method before fulfillment'); } return { sfOrder, clientId, isAlreadyProvisioned: false }; } } ``` ##### OrderWhmcsMapper ```typescript class OrderWhmcsMapper { mapOrderItemsToWhmcs(orderItems: any[]): WhmcsOrderItem[] { return orderItems.map(item => ({ productId: item.product.whmcsProductId, // From WH_Product_ID__c billingCycle: item.product.billingCycle.toLowerCase(), // From Billing_Cycle__c quantity: item.quantity })); } } ``` ##### OrderFulfillmentOrchestrator ```typescript class OrderFulfillmentOrchestrator { async executeFulfillment(sfOrderId: string, payload: any, idempotencyKey: string) { const context = { sfOrderId, idempotencyKey, steps: [] }; // Step 1: Validate request context.validation = await this.validator.validateFulfillmentRequest(sfOrderId, idempotencyKey); if (context.validation.isAlreadyProvisioned) { return { success: true, status: 'Already Fulfilled' }; } // Step 2: Update SF status to "Activating" await this.salesforceService.updateOrder({ Id: sfOrderId, Status: 'Activating', Provisioning_Status__c: 'In Progress' }); // Step 3: Get full order details context.orderDetails = await this.orderOrchestrator.getOrder(sfOrderId); // Step 4: Map to WHMCS format context.mappingResult = await this.mapper.mapOrderItemsToWhmcs(context.orderDetails.items); // Step 5: Create WHMCS order context.whmcsResult = await this.whmcsOrderService.addOrder({ clientId: context.validation.clientId, items: context.mappingResult.whmcsItems, paymentMethod: "mailin", noinvoice: true, noemail: true }); // Step 6: Accept/provision WHMCS order await this.whmcsOrderService.acceptOrder(context.whmcsResult.orderId); // Step 7: Update SF with success await this.salesforceService.updateOrder({ Id: sfOrderId, Status: 'Activated', Provisioning_Status__c: 'Fulfilled', WHMCS_Order_ID__c: context.whmcsResult.orderId }); return { success: true, status: 'Fulfilled', whmcsOrderId: context.whmcsResult.orderId }; } } ``` ## 📊 Complete Data Mapping Reference ### Salesforce to WHMCS Mapping #### Order Header Mapping | Source | Target | Example | Notes | |--------|--------|---------|-------| | `Order.AccountId` | Resolved to `clientid` | `1` | Via portal mapping table | | `Order.Id` | Added to order notes | `sfOrderId=8014x000000ABCDXYZ` | For tracking | | N/A | `paymentmethod` | `"mailin"` | Required by WHMCS API | | N/A | `noinvoice` | `true` | Don't create invoice during provisioning | | N/A | `noemail` | `true` | Don't send emails during provisioning | #### OrderItem Array Mapping | Salesforce Field | WHMCS Parameter | Example Value | Format | |------------------|-----------------|---------------|--------| | `Product2.WH_Product_ID__c` | `pid[]` | `["185", "242", "246"]` | String array | | `Product2.Billing_Cycle__c` | `billingcycle[]` | `["monthly", "onetime", "monthly"]` | String array | | `OrderItem.Quantity` | `qty[]` | `[1, 1, 1]` | Number array | #### Product ID Mapping Examples | Product Name | Salesforce SKU | WH_Product_ID__c | WHMCS pid | Billing Cycle | |--------------|----------------|------------------|-----------|---------------| | Internet Gold (Apartment 1G) | `INTERNET-GOLD-APT-1G` | 185 | "185" | "monthly" | | Single Installation | `INTERNET-INSTALL-SINGLE` | 242 | "242" | "onetime" | | Hikari Denwa Service | `INTERNET-ADDON-HOME-PHONE` | 246 | "246" | "monthly" | | Hikari Denwa Installation | `INTERNET-ADDON-DENWA-INSTALL` | 247 | "247" | "onetime" | | Weekend Installation Fee | `INTERNET-INSTALL-WEEKEND` | 245 | "245" | "onetime" | ### WHMCS API Request/Response Format #### AddOrder Request ```json { "action": "AddOrder", "clientid": 1, "paymentmethod": "mailin", "pid": ["185", "242", "246", "247"], "billingcycle": ["monthly", "onetime", "monthly", "onetime"], "qty": [1, 1, 1, 1], "noinvoice": true, "noemail": true, "promocode": "", "configoptions": ["", "", "", ""], "customfields": ["", "", "", ""] } ``` #### AddOrder Response ```json { "result": "success", "orderid": 12345, "serviceids": "67890,67891,67892,67893", "addonids": "", "domainids": "", "invoiceid": 0 } ``` #### AcceptOrder Request ```json { "action": "AcceptOrder", "orderid": 12345 } ``` #### AcceptOrder Response ```json { "result": "success" } ``` ### Status Update Mapping #### Success Flow | Step | Salesforce Order.Status | Provisioning_Status__c | WHMCS_Order_ID__c | |------|------------------------|------------------------|-------------------| | Initial | "Pending Review" | null | null | | CS Approval | "Activating" | "In Progress" | null | | WHMCS Created | "Activating" | "In Progress" | "12345" | | Services Provisioned | "Activated" | "Fulfilled" | "12345" | #### Failure Flow | Step | Salesforce Order.Status | Provisioning_Status__c | Error Fields | |------|------------------------|------------------------|--------------| | Initial | "Pending Review" | null | null | | CS Approval | "Activating" | "In Progress" | null | | Failure | "Draft" | "Failed" | Error_Code__c, Error_Message__c | ## 🔒 Security Implementation - No inbound Salesforce webhooks are used; provisioning is triggered via Platform Events. - Portal authenticates to Salesforce via JWT (Connected App) and requires Platform Event permissions. ### Error Codes ```typescript enum FulfillmentErrorCode { PAYMENT_METHOD_MISSING = "PAYMENT_METHOD_MISSING", ORDER_NOT_FOUND = "ORDER_NOT_FOUND", WHMCS_ERROR = "WHMCS_ERROR", MAPPING_ERROR = "MAPPING_ERROR", FULFILLMENT_ERROR = "FULFILLMENT_ERROR" } ``` ## ⚡ Performance Metrics ### Typical Timeline ``` 10:30:00.000 - CS approves Order 10:30:00.050 - Platform Event published (OrderProvisionRequested__e) 10:30:00.080 - BFF subscriber enqueues provisioning job 10:30:00.200 - Salesforce order updated to "Activating" 10:30:00.500 - Order details retrieved and mapped 10:30:01.000 - WHMCS AddOrder API call 10:30:01.500 - WHMCS AcceptOrder API call 10:30:02.000 - Services provisioned in WHMCS 10:30:02.200 - Salesforce updated to "Activated" Total fulfillment time: ~2.2 seconds (asynchronous trigger) ⚡ ``` ### API Call Performance - **Salesforce getOrder**: ~200ms - **WHMCS AddOrder**: ~400ms - **WHMCS AcceptOrder**: ~300ms - **Salesforce updateOrder**: ~150ms ## 🔧 Configuration Requirements ### Salesforce Setup ``` 1) Platform Event: OrderProvisionRequested__e (fields: OrderId__c [Text 18], IdemKey__c [Text 80, optional]) 2) Permission Set: grant Platform Event permissions and PE object read to the portal integration user 3) Flow (Record‑Triggered): On Order Status = Approved → Create OrderProvisionRequested__e with OrderId__c ``` ### Environment Variables ```bash # BFF Configuration (Salesforce Platform Events) SF_EVENTS_ENABLED=true SF_PROVISION_EVENT_CHANNEL=/event/OrderProvisionRequested__e SF_EVENTS_REPLAY=LATEST # WHMCS API WHMCS_API_IDENTIFIER=your_whmcs_api_id WHMCS_API_SECRET=your_whmcs_api_secret WHMCS_API_URL=https://your-whmcs.com/includes/api.php # Database DATABASE_URL=postgresql://user:pass@host:5432/portal ``` This comprehensive guide ensures consistent implementation across all teams and provides the complete picture of the order fulfillment workflow.