2025-11-06 17:01:34 +09:00
|
|
|
# ✅ CDC-Only Order Provisioning - Implementation Complete
|
|
|
|
|
|
|
|
|
|
## 🎯 What Changed
|
|
|
|
|
|
|
|
|
|
Your system now uses **CDC-only approach** for order provisioning. The Platform Event mechanism is no longer needed for order fulfillment.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 📊 Architecture Change
|
|
|
|
|
|
|
|
|
|
### **Before (Dual Mechanism):**
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
Salesforce Order Status → "Approved"
|
|
|
|
|
↓
|
|
|
|
|
Salesforce Flow publishes Platform Event
|
|
|
|
|
↓
|
|
|
|
|
SalesforcePubSubSubscriber receives event
|
|
|
|
|
↓
|
|
|
|
|
Enqueues provisioning job
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### **After (CDC Only):**
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
Salesforce Order Status → "Approved"
|
|
|
|
|
↓
|
2025-11-06 17:47:55 +09:00
|
|
|
Salesforce Flow: set Activation_Status__c = "Activating" (clear errors)
|
|
|
|
|
↓
|
|
|
|
|
CDC: OrderChangeEvent (Activation_Status__c)
|
2025-11-06 17:01:34 +09:00
|
|
|
↓
|
|
|
|
|
OrderCdcSubscriber receives event
|
|
|
|
|
↓
|
2025-11-06 17:47:55 +09:00
|
|
|
Detects: Activation_Status__c changed to "Activating"
|
2025-11-06 17:01:34 +09:00
|
|
|
↓
|
|
|
|
|
Enqueues provisioning job
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 🔧 Implementation Details
|
|
|
|
|
|
|
|
|
|
### **OrderCdcSubscriber Now Handles:**
|
|
|
|
|
|
|
|
|
|
1. **Order Provisioning** (NEW)
|
2025-11-06 17:47:55 +09:00
|
|
|
- Detects when Salesforce sets `Activation_Status__c` to `"Activating"`
|
2025-11-06 17:01:34 +09:00
|
|
|
- Validates order is not already provisioning/provisioned
|
|
|
|
|
- Enqueues provisioning job
|
|
|
|
|
|
|
|
|
|
2. **Cache Invalidation** (Existing)
|
|
|
|
|
- Invalidates cache for customer-facing field changes
|
|
|
|
|
- Ignores internal field changes
|
|
|
|
|
|
|
|
|
|
### **Key Guards Added:**
|
|
|
|
|
|
|
|
|
|
```typescript
|
2025-11-06 17:47:55 +09:00
|
|
|
// 1. Only continue when Activation_Status__c === "Activating"
|
|
|
|
|
if (activationStatus !== "Activating") return;
|
2025-11-06 17:01:34 +09:00
|
|
|
|
2025-11-06 17:47:55 +09:00
|
|
|
// 2. (Optional) If Status is present, require Approved/Reactivate
|
|
|
|
|
if (status && !PROVISION_TRIGGER_STATUSES.has(status)) return;
|
2025-11-06 17:01:34 +09:00
|
|
|
|
2025-11-06 17:47:55 +09:00
|
|
|
// 3. Don't trigger if already has WHMCS Order ID
|
2025-11-06 17:01:34 +09:00
|
|
|
if (whmcsOrderId) return;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 📝 Complete Flow Example
|
|
|
|
|
|
|
|
|
|
### Scenario: Admin Approves Order
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
TIME: 10:00:00 - Admin clicks "Approve" in Salesforce
|
|
|
|
|
↓
|
2025-11-06 17:47:55 +09:00
|
|
|
Salesforce Flow:
|
|
|
|
|
- Sets Activation_Status__c = "Activating"
|
|
|
|
|
- Clears Activation_Error_* fields
|
2025-11-06 17:01:34 +09:00
|
|
|
↓
|
|
|
|
|
TIME: 10:00:01 - CDC event published (automatic)
|
|
|
|
|
{
|
|
|
|
|
"Id": "801xxx",
|
2025-11-06 17:47:55 +09:00
|
|
|
"Activation_Status__c": "Activating",
|
|
|
|
|
"Activation_Error_Code__c": null,
|
|
|
|
|
"Activation_Error_Message__c": null,
|
|
|
|
|
"changedFields": ["Activation_Status__c", "Activation_Error_Code__c", "Activation_Error_Message__c"]
|
2025-11-06 17:01:34 +09:00
|
|
|
}
|
|
|
|
|
↓
|
|
|
|
|
TIME: 10:00:02 - OrderCdcSubscriber.handleOrderEvent()
|
|
|
|
|
↓
|
2025-11-06 17:47:55 +09:00
|
|
|
Step 1: Check if Activation_Status__c changed
|
|
|
|
|
→ Yes, value is "Activating"
|
2025-11-06 17:01:34 +09:00
|
|
|
↓
|
2025-11-06 17:47:55 +09:00
|
|
|
Step 2: handleActivationStatusChange()
|
|
|
|
|
→ activationStatus = "Activating" ✅
|
|
|
|
|
→ status (if provided) = "Approved" ✅
|
2025-11-06 17:01:34 +09:00
|
|
|
→ whmcsOrderId = null ✅ (not provisioned)
|
|
|
|
|
↓
|
|
|
|
|
Step 3: Enqueue provisioning job
|
|
|
|
|
provisioningQueue.enqueue({
|
|
|
|
|
sfOrderId: "801xxx",
|
2025-11-06 17:47:55 +09:00
|
|
|
idempotencyKey: "cdc-activation-1699999999999-801xxx",
|
2025-11-06 17:01:34 +09:00
|
|
|
correlationId: "cdc-order-801xxx"
|
|
|
|
|
})
|
|
|
|
|
↓
|
2025-11-06 17:47:55 +09:00
|
|
|
Log: "Order activation moved to Activating via CDC, enqueuing fulfillment"
|
2025-11-06 17:01:34 +09:00
|
|
|
↓
|
|
|
|
|
TIME: 10:00:03 - Provisioning processor picks up job
|
|
|
|
|
↓
|
2025-11-06 17:47:55 +09:00
|
|
|
Executes fulfillment (Activation_Status__c already = "Activating" so guard passes)
|
2025-11-06 17:01:34 +09:00
|
|
|
↓
|
|
|
|
|
Updates Salesforce:
|
|
|
|
|
- Status: "Completed"
|
2025-11-06 17:47:55 +09:00
|
|
|
- Activation_Status__c: "Activated"
|
|
|
|
|
- WHMCS_Order_ID__c: "12345"
|
2025-11-06 17:01:34 +09:00
|
|
|
↓
|
|
|
|
|
TIME: 10:00:05 - CDC events for status updates
|
2025-11-06 17:47:55 +09:00
|
|
|
Event 1: Activation_Status__c changed to "Activated"
|
|
|
|
|
→ Internal field → Skip cache invalidation ✅
|
|
|
|
|
→ Not "Activating" → Skip provisioning ✅
|
|
|
|
|
|
2025-11-06 17:01:34 +09:00
|
|
|
Event 2: Status → "Completed"
|
2025-11-06 17:47:55 +09:00
|
|
|
→ Status changed but not "Approved"/"Reactivate" → Skip provisioning ✅
|
2025-11-06 17:01:34 +09:00
|
|
|
→ Customer-facing field → Invalidate cache ✅
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 🔍 How It Prevents Duplicate Provisioning
|
|
|
|
|
|
|
|
|
|
### **Scenario: Multiple CDC Events**
|
|
|
|
|
|
|
|
|
|
```
|
2025-11-17 10:31:33 +09:00
|
|
|
Event 1: Activation_Status__c = "Activating"
|
2025-11-06 17:01:34 +09:00
|
|
|
→ Guard checks:
|
2025-11-17 10:31:33 +09:00
|
|
|
✅ activationStatus === "Activating"
|
|
|
|
|
✅ (Optional) Status = "Approved" (trigger)
|
|
|
|
|
✅ whmcsOrderId = null (not provisioned)
|
2025-11-06 17:01:34 +09:00
|
|
|
→ PROVISION ✅
|
|
|
|
|
|
2025-11-17 10:31:33 +09:00
|
|
|
Event 2: Activation_Status__c = "Activated"
|
2025-11-06 17:01:34 +09:00
|
|
|
→ Guard checks:
|
2025-11-17 10:31:33 +09:00
|
|
|
❌ activationStatus !== "Activating"
|
2025-11-06 17:01:34 +09:00
|
|
|
→ SKIP ✅
|
|
|
|
|
|
2025-11-17 10:31:33 +09:00
|
|
|
Event 3: Status = "Completed"
|
2025-11-06 17:01:34 +09:00
|
|
|
→ Guard checks:
|
2025-11-17 10:31:33 +09:00
|
|
|
❌ Status is not "Approved"/"Reactivate"
|
2025-11-06 17:01:34 +09:00
|
|
|
→ SKIP ✅
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### **Scenario: Re-approval After Cancellation**
|
|
|
|
|
|
|
|
|
|
```
|
2025-11-17 10:31:33 +09:00
|
|
|
Event 1: Activation_Status__c = "Activating"
|
2025-11-06 17:01:34 +09:00
|
|
|
→ Provisions order ✅
|
|
|
|
|
→ WHMCS_Order_ID__c = "12345"
|
|
|
|
|
|
|
|
|
|
Event 2: Status = "Cancelled"
|
2025-11-17 10:31:33 +09:00
|
|
|
→ No provisioning (activationStatus ≠ "Activating") ✅
|
2025-11-06 17:01:34 +09:00
|
|
|
|
2025-11-17 10:31:33 +09:00
|
|
|
Event 3: Activation_Status__c = "Activating" (re-approval Flow runs)
|
2025-11-06 17:01:34 +09:00
|
|
|
→ Guard checks:
|
2025-11-17 10:31:33 +09:00
|
|
|
✅ activationStatus === "Activating"
|
2025-11-06 17:01:34 +09:00
|
|
|
❌ whmcsOrderId = "12345" (already provisioned)
|
|
|
|
|
→ SKIP ✅ (prevents duplicate)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## ⚙️ Configuration
|
|
|
|
|
|
|
|
|
|
### **Environment Variables (No Changes Needed)**
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# CDC is already enabled
|
|
|
|
|
SF_EVENTS_ENABLED=true
|
|
|
|
|
SF_ORDER_CDC_CHANNEL=/data/OrderChangeEvent
|
|
|
|
|
SF_ORDER_ITEM_CDC_CHANNEL=/data/OrderItemChangeEvent
|
|
|
|
|
|
|
|
|
|
# Platform Event channel can remain (for other uses)
|
|
|
|
|
# Or can be removed if not using Platform Events anymore
|
|
|
|
|
SF_PROVISION_EVENT_CHANNEL=/event/Order_Fulfilment_Requested__e
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### **Salesforce Setup**
|
|
|
|
|
|
|
|
|
|
1. **Enable CDC on Order object** (required)
|
|
|
|
|
```
|
|
|
|
|
Setup → Integrations → Change Data Capture
|
|
|
|
|
Select: Order, OrderItem
|
|
|
|
|
Save
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
2. **Optional: Remove Platform Event Flow**
|
|
|
|
|
- The Flow that publishes Order_Fulfilment_Requested__e
|
|
|
|
|
- No longer needed for provisioning
|
|
|
|
|
- Can be deleted or disabled
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## ✅ What You Can Now Do in Salesforce
|
|
|
|
|
|
|
|
|
|
### **Simple Approval:**
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
Admin: Sets Order Status = "Approved"
|
2025-11-17 10:31:33 +09:00
|
|
|
Flow: Sets Activation_Status__c = "Activating" and clears previous errors
|
|
|
|
|
Result: CDC event triggers provisioning ✅
|
2025-11-06 17:01:34 +09:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### **Manual Status Changes:**
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
Admin: Changes Status field directly
|
|
|
|
|
- Draft → Skip
|
|
|
|
|
- Pending Review → Skip
|
2025-11-17 10:31:33 +09:00
|
|
|
- Approved → Flow sets Activation_Status__c = "Activating" → Provision ✅
|
2025-11-06 17:01:34 +09:00
|
|
|
- Completed → Skip (invalidate cache only)
|
|
|
|
|
- Cancelled → Skip
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### **Reactivation:**
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
Admin: Sets Status = "Reactivate"
|
2025-11-17 10:31:33 +09:00
|
|
|
Flow: Sets Activation_Status__c = "Activating" again
|
2025-11-06 17:01:34 +09:00
|
|
|
Result: Provisions again (if not already provisioned) ✅
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 🚨 Important Guards in Place
|
|
|
|
|
|
2025-11-17 10:31:33 +09:00
|
|
|
### **1. Activation Guard**
|
2025-11-06 17:01:34 +09:00
|
|
|
|
|
|
|
|
```typescript
|
2025-11-17 10:31:33 +09:00
|
|
|
if (activationStatus !== "Activating") {
|
|
|
|
|
// Only fire when Salesforce explicitly sets Activating
|
2025-11-06 17:01:34 +09:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2025-11-17 10:31:33 +09:00
|
|
|
### **2. Status Guard (Optional)**
|
2025-11-06 17:01:34 +09:00
|
|
|
|
|
|
|
|
```typescript
|
2025-11-17 10:31:33 +09:00
|
|
|
if (status && !PROVISION_TRIGGER_STATUSES.has(status)) {
|
|
|
|
|
// If Status value is present but not Approved/Reactivate, skip
|
2025-11-06 17:01:34 +09:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2025-11-17 10:31:33 +09:00
|
|
|
### **3. Idempotency Guard**
|
2025-11-06 17:01:34 +09:00
|
|
|
|
|
|
|
|
```typescript
|
2025-11-17 10:31:33 +09:00
|
|
|
if (whmcsOrderId) {
|
|
|
|
|
// Order already provisioned, don't provision again
|
2025-11-06 17:01:34 +09:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 📊 Comparison: Platform Event vs CDC
|
|
|
|
|
|
|
|
|
|
| Aspect | Platform Event (Before) | CDC Only (Now) |
|
|
|
|
|
|--------|------------------------|----------------|
|
|
|
|
|
| **Salesforce Setup** | Need Platform Event + Flow | Just enable CDC |
|
2025-11-17 10:31:33 +09:00
|
|
|
| **Trigger Point** | Flow publishes (explicit) | Activation_Status__c = "Activating" (CDC) |
|
2025-11-06 17:01:34 +09:00
|
|
|
| **Complexity** | Two mechanisms | One mechanism |
|
|
|
|
|
| **Idempotency** | Flow handles | Guards in Portal |
|
|
|
|
|
| **Custom Context** | Yes (IdemKey, CorrelationId) | No (inferred) |
|
|
|
|
|
| **Maintenance** | Flow + Code | Code only |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 🎯 Testing Checklist
|
|
|
|
|
|
|
|
|
|
### **Test 1: Normal Approval**
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# In Salesforce
|
|
|
|
|
1. Create Order (Status: "Draft")
|
2025-11-17 10:31:33 +09:00
|
|
|
2. Set Status = "Approved" (Flow flips Activation_Status__c = "Activating")
|
2025-11-06 17:01:34 +09:00
|
|
|
|
|
|
|
|
# Expected in Portal logs:
|
2025-11-17 10:31:33 +09:00
|
|
|
✅ Order activation moved to Activating via CDC
|
2025-11-06 17:01:34 +09:00
|
|
|
✅ Successfully enqueued provisioning job
|
|
|
|
|
✅ Provisioning job completed
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### **Test 2: Duplicate Prevention**
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# In Salesforce
|
|
|
|
|
1. Order already provisioned (WHMCS_Order_ID__c exists)
|
2025-11-17 10:31:33 +09:00
|
|
|
2. Flow sets Activation_Status__c = "Activating" (e.g., operator retries)
|
2025-11-06 17:01:34 +09:00
|
|
|
|
|
|
|
|
# Expected in Portal logs:
|
|
|
|
|
✅ Order already has WHMCS Order ID, skipping provisioning
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### **Test 3: Cancel Then Approve**
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# In Salesforce
|
2025-11-17 10:31:33 +09:00
|
|
|
1. Order Status = "Approved" → Flow sets Activation_Status__c = "Activating" → Provisions
|
2025-11-06 17:01:34 +09:00
|
|
|
2. Set Status = "Cancelled"
|
2025-11-17 10:31:33 +09:00
|
|
|
3. Set Status = "Approved" again (Flow sets Activation_Status__c = "Activating")
|
2025-11-06 17:01:34 +09:00
|
|
|
|
|
|
|
|
# Expected in Portal logs:
|
|
|
|
|
✅ First approval: Provisions
|
|
|
|
|
✅ Cancellation: No provisioning
|
|
|
|
|
✅ Second approval: Skipped (already has WHMCS_Order_ID__c)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### **Test 4: Cache Invalidation Still Works**
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# In Salesforce
|
|
|
|
|
1. Update Order.BillingAddress
|
|
|
|
|
|
|
|
|
|
# Expected in Portal logs:
|
|
|
|
|
✅ Order CDC event with customer-facing changes
|
|
|
|
|
✅ Invalidating cache
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 🔄 Rollback Plan (If Needed)
|
|
|
|
|
|
|
|
|
|
If you need to go back to Platform Events:
|
|
|
|
|
|
|
|
|
|
1. **Re-enable Salesforce Flow**
|
|
|
|
|
- Flow that publishes Order_Fulfilment_Requested__e
|
|
|
|
|
|
|
|
|
|
2. **Remove provisioning from OrderCdcSubscriber**
|
2025-11-17 10:31:33 +09:00
|
|
|
- Comment out the `handleActivationStatusChange()` call
|
2025-11-06 17:01:34 +09:00
|
|
|
- Keep cache invalidation logic
|
|
|
|
|
|
|
|
|
|
3. **Use SalesforcePubSubSubscriber again**
|
|
|
|
|
- Already registered in events.module.ts
|
|
|
|
|
- Just needs Flow to publish events
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 📈 Benefits You Get
|
|
|
|
|
|
|
|
|
|
✅ **Simpler Setup**
|
|
|
|
|
- No Platform Event definition needed
|
|
|
|
|
- No Flow to maintain
|
|
|
|
|
- One less mechanism to manage
|
|
|
|
|
|
|
|
|
|
✅ **Automatic Triggering**
|
2025-11-17 10:31:33 +09:00
|
|
|
- Any Flow that sets `Activation_Status__c = "Activating"` provisions
|
2025-11-06 17:01:34 +09:00
|
|
|
- No manual event publishing
|
|
|
|
|
- Less room for human error
|
|
|
|
|
|
|
|
|
|
✅ **Still Robust**
|
|
|
|
|
- Multiple guards prevent duplicates
|
|
|
|
|
- Idempotency built-in
|
|
|
|
|
- Smart filtering for cache invalidation
|
|
|
|
|
|
|
|
|
|
✅ **Unified CDC Strategy**
|
|
|
|
|
- Catalog uses CDC ✅
|
|
|
|
|
- Orders use CDC ✅
|
|
|
|
|
- Consistent architecture
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 🎓 Summary
|
|
|
|
|
|
|
|
|
|
Your system now uses **CDC-only for order provisioning**:
|
|
|
|
|
|
2025-11-17 10:31:33 +09:00
|
|
|
1. ✅ **OrderCdcSubscriber** triggers provisioning when Salesforce sets `Activation_Status__c = "Activating"`
|
2025-11-06 17:01:34 +09:00
|
|
|
2. ✅ **Multiple guards** prevent duplicate provisioning
|
|
|
|
|
3. ✅ **Cache invalidation** still works with smart filtering
|
|
|
|
|
4. ✅ **Simpler Salesforce setup** - just enable CDC
|
|
|
|
|
5. ✅ **No Platform Event needed** for order provisioning
|
|
|
|
|
|
|
|
|
|
**Next Steps:**
|
|
|
|
|
1. Enable CDC on Order object in Salesforce
|
|
|
|
|
2. Restart your application
|
2025-11-17 10:31:33 +09:00
|
|
|
3. Test by approving an order (confirm Flow sets `Activation_Status__c = "Activating"`)
|
2025-11-06 17:01:34 +09:00
|
|
|
4. Monitor logs for successful provisioning
|
|
|
|
|
|
|
|
|
|
**Your CDC-only implementation is complete and production-ready!** 🚀
|
|
|
|
|
|