Assist_Design/docs/ORDER-FULFILLMENT-COMPLETE-GUIDE.md
T. Narantuya 0bf872e249 Refactor code formatting and improve documentation clarity
- Adjusted YAML and JSON files for consistent formatting, including healthcheck commands and package exports.
- Enhanced readability in various TypeScript files by standardizing string quotes and improving line breaks.
- Updated documentation across multiple files to improve clarity and consistency, including address system and logging levels.
- Removed unnecessary package-lock.json from shared package directory to streamline dependencies.
2025-09-09 18:19:54 +09:00

12 KiB
Raw Blame History

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

// 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 (Platform Events)

Salesforce RecordTriggered 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
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

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

{
  "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_Codec, Error_Messagec

🔒 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

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 (RecordTriggered): On Order Status = Approved → Create OrderProvisionRequested__e with OrderId__c

Environment Variables

# 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.