diff --git a/src/modules/payments/services/payments.service.ts b/src/modules/payments/services/payments.service.ts new file mode 100644 index 0000000..a1a5db8 --- /dev/null +++ b/src/modules/payments/services/payments.service.ts @@ -0,0 +1,34 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { CreatePaymentDto } from '../dto/create-payment.dto'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Payment } from '../entities/payment.entity'; +import { Repository } from 'typeorm'; +import { QueueService } from '../../queue/queue.service'; +import { PaymentStatusEnum } from '../enumns/payment-status.enum'; + +@Injectable() +export class PaymentsService { + private readonly logger = new Logger(PaymentsService.name); + + constructor( + @InjectRepository(Payment) private readonly repository: Repository, + private queueService: QueueService, + ) {} + + async store(createPaymentDto: CreatePaymentDto) { + const payment = await this.repository.save({ + ...createPaymentDto, + status: PaymentStatusEnum.PENDING, + }); + + await this.queueService.addPaymentJob({ + paymentId: payment.id, + paymentData: createPaymentDto, + }); + + return { + success: true, + message: 'Payment created successfully and added to processing queue', + }; + } +} diff --git a/src/modules/payments/services/process-payment.service.ts b/src/modules/payments/services/process-payment.service.ts new file mode 100644 index 0000000..49138bd --- /dev/null +++ b/src/modules/payments/services/process-payment.service.ts @@ -0,0 +1,73 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Payment } from '../entities/payment.entity'; +import { Repository } from 'typeorm'; +import { HealthService } from '../../health/services/health.service'; +import { ProcessorTypeEnum } from '../enumns/processor-type.enum'; +import { PaymentStatusEnum } from '../enumns/payment-status.enum'; +import { PaymentDefaultProcessor } from '../processor/payment-default.processor'; +import { PaymentFallbackProcessor } from '../processor/payment-fallback.processor'; +import { RetryPaymentService } from './retry-payment.service'; + +@Injectable() +export class ProcessPaymentService { + private readonly logger = new Logger(ProcessPaymentService.name); + + constructor( + @InjectRepository(Payment) private readonly repository: Repository, + private healthService: HealthService, + private paymentDefaultProcessor: PaymentDefaultProcessor, + private paymentFallbackProcessor: PaymentFallbackProcessor, + private retryPaymentService: RetryPaymentService, + ) {} + + async execute(paymentId: string): Promise { + const payment = await this.repository.findOne({ + where: { id: paymentId }, + }); + + if (!payment) { + throw new Error(`Payment ${paymentId} not found`); + } + + await this.repository.update(paymentId, { + status: PaymentStatusEnum.PROCESSING, + }); + + const preferredProcessor = this.healthService.getPreferredProcessor(); + + if (!preferredProcessor) { + throw new Error('No payment processor available'); + } + + let result; + + let processorUsed: ProcessorTypeEnum = + preferredProcessor === ProcessorTypeEnum.DEFAULT + ? ProcessorTypeEnum.DEFAULT + : ProcessorTypeEnum.FALLBACK; + + try { + if (preferredProcessor === ProcessorTypeEnum.DEFAULT) { + result = await this.paymentDefaultProcessor.execute(payment); + processorUsed = ProcessorTypeEnum.DEFAULT; + } else { + result = await this.paymentFallbackProcessor.execute(payment); + processorUsed = ProcessorTypeEnum.FALLBACK; + } + + if (!result) { + await this.retryPaymentService.execute(payment.id, processorUsed); + } + } catch (error) { + this.logger.error( + `Error processing payment ${paymentId}:`, + error.message, + ); + await this.repository.update(payment.id, { + status: PaymentStatusEnum.RETRY, + }); + await this.retryPaymentService.execute(payment.id, processorUsed); + } + } +} diff --git a/src/modules/payments/services/retry-payment.service.ts b/src/modules/payments/services/retry-payment.service.ts new file mode 100644 index 0000000..7afa73d --- /dev/null +++ b/src/modules/payments/services/retry-payment.service.ts @@ -0,0 +1,84 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Payment } from '../entities/payment.entity'; +import { Repository } from 'typeorm'; +import { ProcessorTypeEnum } from '../enumns/processor-type.enum'; +import { HealthService } from '../../health/services/health.service'; +import { PaymentStatusEnum } from '../enumns/payment-status.enum'; +import { PaymentDefaultProcessor } from '../processor/payment-default.processor'; +import { PaymentFallbackProcessor } from '../processor/payment-fallback.processor'; +import { QueueService } from '../../queue/queue.service'; + +@Injectable() +export class RetryPaymentService { + private readonly logger = new Logger(RetryPaymentService.name); + + constructor( + @InjectRepository(Payment) private readonly repository: Repository, + private healthService: HealthService, + private paymentDefaultProcessor: PaymentDefaultProcessor, + private paymentFallbackProcessor: PaymentFallbackProcessor, + private queueService: QueueService, + ) {} + + async execute(paymentId: string, failedProcessor: ProcessorTypeEnum) { + const alternativeProcessor = + failedProcessor === ProcessorTypeEnum.DEFAULT + ? ProcessorTypeEnum.FALLBACK + : ProcessorTypeEnum.DEFAULT; + + const payment = await this.repository.findOne({ + where: { id: paymentId }, + }); + + if (!payment) { + throw new Error(`Payment ${paymentId} not found`); + } + + const isAlternativeAvailable = + alternativeProcessor === ProcessorTypeEnum.DEFAULT + ? this.healthService.shouldUseDefaultProcessor() + : this.healthService.shouldUseFallbackProcessor(); + + if (!isAlternativeAvailable) { + await this.repository.update(payment.id, { + status: PaymentStatusEnum.RETRY, + }); + } + + try { + let result; + + if (failedProcessor === ProcessorTypeEnum.DEFAULT) { + result = await this.paymentDefaultProcessor.execute(payment); + } else { + result = await this.paymentFallbackProcessor.execute(payment); + } + + if (result) { + this.logger.log( + `Nova Tentativa do Payment ${payment?.id} processed successfully via ${alternativeProcessor}`, + ); + } else { + await this.queueService.addRetryPaymentJob({ + paymentId: payment.id, + paymentData: { + amount: payment.amount, + correlationId: payment.correlationId, + }, + }); + + await this.repository.update(payment.id, { + status: PaymentStatusEnum.RETRY, + }); + } + } catch (error) { + this.logger.error('Error processing payment:', error.message); + + await this.repository.update(payment.id, { + status: PaymentStatusEnum.FAILED, + errorMessage: error.message, + }); + } + } +}