This commit is contained in:
2025-12-16 15:42:38 +01:00
parent 29410708c8
commit 362894d1a5
147 changed files with 780 additions and 375 deletions

View File

@@ -0,0 +1,96 @@
import { Controller, Get, Post, Patch, Delete, Body, Query, HttpCode, HttpStatus } from '@nestjs/common';
import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger';
import { PaymentsService } from './PaymentsService';
import { CreatePaymentInput, CreatePaymentOutput, UpdatePaymentStatusInput, UpdatePaymentStatusOutput, GetPaymentsQuery, GetPaymentsOutput, GetMembershipFeesQuery, GetMembershipFeesOutput, UpsertMembershipFeeInput, UpsertMembershipFeeOutput, UpdateMemberPaymentInput, UpdateMemberPaymentOutput, GetPrizesQuery, GetPrizesOutput, CreatePrizeInput, CreatePrizeOutput, AwardPrizeInput, AwardPrizeOutput, DeletePrizeInput, DeletePrizeOutput, GetWalletQuery, GetWalletOutput, ProcessWalletTransactionInput, ProcessWalletTransactionOutput } from './dto/PaymentsDto';
@ApiTags('payments')
@Controller('payments')
export class PaymentsController {
constructor(private readonly paymentsService: PaymentsService) {}
@Get()
@ApiOperation({ summary: 'Get payments based on filters' })
@ApiResponse({ status: 200, description: 'List of payments', type: GetPaymentsOutput })
async getPayments(@Query() query: GetPaymentsQuery): Promise<GetPaymentsOutput> {
return this.paymentsService.getPayments(query);
}
@Post()
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: 'Create a new payment' })
@ApiResponse({ status: 201, description: 'Payment created', type: CreatePaymentOutput })
async createPayment(@Body() input: CreatePaymentInput): Promise<CreatePaymentOutput> {
return this.paymentsService.createPayment(input);
}
@Patch('status')
@ApiOperation({ summary: 'Update the status of a payment' })
@ApiResponse({ status: 200, description: 'Payment status updated', type: UpdatePaymentStatusOutput })
async updatePaymentStatus(@Body() input: UpdatePaymentStatusInput): Promise<UpdatePaymentStatusOutput> {
return this.paymentsService.updatePaymentStatus(input);
}
@Get('membership-fees')
@ApiOperation({ summary: 'Get membership fees and member payments' })
@ApiResponse({ status: 200, description: 'Membership fee configuration and member payments', type: GetMembershipFeesOutput })
async getMembershipFees(@Query() query: GetMembershipFeesQuery): Promise<GetMembershipFeesOutput> {
return this.paymentsService.getMembershipFees(query);
}
@Post('membership-fees')
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: 'Create or update membership fee configuration' })
@ApiResponse({ status: 201, description: 'Membership fee configuration created or updated', type: UpsertMembershipFeeOutput })
async upsertMembershipFee(@Body() input: UpsertMembershipFeeInput): Promise<UpsertMembershipFeeOutput> {
return this.paymentsService.upsertMembershipFee(input);
}
@Patch('membership-fees/member-payment')
@ApiOperation({ summary: 'Record or update a member payment' })
@ApiResponse({ status: 200, description: 'Member payment recorded or updated', type: UpdateMemberPaymentOutput })
async updateMemberPayment(@Body() input: UpdateMemberPaymentInput): Promise<UpdateMemberPaymentOutput> {
return this.paymentsService.updateMemberPayment(input);
}
@Get('prizes')
@ApiOperation({ summary: 'Get prizes for a league or season' })
@ApiResponse({ status: 200, description: 'List of prizes', type: GetPrizesOutput })
async getPrizes(@Query() query: GetPrizesQuery): Promise<GetPrizesOutput> {
return this.paymentsService.getPrizes(query);
}
@Post('prizes')
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: 'Create a new prize' })
@ApiResponse({ status: 201, description: 'Prize created', type: CreatePrizeOutput })
async createPrize(@Body() input: CreatePrizeInput): Promise<CreatePrizeOutput> {
return this.paymentsService.createPrize(input);
}
@Patch('prizes/award')
@ApiOperation({ summary: 'Award a prize to a driver' })
@ApiResponse({ status: 200, description: 'Prize awarded', type: AwardPrizeOutput })
async awardPrize(@Body() input: AwardPrizeInput): Promise<AwardPrizeOutput> {
return this.paymentsService.awardPrize(input);
}
@Delete('prizes')
@ApiOperation({ summary: 'Delete a prize' })
@ApiResponse({ status: 200, description: 'Prize deleted', type: DeletePrizeOutput })
async deletePrize(@Query() query: DeletePrizeInput): Promise<DeletePrizeOutput> {
return this.paymentsService.deletePrize(query);
}
@Get('wallets')
@ApiOperation({ summary: 'Get wallet information and transactions' })
@ApiResponse({ status: 200, description: 'Wallet and transaction data', type: GetWalletOutput })
async getWallet(@Query() query: GetWalletQuery): Promise<GetWalletOutput> {
return this.paymentsService.getWallet(query);
}
@Post('wallets/transactions')
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: 'Process a wallet transaction (deposit or withdrawal)' })
@ApiResponse({ status: 201, description: 'Wallet transaction processed', type: ProcessWalletTransactionOutput })
async processWalletTransaction(@Body() input: ProcessWalletTransactionInput): Promise<ProcessWalletTransactionOutput> {
return this.paymentsService.processWalletTransaction(input);
}
}

