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 {
|
||||
id Int @id @default(autoincrement())
|
||||
externalReference String?
|
||||
reference String?
|
||||
type PaymentType
|
||||
status TransactionStatus
|
||||
merchantPartnerId Int
|
||||
@ -103,7 +104,9 @@ model Payment {
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
customerId Int
|
||||
subscriptionId Int?
|
||||
metadata Json?
|
||||
link String?
|
||||
|
||||
reversementRequests ReversementRequest[]
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import { registerAs } from '@nestjs/config';
|
||||
export default registerAs('operators', () => ({
|
||||
ORANGE_CIV: {
|
||||
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',
|
||||
endpoints: {
|
||||
auth: {
|
||||
@ -20,7 +20,7 @@ export default registerAs('operators', () => ({
|
||||
},
|
||||
},
|
||||
headers: {
|
||||
'X-OAPI-Application-Id': 'BIZAO',
|
||||
'X-OAPI-Application-Id': 'DCB-HUB',
|
||||
'X-Orange-MCO': 'OCI',
|
||||
},
|
||||
transformers: {
|
||||
@ -30,7 +30,7 @@ export default registerAs('operators', () => ({
|
||||
},
|
||||
ORANGE_SEN: {
|
||||
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',
|
||||
endpoints: {
|
||||
auth: {
|
||||
@ -47,7 +47,7 @@ export default registerAs('operators', () => ({
|
||||
},
|
||||
},
|
||||
headers: {
|
||||
'X-OAPI-Application-Id': 'BIZAO',
|
||||
'X-OAPI-Application-Id': 'DCB-HUB',
|
||||
'X-Orange-MCO': 'OSN',
|
||||
},
|
||||
transformers: {
|
||||
|
||||
@ -70,5 +70,6 @@ export interface ChargeResponse {
|
||||
status: 'SUCCESS' | 'FAILED' | 'PENDING';
|
||||
operatorReference: string;
|
||||
amount: number;
|
||||
resourceURL: string;
|
||||
currency: string;
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ export class OrangeAdapter implements IOperatorAdapter {
|
||||
challenge: {
|
||||
method: 'OTP-SMS-AUTH',
|
||||
country: countryCode,
|
||||
service: 'BIZAO',
|
||||
service: 'DCB_HUB',
|
||||
partnerId: 'PDKSUB',
|
||||
inputs: [
|
||||
{
|
||||
@ -114,7 +114,7 @@ export class OrangeAdapter implements IOperatorAdapter {
|
||||
challenge: {
|
||||
method: 'OTP-SMS-AUTH',
|
||||
country: params.country,
|
||||
service: 'BIZAO',
|
||||
service: 'DCB_HUB',
|
||||
partnerId: 'PDKSUB',
|
||||
inputs: [
|
||||
{
|
||||
@ -178,7 +178,7 @@ export class OrangeAdapter implements IOperatorAdapter {
|
||||
chargingMetaData: {
|
||||
onBehalfOf: 'PaymentHub', //from config todo
|
||||
purchaseCategoryCode: 'Service', //todo from config
|
||||
serviceId: 'BIZAO',
|
||||
serviceId: 'DCB_HUB',
|
||||
},
|
||||
},
|
||||
transactionOperationStatus: 'Charged',
|
||||
@ -188,12 +188,17 @@ export class OrangeAdapter implements IOperatorAdapter {
|
||||
};
|
||||
const token = await this.getAccessToken();
|
||||
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(
|
||||
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,
|
||||
{
|
||||
headers: {
|
||||
@ -238,11 +243,11 @@ export class OrangeAdapter implements IOperatorAdapter {
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.accessToken}`,
|
||||
'X-OAPI-Application-Id': 'BIZAO',
|
||||
'X-OAPI-Contact-Id': 'b2b-bizao-97b5878',
|
||||
'X-OAPI-Application-Id': 'DCB_HUB',
|
||||
'X-OAPI-Contact-Id': 'b2b-DCB_HUB-97b5878',
|
||||
'X-OAPI-Resource-Type': 'SMS_OSM',
|
||||
'bizao-alias': params.userAlias,
|
||||
'bizao-token': params.userToken,
|
||||
'DCB_HUB-alias': params.userAlias,
|
||||
'DCB_HUB-token': params.userToken,
|
||||
'X-Orange-MCO': this.getMCO(params.country),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
|
||||
@ -2,29 +2,30 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class OrangeTransformer {
|
||||
transformChargeResponse(bizaoResponse: any): any {
|
||||
transformChargeResponse(orangeResponse: any): any {
|
||||
return {
|
||||
paymentId: bizaoResponse.amountTransaction?.serverReferenceCode,
|
||||
paymentId: orangeResponse.amountTransaction?.serverReferenceCode,
|
||||
status: this.mapStatus(
|
||||
bizaoResponse.amountTransaction?.transactionOperationStatus,
|
||||
orangeResponse.amountTransaction?.transactionOperationStatus,
|
||||
),
|
||||
operatorReference: bizaoResponse.amountTransaction?.serverReferenceCode,
|
||||
operatorReference: orangeResponse.amountTransaction?.serverReferenceCode,
|
||||
amount: parseFloat(
|
||||
bizaoResponse.amountTransaction?.paymentAmount?.totalAmountCharged,
|
||||
orangeResponse.amountTransaction?.paymentAmount?.totalAmountCharged,
|
||||
),
|
||||
resourceURL: orangeResponse.amountTransaction?.resourceURL,
|
||||
currency:
|
||||
bizaoResponse.amountTransaction?.paymentAmount?.chargingInformation
|
||||
orangeResponse.amountTransaction?.paymentAmount?.chargingInformation
|
||||
?.currency,
|
||||
createdAt: new Date(),
|
||||
};
|
||||
}
|
||||
|
||||
private mapStatus(bizaoStatus: string): string {
|
||||
private mapStatus(orangeStatus: string): string {
|
||||
const statusMap = {
|
||||
Charged: 'SUCCESS',
|
||||
Failed: 'FAILED',
|
||||
Pending: 'PENDING',
|
||||
};
|
||||
return statusMap[bizaoStatus] || 'PENDING';
|
||||
return statusMap[orangeStatus] || 'PENDING';
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
Min,
|
||||
IsEnum,
|
||||
IsDateString,
|
||||
isNumber,
|
||||
} from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
@ -35,7 +36,7 @@ export class ChargeDto {
|
||||
|
||||
@ApiProperty({ required: false, description: 'Subscription ID if recurring' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@IsNumber()
|
||||
subscriptionId?: number;
|
||||
|
||||
@ApiProperty({ required: false, description: 'Callback URL for notifications' })
|
||||
@ -64,7 +65,7 @@ export class RefundDto {
|
||||
@ApiProperty({ required: false, description: 'Amount to refund (partial refund)' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@Min(1)
|
||||
amount?: number;
|
||||
|
||||
@ApiProperty({ description: 'Reason for refund' })
|
||||
@ -89,8 +90,8 @@ export class PaymentQueryDto {
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
subscriptionId?: string;
|
||||
@IsNumber()
|
||||
subscriptionId?: number;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsOptional()
|
||||
|
||||
@ -89,7 +89,7 @@ export class PaymentsController {
|
||||
}
|
||||
|
||||
@Get(':paymentId')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
//@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Get payment details' })
|
||||
@ApiResponse({
|
||||
@ -98,8 +98,9 @@ export class PaymentsController {
|
||||
type: PaymentResponseDto,
|
||||
})
|
||||
@ApiResponse({ status: 404, description: 'Payment not found' })
|
||||
async getPayment(@Request() req, @Param('paymentId') paymentId: string) {
|
||||
return this.paymentsService.getPayment(paymentId, req.user.partnerId);
|
||||
async getPayment(@Request() req, @Param('paymentId') paymentId: number) {
|
||||
console.log('Fetching payment with ID:', paymentId);
|
||||
return this.paymentsService.getPayment(paymentId);
|
||||
}
|
||||
|
||||
|
||||
@ -118,11 +119,32 @@ export class PaymentsController {
|
||||
@Param('reference') reference: string,
|
||||
) {
|
||||
return this.paymentsService.getPaymentByReference(
|
||||
reference,
|
||||
req.user.partnerId,
|
||||
reference
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@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')
|
||||
// @UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
|
||||
@ -9,22 +9,71 @@ import { PaymentType, TransactionStatus } from 'generated/prisma';
|
||||
@Injectable()
|
||||
export class PaymentsService {
|
||||
private readonly logger = new Logger(PaymentsService.name);
|
||||
handleWebhook(arg0: { partnerId: any; event: any; payload: any; signature: any; }) {
|
||||
handleWebhook(arg0: {
|
||||
partnerId: any;
|
||||
event: any;
|
||||
payload: any;
|
||||
signature: any;
|
||||
}) {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getPaymentByReference(reference: string, partnerId: any) {
|
||||
throw new Error('Method not implemented.');
|
||||
async getPaymentByReference(reference: string) {
|
||||
const plan = await this.prisma.payment.findFirst({
|
||||
where: { reference: reference },
|
||||
});
|
||||
return plan
|
||||
}
|
||||
getPayment(paymentId: string, partnerId: any) {
|
||||
throw new Error('Method not implemented.');
|
||||
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{
|
||||
processPayment(paymentId: any): any {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
constructor(
|
||||
@ -47,13 +96,14 @@ export class PaymentsService {
|
||||
|
||||
// Créer la transaction dans la base
|
||||
const payment = await this.prisma.payment.create({
|
||||
|
||||
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
|
||||
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,
|
||||
@ -64,28 +114,31 @@ export class PaymentsService {
|
||||
try {
|
||||
// Router vers le bon opérateur
|
||||
this.logger.debug(
|
||||
`[getting adaptator for ]: ${chargeDto.operator}_${chargeDto.country} `)
|
||||
`[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}`);
|
||||
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
|
||||
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
|
||||
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}`);
|
||||
|
||||
|
||||
this.logger.debug(
|
||||
`result frm adaptaor ${result} for payment ${payment.id}`,
|
||||
);
|
||||
|
||||
// Mettre à jour le paiement
|
||||
const updatedPayment = await this.prisma.payment.update({
|
||||
@ -95,7 +148,8 @@ export class PaymentsService {
|
||||
result.status === 'SUCCESS'
|
||||
? TransactionStatus.SUCCESS
|
||||
: TransactionStatus.FAILED,
|
||||
//operatorReference: result.operatorReference,
|
||||
externalReference: result.operatorReference,
|
||||
link: result.resourceURL,
|
||||
completedAt: new Date(),
|
||||
},
|
||||
});
|
||||
@ -113,8 +167,12 @@ export class PaymentsService {
|
||||
|
||||
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({
|
||||
const resultFinal = await this.prisma.payment.update({
|
||||
where: { id: payment.id },
|
||||
data: {
|
||||
status: TransactionStatus.FAILED,
|
||||
@ -137,7 +195,7 @@ export class PaymentsService {
|
||||
|
||||
// Ajouter ces méthodes dans PaymentsService
|
||||
|
||||
async listPayments(filters: any) {
|
||||
async listPayments(filters: any) {
|
||||
const where: any = {
|
||||
partnerId: filters.partnerId,
|
||||
};
|
||||
@ -174,7 +232,6 @@ async listPayments(filters: any) {
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
|
||||
}),
|
||||
this.prisma.payment.count({ where }),
|
||||
]);
|
||||
@ -188,14 +245,14 @@ async listPayments(filters: any) {
|
||||
totalPages: Math.ceil(total / limit),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async getStatistics(params: {
|
||||
async getStatistics(params: {
|
||||
partnerId: string;
|
||||
period: string;
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
}) {
|
||||
}) {
|
||||
const { partnerId, period, startDate, endDate } = params;
|
||||
|
||||
const where: any = { partnerId };
|
||||
@ -226,9 +283,8 @@ async getStatistics(params: {
|
||||
}),
|
||||
]);
|
||||
|
||||
const successRate = totalPayments > 0
|
||||
? (successfulPayments / totalPayments) * 100
|
||||
: 0;
|
||||
const successRate =
|
||||
totalPayments > 0 ? (successfulPayments / totalPayments) * 100 : 0;
|
||||
|
||||
return {
|
||||
totalPayments,
|
||||
@ -241,9 +297,9 @@ async getStatistics(params: {
|
||||
startDate,
|
||||
endDate,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async validatePayment(params: any) {
|
||||
async validatePayment(params: any) {
|
||||
// Valider le user token
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { userToken: params.userToken },
|
||||
@ -282,5 +338,5 @@ async validatePayment(params: any) {
|
||||
country: user.country,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user