refactor use cases

This commit is contained in:
2026-01-08 15:34:51 +01:00
parent d984ab24a8
commit 52e9a2f6a7
362 changed files with 5192 additions and 8409 deletions

View File

@@ -5,7 +5,6 @@ import type { IPaymentRepository } from '@core/payments/domain/repositories/IPay
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 { UseCaseOutputPort } from '@core/shared/application';
// Import use cases
import { GetPaymentsUseCase } from '@core/payments/application/use-cases/GetPaymentsUseCase';
@@ -24,20 +23,6 @@ import { ProcessWalletTransactionUseCase } from '@core/payments/application/use-
// Import concrete in-memory implementations
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
// 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';
import {
PAYMENT_REPOSITORY_TOKEN,
MEMBERSHIP_FEE_REPOSITORY_TOKEN,
@@ -58,88 +43,12 @@ import {
DELETE_PRIZE_USE_CASE_TOKEN,
GET_WALLET_USE_CASE_TOKEN,
PROCESS_WALLET_TRANSACTION_USE_CASE_TOKEN,
GET_PAYMENTS_OUTPUT_PORT_TOKEN,
CREATE_PAYMENT_OUTPUT_PORT_TOKEN,
UPDATE_PAYMENT_STATUS_OUTPUT_PORT_TOKEN,
GET_MEMBERSHIP_FEES_OUTPUT_PORT_TOKEN,
UPSERT_MEMBERSHIP_FEE_OUTPUT_PORT_TOKEN,
UPDATE_MEMBER_PAYMENT_OUTPUT_PORT_TOKEN,
GET_PRIZES_OUTPUT_PORT_TOKEN,
CREATE_PRIZE_OUTPUT_PORT_TOKEN,
AWARD_PRIZE_OUTPUT_PORT_TOKEN,
DELETE_PRIZE_OUTPUT_PORT_TOKEN,
GET_WALLET_OUTPUT_PORT_TOKEN,
PROCESS_WALLET_TRANSACTION_OUTPUT_PORT_TOKEN,
} from './PaymentsTokens';
export * from './PaymentsTokens';
export const PaymentsProviders: Provider[] = [
// Presenters
GetPaymentsPresenter,
CreatePaymentPresenter,
UpdatePaymentStatusPresenter,
GetMembershipFeesPresenter,
UpsertMembershipFeePresenter,
UpdateMemberPaymentPresenter,
GetPrizesPresenter,
CreatePrizePresenter,
AwardPrizePresenter,
DeletePrizePresenter,
GetWalletPresenter,
ProcessWalletTransactionPresenter,
// Output ports
{
provide: GET_PAYMENTS_OUTPUT_PORT_TOKEN,
useExisting: GetPaymentsPresenter,
},
{
provide: CREATE_PAYMENT_OUTPUT_PORT_TOKEN,
useExisting: CreatePaymentPresenter,
},
{
provide: UPDATE_PAYMENT_STATUS_OUTPUT_PORT_TOKEN,
useExisting: UpdatePaymentStatusPresenter,
},
{
provide: GET_MEMBERSHIP_FEES_OUTPUT_PORT_TOKEN,
useExisting: GetMembershipFeesPresenter,
},
{
provide: UPSERT_MEMBERSHIP_FEE_OUTPUT_PORT_TOKEN,
useExisting: UpsertMembershipFeePresenter,
},
{
provide: UPDATE_MEMBER_PAYMENT_OUTPUT_PORT_TOKEN,
useExisting: UpdateMemberPaymentPresenter,
},
{
provide: GET_PRIZES_OUTPUT_PORT_TOKEN,
useExisting: GetPrizesPresenter,
},
{
provide: CREATE_PRIZE_OUTPUT_PORT_TOKEN,
useExisting: CreatePrizePresenter,
},
{
provide: AWARD_PRIZE_OUTPUT_PORT_TOKEN,
useExisting: AwardPrizePresenter,
},
{
provide: DELETE_PRIZE_OUTPUT_PORT_TOKEN,
useExisting: DeletePrizePresenter,
},
{
provide: GET_WALLET_OUTPUT_PORT_TOKEN,
useExisting: GetWalletPresenter,
},
{
provide: PROCESS_WALLET_TRANSACTION_OUTPUT_PORT_TOKEN,
useExisting: ProcessWalletTransactionPresenter,
},
// Logger
{
provide: LOGGER_TOKEN,
@@ -151,66 +60,66 @@ export const PaymentsProviders: Provider[] = [
// Use cases (use cases receive repositories, services receive use cases)
{
provide: GET_PAYMENTS_USE_CASE_TOKEN,
useFactory: (paymentRepo: IPaymentRepository, output: UseCaseOutputPort<unknown>) => new GetPaymentsUseCase(paymentRepo, output),
inject: [PAYMENT_REPOSITORY_TOKEN, GET_PAYMENTS_OUTPUT_PORT_TOKEN],
useFactory: (paymentRepo: IPaymentRepository) => new GetPaymentsUseCase(paymentRepo),
inject: [PAYMENT_REPOSITORY_TOKEN],
},
{
provide: CREATE_PAYMENT_USE_CASE_TOKEN,
useFactory: (paymentRepo: IPaymentRepository, output: UseCaseOutputPort<unknown>) => new CreatePaymentUseCase(paymentRepo, output),
inject: [PAYMENT_REPOSITORY_TOKEN, CREATE_PAYMENT_OUTPUT_PORT_TOKEN],
useFactory: (paymentRepo: IPaymentRepository) => new CreatePaymentUseCase(paymentRepo),
inject: [PAYMENT_REPOSITORY_TOKEN],
},
{
provide: UPDATE_PAYMENT_STATUS_USE_CASE_TOKEN,
useFactory: (paymentRepo: IPaymentRepository, output: UseCaseOutputPort<unknown>) => new UpdatePaymentStatusUseCase(paymentRepo, output),
inject: [PAYMENT_REPOSITORY_TOKEN, UPDATE_PAYMENT_STATUS_OUTPUT_PORT_TOKEN],
useFactory: (paymentRepo: IPaymentRepository) => new UpdatePaymentStatusUseCase(paymentRepo),
inject: [PAYMENT_REPOSITORY_TOKEN],
},
{
provide: GET_MEMBERSHIP_FEES_USE_CASE_TOKEN,
useFactory: (membershipFeeRepo: IMembershipFeeRepository, memberPaymentRepo: IMemberPaymentRepository, output: UseCaseOutputPort<unknown>) =>
new GetMembershipFeesUseCase(membershipFeeRepo, memberPaymentRepo, output),
inject: [MEMBERSHIP_FEE_REPOSITORY_TOKEN, MEMBER_PAYMENT_REPOSITORY_TOKEN, GET_MEMBERSHIP_FEES_OUTPUT_PORT_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, output: UseCaseOutputPort<unknown>) => new UpsertMembershipFeeUseCase(membershipFeeRepo, output),
inject: [MEMBERSHIP_FEE_REPOSITORY_TOKEN, UPSERT_MEMBERSHIP_FEE_OUTPUT_PORT_TOKEN],
useFactory: (membershipFeeRepo: IMembershipFeeRepository) => new UpsertMembershipFeeUseCase(membershipFeeRepo),
inject: [MEMBERSHIP_FEE_REPOSITORY_TOKEN],
},
{
provide: UPDATE_MEMBER_PAYMENT_USE_CASE_TOKEN,
useFactory: (membershipFeeRepo: IMembershipFeeRepository, memberPaymentRepo: IMemberPaymentRepository, output: UseCaseOutputPort<unknown>) =>
new UpdateMemberPaymentUseCase(membershipFeeRepo, memberPaymentRepo, output),
inject: [MEMBERSHIP_FEE_REPOSITORY_TOKEN, MEMBER_PAYMENT_REPOSITORY_TOKEN, UPDATE_MEMBER_PAYMENT_OUTPUT_PORT_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, output: UseCaseOutputPort<unknown>) => new GetPrizesUseCase(prizeRepo, output),
inject: [PRIZE_REPOSITORY_TOKEN, GET_PRIZES_OUTPUT_PORT_TOKEN],
useFactory: (prizeRepo: IPrizeRepository) => new GetPrizesUseCase(prizeRepo),
inject: [PRIZE_REPOSITORY_TOKEN],
},
{
provide: CREATE_PRIZE_USE_CASE_TOKEN,
useFactory: (prizeRepo: IPrizeRepository, output: UseCaseOutputPort<unknown>) => new CreatePrizeUseCase(prizeRepo, output),
inject: [PRIZE_REPOSITORY_TOKEN, CREATE_PRIZE_OUTPUT_PORT_TOKEN],
useFactory: (prizeRepo: IPrizeRepository) => new CreatePrizeUseCase(prizeRepo),
inject: [PRIZE_REPOSITORY_TOKEN],
},
{
provide: AWARD_PRIZE_USE_CASE_TOKEN,
useFactory: (prizeRepo: IPrizeRepository, output: UseCaseOutputPort<unknown>) => new AwardPrizeUseCase(prizeRepo, output),
inject: [PRIZE_REPOSITORY_TOKEN, AWARD_PRIZE_OUTPUT_PORT_TOKEN],
useFactory: (prizeRepo: IPrizeRepository) => new AwardPrizeUseCase(prizeRepo),
inject: [PRIZE_REPOSITORY_TOKEN],
},
{
provide: DELETE_PRIZE_USE_CASE_TOKEN,
useFactory: (prizeRepo: IPrizeRepository, output: UseCaseOutputPort<unknown>) => new DeletePrizeUseCase(prizeRepo, output),
inject: [PRIZE_REPOSITORY_TOKEN, DELETE_PRIZE_OUTPUT_PORT_TOKEN],
useFactory: (prizeRepo: IPrizeRepository) => new DeletePrizeUseCase(prizeRepo),
inject: [PRIZE_REPOSITORY_TOKEN],
},
{
provide: GET_WALLET_USE_CASE_TOKEN,
useFactory: (walletRepo: IWalletRepository, transactionRepo: ITransactionRepository, output: UseCaseOutputPort<unknown>) =>
new GetWalletUseCase(walletRepo, transactionRepo, output),
inject: [WALLET_REPOSITORY_TOKEN, TRANSACTION_REPOSITORY_TOKEN, GET_WALLET_OUTPUT_PORT_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, output: UseCaseOutputPort<unknown>) =>
new ProcessWalletTransactionUseCase(walletRepo, transactionRepo, output),
inject: [WALLET_REPOSITORY_TOKEN, TRANSACTION_REPOSITORY_TOKEN, PROCESS_WALLET_TRANSACTION_OUTPUT_PORT_TOKEN],
useFactory: (walletRepo: IWalletRepository, transactionRepo: ITransactionRepository) =>
new ProcessWalletTransactionUseCase(walletRepo, transactionRepo),
inject: [WALLET_REPOSITORY_TOKEN, TRANSACTION_REPOSITORY_TOKEN],
},
];

View File

@@ -6,42 +6,23 @@ describe('PaymentsService', () => {
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
function makeService(overrides?: Partial<Record<string, any>>) {
const getPaymentsUseCase = overrides?.getPaymentsUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
const createPaymentUseCase = overrides?.createPaymentUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
const getPaymentsUseCase = overrides?.getPaymentsUseCase ?? { execute: vi.fn(async () => Result.ok({ payments: [] })) };
const createPaymentUseCase = overrides?.createPaymentUseCase ?? { execute: vi.fn(async () => Result.ok({ paymentId: 'p1' })) };
const updatePaymentStatusUseCase =
overrides?.updatePaymentStatusUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
overrides?.updatePaymentStatusUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
const getMembershipFeesUseCase =
overrides?.getMembershipFeesUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
overrides?.getMembershipFeesUseCase ?? { execute: vi.fn(async () => Result.ok({ fee: null, payments: [] })) };
const upsertMembershipFeeUseCase =
overrides?.upsertMembershipFeeUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
overrides?.upsertMembershipFeeUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
const updateMemberPaymentUseCase =
overrides?.updateMemberPaymentUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
const getPrizesUseCase = overrides?.getPrizesUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
const createPrizeUseCase = overrides?.createPrizeUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
const awardPrizeUseCase = overrides?.awardPrizeUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
const deletePrizeUseCase = overrides?.deletePrizeUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
const getWalletUseCase = overrides?.getWalletUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
overrides?.updateMemberPaymentUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
const getPrizesUseCase = overrides?.getPrizesUseCase ?? { execute: vi.fn(async () => Result.ok({ prizes: [] })) };
const createPrizeUseCase = overrides?.createPrizeUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
const awardPrizeUseCase = overrides?.awardPrizeUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
const deletePrizeUseCase = overrides?.deletePrizeUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
const getWalletUseCase = overrides?.getWalletUseCase ?? { execute: vi.fn(async () => Result.ok({ balance: 0 })) };
const processWalletTransactionUseCase =
overrides?.processWalletTransactionUseCase ?? { execute: vi.fn(async () => Result.ok(undefined)) };
const getPaymentsPresenter = overrides?.getPaymentsPresenter ?? { getResponseModel: vi.fn(() => ({ payments: [] })) };
const createPaymentPresenter =
overrides?.createPaymentPresenter ?? { getResponseModel: vi.fn(() => ({ paymentId: 'p1' })) };
const updatePaymentStatusPresenter =
overrides?.updatePaymentStatusPresenter ?? { getResponseModel: vi.fn(() => ({ success: true })) };
const getMembershipFeesPresenter = overrides?.getMembershipFeesPresenter ?? { viewModel: { fee: null, payments: [] } };
const upsertMembershipFeePresenter = overrides?.upsertMembershipFeePresenter ?? { viewModel: { success: true } };
const updateMemberPaymentPresenter = overrides?.updateMemberPaymentPresenter ?? { viewModel: { success: true } };
const getPrizesPresenter = overrides?.getPrizesPresenter ?? { viewModel: { prizes: [] } };
const createPrizePresenter = overrides?.createPrizePresenter ?? { viewModel: { success: true } };
const awardPrizePresenter = overrides?.awardPrizePresenter ?? { viewModel: { success: true } };
const deletePrizePresenter = overrides?.deletePrizePresenter ?? { viewModel: { success: true } };
const getWalletPresenter = overrides?.getWalletPresenter ?? { viewModel: { balance: 0 } };
const processWalletTransactionPresenter =
overrides?.processWalletTransactionPresenter ?? { viewModel: { success: true } };
overrides?.processWalletTransactionUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
const service = new PaymentsService(
getPaymentsUseCase as any,
@@ -57,18 +38,6 @@ describe('PaymentsService', () => {
getWalletUseCase as any,
processWalletTransactionUseCase as any,
logger as any,
getPaymentsPresenter as any,
createPaymentPresenter as any,
updatePaymentStatusPresenter as any,
getMembershipFeesPresenter as any,
upsertMembershipFeePresenter as any,
updateMemberPaymentPresenter as any,
getPrizesPresenter as any,
createPrizePresenter as any,
awardPrizePresenter as any,
deletePrizePresenter as any,
getWalletPresenter as any,
processWalletTransactionPresenter as any,
);
return {
@@ -85,26 +54,13 @@ describe('PaymentsService', () => {
deletePrizeUseCase,
getWalletUseCase,
processWalletTransactionUseCase,
getPaymentsPresenter,
createPaymentPresenter,
updatePaymentStatusPresenter,
getMembershipFeesPresenter,
upsertMembershipFeePresenter,
updateMemberPaymentPresenter,
getPrizesPresenter,
createPrizePresenter,
awardPrizePresenter,
deletePrizePresenter,
getWalletPresenter,
processWalletTransactionPresenter,
};
}
it('getPayments returns presenter model on success', async () => {
const { service, getPaymentsUseCase, getPaymentsPresenter } = makeService();
const { service, getPaymentsUseCase } = makeService();
await expect(service.getPayments({ leagueId: 'l1' } as any)).resolves.toEqual({ payments: [] });
expect(getPaymentsUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
expect(getPaymentsPresenter.getResponseModel).toHaveBeenCalled();
});
it('getPayments throws when use case returns error (code message)', async () => {
@@ -115,12 +71,9 @@ describe('PaymentsService', () => {
});
it('createPayment returns presenter model on success', async () => {
const { service, createPaymentUseCase, createPaymentPresenter } = makeService({
createPaymentPresenter: { getResponseModel: vi.fn(() => ({ paymentId: 'p1' })) },
});
const { service, createPaymentUseCase } = makeService();
await expect(service.createPayment({ leagueId: 'l1' } as any)).resolves.toEqual({ paymentId: 'p1' });
expect(createPaymentUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
expect(createPaymentPresenter.getResponseModel).toHaveBeenCalled();
});
it('createPayment throws when use case returns error', async () => {
@@ -131,12 +84,9 @@ describe('PaymentsService', () => {
});
it('updatePaymentStatus returns presenter model on success', async () => {
const { service, updatePaymentStatusUseCase, updatePaymentStatusPresenter } = makeService({
updatePaymentStatusPresenter: { getResponseModel: vi.fn(() => ({ success: true })) },
});
const { service, updatePaymentStatusUseCase } = makeService();
await expect(service.updatePaymentStatus({ paymentId: 'p1' } as any)).resolves.toEqual({ success: true });
expect(updatePaymentStatusUseCase.execute).toHaveBeenCalledWith({ paymentId: 'p1' });
expect(updatePaymentStatusPresenter.getResponseModel).toHaveBeenCalled();
});
it('updatePaymentStatus throws when use case returns error', async () => {
@@ -147,8 +97,8 @@ describe('PaymentsService', () => {
});
it('getMembershipFees returns viewModel on success', async () => {
const { service, getMembershipFeesUseCase, getMembershipFeesPresenter } = makeService({
getMembershipFeesPresenter: { viewModel: { fee: { amount: 1 }, payments: [] } },
const { service, getMembershipFeesUseCase } = makeService({
getMembershipFeesUseCase: { execute: vi.fn(async () => Result.ok({ fee: { amount: 1 }, payments: [] })) }
});
await expect(service.getMembershipFees({ leagueId: 'l1', driverId: 'd1' } as any)).resolves.toEqual({
@@ -156,7 +106,6 @@ describe('PaymentsService', () => {
payments: [],
});
expect(getMembershipFeesUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1', driverId: 'd1' });
expect(getMembershipFeesPresenter.viewModel).toBeDefined();
});
it('getMembershipFees throws when use case returns error', async () => {
@@ -167,9 +116,7 @@ describe('PaymentsService', () => {
});
it('upsertMembershipFee returns viewModel on success', async () => {
const { service, upsertMembershipFeeUseCase } = makeService({
upsertMembershipFeePresenter: { viewModel: { success: true } },
});
const { service, upsertMembershipFeeUseCase } = makeService();
await expect(service.upsertMembershipFee({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true });
expect(upsertMembershipFeeUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
@@ -186,9 +133,7 @@ describe('PaymentsService', () => {
});
it('updateMemberPayment returns viewModel on success', async () => {
const { service, updateMemberPaymentUseCase } = makeService({
updateMemberPaymentPresenter: { viewModel: { success: true } },
});
const { service, updateMemberPaymentUseCase } = makeService();
await expect(service.updateMemberPayment({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true });
expect(updateMemberPaymentUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
@@ -203,10 +148,9 @@ describe('PaymentsService', () => {
});
it('getPrizes maps seasonId optional', async () => {
const getPrizesUseCase = { execute: vi.fn(async () => Result.ok(undefined)) };
const getPrizesUseCase = { execute: vi.fn(async () => Result.ok({ prizes: [] })) };
const { service } = makeService({
getPrizesUseCase,
getPrizesPresenter: { viewModel: { prizes: [] } },
});
await expect(service.getPrizes({ leagueId: 'l1' } as any)).resolves.toEqual({ prizes: [] });
@@ -217,10 +161,9 @@ describe('PaymentsService', () => {
});
it('createPrize calls use case and returns viewModel', async () => {
const createPrizeUseCase = { execute: vi.fn(async () => Result.ok(undefined)) };
const createPrizeUseCase = { execute: vi.fn(async () => Result.ok({ success: true })) };
const { service } = makeService({
createPrizeUseCase,
createPrizePresenter: { viewModel: { success: true } },
});
await expect(service.createPrize({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true });
@@ -228,10 +171,9 @@ describe('PaymentsService', () => {
});
it('awardPrize calls use case and returns viewModel', async () => {
const awardPrizeUseCase = { execute: vi.fn(async () => Result.ok(undefined)) };
const awardPrizeUseCase = { execute: vi.fn(async () => Result.ok({ success: true })) };
const { service } = makeService({
awardPrizeUseCase,
awardPrizePresenter: { viewModel: { success: true } },
});
await expect(service.awardPrize({ prizeId: 'p1' } as any)).resolves.toEqual({ success: true });
@@ -239,10 +181,9 @@ describe('PaymentsService', () => {
});
it('deletePrize calls use case and returns viewModel', async () => {
const deletePrizeUseCase = { execute: vi.fn(async () => Result.ok(undefined)) };
const deletePrizeUseCase = { execute: vi.fn(async () => Result.ok({ success: true })) };
const { service } = makeService({
deletePrizeUseCase,
deletePrizePresenter: { viewModel: { success: true } },
});
await expect(service.deletePrize({ prizeId: 'p1' } as any)).resolves.toEqual({ success: true });
@@ -250,10 +191,9 @@ describe('PaymentsService', () => {
});
it('getWallet calls use case and returns viewModel', async () => {
const getWalletUseCase = { execute: vi.fn(async () => Result.ok(undefined)) };
const getWalletUseCase = { execute: vi.fn(async () => Result.ok({ balance: 10 })) };
const { service } = makeService({
getWalletUseCase,
getWalletPresenter: { viewModel: { balance: 10 } },
});
await expect(service.getWallet({ leagueId: 'l1' } as any)).resolves.toEqual({ balance: 10 });
@@ -261,10 +201,9 @@ describe('PaymentsService', () => {
});
it('processWalletTransaction calls use case and returns viewModel', async () => {
const processWalletTransactionUseCase = { execute: vi.fn(async () => Result.ok(undefined)) };
const processWalletTransactionUseCase = { execute: vi.fn(async () => Result.ok({ success: true })) };
const { service } = makeService({
processWalletTransactionUseCase,
processWalletTransactionPresenter: { viewModel: { success: true } },
});
await expect(service.processWalletTransaction({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true });

View File

@@ -15,20 +15,6 @@ import type { UpdateMemberPaymentUseCase } from '@core/payments/application/use-
import type { UpdatePaymentStatusUseCase } from '@core/payments/application/use-cases/UpdatePaymentStatusUseCase';
import type { UpsertMembershipFeeUseCase } from '@core/payments/application/use-cases/UpsertMembershipFeeUseCase';
// Presenters
import { AwardPrizePresenter } from './presenters/AwardPrizePresenter';
import { CreatePaymentPresenter } from './presenters/CreatePaymentPresenter';
import { CreatePrizePresenter } from './presenters/CreatePrizePresenter';
import { DeletePrizePresenter } from './presenters/DeletePrizePresenter';
import { GetMembershipFeesPresenter } from './presenters/GetMembershipFeesPresenter';
import { GetPaymentsPresenter } from './presenters/GetPaymentsPresenter';
import { GetPrizesPresenter } from './presenters/GetPrizesPresenter';
import { GetWalletPresenter } from './presenters/GetWalletPresenter';
import { ProcessWalletTransactionPresenter } from './presenters/ProcessWalletTransactionPresenter';
import { UpdateMemberPaymentPresenter } from './presenters/UpdateMemberPaymentPresenter';
import { UpdatePaymentStatusPresenter } from './presenters/UpdatePaymentStatusPresenter';
import { UpsertMembershipFeePresenter } from './presenters/UpsertMembershipFeePresenter';
// DTOs
import type {
AwardPrizeInput,
@@ -90,18 +76,6 @@ export class PaymentsService {
@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,
private readonly getPaymentsPresenter: GetPaymentsPresenter,
private readonly createPaymentPresenter: CreatePaymentPresenter,
private readonly updatePaymentStatusPresenter: UpdatePaymentStatusPresenter,
private readonly getMembershipFeesPresenter: GetMembershipFeesPresenter,
private readonly upsertMembershipFeePresenter: UpsertMembershipFeePresenter,
private readonly updateMemberPaymentPresenter: UpdateMemberPaymentPresenter,
private readonly getPrizesPresenter: GetPrizesPresenter,
private readonly createPrizePresenter: CreatePrizePresenter,
private readonly awardPrizePresenter: AwardPrizePresenter,
private readonly deletePrizePresenter: DeletePrizePresenter,
private readonly getWalletPresenter: GetWalletPresenter,
private readonly processWalletTransactionPresenter: ProcessWalletTransactionPresenter,
) {}
async getPayments(query: GetPaymentsQuery): Promise<GetPaymentsOutput> {
@@ -111,7 +85,11 @@ export class PaymentsService {
if (result.isErr()) {
throw new Error(result.unwrapErr().code ?? 'Failed to get payments');
}
return this.getPaymentsPresenter.getResponseModel();
const value = result.value;
if (!value) {
throw new Error('Failed to get payments: no value returned');
}
return value;
}
async createPayment(input: CreatePaymentInput): Promise<CreatePaymentOutput> {
@@ -121,7 +99,11 @@ export class PaymentsService {
if (result.isErr()) {
throw new Error(result.unwrapErr().code ?? 'Failed to create payment');
}
return this.createPaymentPresenter.getResponseModel();
const value = result.value;
if (!value) {
throw new Error('Failed to create payment: no value returned');
}
return value;
}
async updatePaymentStatus(input: UpdatePaymentStatusInput): Promise<UpdatePaymentStatusOutput> {
@@ -131,7 +113,11 @@ export class PaymentsService {
if (result.isErr()) {
throw new Error(result.unwrapErr().code ?? 'Failed to update payment status');
}
return this.updatePaymentStatusPresenter.getResponseModel();
const value = result.value;
if (!value) {
throw new Error('Failed to update payment status: no value returned');
}
return value;
}
async getMembershipFees(query: GetMembershipFeesQuery): Promise<GetMembershipFeesOutput> {
@@ -141,7 +127,11 @@ export class PaymentsService {
if (result.isErr()) {
throw new Error(result.unwrapErr().code ?? 'Failed to get membership fees');
}
return this.getMembershipFeesPresenter.viewModel;
const value = result.value;
if (!value) {
throw new Error('Failed to get membership fees: no value returned');
}
return value;
}
async upsertMembershipFee(input: UpsertMembershipFeeInput): Promise<UpsertMembershipFeeOutput> {
@@ -153,7 +143,11 @@ export class PaymentsService {
// but we keep the check for consistency
throw new Error('Failed to upsert membership fee');
}
return this.upsertMembershipFeePresenter.viewModel;
const value = result.value;
if (!value) {
throw new Error('Failed to upsert membership fee: no value returned');
}
return value;
}
async updateMemberPayment(input: UpdateMemberPaymentInput): Promise<UpdateMemberPaymentOutput> {
@@ -163,7 +157,11 @@ export class PaymentsService {
if (result.isErr()) {
throw new Error(result.unwrapErr().code ?? 'Failed to update member payment');
}
return this.updateMemberPaymentPresenter.viewModel;
const value = result.value;
if (!value) {
throw new Error('Failed to update member payment: no value returned');
}
return value;
}
async getPrizes(query: GetPrizesQuery): Promise<GetPrizesOutput> {
@@ -175,42 +173,89 @@ export class PaymentsService {
if (query.seasonId !== undefined) {
input.seasonId = query.seasonId;
}
await this.getPrizesUseCase.execute(input);
return this.getPrizesPresenter.viewModel;
const result = await this.getPrizesUseCase.execute(input);
if (result.isErr()) {
throw new Error('Failed to get prizes');
}
const value = result.value;
if (!value) {
throw new Error('Failed to get prizes: no value returned');
}
return value;
}
async createPrize(input: CreatePrizeInput): Promise<CreatePrizeOutput> {
this.logger.debug('[PaymentsService] Creating prize', { input });
await this.createPrizeUseCase.execute(input);
return this.createPrizePresenter.viewModel;
const result = await this.createPrizeUseCase.execute(input);
if (result.isErr()) {
const err = result.unwrapErr();
throw new Error(err.code ?? 'Failed to create prize');
}
const value = result.value;
if (!value) {
throw new Error('Failed to create prize: no value returned');
}
return value;
}
async awardPrize(input: AwardPrizeInput): Promise<AwardPrizeOutput> {
this.logger.debug('[PaymentsService] Awarding prize', { input });
await this.awardPrizeUseCase.execute(input);
return this.awardPrizePresenter.viewModel;
const result = await this.awardPrizeUseCase.execute(input);
if (result.isErr()) {
const err = result.unwrapErr();
throw new Error(err.code ?? 'Failed to award prize');
}
const value = result.value;
if (!value) {
throw new Error('Failed to award prize: no value returned');
}
return value;
}
async deletePrize(input: DeletePrizeInput): Promise<DeletePrizeOutput> {
this.logger.debug('[PaymentsService] Deleting prize', { input });
await this.deletePrizeUseCase.execute(input);
return this.deletePrizePresenter.viewModel;
const result = await this.deletePrizeUseCase.execute(input);
if (result.isErr()) {
const err = result.unwrapErr();
throw new Error(err.code ?? 'Failed to delete prize');
}
const value = result.value;
if (!value) {
throw new Error('Failed to delete prize: no value returned');
}
return value;
}
async getWallet(query: GetWalletQuery): Promise<GetWalletOutput> {
this.logger.debug('[PaymentsService] Getting wallet', { query });
await this.getWalletUseCase.execute({ leagueId: query.leagueId! });
return this.getWalletPresenter.viewModel;
const result = await this.getWalletUseCase.execute({ leagueId: query.leagueId! });
if (result.isErr()) {
const err = result.unwrapErr();
throw new Error(err.code ?? 'Failed to get wallet');
}
const value = result.value;
if (!value) {
throw new Error('Failed to get wallet: no value returned');
}
return value;
}
async processWalletTransaction(input: ProcessWalletTransactionInput): Promise<ProcessWalletTransactionOutput> {
this.logger.debug('[PaymentsService] Processing wallet transaction', { input });
await this.processWalletTransactionUseCase.execute(input);
return this.processWalletTransactionPresenter.viewModel;
const result = await this.processWalletTransactionUseCase.execute(input);
if (result.isErr()) {
const err = result.unwrapErr();
throw new Error(err.code ?? 'Failed to process wallet transaction');
}
const value = result.value;
if (!value) {
throw new Error('Failed to process wallet transaction: no value returned');
}
return value;
}
}

View File

@@ -27,17 +27,4 @@ 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 GET_PAYMENTS_OUTPUT_PORT_TOKEN = 'GetPaymentsOutputPort_TOKEN';
export const CREATE_PAYMENT_OUTPUT_PORT_TOKEN = 'CreatePaymentOutputPort_TOKEN';
export const UPDATE_PAYMENT_STATUS_OUTPUT_PORT_TOKEN = 'UpdatePaymentStatusOutputPort_TOKEN';
export const GET_MEMBERSHIP_FEES_OUTPUT_PORT_TOKEN = 'GetMembershipFeesOutputPort_TOKEN';
export const UPSERT_MEMBERSHIP_FEE_OUTPUT_PORT_TOKEN = 'UpsertMembershipFeeOutputPort_TOKEN';
export const UPDATE_MEMBER_PAYMENT_OUTPUT_PORT_TOKEN = 'UpdateMemberPaymentOutputPort_TOKEN';
export const GET_PRIZES_OUTPUT_PORT_TOKEN = 'GetPrizesOutputPort_TOKEN';
export const CREATE_PRIZE_OUTPUT_PORT_TOKEN = 'CreatePrizeOutputPort_TOKEN';
export const AWARD_PRIZE_OUTPUT_PORT_TOKEN = 'AwardPrizeOutputPort_TOKEN';
export const DELETE_PRIZE_OUTPUT_PORT_TOKEN = 'DeletePrizeOutputPort_TOKEN';
export const GET_WALLET_OUTPUT_PORT_TOKEN = 'GetWalletOutputPort_TOKEN';
export const PROCESS_WALLET_TRANSACTION_OUTPUT_PORT_TOKEN = 'ProcessWalletTransactionOutputPort_TOKEN';
export const PROCESS_WALLET_TRANSACTION_USE_CASE_TOKEN = 'ProcessWalletTransactionUseCase';