payment object

This commit is contained in:
Mamadou Khoussa [028918 DSI/DAC/DIF/DS] 2025-11-14 12:09:56 +00:00
parent 767201ec06
commit d8ad43a56a
11 changed files with 283 additions and 59 deletions

View File

@ -1,5 +1,6 @@
import { Injectable } from '@nestjs/common'; import { Inject, Injectable, Logger } from '@nestjs/common';
import { HttpService } from '@nestjs/axios'; import { HttpService } from '@nestjs/axios';
import axios, { AxiosInstance, AxiosError } from 'axios';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { import {
@ -10,27 +11,49 @@ import {
ChargeResponse, ChargeResponse,
} from './operator.adapter.interface'; } from './operator.adapter.interface';
import { OrangeTransformer } from '../transformers/orange.transformer'; import { OrangeTransformer } from '../transformers/orange.transformer';
import {
DEFAULT_ORANGE_CONFIG,
COUNTRY_CODE_MAPPING,
} from './orange.config';
import type { OrangeConfig } from './orange.config';
@Injectable() @Injectable()
export class OrangeAdapter implements IOperatorAdapter { export class OrangeAdapter implements IOperatorAdapter {
private readonly logger = new Logger(OrangeAdapter.name);
private config: OrangeConfig;
private baseUrl: string; private baseUrl: string;
private accessToken: string; private accessToken: string;
private transformer: OrangeTransformer; private transformer: OrangeTransformer;
private tokenExpiresAt: number = 0;
private axiosInstance: AxiosInstance;
constructor( constructor(
private readonly httpService: HttpService, private readonly httpService: HttpService,
private readonly configService: ConfigService, @Inject('ORANGE_CONFIG')config: OrangeConfig,
) { ) {
this.baseUrl = this.configService.get('ORANGE_API_URL') as string; this.config = { ...DEFAULT_ORANGE_CONFIG, ...config } as OrangeConfig;
this.accessToken = this.configService.get('ORANGE_ACCESS_TOKEN') as string; this.axiosInstance = axios.create({
baseURL: this.config.baseUrl,
timeout: this.config.timeout,
headers: {
'Content-Type': 'application/json',
Accept: '*/*',
},
});
this.axiosInstance.interceptors.response.use(
response => response,
error => this.handleError(error)
);
this.transformer = new OrangeTransformer(); this.transformer = new OrangeTransformer();
} }
async initializeAuth(params: AuthInitParams): Promise<AuthInitResponse> { async initializeAuth(params: AuthInitParams): Promise<AuthInitResponse> {
const countryCode = this.getCountryCode(params.country); const countryCode = this.getCountryCode(params.country);
const bizaoRequest = { const hubRequest = {
challenge: { challenge: {
method: 'OTP-SMS-AUTH', method: 'OTP-SMS-AUTH',
country: countryCode, country: countryCode,
@ -64,7 +87,7 @@ export class OrangeAdapter implements IOperatorAdapter {
const response = await firstValueFrom( const response = await firstValueFrom(
this.httpService.post( this.httpService.post(
`${this.baseUrl}/challenge/v1/challenges`, `${this.baseUrl}/challenge/v1/challenges`,
bizaoRequest, hubRequest,
{ {
headers: { headers: {
Authorization: `Bearer ${this.accessToken}`, Authorization: `Bearer ${this.accessToken}`,
@ -87,7 +110,7 @@ export class OrangeAdapter implements IOperatorAdapter {
} }
async validateAuth(params: any): Promise<any> { async validateAuth(params: any): Promise<any> {
const bizaoRequest = { const hubRequest = {
challenge: { challenge: {
method: 'OTP-SMS-AUTH', method: 'OTP-SMS-AUTH',
country: params.country, country: params.country,
@ -113,7 +136,7 @@ export class OrangeAdapter implements IOperatorAdapter {
const response = await firstValueFrom( const response = await firstValueFrom(
this.httpService.post( this.httpService.post(
`${this.baseUrl}/challenge/v1/challenges/${params.challengeId}`, `${this.baseUrl}/challenge/v1/challenges/${params.challengeId}`,
bizaoRequest, hubRequest,
{ {
headers: { headers: {
Authorization: `Bearer ${this.accessToken}`, Authorization: `Bearer ${this.accessToken}`,
@ -139,7 +162,11 @@ export class OrangeAdapter implements IOperatorAdapter {
} }
async charge(params: ChargeParams): Promise<ChargeResponse> { async charge(params: ChargeParams): Promise<ChargeResponse> {
const bizaoRequest = { this.logger.debug(
`[orange adapter charge ]: ${JSON.stringify(params, null, 2)}`,
);
const hubRequest = {
amountTransaction: { amountTransaction: {
endUserId: 'acr:OrangeAPIToken', endUserId: 'acr:OrangeAPIToken',
paymentAmount: { paymentAmount: {
@ -149,31 +176,38 @@ export class OrangeAdapter implements IOperatorAdapter {
description: params.description, description: params.description,
}, },
chargingMetaData: { chargingMetaData: {
onBehalfOf: 'PaymentHub', onBehalfOf: 'PaymentHub', //from config todo
purchaseCategoryCode: 'Service', //todo from config
serviceId: 'BIZAO', serviceId: 'BIZAO',
}, },
}, },
transactionOperationStatus: 'Charged', transactionOperationStatus: 'Charged',
referenceCode: params.reference, referenceCode: params.reference,
clientCorrelator: `${params.reference}-${Date.now()}`, clientCorrelator: `${params.reference}-${Date.now()}`, //uniquely identifies this create charge request.
}, },
}; };
const token = await this.getAccessToken();
this.logger.debug(
`[requesting to ]: ${this.config.baseUrl}/payment/v1/acr%3AOrangeAPIToken/transactions/amount`,
);
const response = await firstValueFrom( const response = await firstValueFrom(
this.httpService.post( this.httpService.post(
`${this.baseUrl}/payment/v1/acr%3AOrangeAPIToken/transactions/amount`, `${this.config.baseUrl}}/payment/v1/acr%3AOrangeAPIToken/transactions/amount`,
bizaoRequest, hubRequest,
{ {
headers: { headers: {
Authorization: `Bearer ${this.accessToken}`, Authorization: `Bearer ${token}`,
'bizao-token': params.userToken, 'X-Orange-ISE2': params.userToken,
'bizao-alias': params.userAlias, 'X-Orange-MCO': 'orange', //from country todo
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
}, },
), ),
); );
this.logger.debug(`[response fromm orange ]: ${JSON.stringify(response.data, null, 2)}`,)
return this.transformer.transformChargeResponse(response.data); return this.transformer.transformChargeResponse(response.data);
} }
@ -257,4 +291,53 @@ export class OrangeAdapter implements IOperatorAdapter {
}; };
return senderMap[country]; return senderMap[country];
} }
private async getAccessToken(): Promise<string> {
// Vérifier si le token est encore valide (avec une marge de 60 secondes)
if (this.accessToken && Date.now() < this.tokenExpiresAt - 60000) {
return this.accessToken;
}
try {
const auth = Buffer.from(
`${this.config.clientId}:${this.config.clientSecret}`,
).toString('base64');
//this.logger.debug( `request to get acces token , ${this.config.baseUrl}${this.config.tokenEndpoint}`)
const response = await axios.post(
`${this.config.baseUrl}${this.config.tokenEndpoint}`,
'grant_type=client_credentials',
{
headers: {
Authorization: `Basic ${auth}`,
'Content-Type': 'application/x-www-form-urlencoded',
Accept: '*/*',
},
},
);
this.accessToken = response.data.access_token;
const expiresIn = response.data.expires_in || 3600;
this.tokenExpiresAt = Date.now() + expiresIn * 1000;
return this.accessToken as string;
} catch (error) {
throw new Error(`Failed to obtain Orange access token: ${error.message}`);
}
}
private handleError(error: AxiosError): never {
if (error.response) {
const data = error.response.data as any;
throw new Error(
`Orange API Error: ${data?.error?.message || error.message} (Code: ${data?.error?.code || error.response.status})`
);
} else if (error.request) {
throw new Error(`No response from Orange API: ${error.message}`);
} else {
throw new Error(`Request error: ${error.message}`);
}
}
} }

View File

@ -0,0 +1,46 @@
export interface OrangeConfig {
baseUrl: string;
partnerId: string;
clientId: string;
clientSecret: string;
defaultService: string;
defaultOtpLength: number;
defaultSenderName: string;
defaultOtpMessage: string;
tokenEndpoint: string;
challengeEndpoint: string;
timeout: number;
}
export const DEFAULT_ORANGE_CONFIG: Partial<OrangeConfig> = {
defaultOtpLength: 4,
defaultOtpMessage: 'To confirm your purchase please enter the code %OTP%',
tokenEndpoint: '/oauth/v3/token',
challengeEndpoint: '/challenge/v1/challenges',
timeout: 30000, // 30 secondes
};
/**
* Mapping des codes pays ISO vers les codes Orange
*/
export const COUNTRY_CODE_MAPPING: Record<string, string> = {
'SN': 'SEN', // Sénégal
'CI': 'CIV', // Côte d'Ivoire
'CM': 'CMR', // Cameroun
'CD': 'COD', // RD Congo
'BF': 'BFA', // Burkina Faso
'TN': 'TUN', // Tunisie
'ML': 'MLI', // Mali
'GN': 'GIN', // Guinée
'NE': 'NER', // Niger
'MG': 'MDG', // Madagascar
};
/**
* Mapping des méthodes OTP génériques vers Orange
*/
export const OTP_METHOD_MAPPING: Record<string, string> = {
'SMS': 'OTP-SMS-AUTH',
'USSD': 'OTP-USSD-AUTH',
'IVR': 'OTP-IVR-AUTH',
};

View File

@ -8,6 +8,8 @@ import { MTNAdapter } from './adapters/mtn.adapter';
import { OrangeTransformer } from './transformers/orange.transformer'; import { OrangeTransformer } from './transformers/orange.transformer';
import { MTNTransformer } from './transformers/mtn.transformer'; import { MTNTransformer } from './transformers/mtn.transformer';
import { PrismaService } from '../../shared/services/prisma.service'; import { PrismaService } from '../../shared/services/prisma.service';
import { OrangeConfig } from './adapters/orange.config';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({ @Module({
imports: [ imports: [
@ -15,9 +17,32 @@ import { PrismaService } from '../../shared/services/prisma.service';
timeout: 30000, timeout: 30000,
maxRedirects: 3, maxRedirects: 3,
}), }),
ConfigModule
], ],
controllers: [OperatorsController], controllers: [OperatorsController],
providers: [ providers: [
{
provide: 'ORANGE_CONFIG',
useFactory: (configService: ConfigService): OrangeConfig => ({
baseUrl: configService.get<string>('ORANGE_BASE_URL', 'https://webhook.site/69ce9344-f87b-421a-a494-c59eca7c54ce'),
//tokenUrl: configService.get<string>('ORANGE_BASE_URL', 'https://webhook.site/69ce9344-f87b-421a-a494-c59eca7c54ce'),
partnerId: configService.get<string>('ORANGE_PARTNER_ID', 'PDKSUB'),
clientId: configService.get<string>('ORANGE_CLIENT_ID', 'admin'),
clientSecret: configService.get<string>('ORANGE_CLIENT_SECRET', 'admin'),
defaultService: configService.get<string>('ORANGE_DEFAULT_SERVICE', 'DCB_SERVICE'),
defaultOtpLength: configService.get<number>('ORANGE_DEFAULT_OTP_LENGTH', 4),
defaultSenderName: configService.get<string>('ORANGE_DEFAULT_SENDER_NAME', 'OTP'),
defaultOtpMessage: configService.get<string>(
'ORANGE_DEFAULT_OTP_MESSAGE',
'To confirm your purchase please enter the code %OTP%'
),
tokenEndpoint: '/oauth/v3/token',
challengeEndpoint: '/challenge/v1/challenges',
timeout: configService.get<number>('ORANGE_TIMEOUT', 30000),
}),
inject: [ConfigService],
},
OperatorsService, OperatorsService,
OperatorAdapterFactory, OperatorAdapterFactory,
OrangeAdapter, OrangeAdapter,

View File

@ -1,10 +1,11 @@
import { BadRequestException, NotFoundException } from "@nestjs/common"; import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
import { ConfigService } from "@nestjs/config"; import { ConfigService } from "@nestjs/config";
import { PrismaService } from "src/shared/services/prisma.service"; import { PrismaService } from "src/shared/services/prisma.service";
import { OperatorAdapterFactory } from "./adapters/operator-adapter.factory"; import { OperatorAdapterFactory } from "./adapters/operator-adapter.factory";
import { HttpService } from "@nestjs/axios"; import { HttpService } from "@nestjs/axios";
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
//todo tomaj //todo tomaj
@Injectable()
export class OperatorsService{ export class OperatorsService{
constructor( constructor(
@ -127,12 +128,6 @@ export class OperatorsService{
} }
private getCountryName(code: string): string { private getCountryName(code: string): string {
const countries = { const countries = {
CI: 'Côte d\'Ivoire', CI: 'Côte d\'Ivoire',

View File

@ -36,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() @IsString()
subscriptionId?: string; subscriptionId?: number;
@ApiProperty({ required: false, description: 'Callback URL for notifications' }) @ApiProperty({ required: false, description: 'Callback URL for notifications' })
@IsOptional() @IsOptional()
@ -49,7 +49,15 @@ export class ChargeDto {
@ApiProperty({ required: false, description: 'partnerId ' }) @ApiProperty({ required: false, description: 'partnerId ' })
@IsOptional() @IsOptional()
partnerId: string; partnerId: number;
@ApiProperty({ required: false, description: 'country ' })
@IsOptional()
country: string;
@ApiProperty({ required: false, description: 'operator ' })
@IsOptional()
operator: string;
} }
export class RefundDto { export class RefundDto {

View File

@ -10,6 +10,9 @@ import {
HttpCode, HttpCode,
HttpStatus, HttpStatus,
BadRequestException, BadRequestException,
Headers,
Logger,
ParseIntPipe,
} from '@nestjs/common'; } from '@nestjs/common';
import { import {
ApiTags, ApiTags,
@ -32,10 +35,12 @@ import { ApiKeyGuard } from '../../common/guards/api-key.guard';
@ApiTags('payments') @ApiTags('payments')
@Controller('payments') @Controller('payments')
export class PaymentsController { export class PaymentsController {
private readonly logger = new Logger(PaymentsController.name);
constructor(private readonly paymentsService: PaymentsService) {} constructor(private readonly paymentsService: PaymentsService) {}
@Post('charge') @Post('charge')
@UseGuards(JwtAuthGuard) //@UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@HttpCode(HttpStatus.CREATED) @HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: 'Create a new charge' }) @ApiOperation({ summary: 'Create a new charge' })
@ -46,10 +51,18 @@ export class PaymentsController {
}) })
@ApiResponse({ status: 400, description: 'Bad request' }) @ApiResponse({ status: 400, description: 'Bad request' })
@ApiResponse({ status: 401, description: 'Unauthorized' }) @ApiResponse({ status: 401, description: 'Unauthorized' })
async createCharge(@Request() req, @Body() chargeDto: ChargeDto) { async createCharge(
@Headers('X-Merchant-ID') merchantId: string,
@Headers('X-COUNTRY') coutnry: string,
@Headers('X-OPERATOR') operator: string,
@Request() req, @Body() chargeDto: ChargeDto) {
this.logger.debug(
`[request charge to hub ]: ${JSON.stringify(chargeDto, null, 2)}`,
)
return this.paymentsService.createCharge({ return this.paymentsService.createCharge({
...chargeDto, ...chargeDto,
partnerId: req.user.partnerId, country: coutnry,
operator: operator,
}); });
} }
@ -92,7 +105,7 @@ export class PaymentsController {
@Get('reference/:reference') @Get('reference/:reference')
@UseGuards(JwtAuthGuard) //@UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@ApiOperation({ summary: 'Get payment by reference' }) @ApiOperation({ summary: 'Get payment by reference' })
@ApiResponse({ @ApiResponse({
@ -111,7 +124,7 @@ export class PaymentsController {
} }
@Post(':paymentId/retry') @Post(':paymentId/retry')
@UseGuards(JwtAuthGuard) // @UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Retry a failed payment' }) @ApiOperation({ summary: 'Retry a failed payment' })
@ -130,7 +143,7 @@ export class PaymentsController {
@Post('validate') @Post('validate')
@UseGuards(JwtAuthGuard) //@UseGuards(JwtAuthGuard)
@ApiBearerAuth() @ApiBearerAuth()
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Validate payment before processing' }) @ApiOperation({ summary: 'Validate payment before processing' })
@ -143,7 +156,7 @@ export class PaymentsController {
// Webhook endpoints // Webhook endpoints
@Post('webhook/callback') @Post('webhook/callback')
@UseGuards(ApiKeyGuard) //@UseGuards(ApiKeyGuard)
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@ApiOperation({ summary: 'Webhook callback for payment updates' }) @ApiOperation({ summary: 'Webhook callback for payment updates' })
async handleWebhook(@Request() req, @Body() payload: any) { async handleWebhook(@Request() req, @Body() payload: any) {

View File

@ -6,6 +6,8 @@ import { PaymentProcessor } from './processors/payment.processor';
import { WebhookService } from './services/webhook.service'; import { WebhookService } from './services/webhook.service';
import { PrismaService } from '../../shared/services/prisma.service'; import { PrismaService } from '../../shared/services/prisma.service';
import { OperatorsModule } from '../operators/operators.module'; import { OperatorsModule } from '../operators/operators.module';
import { Subscription } from 'rxjs';
import { SubscriptionsModule } from '../subscriptions/subscriptions.module';
@Module({ @Module({
imports: [ imports: [
@ -16,6 +18,8 @@ import { OperatorsModule } from '../operators/operators.module';
name: 'webhooks', name: 'webhooks',
}), }),
OperatorsModule, OperatorsModule,
SubscriptionsModule,
], ],
controllers: [PaymentsController], controllers: [PaymentsController],
providers: [PaymentsService, PaymentProcessor, WebhookService, PrismaService], providers: [PaymentsService, PaymentProcessor, WebhookService, PrismaService],

View File

@ -1,4 +1,4 @@
import { Injectable, BadRequestException } from '@nestjs/common'; 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';
@ -8,6 +8,7 @@ import { PaymentType, TransactionStatus } from 'generated/prisma';
@Injectable() @Injectable()
export class PaymentsService { 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.'); throw new Error('Method not implemented.');
} }
@ -33,7 +34,7 @@ export class PaymentsService {
) {} ) {}
async createCharge(chargeDto: ChargeDto) { async createCharge(chargeDto: ChargeDto) {
// Récupérer les informations de l'utilisateur /* Récupérer les informations de l'utilisateur
const user = await this.prisma.user.findUnique({ const user = await this.prisma.user.findUnique({
where: { userToken: chargeDto.userToken }, where: { userToken: chargeDto.userToken },
// include: { operator: true }, // include: { operator: true },
@ -42,12 +43,13 @@ export class PaymentsService {
if (!user) { if (!user) {
throw new BadRequestException('Invalid user token'); throw new BadRequestException('Invalid user token');
} }
*/
// 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:1, // À remplacer par le bon partnerId 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,
@ -61,21 +63,29 @@ export class PaymentsService {
try { try {
// Router vers le bon opérateur // Router vers le bon opérateur
this.logger.debug(
`[getting adaptator for ]: ${chargeDto.operator}_${chargeDto.country} `)
const adapter = this.operatorsService.getAdapter( const adapter = this.operatorsService.getAdapter(
'user.operator.code', chargeDto.operator,
user.country, chargeDto.country,
); );
this.logger.debug(`Processing payment ${payment.id} through operator adapter ${adapter.constructor.name}`);
const chargeParams = { const chargeParams = {
userToken: user.userToken, userToken: chargeDto.userToken,
userAlias: user.userAlias, 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,
reference: 'payment.reference,',//todo À remplacer par payment.reference subscriptionId: chargeDto.subscriptionId,
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}`);
// Mettre à jour le paiement // Mettre à jour le paiement
const updatedPayment = await this.prisma.payment.update({ const updatedPayment = await this.prisma.payment.update({
@ -104,7 +114,7 @@ export class PaymentsService {
return updatedPayment; return updatedPayment;
} catch (error) { } catch (error) {
// En cas d'erreur, marquer comme échoué // En cas d'erreur, marquer comme échoué
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,
@ -112,7 +122,7 @@ export class PaymentsService {
}, },
}); });
throw error; return { ...resultFinal };
} }
} }

View File

@ -11,6 +11,7 @@ import {
UseGuards, UseGuards,
Request, Request,
Logger, Logger,
ParseIntPipe,
} from '@nestjs/common'; } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { SubscriptionsService } from './subscriptions.service'; import { SubscriptionsService } from './subscriptions.service';
@ -37,12 +38,24 @@ export class SubscriptionsController {
return this.subscriptionsService.create(merchantId, dto); return this.subscriptionsService.create(merchantId, dto);
} }
@Get('/')
@ApiOperation({ summary: 'Get subscription list' })
async getAll(@Request() req) {
return this.subscriptionsService.findAll();
}
@Get('merchant/:merchantId')
@ApiOperation({ summary: 'Get subscription list by merchant' })
async getAllByMErchant(@Request() req, @Param('merchantId', ParseIntPipe) merchantId: number) {
return this.subscriptionsService.findAllByMerchant(merchantId);
}
@Get(':id') @Get(':id')
@ApiOperation({ summary: 'Get subscription details' }) @ApiOperation({ summary: 'Get subscription details' })
async get(@Request() req, @Param('id') id: string) { async get(@Request() req, @Param('id') id: number) {
return this.subscriptionsService.get(id, req.user.partnerId); return this.subscriptionsService.get(id);
} }
@ -56,9 +69,5 @@ export class SubscriptionsController {
return this.subscriptionsService.cancel(id, req.user.partnerId, reason); return this.subscriptionsService.cancel(id, req.user.partnerId, reason);
} }
@Get(':id/invoices')
@ApiOperation({ summary: 'Get subscription invoices' })
async getInvoices(@Request() req, @Param('id') id: string) {
return this.subscriptionsService.getInvoices(id, req.user.partnerId);
}
} }

View File

@ -18,7 +18,7 @@ import { PaymentsModule } from '../payments/payments.module';
BullModule.registerQueue({ BullModule.registerQueue({
name: 'billing', name: 'billing',
}), }),
PaymentsModule, // PaymentsModule,
], ],
controllers: [SubscriptionsController], controllers: [SubscriptionsController],
providers: [ providers: [

View File

@ -4,23 +4,54 @@ import bull from 'bull';
import { PrismaService } from '../../shared/services/prisma.service'; import { PrismaService } from '../../shared/services/prisma.service';
import { PaymentsService } from '../payments/payments.service'; import { PaymentsService } from '../payments/payments.service';
import { CreateSubscriptionDto, UpdateSubscriptionDto } from './dto/subscription.dto'; import { CreateSubscriptionDto, UpdateSubscriptionDto } from './dto/subscription.dto';
import { Subscription } from 'generated/prisma';
//import { SubscriptionStatus } from '@prisma/client'; //import { SubscriptionStatus } from '@prisma/client';
//import { SubscriptionStatus, Prisma } from '@prisma/client'; //import { SubscriptionStatus, Prisma } from '@prisma/client';
@Injectable() @Injectable()
export class SubscriptionsService { export class SubscriptionsService {
get(id: string, partnerId: any) { async get(id: number):Promise<any> {
throw new Error('Method not implemented.'); const service = await this.prisma.subscription.findUnique({
where: { id },
});
if (!service) {
throw new NotFoundException(`Service with ID ${id} not found`);
} }
list(arg0: { partnerId: any; status: string | undefined; userId: string | undefined; page: number; limit: number; }) {
throw new Error('Method not implemented.'); return service;
} }
async findAllByMerchant(merchantId: number): Promise<Subscription[]> {
// Check if merchant exists
return this.prisma.subscription.findMany({
where: { merchantPartnerId: merchantId },
orderBy: {
createdAt: 'desc',
},
});
}
async findAll(): Promise<Subscription[]> {
// Check if merchant exists
return this.prisma.subscription.findMany({
// where: { merchantPartnerId: merchantId },
orderBy: {
createdAt: 'desc',
},
});
}
getInvoices(id: string, partnerId: any) { getInvoices(id: string, partnerId: any) {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
constructor( constructor(
private readonly prisma: PrismaService, private readonly prisma: PrismaService,
private readonly paymentsService: PaymentsService,
@InjectQueue('subscriptions') private subscriptionQueue: bull.Queue, @InjectQueue('subscriptions') private subscriptionQueue: bull.Queue,
@InjectQueue('billing') private billingQueue: bull.Queue, @InjectQueue('billing') private billingQueue: bull.Queue,
) {} ) {}