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

399 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 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
```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 (RecordTriggered): 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.