infis on payment and subs

This commit is contained in:
Mamadou Khoussa [028918 DSI/DAC/DIF/DS] 2025-11-14 13:27:42 +00:00
parent d8ad43a56a
commit 6ea3ece796
11 changed files with 287 additions and 192 deletions

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "payments" ADD COLUMN "link" TEXT;

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "payments" ADD COLUMN "subscriptionId" INTEGER;

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "payments" ADD COLUMN "reference" TEXT;

View File

@ -93,6 +93,7 @@ model ReversementRequest {
model Payment { model Payment {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
externalReference String? externalReference String?
reference String?
type PaymentType type PaymentType
status TransactionStatus status TransactionStatus
merchantPartnerId Int merchantPartnerId Int
@ -103,7 +104,9 @@ model Payment {
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
customerId Int customerId Int
subscriptionId Int?
metadata Json? metadata Json?
link String?
reversementRequests ReversementRequest[] reversementRequests ReversementRequest[]

View File

@ -3,7 +3,7 @@ import { registerAs } from '@nestjs/config';
export default registerAs('operators', () => ({ export default registerAs('operators', () => ({
ORANGE_CIV: { ORANGE_CIV: {
name: 'Orange Côte d Ivoire', name: 'Orange Côte d Ivoire',
baseUrl: process.env.ORANGE_CIV_BASE_URL || 'https://api.bizao.com', baseUrl: process.env.ORANGE_CIV_BASE_URL || 'https://api.DCB-HUB.com',
authType: 'OTP', authType: 'OTP',
endpoints: { endpoints: {
auth: { auth: {
@ -20,7 +20,7 @@ export default registerAs('operators', () => ({
}, },
}, },
headers: { headers: {
'X-OAPI-Application-Id': 'BIZAO', 'X-OAPI-Application-Id': 'DCB-HUB',
'X-Orange-MCO': 'OCI', 'X-Orange-MCO': 'OCI',
}, },
transformers: { transformers: {
@ -30,7 +30,7 @@ export default registerAs('operators', () => ({
}, },
ORANGE_SEN: { ORANGE_SEN: {
name: 'Orange Sénégal', name: 'Orange Sénégal',
baseUrl: process.env.ORANGE_SEN_BASE_URL || 'https://api.bizao.com', baseUrl: process.env.ORANGE_SEN_BASE_URL || 'https://api.DCB-HUB.com',
authType: 'OTP', authType: 'OTP',
endpoints: { endpoints: {
auth: { auth: {
@ -47,7 +47,7 @@ export default registerAs('operators', () => ({
}, },
}, },
headers: { headers: {
'X-OAPI-Application-Id': 'BIZAO', 'X-OAPI-Application-Id': 'DCB-HUB',
'X-Orange-MCO': 'OSN', 'X-Orange-MCO': 'OSN',
}, },
transformers: { transformers: {

View File

@ -70,5 +70,6 @@ export interface ChargeResponse {
status: 'SUCCESS' | 'FAILED' | 'PENDING'; status: 'SUCCESS' | 'FAILED' | 'PENDING';
operatorReference: string; operatorReference: string;
amount: number; amount: number;
resourceURL: string;
currency: string; currency: string;
} }

View File

@ -57,7 +57,7 @@ export class OrangeAdapter implements IOperatorAdapter {
challenge: { challenge: {
method: 'OTP-SMS-AUTH', method: 'OTP-SMS-AUTH',
country: countryCode, country: countryCode,
service: 'BIZAO', service: 'DCB_HUB',
partnerId: 'PDKSUB', partnerId: 'PDKSUB',
inputs: [ inputs: [
{ {
@ -114,7 +114,7 @@ export class OrangeAdapter implements IOperatorAdapter {
challenge: { challenge: {
method: 'OTP-SMS-AUTH', method: 'OTP-SMS-AUTH',
country: params.country, country: params.country,
service: 'BIZAO', service: 'DCB_HUB',
partnerId: 'PDKSUB', partnerId: 'PDKSUB',
inputs: [ inputs: [
{ {
@ -178,7 +178,7 @@ export class OrangeAdapter implements IOperatorAdapter {
chargingMetaData: { chargingMetaData: {
onBehalfOf: 'PaymentHub', //from config todo onBehalfOf: 'PaymentHub', //from config todo
purchaseCategoryCode: 'Service', //todo from config purchaseCategoryCode: 'Service', //todo from config
serviceId: 'BIZAO', serviceId: 'DCB_HUB',
}, },
}, },
transactionOperationStatus: 'Charged', transactionOperationStatus: 'Charged',
@ -188,12 +188,17 @@ export class OrangeAdapter implements IOperatorAdapter {
}; };
const token = await this.getAccessToken(); const token = await this.getAccessToken();
this.logger.debug( this.logger.debug(
`[requesting to ]: ${this.config.baseUrl}/payment/v1/acr%3AOrangeAPIToken/transactions/amount`, `[requesting to ]: ${this.config.baseUrl}/payment/mea/v1/acr%3AX-Orange-ISE2/transactions/amount`,
);
this.logger.debug(
`[requesting token ]: ${token} `,
); );
const response = await firstValueFrom( const response = await firstValueFrom(
this.httpService.post( this.httpService.post(
`${this.config.baseUrl}}/payment/v1/acr%3AOrangeAPIToken/transactions/amount`, `${this.config.baseUrl}/payment/mea/v1/acr%3AX-Orange-ISE2/transactions/amount`,
hubRequest, hubRequest,
{ {
headers: { headers: {
@ -238,11 +243,11 @@ export class OrangeAdapter implements IOperatorAdapter {
{ {
headers: { headers: {
Authorization: `Bearer ${this.accessToken}`, Authorization: `Bearer ${this.accessToken}`,
'X-OAPI-Application-Id': 'BIZAO', 'X-OAPI-Application-Id': 'DCB_HUB',
'X-OAPI-Contact-Id': 'b2b-bizao-97b5878', 'X-OAPI-Contact-Id': 'b2b-DCB_HUB-97b5878',
'X-OAPI-Resource-Type': 'SMS_OSM', 'X-OAPI-Resource-Type': 'SMS_OSM',
'bizao-alias': params.userAlias, 'DCB_HUB-alias': params.userAlias,
'bizao-token': params.userToken, 'DCB_HUB-token': params.userToken,
'X-Orange-MCO': this.getMCO(params.country), 'X-Orange-MCO': this.getMCO(params.country),
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },

View File

@ -2,29 +2,30 @@ import { Injectable } from '@nestjs/common';
@Injectable() @Injectable()
export class OrangeTransformer { export class OrangeTransformer {
transformChargeResponse(bizaoResponse: any): any { transformChargeResponse(orangeResponse: any): any {
return { return {
paymentId: bizaoResponse.amountTransaction?.serverReferenceCode, paymentId: orangeResponse.amountTransaction?.serverReferenceCode,
status: this.mapStatus( status: this.mapStatus(
bizaoResponse.amountTransaction?.transactionOperationStatus, orangeResponse.amountTransaction?.transactionOperationStatus,
), ),
operatorReference: bizaoResponse.amountTransaction?.serverReferenceCode, operatorReference: orangeResponse.amountTransaction?.serverReferenceCode,
amount: parseFloat( amount: parseFloat(
bizaoResponse.amountTransaction?.paymentAmount?.totalAmountCharged, orangeResponse.amountTransaction?.paymentAmount?.totalAmountCharged,
), ),
resourceURL: orangeResponse.amountTransaction?.resourceURL,
currency: currency:
bizaoResponse.amountTransaction?.paymentAmount?.chargingInformation orangeResponse.amountTransaction?.paymentAmount?.chargingInformation
?.currency, ?.currency,
createdAt: new Date(), createdAt: new Date(),
}; };
} }
private mapStatus(bizaoStatus: string): string { private mapStatus(orangeStatus: string): string {
const statusMap = { const statusMap = {
Charged: 'SUCCESS', Charged: 'SUCCESS',
Failed: 'FAILED', Failed: 'FAILED',
Pending: 'PENDING', Pending: 'PENDING',
}; };
return statusMap[bizaoStatus] || 'PENDING'; return statusMap[orangeStatus] || 'PENDING';
} }
} }

View File

@ -6,6 +6,7 @@ import {
Min, Min,
IsEnum, IsEnum,
IsDateString, IsDateString,
isNumber,
} from 'class-validator'; } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
@ -35,7 +36,7 @@ export class ChargeDto {
@ApiProperty({ required: false, description: 'Subscription ID if recurring' }) @ApiProperty({ required: false, description: 'Subscription ID if recurring' })
@IsOptional() @IsOptional()
@IsString() @IsNumber()
subscriptionId?: number; subscriptionId?: number;
@ApiProperty({ required: false, description: 'Callback URL for notifications' }) @ApiProperty({ required: false, description: 'Callback URL for notifications' })
@ -64,7 +65,7 @@ export class RefundDto {
@ApiProperty({ required: false, description: 'Amount to refund (partial refund)' }) @ApiProperty({ required: false, description: 'Amount to refund (partial refund)' })
@IsOptional() @IsOptional()
@IsNumber() @IsNumber()
@Min(0) @Min(1)
amount?: number; amount?: number;
@ApiProperty({ description: 'Reason for refund' }) @ApiProperty({ description: 'Reason for refund' })
@ -89,8 +90,8 @@ export class PaymentQueryDto {
@ApiProperty({ required: false }) @ApiProperty({ required: false })
@IsOptional() @IsOptional()
@IsString() @IsNumber()
subscriptionId?: string; subscriptionId?: number;
@ApiProperty({ required: false }) @ApiProperty({ required: false })
@IsOptional() @IsOptional()

View File

@ -89,7 +89,7 @@ export class PaymentsController {
} }
@Get(':paymentId') @Get(':paymentId')
@UseGuards(JwtAuthGuard) //@UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOperation({ summary: 'Get payment details' }) @ApiOperation({ summary: 'Get payment details' })
@ApiResponse({ @ApiResponse({
@ -98,8 +98,9 @@ export class PaymentsController {
type: PaymentResponseDto, type: PaymentResponseDto,
}) })
@ApiResponse({ status: 404, description: 'Payment not found' }) @ApiResponse({ status: 404, description: 'Payment not found' })
async getPayment(@Request() req, @Param('paymentId') paymentId: string) { async getPayment(@Request() req, @Param('paymentId') paymentId: number) {
return this.paymentsService.getPayment(paymentId, req.user.partnerId); console.log('Fetching payment with ID:', paymentId);
return this.paymentsService.getPayment(paymentId);
} }
@ -118,11 +119,32 @@ export class PaymentsController {
@Param('reference') reference: string, @Param('reference') reference: string,
) { ) {
return this.paymentsService.getPaymentByReference( return this.paymentsService.getPaymentByReference(
reference, reference
req.user.partnerId,
); );
} }
@Get('/')
@ApiOperation({ summary: 'Get payments list' })
async getAll(@Request() req) {
return this.paymentsService.findAll();
}
@Get('merchant/:merchantId')
@ApiOperation({ summary: 'Get payments list by merchant' })
async getAllByPaymentByMerchant(@Request() req, @Param('merchantId', ParseIntPipe) merchantId: number) {
return this.paymentsService.findAllByMerchant(merchantId);
}
@Get('merchant/:merchantId/subscription/:subscriptionId')
@ApiOperation({ summary: 'Get payments list by merchant' })
async getAllBySubscription(@Request() req,
@Param('merchantId', ParseIntPipe) merchantId: number,
@Param('subscriptionId', ParseIntPipe) subscriptionId: number) {
return this.paymentsService.findAllByMerchantSubscription(merchantId,subscriptionId);
}
@Post(':paymentId/retry') @Post(':paymentId/retry')
// @UseGuards(JwtAuthGuard) // @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()

View File

@ -2,29 +2,78 @@ import { Injectable, BadRequestException, Logger } from '@nestjs/common';
import { OperatorsService } from '../operators/operators.service'; import { OperatorsService } from '../operators/operators.service';
import { PrismaService } from '../../shared/services/prisma.service'; import { PrismaService } from '../../shared/services/prisma.service';
import { EventEmitter2 } from '@nestjs/event-emitter'; import { EventEmitter2 } from '@nestjs/event-emitter';
import { ChargeDto } from './dto/payment.dto'; import { ChargeDto } from './dto/payment.dto';
import { RefundDto } from './dto/payment.dto'; import { RefundDto } from './dto/payment.dto';
import { PaymentType, TransactionStatus } from 'generated/prisma'; import { PaymentType, TransactionStatus } from 'generated/prisma';
@Injectable() @Injectable()
export class PaymentsService { export class PaymentsService {
private readonly logger = new Logger(PaymentsService.name); private readonly logger = new Logger(PaymentsService.name);
handleWebhook(arg0: { partnerId: any; event: any; payload: any; signature: any; }) { handleWebhook(arg0: {
throw new Error('Method not implemented.'); partnerId: any;
event: any;
payload: any;
signature: any;
}) {
throw new Error('Method not implemented.');
}
async getPaymentByReference(reference: string) {
const plan = await this.prisma.payment.findFirst({
where: { reference: reference },
});
return plan
}
async getPayment(id: number) {
const data = await this.prisma.payment.findUnique({
where: { id },
});
return data;
}
async findAllByMerchant(merchantId: number): Promise<any[]> {
// Check if merchant exists
return this.prisma.payment.findMany({
where: { merchantPartnerId: merchantId },
orderBy: {
createdAt: 'desc',
},
});
} }
getPaymentByReference(reference: string, partnerId: any) {
throw new Error('Method not implemented.'); async findAllByMerchantSubscription(merchantId: number, subscriptionId: number): Promise<any[]> {
// Check if merchant exists
return this.prisma.payment.findMany({
where: { merchantPartnerId: merchantId, subscriptionId: subscriptionId },
orderBy: {
createdAt: 'desc',
},
});
} }
getPayment(paymentId: string, partnerId: any) {
throw new Error('Method not implemented.'); async findAll(): Promise<any[]> {
} // Check if merchant exists
refundPayment(paymentId: string, partnerId: any, refundDto: RefundDto) { return this.prisma.payment.findMany({
throw new Error('Method not implemented.'); // where: { merchantPartnerId: merchantId },
orderBy: {
createdAt: 'desc',
},
});
} }
refundPayment(paymentId: string, partnerId: any, refundDto: RefundDto) {
throw new Error('Method not implemented.');
}
retryPayment(paymentId: any, attempt: any) { retryPayment(paymentId: any, attempt: any) {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
processPayment(paymentId: any) :any{ processPayment(paymentId: any): any {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
constructor( constructor(
@ -47,13 +96,14 @@ export class PaymentsService {
// Créer la transaction dans la base // Créer la transaction dans la base
const payment = await this.prisma.payment.create({ const payment = await this.prisma.payment.create({
data: { data: {
merchantPartnerId:chargeDto.partnerId , // À remplacer par le bon partnerId subscriptionId: chargeDto.subscriptionId,
merchantPartnerId: chargeDto.partnerId, // À remplacer par le bon partnerId
customerId: 1, // todo À remplacer par user.id customerId: 1, // todo À remplacer par user.id
amount: chargeDto.amount, amount: chargeDto.amount,
currency: chargeDto.currency, currency: chargeDto.currency,
type: PaymentType.MM, type: PaymentType.MM,
reference: chargeDto.reference || this.generateReference(),
//description: chargeDto.description, //description: chargeDto.description,
//reference: chargeDto.reference || this.generateReference(), //reference: chargeDto.reference || this.generateReference(),
status: TransactionStatus.PENDING, status: TransactionStatus.PENDING,
@ -64,28 +114,31 @@ export class PaymentsService {
try { try {
// Router vers le bon opérateur // Router vers le bon opérateur
this.logger.debug( this.logger.debug(
`[getting adaptator for ]: ${chargeDto.operator}_${chargeDto.country} `) `[getting adaptator for ]: ${chargeDto.operator}_${chargeDto.country} `,
);
const adapter = this.operatorsService.getAdapter( const adapter = this.operatorsService.getAdapter(
chargeDto.operator, chargeDto.operator,
chargeDto.country, chargeDto.country,
); );
this.logger.debug(`Processing payment ${payment.id} through operator adapter ${adapter.constructor.name}`); this.logger.debug(
`Processing payment ${payment.id} through operator adapter ${adapter.constructor.name}`,
);
const chargeParams = { const chargeParams = {
userToken: chargeDto.userToken, userToken: chargeDto.userToken,
userAlias: chargeDto.userToken,//todo make alias in contrat userAlias: chargeDto.userToken, //todo make alias in contrat
amount: chargeDto.amount, amount: chargeDto.amount,
currency: chargeDto.currency, currency: chargeDto.currency,
description: chargeDto.description, description: chargeDto.description,
subscriptionId: chargeDto.subscriptionId, subscriptionId: chargeDto.subscriptionId,
reference: chargeDto.reference +'',//todo make reference in contrat reference: chargeDto.reference + '', //todo make reference in contrat
}; };
const result = await adapter.charge(chargeParams); const result = await adapter.charge(chargeParams);
this.logger.debug(`result frm adaptaor ${result} for payment ${payment.id}`); this.logger.debug(
`result frm adaptaor ${result} for payment ${payment.id}`,
);
// Mettre à jour le paiement // Mettre à jour le paiement
const updatedPayment = await this.prisma.payment.update({ const updatedPayment = await this.prisma.payment.update({
@ -95,7 +148,8 @@ export class PaymentsService {
result.status === 'SUCCESS' result.status === 'SUCCESS'
? TransactionStatus.SUCCESS ? TransactionStatus.SUCCESS
: TransactionStatus.FAILED, : TransactionStatus.FAILED,
//operatorReference: result.operatorReference, externalReference: result.operatorReference,
link: result.resourceURL,
completedAt: new Date(), completedAt: new Date(),
}, },
}); });
@ -113,8 +167,12 @@ export class PaymentsService {
return updatedPayment; return updatedPayment;
} catch (error) { } catch (error) {
this.logger.debug(
`error ${error.message} processing payment ${payment.id}`,
);
// En cas d'erreur, marquer comme échoué // En cas d'erreur, marquer comme échoué
const resultFinal= await this.prisma.payment.update({ const resultFinal = await this.prisma.payment.update({
where: { id: payment.id }, where: { id: payment.id },
data: { data: {
status: TransactionStatus.FAILED, status: TransactionStatus.FAILED,
@ -137,150 +195,148 @@ export class PaymentsService {
// Ajouter ces méthodes dans PaymentsService // Ajouter ces méthodes dans PaymentsService
async listPayments(filters: any) { async listPayments(filters: any) {
const where: any = { const where: any = {
partnerId: filters.partnerId, partnerId: filters.partnerId,
};
if (filters.status) {
where.status = filters.status;
}
if (filters.userId) {
where.userId = filters.userId;
}
if (filters.subscriptionId) {
where.subscriptionId = filters.subscriptionId;
}
if (filters.startDate || filters.endDate) {
where.createdAt = {};
if (filters.startDate) {
where.createdAt.gte = new Date(filters.startDate);
}
if (filters.endDate) {
where.createdAt.lte = new Date(filters.endDate);
}
}
const page = filters.page || 1;
const limit = filters.limit || 20;
const skip = (page - 1) * limit;
const [payments, total] = await Promise.all([
this.prisma.payment.findMany({
where,
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
this.prisma.payment.count({ where }),
]);
return {
data: payments,
meta: {
total,
page,
limit,
totalPages: Math.ceil(total / limit),
},
};
}
async getStatistics(params: {
partnerId: string;
period: string;
startDate?: Date;
endDate?: Date;
}) {
const { partnerId, period, startDate, endDate } = params;
const where: any = { partnerId };
if (startDate || endDate) {
where.createdAt = {};
if (startDate) where.createdAt.gte = startDate;
if (endDate) where.createdAt.lte = endDate;
}
const [
totalPayments,
successfulPayments,
failedPayments,
totalRevenue,
avgPaymentAmount,
] = await Promise.all([
this.prisma.payment.count({ where }),
this.prisma.payment.count({ where: { ...where, status: 'SUCCESS' } }),
this.prisma.payment.count({ where: { ...where, status: 'FAILED' } }),
this.prisma.payment.aggregate({
where: { ...where, status: 'SUCCESS' },
_sum: { amount: true },
}),
this.prisma.payment.aggregate({
where: { ...where, status: 'SUCCESS' },
_avg: { amount: true },
}),
]);
const successRate = totalPayments > 0
? (successfulPayments / totalPayments) * 100
: 0;
return {
totalPayments,
successfulPayments,
failedPayments,
successRate: Math.round(successRate * 100) / 100,
totalRevenue: totalRevenue._sum.amount || 0,
avgPaymentAmount: avgPaymentAmount._avg.amount || 0,
period,
startDate,
endDate,
};
}
async validatePayment(params: any) {
// Valider le user token
const user = await this.prisma.user.findUnique({
where: { userToken: params.userToken },
});
if (!user) {
return {
valid: false,
error: 'Invalid user token',
}; };
}
// Vérifier les limites if (filters.status) {
const todayPayments = await this.prisma.payment.count({ where.status = filters.status;
where: { }
customerId: 1, // todo À remplacer par user.id
status: 'SUCCESS', if (filters.userId) {
createdAt: { where.userId = filters.userId;
gte: new Date(new Date().setHours(0, 0, 0, 0)), }
if (filters.subscriptionId) {
where.subscriptionId = filters.subscriptionId;
}
if (filters.startDate || filters.endDate) {
where.createdAt = {};
if (filters.startDate) {
where.createdAt.gte = new Date(filters.startDate);
}
if (filters.endDate) {
where.createdAt.lte = new Date(filters.endDate);
}
}
const page = filters.page || 1;
const limit = filters.limit || 20;
const skip = (page - 1) * limit;
const [payments, total] = await Promise.all([
this.prisma.payment.findMany({
where,
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
this.prisma.payment.count({ where }),
]);
return {
data: payments,
meta: {
total,
page,
limit,
totalPages: Math.ceil(total / limit),
}, },
},
});
if (todayPayments >= 10) {
return {
valid: false,
error: 'Daily payment limit reached',
}; };
} }
return { async getStatistics(params: {
valid: true, partnerId: string;
user: { period: string;
id: user.id, startDate?: Date;
msisdn: user.msisdn, endDate?: Date;
country: user.country, }) {
}, const { partnerId, period, startDate, endDate } = params;
};
} const where: any = { partnerId };
if (startDate || endDate) {
where.createdAt = {};
if (startDate) where.createdAt.gte = startDate;
if (endDate) where.createdAt.lte = endDate;
}
const [
totalPayments,
successfulPayments,
failedPayments,
totalRevenue,
avgPaymentAmount,
] = await Promise.all([
this.prisma.payment.count({ where }),
this.prisma.payment.count({ where: { ...where, status: 'SUCCESS' } }),
this.prisma.payment.count({ where: { ...where, status: 'FAILED' } }),
this.prisma.payment.aggregate({
where: { ...where, status: 'SUCCESS' },
_sum: { amount: true },
}),
this.prisma.payment.aggregate({
where: { ...where, status: 'SUCCESS' },
_avg: { amount: true },
}),
]);
const successRate =
totalPayments > 0 ? (successfulPayments / totalPayments) * 100 : 0;
return {
totalPayments,
successfulPayments,
failedPayments,
successRate: Math.round(successRate * 100) / 100,
totalRevenue: totalRevenue._sum.amount || 0,
avgPaymentAmount: avgPaymentAmount._avg.amount || 0,
period,
startDate,
endDate,
};
}
async validatePayment(params: any) {
// Valider le user token
const user = await this.prisma.user.findUnique({
where: { userToken: params.userToken },
});
if (!user) {
return {
valid: false,
error: 'Invalid user token',
};
}
// Vérifier les limites
const todayPayments = await this.prisma.payment.count({
where: {
customerId: 1, // todo À remplacer par user.id
status: 'SUCCESS',
createdAt: {
gte: new Date(new Date().setHours(0, 0, 0, 0)),
},
},
});
if (todayPayments >= 10) {
return {
valid: false,
error: 'Daily payment limit reached',
};
}
return {
valid: true,
user: {
id: user.id,
msisdn: user.msisdn,
country: user.country,
},
};
}
} }