View File

@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { PaymentsService } from './PaymentsService';
import { PaymentsController } from './PaymentsController';
import { PaymentsProviders } from './PaymentsProviders';
@Module({
controllers: [PaymentsController],
providers: PaymentsProviders,
exports: [PaymentsService],
})
export class PaymentsModule {}

View File

@@ -0,0 +1,161 @@
import { Provider } from '@nestjs/common';
import { PaymentsService } from './PaymentsService';
// Import core interfaces
import type { IPaymentRepository } from '@core/payments/domain/repositories/IPaymentRepository';
import type { IMembershipFeeRepository, IMemberPaymentRepository } from '@core/payments/domain/repositories/IMembershipFeeRepository';
import type { IPrizeRepository } from '@core/payments/domain/repositories/IPrizeRepository';
import type { IWalletRepository, ITransactionRepository } from '@core/payments/domain/repositories/IWalletRepository';
import type { Logger } from '@core/shared/application/Logger';
// Import use cases
import { GetPaymentsUseCase } from '@core/payments/application/use-cases/GetPaymentsUseCase';
import { CreatePaymentUseCase } from '@core/payments/application/use-cases/CreatePaymentUseCase';
import { UpdatePaymentStatusUseCase } from '@core/payments/application/use-cases/UpdatePaymentStatusUseCase';
import { GetMembershipFeesUseCase } from '@core/payments/application/use-cases/GetMembershipFeesUseCase';
import { UpsertMembershipFeeUseCase } from '@core/payments/application/use-cases/UpsertMembershipFeeUseCase';
import { UpdateMemberPaymentUseCase } from '@core/payments/application/use-cases/UpdateMemberPaymentUseCase';
import { GetPrizesUseCase } from '@core/payments/application/use-cases/GetPrizesUseCase';
import { CreatePrizeUseCase } from '@core/payments/application/use-cases/CreatePrizeUseCase';
import { AwardPrizeUseCase } from '@core/payments/application/use-cases/AwardPrizeUseCase';
import { DeletePrizeUseCase } from '@core/payments/application/use-cases/DeletePrizeUseCase';
import { GetWalletUseCase } from '@core/payments/application/use-cases/GetWalletUseCase';
import { ProcessWalletTransactionUseCase } from '@core/payments/application/use-cases/ProcessWalletTransactionUseCase';
// Import concrete in-memory implementations
import { InMemoryPaymentRepository } from '/payments/persistence/inmemory/InMemoryPaymentRepository';
import { InMemoryMembershipFeeRepository, InMemoryMemberPaymentRepository } from '/payments/persistence/inmemory/InMemoryMembershipFeeRepository';
import { InMemoryPrizeRepository } from '/payments/persistence/inmemory/InMemoryPrizeRepository';
import { InMemoryWalletRepository, InMemoryTransactionRepository } from '/payments/persistence/inmemory/InMemoryWalletRepository';
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
// Repository injection tokens
export const PAYMENT_REPOSITORY_TOKEN = 'IPaymentRepository';
export const MEMBERSHIP_FEE_REPOSITORY_TOKEN = 'IMembershipFeeRepository';
export const MEMBER_PAYMENT_REPOSITORY_TOKEN = 'IMemberPaymentRepository';
export const PRIZE_REPOSITORY_TOKEN = 'IPrizeRepository';
export const WALLET_REPOSITORY_TOKEN = 'IWalletRepository';
export const TRANSACTION_REPOSITORY_TOKEN = 'ITransactionRepository';
export const LOGGER_TOKEN = 'Logger';
// Use case injection tokens
export const GET_PAYMENTS_USE_CASE_TOKEN = 'GetPaymentsUseCase';
export const CREATE_PAYMENT_USE_CASE_TOKEN = 'CreatePaymentUseCase';
export const UPDATE_PAYMENT_STATUS_USE_CASE_TOKEN = 'UpdatePaymentStatusUseCase';
export const GET_MEMBERSHIP_FEES_USE_CASE_TOKEN = 'GetMembershipFeesUseCase';
export const UPSERT_MEMBERSHIP_FEE_USE_CASE_TOKEN = 'UpsertMembershipFeeUseCase';
export const UPDATE_MEMBER_PAYMENT_USE_CASE_TOKEN = 'UpdateMemberPaymentUseCase';
export const GET_PRIZES_USE_CASE_TOKEN = 'GetPrizesUseCase';
export const CREATE_PRIZE_USE_CASE_TOKEN = 'CreatePrizeUseCase';
export const AWARD_PRIZE_USE_CASE_TOKEN = 'AwardPrizeUseCase';
export const DELETE_PRIZE_USE_CASE_TOKEN = 'DeletePrizeUseCase';
export const GET_WALLET_USE_CASE_TOKEN = 'GetWalletUseCase';
export const PROCESS_WALLET_TRANSACTION_USE_CASE_TOKEN = 'ProcessWalletTransactionUseCase';
export const PaymentsProviders: Provider[] = [
PaymentsService,
// Logger
{
provide: LOGGER_TOKEN,
useClass: ConsoleLogger,
},
// Repositories (repositories are injected into use cases, NOT into services)
{
provide: PAYMENT_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryPaymentRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: MEMBERSHIP_FEE_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryMembershipFeeRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: MEMBER_PAYMENT_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryMemberPaymentRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: PRIZE_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryPrizeRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: WALLET_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryWalletRepository(logger),
inject: [LOGGER_TOKEN],
},
{
provide: TRANSACTION_REPOSITORY_TOKEN,
useFactory: (logger: Logger) => new InMemoryTransactionRepository(logger),
inject: [LOGGER_TOKEN],
},
// Use cases (use cases receive repositories, services receive use cases)
{
provide: GET_PAYMENTS_USE_CASE_TOKEN,
useFactory: (paymentRepo: IPaymentRepository) => new GetPaymentsUseCase(paymentRepo),
inject: [PAYMENT_REPOSITORY_TOKEN],
},
{
provide: CREATE_PAYMENT_USE_CASE_TOKEN,
useFactory: (paymentRepo: IPaymentRepository) => new CreatePaymentUseCase(paymentRepo),
inject: [PAYMENT_REPOSITORY_TOKEN],
},
{
provide: UPDATE_PAYMENT_STATUS_USE_CASE_TOKEN,
useFactory: (paymentRepo: IPaymentRepository) => new UpdatePaymentStatusUseCase(paymentRepo),
inject: [PAYMENT_REPOSITORY_TOKEN],
},
{
provide: GET_MEMBERSHIP_FEES_USE_CASE_TOKEN,
useFactory: (membershipFeeRepo: IMembershipFeeRepository, memberPaymentRepo: IMemberPaymentRepository) =>
new GetMembershipFeesUseCase(membershipFeeRepo, memberPaymentRepo),
inject: [MEMBERSHIP_FEE_REPOSITORY_TOKEN, MEMBER_PAYMENT_REPOSITORY_TOKEN],
},
{
provide: UPSERT_MEMBERSHIP_FEE_USE_CASE_TOKEN,
useFactory: (membershipFeeRepo: IMembershipFeeRepository) => new UpsertMembershipFeeUseCase(membershipFeeRepo),
inject: [MEMBERSHIP_FEE_REPOSITORY_TOKEN],
},
{
provide: UPDATE_MEMBER_PAYMENT_USE_CASE_TOKEN,
useFactory: (membershipFeeRepo: IMembershipFeeRepository, memberPaymentRepo: IMemberPaymentRepository) =>
new UpdateMemberPaymentUseCase(membershipFeeRepo, memberPaymentRepo),
inject: [MEMBERSHIP_FEE_REPOSITORY_TOKEN, MEMBER_PAYMENT_REPOSITORY_TOKEN],
},
{
provide: GET_PRIZES_USE_CASE_TOKEN,
useFactory: (prizeRepo: IPrizeRepository) => new GetPrizesUseCase(prizeRepo),
inject: [PRIZE_REPOSITORY_TOKEN],
},
{
provide: CREATE_PRIZE_USE_CASE_TOKEN,
useFactory: (prizeRepo: IPrizeRepository) => new CreatePrizeUseCase(prizeRepo),
inject: [PRIZE_REPOSITORY_TOKEN],
},
{
provide: AWARD_PRIZE_USE_CASE_TOKEN,
useFactory: (prizeRepo: IPrizeRepository) => new AwardPrizeUseCase(prizeRepo),
inject: [PRIZE_REPOSITORY_TOKEN],
},
{
provide: DELETE_PRIZE_USE_CASE_TOKEN,
useFactory: (prizeRepo: IPrizeRepository) => new DeletePrizeUseCase(prizeRepo),
inject: [PRIZE_REPOSITORY_TOKEN],
},
{
provide: GET_WALLET_USE_CASE_TOKEN,
useFactory: (walletRepo: IWalletRepository, transactionRepo: ITransactionRepository) =>
new GetWalletUseCase(walletRepo, transactionRepo),
inject: [WALLET_REPOSITORY_TOKEN, TRANSACTION_REPOSITORY_TOKEN],
},
{
provide: PROCESS_WALLET_TRANSACTION_USE_CASE_TOKEN,
useFactory: (walletRepo: IWalletRepository, transactionRepo: ITransactionRepository) =>
new ProcessWalletTransactionUseCase(walletRepo, transactionRepo),
inject: [WALLET_REPOSITORY_TOKEN, TRANSACTION_REPOSITORY_TOKEN],
},
];

