Assist_Design/apps/bff/src/integrations/salesforce/events/account-events.subscriber.ts
barsa bde9f706ce feat: add VPN services and call history management features
- Implemented VpnServicesService for managing VPN plans and activation fees.
- Created SimCallHistoryFormatterService for formatting call history data.
- Developed SimCallHistoryParserService to parse call history CSV files.
- Added AnimatedContainer and AnimatedBackground components for UI animations.
- Introduced BentoServiceCard, FloatingGlassCard, GlowButton, and ValuePropCard components for landing page.
- Implemented useCountUp hook for animated number counting.
- Added cancellation months utility functions for subscription management.
2026-01-13 16:19:39 +09:00

129 lines
4.4 KiB
TypeScript

/**
* Salesforce Account Platform Events Subscriber
*
* Handles real-time Account Platform Events for:
* - Eligibility status changes
* - Verification status changes
*
* When events are received:
* 1. Invalidate Redis caches (eligibility + verification)
* 2. Send SSE to connected portal clients
* 3. Create in-app notifications (for final status changes)
*
* @see docs/integrations/salesforce/platform-events.md
*/
import { Injectable, Inject, Optional } from "@nestjs/common";
import type { OnModuleInit } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { Logger } from "nestjs-pino";
import {
PubSubClientService,
isDataCallback,
extractPayload,
extractStringField,
} from "./shared/index.js";
import { ServicesCacheService } from "@bff/modules/services/application/services-cache.service.js";
import { RealtimeService } from "@bff/infra/realtime/realtime.service.js";
import { AccountNotificationHandler } from "@bff/modules/notifications/account-cdc-listener.service.js";
@Injectable()
export class AccountEventsSubscriber implements OnModuleInit {
constructor(
private readonly config: ConfigService,
private readonly pubSubClient: PubSubClientService,
private readonly servicesCache: ServicesCacheService,
private readonly realtime: RealtimeService,
@Inject(Logger) private readonly logger: Logger,
@Optional() private readonly accountNotificationHandler?: AccountNotificationHandler
) {}
async onModuleInit(): Promise<void> {
const channel = this.config.get<string>("SF_ACCOUNT_EVENT_CHANNEL")?.trim();
if (!channel) {
this.logger.warn("SF_ACCOUNT_EVENT_CHANNEL not configured; skipping account events");
return;
}
try {
await this.pubSubClient.subscribe(
channel,
this.handleAccountEvent.bind(this, channel),
"account-platform-event"
);
} catch (error) {
this.logger.warn("Failed to subscribe to Account Platform Events", {
channel,
error: error instanceof Error ? error.message : String(error),
});
}
}
/**
* Handle Account Platform Events (eligibility + verification updates)
*
* Salesforce Flow fires this event when these fields change:
* - Internet_Eligibility__c, Internet_Eligibility_Status__c
* - Id_Verification_Status__c, Id_Verification_Rejection_Message__c
*
* Platform Event fields:
* - Account_Id__c (required)
* - Eligibility_Status__c (only if changed)
* - Verification_Status__c (only if changed)
* - Rejection_Message__c (when rejected)
*
* Actions:
* - ALWAYS invalidate both caches
* - ALWAYS send SSE to portal
* - Create notification only for final states (eligible/verified/rejected)
*/
private async handleAccountEvent(
channel: string,
subscription: { topicName?: string },
callbackType: string,
data: unknown
): Promise<void> {
if (!isDataCallback(callbackType)) return;
const payload = extractPayload(data);
const accountId = extractStringField(payload, ["Account_Id__c"]);
if (!accountId) {
this.logger.warn("Account event missing Account_Id__c", { channel });
return;
}
const eligibilityStatus = extractStringField(payload, ["Eligibility_Status__c"]);
const verificationStatus = extractStringField(payload, ["Verification_Status__c"]);
const rejectionMessage = extractStringField(payload, ["Rejection_Message__c"]);
this.logger.log("Account platform event received", {
channel,
accountIdTail: accountId.slice(-4),
hasEligibilityStatus: eligibilityStatus !== undefined,
hasVerificationStatus: verificationStatus !== undefined,
});
// ALWAYS invalidate caches
await this.servicesCache.invalidateEligibility(accountId);
await this.servicesCache.invalidateVerification(accountId);
// ALWAYS notify portal to refetch
this.realtime.publish(`account:sf:${accountId}`, "account.updated", {
timestamp: new Date().toISOString(),
});
// Create notifications for status changes (handler filters to final states)
if (this.accountNotificationHandler && (eligibilityStatus || verificationStatus)) {
void this.accountNotificationHandler.processAccountEvent({
accountId,
eligibilityStatus,
eligibilityValue: undefined,
verificationStatus,
verificationRejectionMessage: rejectionMessage,
});
}
}
}