fix convert message

This commit is contained in:
Mamadou Khoussa [028918 DSI/DAC/DIF/DS] 2025-10-30 01:43:10 +00:00
parent 8406d79800
commit f7820ddccf
6 changed files with 185 additions and 30 deletions

View File

@ -41,10 +41,11 @@ export class RedisCacheService {
async get<T>(key: string, prefix?: string): Promise<T | null> { async get<T>(key: string, prefix?: string): Promise<T | null> {
try { try {
const fullKey = this.buildKey(key, prefix); const fullKey = this.buildKey(key, prefix);
this.logger.debug(`Cache fullkey: ${fullKey}`);
const data = await this.redis.get(fullKey); const data = await this.redis.get(fullKey);
if (!data) { if (!data) {
this.logger.debug(`Cache miss: ${fullKey}`); this.logger.debug(`Error Cache miss: ${fullKey}`);
return null; return null;
} }

View File

@ -34,6 +34,28 @@ export interface OrangeChallengeResponse {
}; };
} }
export interface OrangeVerifyResponse {
challenge: {
method: string,
country: string,
service: string,
partnerId:string,
inputs: [ ]
result:any[ ],/*[
{
type: 'ise2',
value: 'PDKSUB-200-KzIyMTc3MTcxNzE3MS1TRU4tMTc2MTc4MzI2NjAy'
}
]*/
}
error?: {
code: number | string;
message: string;
description?: string;
};
}
/** /**
* Builder pour construire des requêtes Orange Challenge * Builder pour construire des requêtes Orange Challenge
*/ */
@ -147,6 +169,16 @@ export class OrangeChallengeRequestBuilder {
return this; return this;
} }
withInfo(infoValue: string): this {
this.request.challenge.inputs?.push(
{
"type": "info",
"value": infoValue
}
)
return this;
}
/** /**
* Construire la requête finale * Construire la requête finale
*/ */

View File

@ -2,7 +2,8 @@ import axios, { AxiosInstance, AxiosError } from 'axios';
import { import {
OrangeChallengeRequest, OrangeChallengeRequest,
OrangeChallengeResponse, OrangeChallengeResponse,
OrangeChallengeRequestBuilder OrangeChallengeRequestBuilder,
OrangeVerifyResponse
} from './dtos/orange.challenge.dto' } from './dtos/orange.challenge.dto'
import { import {
OrangeConfig, OrangeConfig,
@ -13,7 +14,7 @@ import {
//import { OtpChallengeResponseDto, OtpChallengeStatusEnum } from '../../dtos/otp-challenge-response.dto'; //import { OtpChallengeResponseDto, OtpChallengeStatusEnum } from '../../dtos/otp-challenge-response.dto';
import { OtpChallengeRequestDto } from '../dto/challenge.request.dto'; import { OtpChallengeRequestDto } from '../dto/challenge.request.dto';
import { OtpChallengeResponseDto, OtpChallengeStatusEnum } from '../dto/challenge.response.dto'; import { OtpChallengeResponseDto, OtpChallengeStatusEnum, OtpVerifResponseDto } from '../dto/challenge.response.dto';
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
import { log } from 'console'; import { log } from 'console';
@ -90,7 +91,49 @@ export class OrangeAdapter {
/** /**
* Convertir la requête générique en format Orange * Convertir la requête générique en format Orange
*/ */
private mapToOrangeRequest(request: OtpChallengeRequestDto): OrangeChallengeRequest { private mapToOrangeRequestChallenge(request: OtpChallengeRequestDto): OrangeChallengeRequest {
const builder = new OrangeChallengeRequestBuilder();
// Mapper le pays
const orangeCountry = COUNTRY_CODE_MAPPING[request.country] || request.country;
builder.withCountry(orangeCountry);
// Mapper la méthode
const orangeMethod = OTP_METHOD_MAPPING[request.method] || 'OTP-SMS-AUTH';
builder.withMethod(orangeMethod);
// Ajouter les informations de base
builder
.withService(request.service)
.withPartnerId(this.config.partnerId);
// Ajouter l'identifiant
builder.withIdentifier(request.identifier.type, request.identifier.value);
// Ajouter le code de confirmation si présent
/* todo voir si mandatory
if (request.confirmationCode) {
builder.withConfirmationCode(request.confirmationCode);
} else {
builder.withConfirmationCode(''); // Orange requiert ce champ même vide
}*/
// Configuration du message OTP
const message = request.config?.message || this.config.defaultOtpMessage;
builder.withMessage(message);
// Longueur de l'OTP
const otpLength = request.config?.length || this.config.defaultOtpLength;
builder.withOtpLength(otpLength);
// Nom de l'expéditeur
const senderName = request.config?.senderName || this.config.defaultSenderName;
builder.withSenderName(senderName);
return builder.build();
}
private mapToOrangeRequestVerify(request: OtpChallengeRequestDto): OrangeChallengeRequest {
const builder = new OrangeChallengeRequestBuilder(); const builder = new OrangeChallengeRequestBuilder();
// Mapper le pays // Mapper le pays
@ -116,17 +159,11 @@ export class OrangeAdapter {
builder.withConfirmationCode(''); // Orange requiert ce champ même vide builder.withConfirmationCode(''); // Orange requiert ce champ même vide
} }
// Configuration du message OTP
const message = request.config?.message || this.config.defaultOtpMessage;
builder.withMessage(message);
// Longueur de l'OTP
const otpLength = request.config?.length || this.config.defaultOtpLength;
builder.withOtpLength(otpLength);
// Nom de l'expéditeur // Nom de l'expéditeur
const senderName = request.config?.senderName || this.config.defaultSenderName; const senderName = request.config?.senderName || this.config.defaultSenderName;
builder.withSenderName(senderName); builder.withInfo("ise2");
return builder.build(); return builder.build();
} }
@ -134,7 +171,7 @@ export class OrangeAdapter {
/** /**
* Convertir la réponse Orange en format générique * Convertir la réponse Orange en format générique
*/ */
private mapFromOrangeResponse( private mapFromOrangeChallengeResponse(
orangeResponse: OrangeChallengeResponse, orangeResponse: OrangeChallengeResponse,
request: OtpChallengeRequestDto request: OtpChallengeRequestDto
): OtpChallengeResponseDto { ): OtpChallengeResponseDto {
@ -167,6 +204,36 @@ export class OrangeAdapter {
return response; return response;
} }
private mapFromOrangeVerifyResponse(
orangeResponse: OrangeVerifyResponse,
request: OtpChallengeRequestDto
): OtpVerifResponseDto {
console.log('mapFromOrangeVerifyResponse',orangeResponse.challenge.result)
const response: OtpVerifResponseDto = {
merchantId: request.merchantId,
status: this.mapOrangeResponseStatus(orangeResponse),
userAlias: orangeResponse.challenge.result?.[0]['value'] || 'not presenter',
metadata: {
provider: 'orange',
country: request.country,
method: request.method
}
};
// Ajouter l'erreur si présente
if (orangeResponse.error) {
response.error = {
code: orangeResponse.error.code.toString(),
message: orangeResponse.error.message,
description: orangeResponse.error.description
};
response.status = OtpChallengeStatusEnum.FAILED;
}
return response;
}
/** /**
* Mapper le statut Orange vers le statut générique * Mapper le statut Orange vers le statut générique
*/ */
@ -182,6 +249,16 @@ export class OrangeAdapter {
return OtpChallengeStatusEnum.PENDING; return OtpChallengeStatusEnum.PENDING;
} }
private mapOrangeResponseStatus(orangeResponse: OrangeVerifyResponse): OtpChallengeStatusEnum {
if (orangeResponse.error) {
return OtpChallengeStatusEnum.FAILED;
}else{
return OtpChallengeStatusEnum.VERIFIED;
}
}
/** /**
* Gérer les erreurs HTTP * Gérer les erreurs HTTP
*/ */
@ -208,7 +285,7 @@ export class OrangeAdapter {
//this.logger.debug(`initiateChallenge --> acces token ${token}`); //this.logger.debug(`initiateChallenge --> acces token ${token}`);
// Mapper la requête // Mapper la requête
const orangeRequest = this.mapToOrangeRequest(request); const orangeRequest = this.mapToOrangeRequestChallenge(request);
this.logger.debug( this.logger.debug(
`[request to orange ]: ${JSON.stringify(orangeRequest, null, 2)}`, `[request to orange ]: ${JSON.stringify(orangeRequest, null, 2)}`,
@ -226,7 +303,7 @@ export class OrangeAdapter {
); );
// Mapper la réponse // Mapper la réponse
return this.mapFromOrangeResponse(response.data, request); return this.mapFromOrangeChallengeResponse(response.data, request);
} catch (error) { } catch (error) {
// En cas d'erreur, retourner une réponse avec le statut FAILED // En cas d'erreur, retourner une réponse avec le statut FAILED
return { return {
@ -249,7 +326,7 @@ export class OrangeAdapter {
challengeId: string, challengeId: string,
otpCode: string, otpCode: string,
originalRequest: OtpChallengeRequestDto originalRequest: OtpChallengeRequestDto
): Promise<OtpChallengeResponseDto> { ): Promise<OtpVerifResponseDto> {
try { try {
// Créer une nouvelle requête avec le code de confirmation // Créer une nouvelle requête avec le code de confirmation
const verifyRequest: OtpChallengeRequestDto = { const verifyRequest: OtpChallengeRequestDto = {
@ -261,11 +338,15 @@ export class OrangeAdapter {
const token = await this.getAccessToken(); const token = await this.getAccessToken();
// Mapper la requête // Mapper la requête
const orangeRequest = this.mapToOrangeRequest(verifyRequest); const orangeRequest = this.mapToOrangeRequestVerify(verifyRequest);
this.logger.debug(
`[request to orange (verify) ]: ${JSON.stringify(orangeRequest, null, 2)}`,
)
// Appeler l'API Orange pour vérification // Appeler l'API Orange pour vérification todo use request otp challenge
const response = await this.axiosInstance.post<OrangeChallengeResponse>( //
this.config.challengeEndpoint, const response = await this.axiosInstance.post<OrangeVerifyResponse>(
`${this.config.challengeEndpoint}/${challengeId}`,
orangeRequest, orangeRequest,
{ {
headers: { headers: {
@ -273,9 +354,17 @@ export class OrangeAdapter {
} }
} }
); );
//${JSON.stringify(response, null, 2)}
this.logger.debug(
`[response from orange (verify) ${JSON.stringify(response.data, null, 2)} ]: `,
)
// Mapper la réponse // Mapper la réponse
const mappedResponse = this.mapFromOrangeResponse(response.data, verifyRequest); const mappedResponse = this.mapFromOrangeVerifyResponse(response.data, verifyRequest);
this.logger.debug(
`[response parsed from orange (verify) ${JSON.stringify(mappedResponse, null, 2)} ]: `,
)
// Si pas d'erreur, c'est vérifié // Si pas d'erreur, c'est vérifié
if (!mappedResponse.error) { if (!mappedResponse.error) {
@ -285,7 +374,7 @@ export class OrangeAdapter {
return mappedResponse; return mappedResponse;
} catch (error) { } catch (error) {
return { return {
challengeId, userAlias:'undefined',
merchantId: originalRequest.merchantId, merchantId: originalRequest.merchantId,
status: OtpChallengeStatusEnum.FAILED, status: OtpChallengeStatusEnum.FAILED,
error: { error: {

View File

@ -40,6 +40,36 @@ export class OtpChallengeResponseDto {
@IsBoolean() @IsBoolean()
requiresConfirmation?: boolean; requiresConfirmation?: boolean;
@IsOptional()
metadata?: Record<string, any>;
@IsOptional()
error?: {
code: string;
message: string;
description?: string;
};
}
export class OtpVerifResponseDto {
@IsString()
@IsNotEmpty()
merchantId: string;
@IsString()
@IsNotEmpty()
userAlias: string;
@IsEnum(OtpChallengeStatusEnum)
@IsNotEmpty()
status: OtpChallengeStatusEnum;
@IsOptional()
@IsString()
message?: string;
@IsOptional() @IsOptional()
metadata?: Record<string, any>; metadata?: Record<string, any>;

View File

@ -23,6 +23,7 @@ import {
import { import {
OtpChallengeResponseDto, OtpChallengeResponseDto,
OtpChallengeStatusEnum, OtpChallengeStatusEnum,
OtpVerifResponseDto,
} from './dto/challenge.response.dto'; } from './dto/challenge.response.dto';
import { OtpChallengeRequestDto } from './dto/challenge.request.dto'; import { OtpChallengeRequestDto } from './dto/challenge.request.dto';
import { OtpChallengeService } from './otp.challenge.service'; import { OtpChallengeService } from './otp.challenge.service';
@ -177,7 +178,7 @@ export class OtpChallengeController {
@Body('otpCode') otpCode: string, @Body('otpCode') otpCode: string,
@Headers('X-Merchant-ID') merchantId: string, @Headers('X-Merchant-ID') merchantId: string,
@Headers('x-API-KEY') apiKey: string, @Headers('x-API-KEY') apiKey: string,
): Promise<OtpChallengeResponseDto> { ): Promise<OtpVerifResponseDto> {
this.logger.log( this.logger.log(
`[VERIFY] Merchant: ${merchantId}, Challenge: ${challengeId}`, `[VERIFY] Merchant: ${merchantId}, Challenge: ${challengeId}`,
); );
@ -193,6 +194,7 @@ export class OtpChallengeController {
otpCode, otpCode,
merchantId, merchantId,
); );
this.logger.log(`[VERIFY] Result - object: ${response}`);
// Logger le résultat // Logger le résultat
this.logger.log(`[VERIFY] Result - Status: ${response.status}`); this.logger.log(`[VERIFY] Result - Status: ${response.status}`);

View File

@ -3,7 +3,7 @@ import type { OrangeConfig } from './adaptor/orange.config';
import { OrangeAdapter } from './adaptor/orange.adaptor'; import { OrangeAdapter } from './adaptor/orange.adaptor';
import { OtpChallengeRequestDto } from './dto/challenge.request.dto'; import { OtpChallengeRequestDto } from './dto/challenge.request.dto';
import { IOtpChallengeService } from './otp.challenge.interface'; import { IOtpChallengeService } from './otp.challenge.interface';
import { OtpChallengeResponseDto, OtpChallengeStatusEnum } from './dto/challenge.response.dto'; import { OtpChallengeResponseDto, OtpChallengeStatusEnum, OtpVerifResponseDto } from './dto/challenge.response.dto';
import { RedisCacheService } from 'src/common/services/cache.redis'; import { RedisCacheService } from 'src/common/services/cache.redis';
import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { CACHE_MANAGER } from '@nestjs/cache-manager';
@ -58,14 +58,15 @@ export class OtpChallengeService implements IOtpChallengeService {
challengeId: string, challengeId: string,
otpCode: string, otpCode: string,
merchantId: string merchantId: string
): Promise<OtpChallengeResponseDto> { ): Promise<any> {
try { try {
// Récupérer le challenge depuis le cache // Récupérer le challenge depuis le cache
const cached:any = this.cacheService.get(challengeId); const cached:any =await this.cacheService.get(challengeId,this.CACHE_PREFIX,);
this.logger.debug(`cache retrieve , ${cached}`)
if (!cached) { if (!cached) {
return { return {
challengeId, userAlias:"",
merchantId, merchantId,
status: OtpChallengeStatusEnum.FAILED, status: OtpChallengeStatusEnum.FAILED,
error: { error: {
@ -79,7 +80,7 @@ export class OtpChallengeService implements IOtpChallengeService {
// Vérifier que le merchantId correspond // Vérifier que le merchantId correspond
if (cached.request.merchantId !== merchantId) { if (cached.request.merchantId !== merchantId) {
return { return {
challengeId, userAlias:"",
merchantId, merchantId,
status: OtpChallengeStatusEnum.FAILED, status: OtpChallengeStatusEnum.FAILED,
error: { error: {
@ -105,7 +106,7 @@ export class OtpChallengeService implements IOtpChallengeService {
return response; return response;
} catch (error) { } catch (error) {
return { return {
challengeId, userAlias:"",
merchantId, merchantId,
status: OtpChallengeStatusEnum.FAILED, status: OtpChallengeStatusEnum.FAILED,
error: { error: {