View File

@@ -0,0 +1,190 @@
import { Injectable, Inject } from '@nestjs/common';
import type { Logger } from '@core/shared/application/Logger';
// Use cases
import type { GetPaymentsUseCase } from '@core/payments/application/use-cases/GetPaymentsUseCase';
import type { CreatePaymentUseCase } from '@core/payments/application/use-cases/CreatePaymentUseCase';
import type { UpdatePaymentStatusUseCase } from '@core/payments/application/use-cases/UpdatePaymentStatusUseCase';
import type { GetMembershipFeesUseCase } from '@core/payments/application/use-cases/GetMembershipFeesUseCase';
import type { UpsertMembershipFeeUseCase } from '@core/payments/application/use-cases/UpsertMembershipFeeUseCase';
import type { UpdateMemberPaymentUseCase } from '@core/payments/application/use-cases/UpdateMemberPaymentUseCase';
import type { GetPrizesUseCase } from '@core/payments/application/use-cases/GetPrizesUseCase';
import type { CreatePrizeUseCase } from '@core/payments/application/use-cases/CreatePrizeUseCase';
import type { AwardPrizeUseCase } from '@core/payments/application/use-cases/AwardPrizeUseCase';
import type { DeletePrizeUseCase } from '@core/payments/application/use-cases/DeletePrizeUseCase';
import type { GetWalletUseCase } from '@core/payments/application/use-cases/GetWalletUseCase';
import type { ProcessWalletTransactionUseCase } from '@core/payments/application/use-cases/ProcessWalletTransactionUseCase';
// Presenters
import { GetPaymentsPresenter } from './presenters/GetPaymentsPresenter';
import { CreatePaymentPresenter } from './presenters/CreatePaymentPresenter';
import { UpdatePaymentStatusPresenter } from './presenters/UpdatePaymentStatusPresenter';
import { GetMembershipFeesPresenter } from './presenters/GetMembershipFeesPresenter';
import { UpsertMembershipFeePresenter } from './presenters/UpsertMembershipFeePresenter';
import { UpdateMemberPaymentPresenter } from './presenters/UpdateMemberPaymentPresenter';
import { GetPrizesPresenter } from './presenters/GetPrizesPresenter';
import { CreatePrizePresenter } from './presenters/CreatePrizePresenter';
import { AwardPrizePresenter } from './presenters/AwardPrizePresenter';
import { DeletePrizePresenter } from './presenters/DeletePrizePresenter';
import { GetWalletPresenter } from './presenters/GetWalletPresenter';
import { ProcessWalletTransactionPresenter } from './presenters/ProcessWalletTransactionPresenter';
// DTOs
import type {
CreatePaymentInput,
CreatePaymentOutput,
UpdatePaymentStatusInput,
UpdatePaymentStatusOutput,
GetPaymentsQuery,
GetPaymentsOutput,
GetMembershipFeesQuery,
GetMembershipFeesOutput,
UpsertMembershipFeeInput,
UpsertMembershipFeeOutput,
UpdateMemberPaymentInput,
UpdateMemberPaymentOutput,
GetPrizesQuery,
GetPrizesOutput,
CreatePrizeInput,
CreatePrizeOutput,
AwardPrizeInput,
AwardPrizeOutput,
DeletePrizeInput,
DeletePrizeOutput,
GetWalletQuery,
GetWalletOutput,
ProcessWalletTransactionInput,
ProcessWalletTransactionOutput,
} from './dto/PaymentsDto';
// Injection tokens
import {
GET_PAYMENTS_USE_CASE_TOKEN,
CREATE_PAYMENT_USE_CASE_TOKEN,
UPDATE_PAYMENT_STATUS_USE_CASE_TOKEN,
GET_MEMBERSHIP_FEES_USE_CASE_TOKEN,
UPSERT_MEMBERSHIP_FEE_USE_CASE_TOKEN,
UPDATE_MEMBER_PAYMENT_USE_CASE_TOKEN,
GET_PRIZES_USE_CASE_TOKEN,
CREATE_PRIZE_USE_CASE_TOKEN,
AWARD_PRIZE_USE_CASE_TOKEN,
DELETE_PRIZE_USE_CASE_TOKEN,
GET_WALLET_USE_CASE_TOKEN,
PROCESS_WALLET_TRANSACTION_USE_CASE_TOKEN,
LOGGER_TOKEN,
} from './PaymentsProviders';
@Injectable()
export class PaymentsService {
constructor(
@Inject(GET_PAYMENTS_USE_CASE_TOKEN) private readonly getPaymentsUseCase: GetPaymentsUseCase,
@Inject(CREATE_PAYMENT_USE_CASE_TOKEN) private readonly createPaymentUseCase: CreatePaymentUseCase,
@Inject(UPDATE_PAYMENT_STATUS_USE_CASE_TOKEN) private readonly updatePaymentStatusUseCase: UpdatePaymentStatusUseCase,
@Inject(GET_MEMBERSHIP_FEES_USE_CASE_TOKEN) private readonly getMembershipFeesUseCase: GetMembershipFeesUseCase,
@Inject(UPSERT_MEMBERSHIP_FEE_USE_CASE_TOKEN) private readonly upsertMembershipFeeUseCase: UpsertMembershipFeeUseCase,
@Inject(UPDATE_MEMBER_PAYMENT_USE_CASE_TOKEN) private readonly updateMemberPaymentUseCase: UpdateMemberPaymentUseCase,
@Inject(GET_PRIZES_USE_CASE_TOKEN) private readonly getPrizesUseCase: GetPrizesUseCase,
@Inject(CREATE_PRIZE_USE_CASE_TOKEN) private readonly createPrizeUseCase: CreatePrizeUseCase,
@Inject(AWARD_PRIZE_USE_CASE_TOKEN) private readonly awardPrizeUseCase: AwardPrizeUseCase,
@Inject(DELETE_PRIZE_USE_CASE_TOKEN) private readonly deletePrizeUseCase: DeletePrizeUseCase,
@Inject(GET_WALLET_USE_CASE_TOKEN) private readonly getWalletUseCase: GetWalletUseCase,
@Inject(PROCESS_WALLET_TRANSACTION_USE_CASE_TOKEN) private readonly processWalletTransactionUseCase: ProcessWalletTransactionUseCase,
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
) {}
async getPayments(query: GetPaymentsQuery): Promise<GetPaymentsOutput> {
this.logger.debug('[PaymentsService] Getting payments', { query });
const presenter = new GetPaymentsPresenter();
await this.getPaymentsUseCase.execute(query, presenter);
return presenter.viewModel;
}
async createPayment(input: CreatePaymentInput): Promise<CreatePaymentOutput> {
this.logger.debug('[PaymentsService] Creating payment', { input });
const presenter = new CreatePaymentPresenter();
await this.createPaymentUseCase.execute(input, presenter);
return presenter.viewModel;
}
async updatePaymentStatus(input: UpdatePaymentStatusInput): Promise<UpdatePaymentStatusOutput> {
this.logger.debug('[PaymentsService] Updating payment status', { input });
const presenter = new UpdatePaymentStatusPresenter();
await this.updatePaymentStatusUseCase.execute(input, presenter);
return presenter.viewModel;
}
async getMembershipFees(query: GetMembershipFeesQuery): Promise<GetMembershipFeesOutput> {
this.logger.debug('[PaymentsService] Getting membership fees', { query });
const presenter = new GetMembershipFeesPresenter();
await this.getMembershipFeesUseCase.execute(query, presenter);
return presenter.viewModel;
}
async upsertMembershipFee(input: UpsertMembershipFeeInput): Promise<UpsertMembershipFeeOutput> {
this.logger.debug('[PaymentsService] Upserting membership fee', { input });
const presenter = new UpsertMembershipFeePresenter();
await this.upsertMembershipFeeUseCase.execute(input, presenter);
return presenter.viewModel;
}
async updateMemberPayment(input: UpdateMemberPaymentInput): Promise<UpdateMemberPaymentOutput> {
this.logger.debug('[PaymentsService] Updating member payment', { input });
const presenter = new UpdateMemberPaymentPresenter();
await this.updateMemberPaymentUseCase.execute(input, presenter);
return presenter.viewModel;
}
async getPrizes(query: GetPrizesQuery): Promise<GetPrizesOutput> {
this.logger.debug('[PaymentsService] Getting prizes', { query });
const presenter = new GetPrizesPresenter();
await this.getPrizesUseCase.execute({ leagueId: query.leagueId!, seasonId: query.seasonId }, presenter);
return presenter.viewModel;
}
async createPrize(input: CreatePrizeInput): Promise<CreatePrizeOutput> {
this.logger.debug('[PaymentsService] Creating prize', { input });
const presenter = new CreatePrizePresenter();
await this.createPrizeUseCase.execute(input, presenter);
return presenter.viewModel;
}
async awardPrize(input: AwardPrizeInput): Promise<AwardPrizeOutput> {
this.logger.debug('[PaymentsService] Awarding prize', { input });
const presenter = new AwardPrizePresenter();
await this.awardPrizeUseCase.execute(input, presenter);
return presenter.viewModel;
}
async deletePrize(input: DeletePrizeInput): Promise<DeletePrizeOutput> {
this.logger.debug('[PaymentsService] Deleting prize', { input });
const presenter = new DeletePrizePresenter();
await this.deletePrizeUseCase.execute(input, presenter);
return presenter.viewModel;
}
async getWallet(query: GetWalletQuery): Promise<GetWalletOutput> {
this.logger.debug('[PaymentsService] Getting wallet', { query });
const presenter = new GetWalletPresenter();
await this.getWalletUseCase.execute({ leagueId: query.leagueId! }, presenter);
return presenter.viewModel;
}
async processWalletTransaction(input: ProcessWalletTransactionInput): Promise<ProcessWalletTransactionOutput> {
this.logger.debug('[PaymentsService] Processing wallet transaction', { input });
const presenter = new ProcessWalletTransactionPresenter();
await this.processWalletTransactionUseCase.execute(input, presenter);
return presenter.viewModel;
}
}

