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
// 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)
// 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
// 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
// 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)
-- 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
// 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
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
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
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
| 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
{
"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
{
"result": "success",
"orderid": 12345,
"serviceids": "67890,67891,67892,67893",
"addonids": "",
"domainids": "",
"invoiceid": 0
}
AcceptOrder Request
{
"action": "AcceptOrder",
"orderid": 12345
}
AcceptOrder Response
{
"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
// 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
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
// Quick Action configuration
Global class OrderProvisioningQuickAction {
@InvocableMethod(label='Provision Order' description='Provision order in WHMCS')
public static void provisionOrder(List<Id> 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<String, Object>{
'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
# 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.