dcb-service-core-api/src/modules/payments/payments.service.ts
Mamadou Khoussa [028918 DSI/DAC/DIF/DS] 0af15e26fc fix subscription from orange
2025-11-14 16:50:42 +00:00

347 lines
9.2 KiB
TypeScript

import { Injectable, BadRequestException, Logger } from '@nestjs/common';
import { OperatorsService } from '../operators/operators.service';
import { PrismaService } from '../../shared/services/prisma.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { ChargeDto } from './dto/payment.dto';
import { RefundDto } from './dto/payment.dto';
import { PaymentType, TransactionStatus } from 'generated/prisma';
@Injectable()
export class PaymentsService {
private readonly logger = new Logger(PaymentsService.name);
constructor(
private readonly operatorsService: OperatorsService,
private readonly prisma: PrismaService,
private readonly eventEmitter: EventEmitter2,
) {}
handleWebhook(arg0: {
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',
},
});
}
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',
},
});
}
async findAll(): Promise<any[]> {
// Check if merchant exists
return this.prisma.payment.findMany({
// where: { merchantPartnerId: merchantId },
orderBy: {
createdAt: 'desc',
},
});
}
refundPayment(paymentId: string, partnerId: any, refundDto: RefundDto) {
throw new Error('Method not implemented.');
}
retryPayment(paymentId: any, attempt: any) {
throw new Error('Method not implemented.');
}
processPayment(paymentId: any): any {
throw new Error('Method not implemented.');
}
async createCharge(chargeDto: ChargeDto) {
/* Récupérer les informations de l'utilisateur
const user = await this.prisma.user.findUnique({
where: { userToken: chargeDto.userToken },
// include: { operator: true },
});
if (!user) {
throw new BadRequestException('Invalid user token');
}
*/
// Créer la transaction dans la base
const payment = await this.prisma.payment.create({
data: {
subscriptionId: chargeDto.subscriptionId,
merchantPartnerId: chargeDto.partnerId, // À remplacer par le bon partnerId
customerId: 1, // todo À remplacer par user.id
amount: chargeDto.amount,
currency: chargeDto.currency,
type: PaymentType.MM,
reference: chargeDto.reference || this.generateReference(),
//description: chargeDto.description,
//reference: chargeDto.reference || this.generateReference(),
status: TransactionStatus.PENDING,
metadata: chargeDto.metadata,
},
});
try {
// Router vers le bon opérateur
this.logger.debug(
`[getting adaptator for ]: ${chargeDto.operator}_${chargeDto.country} `,
);
const adapter = this.operatorsService.getAdapter(
chargeDto.operator,
chargeDto.country,
);
this.logger.debug(
`Processing payment ${payment.id} through operator adapter ${adapter.constructor.name}`,
);
const chargeParams = {
userToken: chargeDto.userToken,
userAlias: chargeDto.userToken, //todo make alias in contrat
amount: chargeDto.amount,
currency: chargeDto.currency,
description: chargeDto.description,
subscriptionId: chargeDto.subscriptionId,
reference: chargeDto.reference + '', //todo make reference in contrat
};
const result = await adapter.charge(chargeParams);
this.logger.debug(
`result frm adaptaor ${result} for payment ${payment.id}`,
);
// Mettre à jour le paiement
const updatedPayment = await this.prisma.payment.update({
where: { id: payment.id },
data: {
status:
result.status === 'SUCCESS'
? TransactionStatus.SUCCESS
: TransactionStatus.FAILED,
externalReference: result.operatorReference,
link: result.resourceURL,
completedAt: new Date(),
},
});
// Émettre un événement
this.eventEmitter.emit('payment.completed', {
payment: updatedPayment,
operator: 'user.operator.code',
});
// Appeler le callback du partenaire si fourni
if (chargeDto.callbackUrl) {
await this.notifyPartner(chargeDto.callbackUrl, updatedPayment);
}
return updatedPayment;
} catch (error) {
this.logger.debug(
`error ${error.message} processing payment ${payment.id}`,
);
// En cas d'erreur, marquer comme échoué
const resultFinal = await this.prisma.payment.update({
where: { id: payment.id },
data: {
status: TransactionStatus.FAILED,
failureReason: error.message,
},
});
return { ...resultFinal };
}
}
private generateReference(): string {
return `PAY-${Date.now()}-${Math.random().toString(36).substring(7)}`;
}
private async notifyPartner(callbackUrl: string, payment: any) {
// Implémenter la notification webhook
// Utiliser Bull Queue pour gérer les retries
}
// Ajouter ces méthodes dans PaymentsService
async listPayments(filters: any) {
const where: any = {
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
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,
},
};
}
}