View File

@@ -0,0 +1,566 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty, IsNumber, IsEnum, IsOptional, IsDate, IsBoolean } from 'class-validator';
export enum PaymentType {
SPONSORSHIP = 'sponsorship',
MEMBERSHIP_FEE = 'membership_fee',
}
export enum PayerType {
SPONSOR = 'sponsor',
DRIVER = 'driver',
}
export enum PaymentStatus {
PENDING = 'pending',
COMPLETED = 'completed',
FAILED = 'failed',
REFUNDED = 'refunded',
}
export class PaymentDto {
@ApiProperty()
@IsString()
id: string;
@ApiProperty({ enum: PaymentType })
@IsEnum(PaymentType)
type: PaymentType;
@ApiProperty()
@IsNumber()
amount: number;
@ApiProperty()
@IsNumber()
platformFee: number;
@ApiProperty()
@IsNumber()
netAmount: number;
@ApiProperty()
@IsString()
payerId: string;
@ApiProperty({ enum: PayerType })
@IsEnum(PayerType)
payerType: PayerType;
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
seasonId?: string;
@ApiProperty({ enum: PaymentStatus })
@IsEnum(PaymentStatus)
status: PaymentStatus;
@ApiProperty()
@IsDate()
createdAt: Date;
@ApiProperty({ required: false })
@IsOptional()
@IsDate()
completedAt?: Date;
}
export class CreatePaymentInput {
@ApiProperty({ enum: PaymentType })
@IsEnum(PaymentType)
type: PaymentType;
@ApiProperty()
@IsNumber()
amount: number;
@ApiProperty()
@IsString()
payerId: string;
@ApiProperty({ enum: PayerType })
@IsEnum(PayerType)
payerType: PayerType;
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
seasonId?: string;
}
export class CreatePaymentOutput {
@ApiProperty({ type: PaymentDto })
payment: PaymentDto;
}
export class UpdatePaymentStatusInput {
@ApiProperty()
@IsString()
paymentId: string;
@ApiProperty({ enum: PaymentStatus })
@IsEnum(PaymentStatus)
status: PaymentStatus;
}
export class UpdatePaymentStatusOutput {
@ApiProperty({ type: PaymentDto })
payment: PaymentDto;
}
export enum MembershipFeeType {
SEASON = 'season',
MONTHLY = 'monthly',
PER_RACE = 'per_race',
}
export enum MemberPaymentStatus {
PENDING = 'pending',
PAID = 'paid',
OVERDUE = 'overdue',
}
export class MembershipFeeDto {
@ApiProperty()
@IsString()
id: string;
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
seasonId?: string;
@ApiProperty({ enum: MembershipFeeType })
@IsEnum(MembershipFeeType)
type: MembershipFeeType;
@ApiProperty()
@IsNumber()
amount: number;
@ApiProperty()
@IsBoolean()
enabled: boolean;
@ApiProperty()
@IsDate()
createdAt: Date;
@ApiProperty()
@IsDate()
updatedAt: Date;
}
export class MemberPaymentDto {
@ApiProperty()
@IsString()
id: string;
@ApiProperty()
@IsString()
feeId: string;
@ApiProperty()
@IsString()
driverId: string;
@ApiProperty()
@IsNumber()
amount: number;
@ApiProperty()
@IsNumber()
platformFee: number;
@ApiProperty()
@IsNumber()
netAmount: number;
@ApiProperty({ enum: MemberPaymentStatus })
@IsEnum(MemberPaymentStatus)
status: MemberPaymentStatus;
@ApiProperty()
@IsDate()
dueDate: Date;
@ApiProperty({ required: false })
@IsOptional()
@IsDate()
paidAt?: Date;
}
export class GetMembershipFeesQuery {
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
driverId?: string;
}
export class GetMembershipFeesOutput {
@ApiProperty({ type: MembershipFeeDto, nullable: true })
fee: MembershipFeeDto | null;
@ApiProperty({ type: [MemberPaymentDto] })
payments: MemberPaymentDto[];
}
export class UpsertMembershipFeeInput {
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
seasonId?: string;
@ApiProperty({ enum: MembershipFeeType })
@IsEnum(MembershipFeeType)
type: MembershipFeeType;
@ApiProperty()
@IsNumber()
amount: number;
}
export class UpsertMembershipFeeOutput {
@ApiProperty({ type: MembershipFeeDto })
fee: MembershipFeeDto;
}
export class UpdateMemberPaymentInput {
@ApiProperty()
@IsString()
feeId: string;
@ApiProperty()
@IsString()
driverId: string;
@ApiProperty({ required: false, enum: MemberPaymentStatus })
@IsOptional()
@IsEnum(MemberPaymentStatus)
status?: MemberPaymentStatus;
@ApiProperty({ required: false })
@IsOptional()
@IsDate()
paidAt?: Date | string;
}
export class UpdateMemberPaymentOutput {
@ApiProperty({ type: MemberPaymentDto })
payment: MemberPaymentDto;
}
export class GetPaymentsQuery {
@ApiProperty({ required: false })
@IsOptional()
@IsString()
leagueId?: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
payerId?: string;
@ApiProperty({ required: false, enum: PaymentType })
@IsOptional()
@IsEnum(PaymentType)
type?: PaymentType;
}
export class GetPaymentsOutput {
@ApiProperty({ type: [PaymentDto] })
payments: PaymentDto[];
}
export enum PrizeType {
CASH = 'cash',
MERCHANDISE = 'merchandise',
OTHER = 'other',
}
export class PrizeDto {
@ApiProperty()
@IsString()
id: string;
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty()
@IsString()
seasonId: string;
@ApiProperty()
@IsNumber()
position: number;
@ApiProperty()
@IsString()
name: string;
@ApiProperty()
@IsNumber()
amount: number;
@ApiProperty({ enum: PrizeType })
@IsEnum(PrizeType)
type: PrizeType;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
description?: string;
@ApiProperty()
@IsBoolean()
awarded: boolean;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
awardedTo?: string;
@ApiProperty({ required: false })
@IsOptional()
@IsDate()
awardedAt?: Date;
@ApiProperty()
@IsDate()
createdAt: Date;
}
export class GetPrizesQuery {
@ApiProperty()
@IsString()
leagueId?: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
seasonId?: string;
}
export class GetPrizesOutput {
@ApiProperty({ type: [PrizeDto] })
prizes: PrizeDto[];
}
export class CreatePrizeInput {
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty()
@IsString()
seasonId: string;
@ApiProperty()
@IsNumber()
position: number;
@ApiProperty()
@IsString()
name: string;
@ApiProperty()
@IsNumber()
amount: number;
@ApiProperty({ enum: PrizeType })
@IsEnum(PrizeType)
type: PrizeType;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
description?: string;
}
export class CreatePrizeOutput {
@ApiProperty({ type: PrizeDto })
prize: PrizeDto;
}
export class AwardPrizeInput {
@ApiProperty()
@IsString()
prizeId: string;
@ApiProperty()
@IsString()
driverId: string;
}
export class AwardPrizeOutput {
@ApiProperty({ type: PrizeDto })
prize: PrizeDto;
}
export class DeletePrizeInput {
@ApiProperty()
@IsString()
prizeId: string;
}
export class DeletePrizeOutput {
@ApiProperty()
@IsBoolean()
success: boolean;
}
export enum TransactionType {
DEPOSIT = 'deposit',
WITHDRAWAL = 'withdrawal',
PLATFORM_FEE = 'platform_fee',
}
export enum ReferenceType {
SPONSORSHIP = 'sponsorship',
MEMBERSHIP_FEE = 'membership_fee',
PRIZE = 'prize',
}
export class WalletDto {
@ApiProperty()
@IsString()
id: string;
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty()
@IsNumber()
balance: number;
@ApiProperty()
@IsNumber()
totalRevenue: number;
@ApiProperty()
@IsNumber()
totalPlatformFees: number;
@ApiProperty()
@IsNumber()
totalWithdrawn: number;
@ApiProperty()
@IsDate()
createdAt: Date;
@ApiProperty()
@IsString()
currency: string;
}
export class TransactionDto {
@ApiProperty()
@IsString()
id: string;
@ApiProperty()
@IsString()
walletId: string;
@ApiProperty({ enum: TransactionType })
@IsEnum(TransactionType)
type: TransactionType;
@ApiProperty()
@IsNumber()
amount: number;
@ApiProperty()
@IsString()
description: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
referenceId?: string;
@ApiProperty({ required: false, enum: ReferenceType })
@IsOptional()
@IsEnum(ReferenceType)
referenceType?: ReferenceType;
@ApiProperty()
@IsDate()
createdAt: Date;
}
export class GetWalletQuery {
@ApiProperty()
@IsString()
leagueId?: string;
}
export class GetWalletOutput {
@ApiProperty({ type: WalletDto })
wallet: WalletDto;
@ApiProperty({ type: [TransactionDto] })
transactions: TransactionDto[];
}
export class ProcessWalletTransactionInput {
@ApiProperty()
@IsString()
leagueId: string;
@ApiProperty({ enum: TransactionType })
@IsEnum(TransactionType)
type: TransactionType;
@ApiProperty()
@IsNumber()
amount: number;
@ApiProperty()
@IsString()
@IsNotEmpty()
description: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
referenceId?: string;
@ApiProperty({ required: false, enum: ReferenceType })
@IsOptional()
@IsEnum(ReferenceType)
referenceType?: ReferenceType;
}
export class ProcessWalletTransactionOutput {
@ApiProperty({ type: WalletDto })
wallet: WalletDto;
@ApiProperty({ type: TransactionDto })
transaction: TransactionDto;
}

