Files
gridpilot.gg/core/racing/application/use-cases/AcceptSponsorshipRequestUseCase.ts
2025-12-20 12:22:48 +01:00

147 lines
7.2 KiB
TypeScript

/**
* 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 '@/notifications/application/ports/NotificationService';
import type { IWalletRepository } from '@core/payments/domain/repositories/IWalletRepository';
import type { AsyncUseCase, Logger } from '@core/shared/application';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { SeasonSponsorship } from '../../domain/entities/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';
import type { AcceptSponsorshipRequestDTO } from '../dto/AcceptSponsorshipRequestDTO';
import type { ProcessPaymentInputPort } from '../ports/input/ProcessPaymentInputPort';
import type { AcceptSponsorshipOutputPort } from '../ports/output/AcceptSponsorshipOutputPort';
import type { ProcessPaymentOutputPort } from '../ports/output/ProcessPaymentOutputPort';
export class AcceptSponsorshipRequestUseCase
implements AsyncUseCase<AcceptSponsorshipRequestDTO, AcceptSponsorshipOutputPort, string> {
constructor(
private readonly sponsorshipRequestRepo: ISponsorshipRequestRepository,
private readonly seasonSponsorshipRepo: ISeasonSponsorshipRepository,
private readonly seasonRepository: ISeasonRepository,
private readonly notificationService: NotificationService,
private readonly paymentProcessor: (input: ProcessPaymentInputPort) => Promise<ProcessPaymentOutputPort>,
private readonly walletRepository: IWalletRepository,
private readonly leagueWalletRepository: ILeagueWalletRepository,
private readonly logger: Logger,
) {}
async execute(dto: AcceptSponsorshipRequestDTO): Promise<Result<AcceptSponsorshipOutputPort, ApplicationErrorCode<string>>> {
this.logger.debug(`Attempting to accept sponsorship request: ${dto.requestId}`, { requestId: dto.requestId, respondedBy: dto.respondedBy });
// Find the request
const request = await this.sponsorshipRequestRepo.findById(dto.requestId);
if (!request) {
this.logger.warn(`Sponsorship request not found: ${dto.requestId}`, { requestId: dto.requestId });
return Result.err({ code: 'SPONSORSHIP_REQUEST_NOT_FOUND' });
}
if (!request.isPending()) {
this.logger.warn(`Cannot accept a ${request.status} sponsorship request: ${dto.requestId}`, { requestId: dto.requestId, status: request.status });
return Result.err({ code: 'SPONSORSHIP_REQUEST_NOT_PENDING' });
}
this.logger.info(`Sponsorship request ${dto.requestId} found and is pending. Proceeding with acceptance.`, { requestId: dto.requestId });
// Accept the request
const acceptedRequest = request.accept(dto.respondedBy);
await this.sponsorshipRequestRepo.update(acceptedRequest);
this.logger.debug(`Sponsorship request ${dto.requestId} accepted and updated in repository.`, { requestId: dto.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 ${dto.requestId} is for a season. Creating SeasonSponsorship record.`, { requestId: dto.requestId, entityType: request.entityType });
const season = await this.seasonRepository.findById(request.entityId);
if (!season) {
this.logger.warn(`Season not found for sponsorship request ${dto.requestId} and entityId ${request.entityId}`, { requestId: dto.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 ${dto.requestId}.`, { sponsorshipId, requestId: dto.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: ProcessPaymentInputPort = {
amount: request.offeredAmount.amount, // Extract primitive number from value object
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 });
return Result.ok({
requestId: acceptedRequest.id,
sponsorshipId,
status: 'accepted',
acceptedAt: acceptedRequest.respondedAt!,
platformFee: acceptedRequest.getPlatformFee().amount,
netAmount: acceptedRequest.getNetAmount().amount,
});
}
}