# Salesforce-to-Portal Order Communication Guide

Note: 2025 update — Async-first via Salesforce Platform Events is now the recommended pattern. The legacy webhook path remains referenced only historically and should be phased out.

## Overview

This guide focuses specifically on **secure communication between Salesforce and your Portal for order provisioning**. This is NOT about invoices or billing - it's about the order approval and provisioning workflow.

## The Order Flow

```
1. Customer places order → Portal creates Salesforce Order (Status: "Pending Review")
2. Salesforce operator approves Order
3. Salesforce Flow publishes OrderProvisionRequested__e
4. Portal subscriber enqueues a job → provisions in WHMCS → updates Salesforce Order status
5. Customer sees updated status in Portal
```

## 1. Salesforce → Portal (Order Provisioning)

### Recommended (2025): Async via Platform Events ✅

High-level flow

```
1. Operator marks Order Approved in Salesforce
2. Record-Triggered Flow publishes Order_Fulfilment_Requested__e
3. Portal subscribes via Pub/Sub API (gRPC) and enqueues a provisioning job
4. Portal provisions in WHMCS and updates Salesforce Order status
5. Portal UI polls BFF for status (unchanged)
```

Salesforce Setup

- Create High-Volume Platform Event: `Order_Fulfilment_Requested__e`
  - Fields (example API names):
    - `OrderId__c` (Text 18) — required
    - `IdemKey__c` (Text 80) — optional (Idempotency key)
    - `CorrelationId__c` (Text 80) — optional
    - `RequestedBy__c` (Text 80) — optional
    - `Version__c` (Number) — optional
- Flow: Record-Triggered on Order when Status changes to Approved
  - Actions:
    - Update `Activation_Status__c = Activating`
    - Create `OrderProvisionRequested__e` and set `OrderId__c = $Record.Id`
    - Optionally clear activation error fields

Portal Setup

- Env
  - `SF_EVENTS_ENABLED=true`
  - `SF_PROVISION_EVENT_CHANNEL=/event/OrderProvisionRequested__e`
  - `SF_EVENTS_REPLAY=LATEST` (or `ALL` to start from earliest retained)
- Subscriber (auto-start, durable replay): `apps/bff/src/vendors/salesforce/events/pubsub.subscriber.ts`
  - Subscribes to the channel and enqueues a job to the `provisioning` queue
- Worker: `apps/bff/src/orders/queue/provisioning.processor.ts`
  - Guards on `Activation_Status__c = Activating`; provisions in WHMCS and then updates Salesforce

Why async: resilient (replay within retention), decoupled, no inbound allowlisting. No auto-retries from the portal; operators retry from Salesforce by flipping status to Activating.

### Legacy Implementation (Webhook) — Removed

The old endpoint `POST /orders/{sfOrderId}/fulfill` has been removed. Provisioning is now triggered solely via Platform Events published from Salesforce.

### Enhanced Security Implementation (Legacy Webhook)

All Quick Actions and Named Credentials previously used to call the portal should be retired.

### Salesforce Apex Implementation (not needed)

No Apex callout to the portal is required. Use a Record-Triggered Flow to publish the Platform Event.

## 2. Security Configuration

### Environment Variables

```bash
# Platform Events (BFF)
SF_EVENTS_ENABLED=true
SF_PROVISION_EVENT_CHANNEL=/event/OrderProvisionRequested__e
SF_EVENTS_REPLAY=LATEST  # or ALL
```

### Salesforce Named Credential

Not required for the async path; the portal pulls events from Salesforce.

## 4. Customer Experience

### Portal UI Polling

The portal should poll for order status updates:

```typescript
// In your Portal UI
export function useOrderStatus(sfOrderId: string) {
  const [status, setStatus] = useState<OrderStatus>("Pending Review");

  useEffect(() => {
    const pollStatus = async () => {
      try {
        const response = await fetch(`/api/orders/${sfOrderId}`);
        const data = await response.json();
        setStatus(data.status);

        // Stop polling when order is complete
        if (["Provisioned", "Failed"].includes(data.status)) {
          clearInterval(interval);
        }
      } catch (error) {
        console.error("Failed to fetch order status:", error);
      }
    };

    const interval = setInterval(pollStatus, 5000); // Poll every 5 seconds
    pollStatus(); // Initial fetch

    return () => clearInterval(interval);
  }, [sfOrderId]);

  return status;
}
```

## 5. Monitoring and Alerting

### Key Metrics to Monitor

- **Provisioning Success Rate**: Track successful vs failed provisioning attempts
- **Provisioning Latency**: Time from SF approval to completion
- **WHMCS API Errors**: Monitor WHMCS integration health
- **Event Lag**: Time between event publish and job enqueue/complete

### Alert Conditions

```typescript
// Example monitoring service
@Injectable()
export class OrderProvisioningMonitoringService {
  async recordProvisioningAttempt(sfOrderId: string, success: boolean, duration: number) {
    // Record metrics
    this.metricsService.increment("order.provisioning.attempts", {
      success: success.toString(),
    });

    this.metricsService.histogram("order.provisioning.duration", duration);

    // Alert on high failure rate
    const recentFailureRate = await this.getRecentFailureRate();
    if (recentFailureRate > 0.1) {
      // 10% failure rate
      await this.alertingService.sendAlert("High order provisioning failure rate");
    }
  }
}
```

## 6. Testing

### Event Flow Testing

Validate that an `OrderProvisionRequested__e` event enqueues a job and runs the orchestrator. Check logs and `/health/sf-events` for status and cursor.

## Summary

This focused approach ensures secure communication specifically for your **order provisioning workflow**:

1. **Salesforce Flow** → Publishes Platform Event `OrderProvisionRequested__e`
2. **Portal BFF** → Subscribes, queues job, provisions in WHMCS, updates Salesforce
3. **Customer** → Sees real-time order status in Portal UI

The security relies on your existing JWT-based Salesforce API integration and ; no inbound webhooks are required.