View File

@@ -0,0 +1,26 @@
import type {
IAwardPrizePresenter,
AwardPrizeResultDTO,
AwardPrizeViewModel,
} from '@core/payments/application/presenters/IAwardPrizePresenter';
export class AwardPrizePresenter implements IAwardPrizePresenter {
private result: AwardPrizeViewModel | null = null;
reset() {
this.result = null;
}
present(dto: AwardPrizeResultDTO) {
this.result = dto;
}
getViewModel(): AwardPrizeViewModel | null {
return this.result;
}
get viewModel(): AwardPrizeViewModel {
if (!this.result) throw new Error('Presenter not presented');
return this.result;
}
}

View File

@@ -0,0 +1,26 @@
import type {
ICreatePaymentPresenter,
CreatePaymentResultDTO,
CreatePaymentViewModel,
} from '@core/payments/application/presenters/ICreatePaymentPresenter';
export class CreatePaymentPresenter implements ICreatePaymentPresenter {
private result: CreatePaymentViewModel | null = null;
reset() {
this.result = null;
}
present(dto: CreatePaymentResultDTO) {
this.result = dto;
}
getViewModel(): CreatePaymentViewModel | null {
return this.result;
}
get viewModel(): CreatePaymentViewModel {
if (!this.result) throw new Error('Presenter not presented');
return this.result;
}
}

