# 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 → BFF → 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 ```javascript // Salesforce Quick Action calls BFF // Named Credential: Portal_BFF_Endpoint // Endpoint: https://portal-api.company.com/orders/{!Order.Id}/fulfill POST /orders/8014x000000ABCDXYZ/fulfill Headers: { "X-SF-Signature": "sha256=a1b2c3d4e5f6...", "X-SF-Timestamp": "2024-01-15T10:30:00Z", "X-SF-Nonce": "abc123def456", "Idempotency-Key": "provision_8014x000000ABCDXYZ_1705312200000" } Body: { "orderId": "8014x000000ABCDXYZ", "timestamp": "2024-01-15T10:30:00Z", "nonce": "abc123def456" } ``` ### 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 ### Webhook Security Headers ```typescript // Required headers for Salesforce → BFF webhook { "X-SF-Signature": "sha256=HMAC-SHA256(secret, body)", "X-SF-Timestamp": "2024-01-15T10:30:00Z", "X-SF-Nonce": "unique_random_string", "Idempotency-Key": "provision_{orderId}_{timestamp}" } // Validation rules ├── Signature: HMAC-SHA256 verification with shared secret ├── Timestamp: Max 5 minutes old ├── Nonce: Stored to prevent replay attacks └── Idempotency: Prevents duplicate provisioning ``` ### 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 clicks "Provision Order" 10:30:00.100 - Webhook received and validated 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" 10:30:02.300 - Response sent to Salesforce Total fulfillment time: ~2.3 seconds ⚡ ``` ### API Call Performance - **Salesforce getOrder**: ~200ms - **WHMCS AddOrder**: ~400ms - **WHMCS AcceptOrder**: ~300ms - **Salesforce updateOrder**: ~150ms ## 🔧 Configuration Requirements ### Salesforce Setup ```apex // Quick Action configuration Global class OrderProvisioningQuickAction { @InvocableMethod(label='Provision Order' description='Provision order in WHMCS') public static void provisionOrder(List orderIds) { for (Id orderId : orderIds) { HttpRequest req = new HttpRequest(); req.setEndpoint('callout:Portal_BFF_Endpoint/orders/' + orderId + '/fulfill'); req.setMethod('POST'); req.setHeader('Content-Type', 'application/json'); req.setHeader('X-SF-Signature', generateHmacSignature(orderId)); req.setHeader('X-SF-Timestamp', Datetime.now().formatGmt('yyyy-MM-dd\'T\'HH:mm:ss\'Z\'')); req.setHeader('X-SF-Nonce', generateNonce()); req.setHeader('Idempotency-Key', 'provision_' + orderId + '_' + Datetime.now().getTime()); req.setBody(JSON.serialize(new Map{ 'orderId' => orderId, 'timestamp' => Datetime.now().formatGmt('yyyy-MM-dd\'T\'HH:mm:ss\'Z\''), 'nonce' => generateNonce() })); Http http = new Http(); HttpResponse res = http.send(req); } } } ``` ### Named Credential ``` Name: Portal_BFF_Endpoint URL: https://portal-api.company.com Authentication: Custom (with HMAC signing) ``` ### Environment Variables ```bash # BFF Configuration SALESFORCE_WEBHOOK_SECRET=your_hmac_secret_key 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.