infis on payment and subs
This commit is contained in:
parent
d8ad43a56a
commit
6ea3ece796
2
prisma/migrations/20251114124248_init/migration.sql
Normal file
2
prisma/migrations/20251114124248_init/migration.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "payments" ADD COLUMN "link" TEXT;
|
||||||
2
prisma/migrations/20251114125457_init/migration.sql
Normal file
2
prisma/migrations/20251114125457_init/migration.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "payments" ADD COLUMN "subscriptionId" INTEGER;
|
||||||
2
prisma/migrations/20251114130651_init/migration.sql
Normal file
2
prisma/migrations/20251114130651_init/migration.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "payments" ADD COLUMN "reference" TEXT;
|
||||||
@ -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[]
|
||||||
|
|
||||||
|
|||||||
@ -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: {
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user