View File

@@ -0,0 +1,26 @@
import type {
ICreatePrizePresenter,
CreatePrizeResultDTO,
CreatePrizeViewModel,
} from '@core/payments/application/presenters/ICreatePrizePresenter';
export class CreatePrizePresenter implements ICreatePrizePresenter {
private result: CreatePrizeViewModel | null = null;
reset() {
this.result = null;
}
present(dto: CreatePrizeResultDTO) {
this.result = dto;
}
getViewModel(): CreatePrizeViewModel | null {
return this.result;
}
get viewModel(): CreatePrizeViewModel {
if (!this.result) throw new Error('Presenter not presented');
return this.result;
}
}

View File

@@ -0,0 +1,26 @@
import type {
IDeletePrizePresenter,
DeletePrizeResultDTO,
DeletePrizeViewModel,
} from '@core/payments/application/presenters/IDeletePrizePresenter';
export class DeletePrizePresenter implements IDeletePrizePresenter {
private result: DeletePrizeViewModel | null = null;
reset() {
this.result = null;
}
present(dto: DeletePrizeResultDTO) {
this.result = dto;
}
getViewModel(): DeletePrizeViewModel | null {
return this.result;
}
get viewModel(): DeletePrizeViewModel {
if (!this.result) throw new Error('Presenter not presented');
return this.result;
}
}

