From 1e629442c388d939502786a4f32c1029ab3915f3 Mon Sep 17 00:00:00 2001 From: jos3duardo Date: Sat, 9 Aug 2025 22:35:45 -0400 Subject: [PATCH] Add HealthService to perform health checks on payment processors --- src/modules/health/services/health.service.ts | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 src/modules/health/services/health.service.ts diff --git a/src/modules/health/services/health.service.ts b/src/modules/health/services/health.service.ts new file mode 100644 index 0000000..aedde41 --- /dev/null +++ b/src/modules/health/services/health.service.ts @@ -0,0 +1,87 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { HttpService } from '@nestjs/axios'; +import { ConfigService } from '@nestjs/config'; +import { Cron } from '@nestjs/schedule'; +import { firstValueFrom } from 'rxjs'; +import { ServiceStatusEnum } from '../enumns/service-status.enum'; +import { ProcessorTypeEnum } from '../../payments/enumns/processor-type.enum'; + +@Injectable() +export class HealthService { + private readonly logger = new Logger(HealthService.name); + private defaultServiceStatus: ServiceStatusEnum = ServiceStatusEnum.UP; + private fallbackServiceStatus: ServiceStatusEnum = ServiceStatusEnum.UP; + + constructor( + private readonly httpService: HttpService, + private readonly configService: ConfigService, + ) {} + + @Cron('*/5 * * * * *') + async performHealthChecks() { + const defaultUrl: string = + this.configService.get('paymentProcessors.defaultUrl') ?? ''; + const fallbackUrl: string = + this.configService.get('paymentProcessors.fallbackUrl') ?? ''; + + await Promise.all([ + this.checkServiceHealth( + ProcessorTypeEnum.DEFAULT, + `${defaultUrl}/payments/service-health`, + ), + this.checkServiceHealth( + ProcessorTypeEnum.FALLBACK, + `${fallbackUrl}/payments/service-health`, + ), + ]); + } + + shouldUseDefaultProcessor(): boolean { + return this.defaultServiceStatus === ServiceStatusEnum.UP; + } + + shouldUseFallbackProcessor(): boolean { + return this.fallbackServiceStatus === ServiceStatusEnum.UP; + } + + getPreferredProcessor(): 'default' | 'fallback' | null { + if (this.shouldUseDefaultProcessor()) { + return 'default'; + } else if (this.shouldUseFallbackProcessor()) { + return 'fallback'; + } + return null; + } + + private async checkServiceHealth( + serviceName: string, + url: string, + ): Promise { + let status: ServiceStatusEnum; + + try { + const response = await firstValueFrom( + this.httpService.get(url, { + timeout: 6000, + }), + ); + + status = + response.status === 200 ? ServiceStatusEnum.UP : ServiceStatusEnum.DOWN; + + this.logger.log(`Health check for ${serviceName}: ${status}`); + } catch (error) { + status = ServiceStatusEnum.DOWN; + + this.logger.warn( + `Health check failed for ${serviceName}: ${error.message}`, + ); + } + + if (serviceName === 'default') { + this.defaultServiceStatus = status; + } else { + this.fallbackServiceStatus = status; + } + } +}