/** * Use Case: AcceptSponsorshipRequestUseCase * * Allows an entity owner to accept a sponsorship request. * This creates an active sponsorship and notifies the sponsor. */ import type { NotificationService } from '@core/notifications/application/ports/NotificationService'; import type { IWalletRepository } from '@core/payments/domain/repositories/IWalletRepository'; import type { Logger } from '@core/shared/application'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; import { SeasonSponsorship } from '../../domain/entities/season/SeasonSponsorship'; import type { ILeagueWalletRepository } from '../../domain/repositories/ILeagueWalletRepository'; import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository'; import type { ISeasonSponsorshipRepository } from '../../domain/repositories/ISeasonSponsorshipRepository'; import type { ISponsorshipRequestRepository } from '../../domain/repositories/ISponsorshipRequestRepository'; export interface AcceptSponsorshipRequestInput { requestId: string; respondedBy: string; } export interface AcceptSponsorshipResult { requestId: string; sponsorshipId: string; status: 'accepted'; acceptedAt: Date; platformFee: number; netAmount: number; } export interface ProcessPaymentInput { amount: number; payerId: string; description: string; metadata?: Record; } export interface ProcessPaymentResult { success: boolean; transactionId?: string; error?: string; } export class AcceptSponsorshipRequestUseCase { constructor( private readonly sponsorshipRequestRepo: ISponsorshipRequestRepository, private readonly seasonSponsorshipRepo: ISeasonSponsorshipRepository, private readonly seasonRepository: ISeasonRepository, private readonly notificationService: NotificationService, private readonly paymentProcessor: (input: ProcessPaymentInput) => Promise, private readonly walletRepository: IWalletRepository, private readonly leagueWalletRepository: ILeagueWalletRepository, private readonly logger: Logger, private readonly output: UseCaseOutputPort, ) {} async execute( input: AcceptSponsorshipRequestInput, ): Promise< Result< void, ApplicationErrorCode< | 'SPONSORSHIP_REQUEST_NOT_FOUND' | 'SPONSORSHIP_REQUEST_NOT_PENDING' | 'SEASON_NOT_FOUND' | 'PAYMENT_PROCESSING_FAILED' | 'SPONSOR_WALLET_NOT_FOUND' | 'LEAGUE_WALLET_NOT_FOUND' > > > { this.logger.debug(`Attempting to accept sponsorship request: ${input.requestId}`, { requestId: input.requestId, respondedBy: input.respondedBy, }); // Find the request const request = await this.sponsorshipRequestRepo.findById(input.requestId); if (!request) { this.logger.warn(`Sponsorship request not found: ${input.requestId}`, { requestId: input.requestId }); return Result.err({ code: 'SPONSORSHIP_REQUEST_NOT_FOUND' }); } if (!request.isPending()) { this.logger.warn(`Cannot accept a ${request.status} sponsorship request: ${input.requestId}`, { requestId: input.requestId, status: request.status, }); return Result.err({ code: 'SPONSORSHIP_REQUEST_NOT_PENDING' }); } this.logger.info(`Sponsorship request ${input.requestId} found and is pending. Proceeding with acceptance.`, { requestId: input.requestId, }); // Accept the request const acceptedRequest = request.accept(input.respondedBy); await this.sponsorshipRequestRepo.update(acceptedRequest); this.logger.debug(`Sponsorship request ${input.requestId} accepted and updated in repository.`, { requestId: input.requestId, }); // If this is a season sponsorship, create the SeasonSponsorship record let sponsorshipId = `spons_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; if (request.entityType === 'season') { this.logger.debug(`Sponsorship request ${input.requestId} is for a season. Creating SeasonSponsorship record.`, { requestId: input.requestId, entityType: request.entityType, }); const season = await this.seasonRepository.findById(request.entityId); if (!season) { this.logger.warn( `Season not found for sponsorship request ${input.requestId} and entityId ${request.entityId}`, { requestId: input.requestId, entityId: request.entityId }, ); return Result.err({ code: 'SEASON_NOT_FOUND' }); } const sponsorship = SeasonSponsorship.create({ id: sponsorshipId, seasonId: season.id, leagueId: season.leagueId, sponsorId: request.sponsorId, tier: request.tier, pricing: request.offeredAmount, status: 'active', }); await this.seasonSponsorshipRepo.create(sponsorship); this.logger.info(`Season sponsorship ${sponsorshipId} created for request ${input.requestId}.`, { sponsorshipId, requestId: input.requestId, }); // Notify the sponsor await this.notificationService.sendNotification({ recipientId: request.sponsorId, type: 'sponsorship_request_accepted', title: 'Sponsorship Accepted', body: `Your sponsorship request for ${season.name} has been accepted.`, channel: 'in_app', urgency: 'toast', data: { requestId: request.id, sponsorshipId, }, }); // Process payment using clean input/output ports with primitive types const paymentInput: ProcessPaymentInput = { amount: request.offeredAmount.amount, payerId: request.sponsorId, description: `Sponsorship payment for ${request.entityType} ${request.entityId}`, metadata: { requestId: request.id }, }; const paymentResult = await this.paymentProcessor(paymentInput); if (!paymentResult.success) { this.logger.error( `Payment failed for sponsorship request ${request.id}: ${paymentResult.error}`, undefined, { requestId: request.id }, ); return Result.err({ code: 'PAYMENT_PROCESSING_FAILED' }); } // Update wallets const sponsorWallet = await this.walletRepository.findById(request.sponsorId); if (!sponsorWallet) { this.logger.error(`Sponsor wallet not found for ${request.sponsorId}`, undefined, { sponsorId: request.sponsorId, }); return Result.err({ code: 'SPONSOR_WALLET_NOT_FOUND' }); } const leagueWallet = await this.leagueWalletRepository.findById(season.leagueId); if (!leagueWallet) { this.logger.error(`League wallet not found for ${season.leagueId}`, undefined, { leagueId: season.leagueId, }); return Result.err({ code: 'LEAGUE_WALLET_NOT_FOUND' }); } const netAmount = acceptedRequest.getNetAmount(); // Deduct from sponsor wallet const updatedSponsorWallet = { ...sponsorWallet, balance: sponsorWallet.balance - request.offeredAmount.amount, }; await this.walletRepository.update(updatedSponsorWallet); // Add to league wallet const updatedLeagueWallet = leagueWallet.addFunds(netAmount, paymentResult.transactionId!); await this.leagueWalletRepository.update(updatedLeagueWallet); } this.logger.info(`Sponsorship request ${acceptedRequest.id} successfully accepted.`, { requestId: acceptedRequest.id, sponsorshipId, }); const result: AcceptSponsorshipResult = { requestId: acceptedRequest.id, sponsorshipId, status: 'accepted', acceptedAt: acceptedRequest.respondedAt!, platformFee: acceptedRequest.getPlatformFee().amount, netAmount: acceptedRequest.getNetAmount().amount, }; this.output.present(result); return Result.ok(undefined); } }