View File

@@ -0,0 +1,26 @@
import type {
IGetMembershipFeesPresenter,
GetMembershipFeesResultDTO,
GetMembershipFeesViewModel,
} from '@core/payments/application/presenters/IGetMembershipFeesPresenter';
export class GetMembershipFeesPresenter implements IGetMembershipFeesPresenter {
private result: GetMembershipFeesViewModel | null = null;
reset() {
this.result = null;
}
present(dto: GetMembershipFeesResultDTO) {
this.result = dto;
}
getViewModel(): GetMembershipFeesViewModel | null {
return this.result;
}
get viewModel(): GetMembershipFeesViewModel {
if (!this.result) throw new Error('Presenter not presented');
return this.result;
}
}

View File

@@ -0,0 +1,26 @@
import type {
IGetPaymentsPresenter,
GetPaymentsResultDTO,
GetPaymentsViewModel,
} from '@core/payments/application/presenters/IGetPaymentsPresenter';
export class GetPaymentsPresenter implements IGetPaymentsPresenter {
private result: GetPaymentsViewModel | null = null;
reset() {
this.result = null;
}
present(dto: GetPaymentsResultDTO) {
this.result = dto;
}
getViewModel(): GetPaymentsViewModel | null {
return this.result;
}
get viewModel(): GetPaymentsViewModel {
if (!this.result) throw new Error('Presenter not presented');
return this.result;
}
}

