From afc18988cd72e8cde9763c8a2a8af9480093f3bb Mon Sep 17 00:00:00 2001 From: Temuulen Ankhbayar Date: Sat, 7 Mar 2026 18:19:53 +0900 Subject: [PATCH] feat: trigger Freebit APIs on "Processed" status instead of "Approved" - CDC subscriber now listens for Status="Processed" to fire SIM APIs - On API error, order Status reverts to "Approved" for retry - Provisioning processor validates "Processed" for Physical SIM flow Co-Authored-By: Claude Opus 4.6 --- .../salesforce/events/order-cdc.subscriber.ts | 35 ++++++++----------- .../orders/queue/provisioning.processor.ts | 4 +-- .../order-fulfillment-orchestrator.service.ts | 4 +-- 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/apps/bff/src/integrations/salesforce/events/order-cdc.subscriber.ts b/apps/bff/src/integrations/salesforce/events/order-cdc.subscriber.ts index 8a1e9e77..ed6efa4c 100644 --- a/apps/bff/src/integrations/salesforce/events/order-cdc.subscriber.ts +++ b/apps/bff/src/integrations/salesforce/events/order-cdc.subscriber.ts @@ -49,7 +49,7 @@ const INTERNAL_ORDER_FIELDS = new Set([ const INTERNAL_ORDER_ITEM_FIELDS = new Set(["WHMCS_Service_ID__c"]); /** Statuses that trigger provisioning */ -const PROVISION_TRIGGER_STATUSES = new Set(["Approved", "Reactivate"]); +const PROVISION_TRIGGER_STATUSES = new Set(["Processed", "Reactivate"]); @Injectable() export class OrderCdcSubscriber implements OnModuleInit { @@ -134,9 +134,9 @@ export class OrderCdcSubscriber implements OnModuleInit { await this.handleActivationStatusChange(payload, orderId); } - // Check for provisioning trigger (Status change to "Approved") + // Check for provisioning trigger (Status change to "Processed") if (payload && changedFields.has("Status")) { - await this.handleStatusApprovedChange(payload, orderId); + await this.handleStatusProcessedChange(payload, orderId); } // Cache invalidation - only for customer-facing field changes @@ -222,9 +222,9 @@ export class OrderCdcSubscriber implements OnModuleInit { } /** - * Handle order status changes to "Approved" + * Handle order status changes to "Processed" * - * Enqueues a provisioning job when Status changes to "Approved". + * Enqueues a provisioning job when Status changes to "Processed". * The provisioning processor will fetch the full order from Salesforce * and validate the conditions (SIM_Type__c, Assign_Physical_SIM__c, etc.) * @@ -232,27 +232,20 @@ export class OrderCdcSubscriber implements OnModuleInit { * because CDC only includes CHANGED fields. If only Status was updated, those fields * will be null in the payload even though they have values on the record. * - * The processor handles: - * - Physical SIM: Status="Approved" + SIM_Type="Physical SIM" + Assigned_Physical_SIM set - * - Standard: Activation_Status__c="Activating" - * - Idempotency via WHMCS_Order_ID__c check + * On API failure, the orchestrator reverts the order Status back to "Approved". */ - private async handleStatusApprovedChange( + private async handleStatusProcessedChange( payload: Record, orderId: string ): Promise { const status = extractStringField(payload, ["Status"]); - // Only trigger when status changes to "Approved" - if (status !== "Approved") { + // Only trigger when status changes to "Processed" + if (status !== "Processed") { return; } - // Note: We intentionally do NOT check SIM_Type__c or Assign_Physical_SIM__c here - // because CDC payloads only contain changed fields. The provisioning processor - // will fetch the full order and validate all conditions. - - this.logger.log("Enqueuing provisioning job for order status change to Approved", { + this.logger.log("Enqueuing provisioning job for order status change to Processed", { orderId, status, }); @@ -260,15 +253,15 @@ export class OrderCdcSubscriber implements OnModuleInit { try { await this.provisioningQueue.enqueue({ sfOrderId: orderId, - idempotencyKey: `cdc-status-approved-${Date.now()}-${orderId}`, - correlationId: `cdc-status-approved-${orderId}`, + idempotencyKey: `cdc-status-processed-${Date.now()}-${orderId}`, + correlationId: `cdc-status-processed-${orderId}`, }); - this.logger.log("Successfully enqueued provisioning job for Approved status", { + this.logger.log("Successfully enqueued provisioning job for Processed status", { orderId, }); } catch (error) { - this.logger.error("Failed to enqueue provisioning job for Approved status", { + this.logger.error("Failed to enqueue provisioning job for Processed status", { orderId, error: error instanceof Error ? error.message : String(error), }); diff --git a/apps/bff/src/modules/orders/queue/provisioning.processor.ts b/apps/bff/src/modules/orders/queue/provisioning.processor.ts index 778d6827..991400dd 100644 --- a/apps/bff/src/modules/orders/queue/provisioning.processor.ts +++ b/apps/bff/src/modules/orders/queue/provisioning.processor.ts @@ -34,10 +34,10 @@ export class ProvisioningProcessor extends WorkerHost { // Guard: Determine if this is a valid provisioning request // Case 1: Standard flow - Activation_Status__c = "Activating" - // Case 2: Physical SIM flow - Status = "Approved" with SIM_Type__c = "Physical SIM" + // Case 2: Physical SIM flow - Status = "Processed" with SIM_Type__c = "Physical SIM" const isStandardActivation = activationStatus === "Activating"; const isPhysicalSimApproval = - orderStatus === "Approved" && simType === "Physical SIM" && !!assignedPhysicalSim; + orderStatus === "Processed" && simType === "Physical SIM" && !!assignedPhysicalSim; if (!isStandardActivation && !isPhysicalSimApproval) { this.logger.log("Skipping provisioning job: Order not in activatable state", { diff --git a/apps/bff/src/modules/orders/services/order-fulfillment-orchestrator.service.ts b/apps/bff/src/modules/orders/services/order-fulfillment-orchestrator.service.ts index d5a5988f..8af9ee51 100644 --- a/apps/bff/src/modules/orders/services/order-fulfillment-orchestrator.service.ts +++ b/apps/bff/src/modules/orders/services/order-fulfillment-orchestrator.service.ts @@ -312,7 +312,7 @@ export class OrderFulfillmentOrchestrator { failedStep: context.steps.find(s => s.status === "failed")?.step, }); - // Update Salesforce with failure status + // Update Salesforce with failure status — revert to "Approved" so it can be retried const errorShortCode = ( this.orderFulfillmentErrorService.getShortCode(error) || String(errorCode) ) @@ -321,7 +321,7 @@ export class OrderFulfillmentOrchestrator { try { await this.salesforceFacade.updateOrder({ Id: sfOrderId, - Status: "Pending Review", + Status: "Approved", Activation_Status__c: "Failed", Activation_Error_Code__c: errorShortCode, Activation_Error_Message__c: userMessage?.slice(0, 255),