View File

@@ -0,0 +1,26 @@
import type {
IGetPrizesPresenter,
GetPrizesResultDTO,
GetPrizesViewModel,
} from '@core/payments/application/presenters/IGetPrizesPresenter';
export class GetPrizesPresenter implements IGetPrizesPresenter {
private result: GetPrizesViewModel | null = null;
reset() {
this.result = null;
}
present(dto: GetPrizesResultDTO) {
this.result = dto;
}
getViewModel(): GetPrizesViewModel | null {
return this.result;
}
get viewModel(): GetPrizesViewModel {
if (!this.result) throw new Error('Presenter not presented');
return this.result;
}
}

View File

@@ -0,0 +1,26 @@
import type {
IGetWalletPresenter,
GetWalletResultDTO,
GetWalletViewModel,
} from '@core/payments/application/presenters/IGetWalletPresenter';
export class GetWalletPresenter implements IGetWalletPresenter {
private result: GetWalletViewModel | null = null;
reset() {
this.result = null;
}
present(dto: GetWalletResultDTO) {
this.result = dto;
}
getViewModel(): GetWalletViewModel | null {
return this.result;
}
get viewModel(): GetWalletViewModel {
if (!this.result) throw new Error('Presenter not presented');
return this.result;
}
}

View File

@@ -0,0 +1,26 @@
import type {
IProcessWalletTransactionPresenter,
ProcessWalletTransactionResultDTO,
ProcessWalletTransactionViewModel,
} from '@core/payments/application/presenters/IProcessWalletTransactionPresenter';
export class ProcessWalletTransactionPresenter implements IProcessWalletTransactionPresenter {
private result: ProcessWalletTransactionViewModel | null = null;
reset() {
this.result = null;
}
present(dto: ProcessWalletTransactionResultDTO) {
this.result = dto;
}
getViewModel(): ProcessWalletTransactionViewModel | null {
return this.result;
}
get viewModel(): ProcessWalletTransactionViewModel {
if (!this.result) throw new Error('Presenter not presented');
return this.result;
}
}

View File

@@ -0,0 +1,26 @@
import type {
IUpdateMemberPaymentPresenter,
UpdateMemberPaymentResultDTO,
UpdateMemberPaymentViewModel,
} from '@core/payments/application/presenters/IUpdateMemberPaymentPresenter';
export class UpdateMemberPaymentPresenter implements IUpdateMemberPaymentPresenter {
private result: UpdateMemberPaymentViewModel | null = null;
reset() {
this.result = null;
}
present(dto: UpdateMemberPaymentResultDTO) {
this.result = dto;
}
getViewModel(): UpdateMemberPaymentViewModel | null {
return this.result;
}
get viewModel(): UpdateMemberPaymentViewModel {
if (!this.result) throw new Error('Presenter not presented');
return this.result;
}
}

View File

@@ -0,0 +1,26 @@
import type {
IUpdatePaymentStatusPresenter,
UpdatePaymentStatusResultDTO,
UpdatePaymentStatusViewModel,
} from '@core/payments/application/presenters/IUpdatePaymentStatusPresenter';
export class UpdatePaymentStatusPresenter implements IUpdatePaymentStatusPresenter {
private result: UpdatePaymentStatusViewModel | null = null;
reset() {
this.result = null;
}
present(dto: UpdatePaymentStatusResultDTO) {
this.result = dto;
}
getViewModel(): UpdatePaymentStatusViewModel | null {
return this.result;
}
get viewModel(): UpdatePaymentStatusViewModel {
if (!this.result) throw new Error('Presenter not presented');
return this.result;
}
}

View File

@@ -0,0 +1,26 @@
import type {
IUpsertMembershipFeePresenter,
UpsertMembershipFeeResultDTO,
UpsertMembershipFeeViewModel,
} from '@core/payments/application/presenters/IUpsertMembershipFeePresenter';
export class UpsertMembershipFeePresenter implements IUpsertMembershipFeePresenter {
private result: UpsertMembershipFeeViewModel | null = null;
reset() {
this.result = null;
}
present(dto: UpsertMembershipFeeResultDTO) {
this.result = dto;
}
getViewModel(): UpsertMembershipFeeViewModel | null {
return this.result;
}
get viewModel(): UpsertMembershipFeeViewModel {
if (!this.result) throw new Error('Presenter not presented');
return this.result;
}
}

View File

@@ -0,0 +1,12 @@
export * from './GetPaymentsPresenter';
export * from './CreatePaymentPresenter';
export * from './UpdatePaymentStatusPresenter';
export * from './GetMembershipFeesPresenter';
export * from './UpsertMembershipFeePresenter';
export * from './UpdateMemberPaymentPresenter';
export * from './GetPrizesPresenter';
export * from './CreatePrizePresenter';
export * from './AwardPrizePresenter';
export * from './DeletePrizePresenter';
export * from './GetWalletPresenter';
export * from './ProcessWalletTransactionPresenter';