This commit is contained in:
Mamadou Khoussa [028918 DSI/DAC/DIF/DS] 2025-09-21 22:47:06 +00:00
parent d2d1ab493c
commit 2e2724c7a6
64 changed files with 13501 additions and 0 deletions

1
.gitignore vendored
View File

@ -0,0 +1 @@
/node_modules

5
.prettierrc Normal file
View File

@ -0,0 +1,5 @@
{
"singleQuote": false,
"trailingComma": "none",
"semi": true
}

13
Dockerfile Normal file
View File

@ -0,0 +1,13 @@
# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3001
CMD ["npm", "run", "start:dev"]

99
README.md Normal file
View File

@ -0,0 +1,99 @@
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" alt="Donate us"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow" alt="Follow us on Twitter"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Project setup
```bash
$ npm install
```
## Compile and run the project
```bash
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
```
## Run tests
```bash
# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
```
## Deployment
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
```bash
$ npm install -g mau
$ mau deploy
```
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
## Resources
Check out a few resources that may come in handy when working with NestJS:
- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).

4
dist/app.module.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
import { MiddlewareConsumer, NestModule } from '@nestjs/common';
export declare class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer): void;
}

39
dist/app.module.js vendored Normal file
View File

@ -0,0 +1,39 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AppModule = void 0;
const app_service_1 = require("./app.service");
const common_1 = require("@nestjs/common");
const auth_controller_1 = require("./controllers/auth.controller");
const subscription_controller_1 = require("./controllers/subscription.controller");
const billing_controller_1 = require("./controllers/billing.controller");
const sms_controller_1 = require("./controllers/sms.controller");
const otp_controller_1 = require("./controllers/otp.controller");
const mock_data_service_1 = require("./services/mock-data.service");
const token_service_1 = require("./services/token.service");
const auth_middleware_1 = require("./middleware/auth.middleware");
let AppModule = class AppModule {
configure(consumer) {
consumer.apply(auth_middleware_1.AuthMiddleware).exclude('/oauth/v3/token').forRoutes('*');
}
};
exports.AppModule = AppModule;
exports.AppModule = AppModule = __decorate([
(0, common_1.Module)({
imports: [],
controllers: [
auth_controller_1.AuthController,
subscription_controller_1.SubscriptionController,
billing_controller_1.BillingController,
sms_controller_1.SmsController,
otp_controller_1.OtpController,
],
providers: [app_service_1.AppService, mock_data_service_1.MockDataService, token_service_1.TokenService],
})
], AppModule);
//# sourceMappingURL=app.module.js.map

1
dist/app.module.js.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"app.module.js","sourceRoot":"","sources":["../src/app.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,+CAA2C;AAE3C,2CAAwE;AACxE,mEAA+D;AAC/D,mFAA+E;AAC/E,yEAAqE;AACrE,iEAA6D;AAC7D,iEAA6D;AAC7D,oEAA+D;AAC/D,4DAAwD;AACxD,kEAA8D;AAavD,IAAM,SAAS,GAAf,MAAM,SAAS;IACpB,SAAS,CAAC,QAA4B;QACpC,QAAQ,CAAC,KAAK,CAAC,gCAAc,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAC3E,CAAC;CACF,CAAA;AAJY,8BAAS;oBAAT,SAAS;IAXrB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,EAAE;QACX,WAAW,EAAE;YACX,gCAAc;YACd,gDAAsB;YACtB,sCAAiB;YACjB,8BAAa;YACb,8BAAa;SACd;QACD,SAAS,EAAE,CAAC,wBAAU,EAAE,mCAAe,EAAE,4BAAY,CAAC;KACvD,CAAC;GACW,SAAS,CAIrB"}

3
dist/app.service.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
export declare class AppService {
getHello(): string;
}

20
dist/app.service.js vendored Normal file
View File

@ -0,0 +1,20 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AppService = void 0;
const common_1 = require("@nestjs/common");
let AppService = class AppService {
getHello() {
return 'Hello World!';
}
};
exports.AppService = AppService;
exports.AppService = AppService = __decorate([
(0, common_1.Injectable)()
], AppService);
//# sourceMappingURL=app.service.js.map

1
dist/app.service.js.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"app.service.js","sourceRoot":"","sources":["../src/app.service.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA4C;AAGrC,IAAM,UAAU,GAAhB,MAAM,UAAU;IACrB,QAAQ;QACN,OAAO,cAAc,CAAC;IACxB,CAAC;CACF,CAAA;AAJY,gCAAU;qBAAV,UAAU;IADtB,IAAA,mBAAU,GAAE;GACA,UAAU,CAItB"}

18
dist/controllers/auth.controller.d.ts vendored Normal file
View File

@ -0,0 +1,18 @@
import { TokenService } from '../services/token.service';
export declare class AuthController {
private tokenService;
constructor(tokenService: TokenService);
generateToken(authHeader: string, grantType: string): {
error: string;
error_description: string;
token_type?: undefined;
access_token?: undefined;
expires_in?: undefined;
} | {
token_type: string;
access_token: string;
expires_in: number;
error?: undefined;
error_description?: undefined;
};
}

56
dist/controllers/auth.controller.js vendored Normal file
View File

@ -0,0 +1,56 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AuthController = void 0;
const common_1 = require("@nestjs/common");
const token_service_1 = require("../services/token.service");
let AuthController = class AuthController {
constructor(tokenService) {
this.tokenService = tokenService;
}
generateToken(authHeader, grantType) {
if (!authHeader || !authHeader.startsWith('Basic ')) {
return {
error: 'invalid_client',
error_description: 'Invalid authentication credentials',
};
}
if (grantType !== 'client_credentials') {
return {
error: 'unsupported_grant_type',
error_description: 'Only client_credentials is supported',
};
}
const token = this.tokenService.generateToken();
return {
token_type: 'Bearer',
access_token: token,
expires_in: 3600,
};
}
};
exports.AuthController = AuthController;
__decorate([
(0, common_1.Post)('token'),
__param(0, (0, common_1.Headers)('authorization')),
__param(1, (0, common_1.Body)('grant_type')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String]),
__metadata("design:returntype", void 0)
], AuthController.prototype, "generateToken", null);
exports.AuthController = AuthController = __decorate([
(0, common_1.Controller)('oauth/v3'),
__metadata("design:paramtypes", [token_service_1.TokenService])
], AuthController);
//# sourceMappingURL=auth.controller.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"auth.controller.js","sourceRoot":"","sources":["../../src/controllers/auth.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AACA,2CAAiE;AACjE,6DAAyD;AAGlD,IAAM,cAAc,GAApB,MAAM,cAAc;IACzB,YAAoB,YAA0B;QAA1B,iBAAY,GAAZ,YAAY,CAAc;IAAG,CAAC;IAElD,aAAa,CACe,UAAkB,EACxB,SAAiB;QAGrC,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YACpD,OAAO;gBACL,KAAK,EAAE,gBAAgB;gBACvB,iBAAiB,EAAE,oCAAoC;aACxD,CAAC;QACJ,CAAC;QACD,IAAI,SAAS,KAAK,oBAAoB,EAAE,CAAC;YACvC,OAAO;gBACL,KAAK,EAAE,wBAAwB;gBAC/B,iBAAiB,EAAE,sCAAsC;aAC1D,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,aAAa,EAAE,CAAC;QAChD,OAAO;YACL,UAAU,EAAE,QAAQ;YACpB,YAAY,EAAE,KAAK;YACnB,UAAU,EAAE,IAAI;SACjB,CAAC;IACJ,CAAC;CACF,CAAA;AA5BY,wCAAc;AAGzB;IADC,IAAA,aAAI,EAAC,OAAO,CAAC;IAEX,WAAA,IAAA,gBAAO,EAAC,eAAe,CAAC,CAAA;IACxB,WAAA,IAAA,aAAI,EAAC,YAAY,CAAC,CAAA;;;;mDAsBpB;yBA3BU,cAAc;IAD1B,IAAA,mBAAU,EAAC,UAAU,CAAC;qCAEa,4BAAY;GADnC,cAAc,CA4B1B"}

View File

@ -0,0 +1,8 @@
import { MockDataService } from '../services/mock-data.service';
export declare class BillingController {
private mockData;
constructor(mockData: MockDataService);
chargeUser(ise2: string, mco: string, body: any): {
amountTransaction: any;
};
}

75
dist/controllers/billing.controller.js vendored Normal file
View File

@ -0,0 +1,75 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BillingController = void 0;
const common_1 = require("@nestjs/common");
const mock_data_service_1 = require("../services/mock-data.service");
let BillingController = class BillingController {
constructor(mockData) {
this.mockData = mockData;
}
chargeUser(ise2, mco, body) {
console.log('[MOCK] Charge request:', { ise2, mco, body });
const { amountTransaction } = body;
if (!amountTransaction) {
throw new common_1.HttpException({
requestError: {
serviceException: {
messageId: 'SVC0002',
text: 'Invalid input value for message part amountTransaction'
}
}
}, common_1.HttpStatus.BAD_REQUEST);
}
const amount = parseFloat(amountTransaction.paymentAmount.chargingInformation.amount);
const result = this.mockData.createTransaction({
endUserId: ise2,
amount,
...amountTransaction,
});
if (!result.success) {
throw new common_1.HttpException({
requestError: {
policyException: {
messageId: result.error.code,
text: result.error.message
}
}
}, common_1.HttpStatus.FORBIDDEN);
}
return {
amountTransaction: {
...amountTransaction,
serverReferenceCode: result.transaction.serverReferenceCode,
transactionOperationStatus: 'Charged',
resourceURL: `http://localhost:3001/payment/mea/v1/transactions/${result.transaction.serverReferenceCode}`
}
};
}
};
exports.BillingController = BillingController;
__decorate([
(0, common_1.Post)('acr:X-Orange-ISE2/transactions/amount'),
__param(0, (0, common_1.Headers)('x-orange-ise2')),
__param(1, (0, common_1.Headers)('x-orange-mco')),
__param(2, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String, Object]),
__metadata("design:returntype", void 0)
], BillingController.prototype, "chargeUser", null);
exports.BillingController = BillingController = __decorate([
(0, common_1.Controller)('payment/mea/v1'),
__metadata("design:paramtypes", [mock_data_service_1.MockDataService])
], BillingController);
//# sourceMappingURL=billing.controller.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"billing.controller.js","sourceRoot":"","sources":["../../src/controllers/billing.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AACA,2CAA4F;AAC5F,qEAAgE;AAGzD,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IAC5B,YAAoB,QAAyB;QAAzB,aAAQ,GAAR,QAAQ,CAAiB;IAAG,CAAC;IAGjD,UAAU,CACkB,IAAY,EACb,GAAW,EAC5B,IAAS;QAEjB,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;QAE3D,MAAM,EAAE,iBAAiB,EAAE,GAAG,IAAI,CAAC;QAEnC,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACvB,MAAM,IAAI,sBAAa,CAAC;gBACtB,YAAY,EAAE;oBACZ,gBAAgB,EAAE;wBAChB,SAAS,EAAE,SAAS;wBACpB,IAAI,EAAE,wDAAwD;qBAC/D;iBACF;aACF,EAAE,mBAAU,CAAC,WAAW,CAAC,CAAC;QAC7B,CAAC;QAED,MAAM,MAAM,GAAG,UAAU,CAAC,iBAAiB,CAAC,aAAa,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAGtF,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,iBAAiB,CAAC;YAC7C,SAAS,EAAE,IAAI;YACf,MAAM;YACN,GAAG,iBAAiB;SACrB,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,sBAAa,CAAC;gBACtB,YAAY,EAAE;oBACZ,eAAe,EAAE;wBACf,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI;wBAC5B,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO;qBAC3B;iBACF;aACF,EAAE,mBAAU,CAAC,SAAS,CAAC,CAAC;QAC3B,CAAC;QAED,OAAO;YACL,iBAAiB,EAAE;gBACjB,GAAG,iBAAiB;gBACpB,mBAAmB,EAAE,MAAM,CAAC,WAAW,CAAC,mBAAmB;gBAC3D,0BAA0B,EAAE,SAAS;gBACrC,WAAW,EAAE,qDAAqD,MAAM,CAAC,WAAW,CAAC,mBAAmB,EAAE;aAC3G;SACF,CAAC;IACJ,CAAC;CACF,CAAA;AArDY,8CAAiB;AAI5B;IADC,IAAA,aAAI,EAAC,uCAAuC,CAAC;IAE3C,WAAA,IAAA,gBAAO,EAAC,eAAe,CAAC,CAAA;IACxB,WAAA,IAAA,gBAAO,EAAC,cAAc,CAAC,CAAA;IACvB,WAAA,IAAA,aAAI,GAAE,CAAA;;;;mDA6CR;4BApDU,iBAAiB;IAD7B,IAAA,mBAAU,EAAC,gBAAgB,CAAC;qCAEG,mCAAe;GADlC,iBAAiB,CAqD7B"}

12
dist/controllers/otp.controller.d.ts vendored Normal file
View File

@ -0,0 +1,12 @@
import { MockDataService } from '../services/mock-data.service';
export declare class OtpController {
private mockData;
constructor(mockData: MockDataService);
createChallenge(body: any): {
challenge: any;
location: string;
};
validateChallenge(challengeId: string, body: any): {
challenge: any;
};
}

109
dist/controllers/otp.controller.js vendored Normal file
View File

@ -0,0 +1,109 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OtpController = void 0;
const common_1 = require("@nestjs/common");
const mock_data_service_1 = require("../services/mock-data.service");
let OtpController = class OtpController {
constructor(mockData) {
this.mockData = mockData;
}
createChallenge(body) {
console.log('[MOCK] OTP Challenge creation:', body);
const { challenge } = body;
if (!challenge || challenge.method !== 'OTP-SMS-AUTH') {
throw new common_1.HttpException({
error: {
code: 232740003,
message: 'Invalid input value',
description: 'Invalid challenge method'
}
}, common_1.HttpStatus.BAD_REQUEST);
}
const msisdnInput = challenge.inputs.find(i => i.type === 'MSISDN');
if (!msisdnInput) {
throw new common_1.HttpException({
error: {
code: 232740000,
message: 'Missing input',
description: 'MSISDN is required'
}
}, common_1.HttpStatus.BAD_REQUEST);
}
const challengeId = this.mockData.createOtpChallenge(msisdnInput.value, challenge.country);
return {
challenge: {
...challenge,
result: []
},
location: `/challenge/v1/challenges/${challengeId}`
};
}
validateChallenge(challengeId, body) {
console.log('[MOCK] OTP Challenge validation:', { challengeId, body });
const { challenge } = body;
const otpInput = challenge.inputs.find(i => i.type === 'confirmationCode');
if (!otpInput || !otpInput.value) {
throw new common_1.HttpException({
error: {
code: 232740203,
message: 'Invalid challenge inputs',
description: 'OTP code is required'
}
}, common_1.HttpStatus.BAD_REQUEST);
}
const result = this.mockData.validateOtp(challengeId, otpInput.value);
if (!result.success) {
throw new common_1.HttpException({
error: {
code: 232740201,
message: 'Authorization denied',
description: result.error
}
}, common_1.HttpStatus.FORBIDDEN);
}
return {
challenge: {
...challenge,
result: [
{
type: 'ise2',
value: result.ise2
}
]
}
};
}
};
exports.OtpController = OtpController;
__decorate([
(0, common_1.Post)('challenges'),
__param(0, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", void 0)
], OtpController.prototype, "createChallenge", null);
__decorate([
(0, common_1.Post)('challenges/:id'),
__param(0, (0, common_1.Param)('id')),
__param(1, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, Object]),
__metadata("design:returntype", void 0)
], OtpController.prototype, "validateChallenge", null);
exports.OtpController = OtpController = __decorate([
(0, common_1.Controller)('challenge/v1'),
__metadata("design:paramtypes", [mock_data_service_1.MockDataService])
], OtpController);
//# sourceMappingURL=otp.controller.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"otp.controller.js","sourceRoot":"","sources":["../../src/controllers/otp.controller.ts"],"names":[],"mappings":"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"}

8
dist/controllers/sms.controller.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
import { MockDataService } from '../services/mock-data.service';
export declare class SmsController {
private mockData;
constructor(mockData: MockDataService);
sendSms(ise2: string, mco: string, body: any): {
outboundSMSMessageRequest: any;
};
}

59
dist/controllers/sms.controller.js vendored Normal file
View File

@ -0,0 +1,59 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SmsController = void 0;
const common_1 = require("@nestjs/common");
const mock_data_service_1 = require("../services/mock-data.service");
let SmsController = class SmsController {
constructor(mockData) {
this.mockData = mockData;
}
sendSms(ise2, mco, body) {
console.log('[MOCK] SMS MT request:', { ise2, mco, body });
const { outboundSMSMessageRequest } = body;
if (!outboundSMSMessageRequest) {
throw new common_1.HttpException({
requestError: {
serviceException: {
messageId: 'SVC0002',
text: 'Invalid request format',
},
},
}, common_1.HttpStatus.BAD_REQUEST);
}
const messageId = `SMS-${Date.now()}-${Math.random().toString(36).substring(7)}`;
return {
outboundSMSMessageRequest: {
...outboundSMSMessageRequest,
clientCorrelator: messageId,
resourceURL: `http://localhost:3001/smsmessaging/v1/outbound/requests/${messageId}`,
},
};
}
};
exports.SmsController = SmsController;
__decorate([
(0, common_1.Post)('/outbound/tel:msisdn/requests'),
__param(0, (0, common_1.Headers)('x-orange-ise2')),
__param(1, (0, common_1.Headers)('x-orange-mco')),
__param(2, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String, Object]),
__metadata("design:returntype", void 0)
], SmsController.prototype, "sendSms", null);
exports.SmsController = SmsController = __decorate([
(0, common_1.Controller)('smsmessaging/service/mea/v1'),
__metadata("design:paramtypes", [mock_data_service_1.MockDataService])
], SmsController);
//# sourceMappingURL=sms.controller.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"sms.controller.js","sourceRoot":"","sources":["../../src/controllers/sms.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AACA,2CAOwB;AACxB,qEAAgE;AAGzD,IAAM,aAAa,GAAnB,MAAM,aAAa;IACxB,YAAoB,QAAyB;QAAzB,aAAQ,GAAR,QAAQ,CAAiB;IAAG,CAAC;IAIjD,OAAO,CACqB,IAAY,EACb,GAAW,EAC5B,IAAS;QAEjB,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;QAE3D,MAAM,EAAE,yBAAyB,EAAE,GAAG,IAAI,CAAC;QAE3C,IAAI,CAAC,yBAAyB,EAAE,CAAC;YAC/B,MAAM,IAAI,sBAAa,CACrB;gBACE,YAAY,EAAE;oBACZ,gBAAgB,EAAE;wBAChB,SAAS,EAAE,SAAS;wBACpB,IAAI,EAAE,wBAAwB;qBAC/B;iBACF;aACF,EACD,mBAAU,CAAC,WAAW,CACvB,CAAC;QACJ,CAAC;QAGD,MAAM,SAAS,GAAG,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QAEjF,OAAO;YACL,yBAAyB,EAAE;gBACzB,GAAG,yBAAyB;gBAC5B,gBAAgB,EAAE,SAAS;gBAC3B,WAAW,EAAE,2DAA2D,SAAS,EAAE;aACpF;SACF,CAAC;IACJ,CAAC;CACF,CAAA;AAvCY,sCAAa;AAKxB;IADA,IAAA,aAAI,EAAC,+BAA+B,CAAC;IAElC,WAAA,IAAA,gBAAO,EAAC,eAAe,CAAC,CAAA;IACxB,WAAA,IAAA,gBAAO,EAAC,cAAc,CAAC,CAAA;IACvB,WAAA,IAAA,aAAI,GAAE,CAAA;;;;4CA8BR;wBAtCU,aAAa;IADzB,IAAA,mBAAU,EAAC,6BAA6B,CAAC;qCAEV,mCAAe;GADlC,aAAa,CAuCzB"}

View File

@ -0,0 +1,9 @@
import { MockDataService } from '../services/mock-data.service';
export declare class SubscriptionController {
private mockData;
constructor(mockData: MockDataService);
createSubscription(ise2: string, mco: string, body: any): any;
getSubscription(id: string): any;
deleteSubscription(id: string): string;
private sendSubscriptionNotification;
}

View File

@ -0,0 +1,113 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SubscriptionController = void 0;
const common_1 = require("@nestjs/common");
const mock_data_service_1 = require("../services/mock-data.service");
let SubscriptionController = class SubscriptionController {
constructor(mockData) {
this.mockData = mockData;
}
createSubscription(ise2, mco, body) {
console.log('[MOCK] Création subscription:', { ise2, mco, body });
if (!ise2 || !mco) {
throw new common_1.HttpException({
requestError: {
serviceException: {
messageId: '2001',
text: 'Missing required headers',
},
},
}, common_1.HttpStatus.BAD_REQUEST);
}
if (Math.random() < 0.1) {
throw new common_1.HttpException({
requestError: {
serviceException: {
messageId: '3010',
text: 'The subscription already exists',
},
},
}, common_1.HttpStatus.CONFLICT);
}
const subscription = this.mockData.createSubscription(body);
setTimeout(() => {
this.sendSubscriptionNotification(subscription, 'orderCreation');
}, 2000);
return subscription;
}
getSubscription(id) {
const subscription = this.mockData.getSubscription(id);
if (!subscription) {
throw new common_1.HttpException({
requestError: {
serviceException: {
messageId: '3011',
text: 'Subscription not found',
},
},
}, common_1.HttpStatus.NOT_FOUND);
}
return subscription;
}
deleteSubscription(id) {
const deleted = this.mockData.deleteSubscription(id);
if (!deleted) {
throw new common_1.HttpException({
requestError: {
serviceException: {
messageId: '3011',
text: 'Subscription not found',
},
},
}, common_1.HttpStatus.NOT_FOUND);
}
setTimeout(() => {
this.sendSubscriptionNotification({ id }, 'orderDeletion');
}, 1000);
return '';
}
sendSubscriptionNotification(subscription, eventType) {
console.log(`[MOCK] Notification ${eventType} pour subscription ${subscription.id}`);
}
};
exports.SubscriptionController = SubscriptionController;
__decorate([
(0, common_1.Post)('digipay_sub/productOrder'),
__param(0, (0, common_1.Headers)('x-orange-ise2')),
__param(1, (0, common_1.Headers)('x-orange-mco')),
__param(2, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String, Object]),
__metadata("design:returntype", void 0)
], SubscriptionController.prototype, "createSubscription", null);
__decorate([
(0, common_1.Get)('digipay_sub/productOrder/:id'),
__param(0, (0, common_1.Param)('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", void 0)
], SubscriptionController.prototype, "getSubscription", null);
__decorate([
(0, common_1.Delete)('digipay_sub/productOrder/:id'),
__param(0, (0, common_1.Param)('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", void 0)
], SubscriptionController.prototype, "deleteSubscription", null);
exports.SubscriptionController = SubscriptionController = __decorate([
(0, common_1.Controller)('payment/mea/v1'),
__metadata("design:paramtypes", [mock_data_service_1.MockDataService])
], SubscriptionController);
//# sourceMappingURL=subscription.controller.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"subscription.controller.js","sourceRoot":"","sources":["../../src/controllers/subscription.controller.ts"],"names":[],"mappings":"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"}

0
dist/data/mock-database.d.ts vendored Normal file
View File

1
dist/data/mock-database.js vendored Normal file
View File

@ -0,0 +1 @@
//# sourceMappingURL=mock-database.js.map

1
dist/data/mock-database.js.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"mock-database.js","sourceRoot":"","sources":["../../src/data/mock-database.ts"],"names":[],"mappings":""}

1
dist/main.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export {};

19
dist/main.js vendored Normal file
View File

@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("@nestjs/core");
const app_module_1 = require("./app.module");
const swagger_1 = require("@nestjs/swagger");
async function bootstrap() {
const app = await core_1.NestFactory.create(app_module_1.AppModule);
const config = new swagger_1.DocumentBuilder()
.setTitle('API DCB / SMS / Payment')
.setDescription('Documentation des endpoints mock Orange DCB')
.setVersion('1.0')
.addBearerAuth()
.build();
const document = swagger_1.SwaggerModule.createDocument(app, config);
swagger_1.SwaggerModule.setup('api-docs', app, document);
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
//# sourceMappingURL=main.js.map

1
dist/main.js.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";;AAAA,uCAA2C;AAC3C,6CAAyC;AACzC,6CAAiE;AAEjE,KAAK,UAAU,SAAS;IACtB,MAAM,GAAG,GAAG,MAAM,kBAAW,CAAC,MAAM,CAAC,sBAAS,CAAC,CAAC;IAEhD,MAAM,MAAM,GAAG,IAAI,yBAAe,EAAE;SACjC,QAAQ,CAAC,yBAAyB,CAAC;SACnC,cAAc,CAAC,8CAA8C,CAAC;SAC9D,UAAU,CAAC,KAAK,CAAC;SACjB,aAAa,EAAE;SACf,KAAK,EAAE,CAAC;IACX,MAAM,QAAQ,GAAG,uBAAa,CAAC,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC3D,uBAAa,CAAC,KAAK,CAAC,UAAU,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC/C,MAAM,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;AAC7C,CAAC;AACD,SAAS,EAAE,CAAC"}

8
dist/middleware/auth.middleware.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
import { NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { TokenService } from '../services/token.service';
export declare class AuthMiddleware implements NestMiddleware {
private tokenService;
constructor(tokenService: TokenService);
use(req: Request, res: Response, next: NextFunction): void;
}

50
dist/middleware/auth.middleware.js vendored Normal file
View File

@ -0,0 +1,50 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AuthMiddleware = void 0;
const common_1 = require("@nestjs/common");
const token_service_1 = require("../services/token.service");
let AuthMiddleware = class AuthMiddleware {
constructor(tokenService) {
this.tokenService = tokenService;
}
use(req, res, next) {
const authHeader = req.headers['authorization'];
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new common_1.HttpException({
requestError: {
serviceException: {
messageId: '401',
text: 'Missing or invalid authorization header',
},
},
}, common_1.HttpStatus.UNAUTHORIZED);
}
const token = authHeader.substring(7);
if (!this.tokenService.validateToken(token)) {
throw new common_1.HttpException({
requestError: {
serviceException: {
messageId: '401',
text: 'Invalid or expired token',
},
},
}, common_1.HttpStatus.UNAUTHORIZED);
}
next();
}
};
exports.AuthMiddleware = AuthMiddleware;
exports.AuthMiddleware = AuthMiddleware = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [token_service_1.TokenService])
], AuthMiddleware);
//# sourceMappingURL=auth.middleware.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"auth.middleware.js","sourceRoot":"","sources":["../../src/middleware/auth.middleware.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,2CAKwB;AAExB,6DAAyD;AAGlD,IAAM,cAAc,GAApB,MAAM,cAAc;IACzB,YAAoB,YAA0B;QAA1B,iBAAY,GAAZ,YAAY,CAAc;IAAG,CAAC;IAElD,GAAG,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB;QACjD,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QAEhD,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YACrD,MAAM,IAAI,sBAAa,CACrB;gBACE,YAAY,EAAE;oBACZ,gBAAgB,EAAE;wBAChB,SAAS,EAAE,KAAK;wBAChB,IAAI,EAAE,yCAAyC;qBAChD;iBACF;aACF,EACD,mBAAU,CAAC,YAAY,CACxB,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAEtC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5C,MAAM,IAAI,sBAAa,CACrB;gBACE,YAAY,EAAE;oBACZ,gBAAgB,EAAE;wBAChB,SAAS,EAAE,KAAK;wBAChB,IAAI,EAAE,0BAA0B;qBACjC;iBACF;aACF,EACD,mBAAU,CAAC,YAAY,CACxB,CAAC;QACJ,CAAC;QAED,IAAI,EAAE,CAAC;IACT,CAAC;CACF,CAAA;AAtCY,wCAAc;yBAAd,cAAc;IAD1B,IAAA,mBAAU,GAAE;qCAEuB,4BAAY;GADnC,cAAc,CAsC1B"}

16
dist/services/mock-data.service.d.ts vendored Normal file
View File

@ -0,0 +1,16 @@
export declare class MockDataService {
private subscriptions;
private transactions;
private otpChallenges;
private userBalances;
constructor();
private initializeMockData;
generateISE2(msisdn: string, country: string): string;
createSubscription(data: any): any;
getSubscription(id: string): any;
deleteSubscription(id: string): boolean;
createTransaction(data: any): any;
createOtpChallenge(msisdn: string, country: string): any;
validateOtp(challengeId: string, inputOtp: string): any;
getUserBalance(ise2: string): any;
}

125
dist/services/mock-data.service.js vendored Normal file
View File

@ -0,0 +1,125 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MockDataService = void 0;
const common_1 = require("@nestjs/common");
const uuid_1 = require("uuid");
let MockDataService = class MockDataService {
constructor() {
this.subscriptions = new Map();
this.transactions = new Map();
this.otpChallenges = new Map();
this.userBalances = new Map();
this.initializeMockData();
}
initializeMockData() {
this.userBalances.set('PDKSUB-200-Q82vHq0+F1WozTeNS/1wBfuULco05YBaeL4yPtJ8ktU=', {
balance: 10000,
currency: 'CDF',
type: 'prepaid',
});
this.userBalances.set('PDKSUB-200-8Ow1iM0hLPZ+LGZ8j4uwEdQxY1hm4mVwrzTdWiUnuI=', {
balance: 5000,
currency: 'TND',
type: 'prepaid',
});
}
generateISE2(msisdn, country) {
const base = `PDKSUB-200-`;
const hash = Buffer.from(`${msisdn}-${country}-${Date.now()}`).toString('base64');
return base + hash.substring(0, 40);
}
createSubscription(data) {
const subscriptionId = (0, uuid_1.v4)();
const subscription = {
id: subscriptionId,
state: 'Completed',
orderDate: new Date().toISOString(),
...data,
};
this.subscriptions.set(subscriptionId, subscription);
return subscription;
}
getSubscription(id) {
return this.subscriptions.get(id);
}
deleteSubscription(id) {
return this.subscriptions.delete(id);
}
createTransaction(data) {
const transactionId = (0, uuid_1.v4)();
const userBalance = this.getUserBalance(data.endUserId);
if (userBalance && userBalance.balance >= data.amount) {
userBalance.balance -= data.amount;
const transaction = {
serverReferenceCode: transactionId,
transactionOperationStatus: 'Charged',
...data,
};
this.transactions.set(transactionId, transaction);
return { success: true, transaction };
}
return {
success: false,
error: {
code: 'POL1000',
message: 'User has insufficient credit for transaction',
},
};
}
createOtpChallenge(msisdn, country) {
const challengeId = (0, uuid_1.v4)();
const otp = Math.floor(1000 + Math.random() * 9000).toString();
const challenge = {
id: challengeId,
msisdn,
country,
otp,
createdAt: Date.now(),
attempts: 0,
};
this.otpChallenges.set(challengeId, challenge);
console.log(`[MOCK] OTP généré pour ${msisdn}: ${otp}`);
return challengeId;
}
validateOtp(challengeId, inputOtp) {
const challenge = this.otpChallenges.get(challengeId);
if (!challenge) {
return { success: false, error: 'Challenge not found' };
}
challenge.attempts++;
if (challenge.attempts > 3) {
return { success: false, error: 'Too many retries' };
}
if (Date.now() - challenge.createdAt > 300000) {
return { success: false, error: 'OTP expired' };
}
if (challenge.otp === inputOtp) {
const ise2 = this.generateISE2(challenge.msisdn, challenge.country);
this.otpChallenges.delete(challengeId);
return { success: true, ise2 };
}
return { success: false, error: 'Invalid OTP' };
}
getUserBalance(ise2) {
return (this.userBalances.get(ise2) || {
balance: 1000,
currency: 'XOF',
type: 'prepaid',
});
}
};
exports.MockDataService = MockDataService;
exports.MockDataService = MockDataService = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [])
], MockDataService);
//# sourceMappingURL=mock-data.service.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"mock-data.service.js","sourceRoot":"","sources":["../../src/services/mock-data.service.ts"],"names":[],"mappings":"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"}

5
dist/services/token.service.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
export declare class TokenService {
private tokens;
generateToken(): string;
validateToken(token: string): boolean;
}

44
dist/services/token.service.js vendored Normal file
View File

@ -0,0 +1,44 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TokenService = void 0;
const common_1 = require("@nestjs/common");
const crypto = require("crypto");
let TokenService = class TokenService {
constructor() {
this.tokens = new Map();
}
generateToken() {
const token = crypto.randomBytes(32).toString('hex');
this.tokens.set(token, {
createdAt: Date.now(),
expiresIn: 3600000,
});
return token;
}
validateToken(token) {
const tokenData = this.tokens.get(token);
if (!tokenData) {
if (token === 'test-token-valid') {
return true;
}
return false;
}
const now = Date.now();
if (now - tokenData.createdAt > tokenData.expiresIn) {
this.tokens.delete(token);
return false;
}
return true;
}
};
exports.TokenService = TokenService;
exports.TokenService = TokenService = __decorate([
(0, common_1.Injectable)()
], TokenService);
//# sourceMappingURL=token.service.js.map

1
dist/services/token.service.js.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"token.service.js","sourceRoot":"","sources":["../../src/services/token.service.ts"],"names":[],"mappings":";;;;;;;;;AACA,2CAA4C;AAC5C,iCAAiC;AAG1B,IAAM,YAAY,GAAlB,MAAM,YAAY;IAAlB;QACG,WAAM,GAAG,IAAI,GAAG,EAAoD,CAAC;IA8B/E,CAAC;IA5BC,aAAa;QACX,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACrD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE;YACrB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,SAAS,EAAE,OAAO;SACnB,CAAC,CAAC;QACH,OAAO,KAAK,CAAC;IACf,CAAC;IAED,aAAa,CAAC,KAAa;QACzB,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAEzC,IAAI,CAAC,SAAS,EAAE,CAAC;YAEf,IAAI,KAAK,KAAK,kBAAkB,EAAE,CAAC;gBACjC,OAAO,IAAI,CAAC;YACd,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,GAAG,GAAG,SAAS,CAAC,SAAS,GAAG,SAAS,CAAC,SAAS,EAAE,CAAC;YACpD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC1B,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AA/BY,oCAAY;uBAAZ,YAAY;IADxB,IAAA,mBAAU,GAAE;GACA,YAAY,CA+BxB"}

1
dist/tsconfig.build.tsbuildinfo vendored Normal file

File diff suppressed because one or more lines are too long

17
docker-compose.yml Normal file
View File

@ -0,0 +1,17 @@
# docker-compose.yml
version: '3.8'
services:
orange-mock-api:
build: .
ports:
- "3001:3001"
environment:
- NODE_ENV=development
- PORT=3001
volumes:
- ./src:/app/src
redis:
image: redis:alpine
ports:
- "6379:6379"

13
document.md Normal file
View File

@ -0,0 +1,13 @@
### Démarrer le mock:
docker-compose up
### Tester l'intégration:
npm run test
### env
ORANGE_MOCK_URL=http://localhost:3001
ORANGE_OAUTH_URL=http://localhost:3001/oauth/v3/token
ORANGE_API_URL=http://localhost:3001/payment/mea/v1
### Swagger
http://localhost:3000/api-docs

35
eslint.config.mjs Normal file
View File

@ -0,0 +1,35 @@
// @ts-check
import eslint from '@eslint/js';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import globals from 'globals';
import tseslint from 'typescript-eslint';
export default tseslint.config(
{
ignores: ['eslint.config.mjs'],
},
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
eslintPluginPrettierRecommended,
{
languageOptions: {
globals: {
...globals.node,
...globals.jest,
},
ecmaVersion: 5,
sourceType: 'module',
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
{
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-floating-promises': 'warn',
'@typescript-eslint/no-unsafe-argument': 'warn'
},
},
);

8
nest-cli.json Normal file
View File

@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}

11579
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

78
package.json Normal file
View File

@ -0,0 +1,78 @@
{
"name": "dcp-orange-mock",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test:integ": "ts-node ./test/test-integration.ts",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/common": "^11.0.1",
"@nestjs/core": "^11.0.1",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/swagger": "^11.2.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"swagger-ui-express": "^5.0.1",
"uuid": "^9.0.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.18.0",
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1",
"@swc/cli": "^0.6.0",
"@swc/core": "^1.10.7",
"@types/express": "^5.0.0",
"@types/jest": "^29.5.14",
"@types/node": "^22.10.7",
"@types/supertest": "^6.0.2",
"axios": "^1.4.0",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
"globals": "^15.14.0",
"jest": "^29.7.0",
"prettier": "^3.4.2",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.20.0"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

28
src/app.module.ts Normal file
View File

@ -0,0 +1,28 @@
import { AppService } from './app.service';
import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common';
import { AuthController } from './controllers/auth.controller';
import { SubscriptionController } from './controllers/subscription.controller';
import { BillingController } from './controllers/billing.controller';
import { SmsController } from './controllers/sms.controller';
import { OtpController } from './controllers/otp.controller';
import { MockDataService } from './services/mock-data.service';
import { TokenService } from './services/token.service';
import { AuthMiddleware } from './middleware/auth.middleware';
@Module({
imports: [],
controllers: [
AuthController,
SubscriptionController,
BillingController,
SmsController,
OtpController,
],
providers: [AppService, MockDataService, TokenService],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(AuthMiddleware).exclude('/oauth/v3/token').forRoutes('*');
}
}

8
src/app.service.ts Normal file
View File

@ -0,0 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}

View File

@ -0,0 +1,34 @@
// src/controllers/auth.controller.ts
import { Controller, Post, Body, Headers } from '@nestjs/common';
import { TokenService } from '../services/token.service';
@Controller('oauth/v3')
export class AuthController {
constructor(private tokenService: TokenService) {}
@Post('token')
generateToken(
@Headers('authorization') authHeader: string,
@Body('grant_type') grantType: string,
) {
// Validation basique du Basic Auth
if (!authHeader || !authHeader.startsWith('Basic ')) {
return {
error: 'invalid_client',
error_description: 'Invalid authentication credentials',
};
}
if (grantType !== 'client_credentials') {
return {
error: 'unsupported_grant_type',
error_description: 'Only client_credentials is supported',
};
}
// Générer un token mock
const token = this.tokenService.generateToken();
return {
token_type: 'Bearer',
access_token: token,
expires_in: 3600,
};
}
}

View File

@ -0,0 +1,59 @@
// src/controllers/billing.controller.ts
import { Controller, Post, Body, Headers, HttpException, HttpStatus } from '@nestjs/common';
import { MockDataService } from '../services/mock-data.service';
@Controller('payment/mea/v1')
export class BillingController {
constructor(private mockData: MockDataService) {}
@Post('acr:X-Orange-ISE2/transactions/amount')
chargeUser(
@Headers('x-orange-ise2') ise2: string,
@Headers('x-orange-mco') mco: string,
@Body() body: any,
) {
console.log('[MOCK] Charge request:', { ise2, mco, body });
const { amountTransaction } = body;
if (!amountTransaction) {
throw new HttpException({
requestError: {
serviceException: {
messageId: 'SVC0002',
text: 'Invalid input value for message part amountTransaction'
}
}
}, HttpStatus.BAD_REQUEST);
}
const amount = parseFloat(amountTransaction.paymentAmount.chargingInformation.amount);
// Simuler une transaction
const result = this.mockData.createTransaction({
endUserId: ise2,
amount,
...amountTransaction,
});
if (!result.success) {
throw new HttpException({
requestError: {
policyException: {
messageId: result.error.code,
text: result.error.message
}
}
}, HttpStatus.FORBIDDEN);
}
return {
amountTransaction: {
...amountTransaction,
serverReferenceCode: result.transaction.serverReferenceCode,
transactionOperationStatus: 'Charged',
resourceURL: `http://localhost:3001/payment/mea/v1/transactions/${result.transaction.serverReferenceCode}`
}
};
}
}

View File

@ -0,0 +1,94 @@
// src/controllers/otp.controller.ts
import { Controller, Post, Body, Param, HttpException, HttpStatus } from '@nestjs/common';
import { MockDataService } from '../services/mock-data.service';
@Controller('challenge/v1')
export class OtpController {
constructor(private mockData: MockDataService) {}
@Post('challenges')
createChallenge(@Body() body: any) {
console.log('[MOCK] OTP Challenge creation:', body);
const { challenge } = body;
if (!challenge || challenge.method !== 'OTP-SMS-AUTH') {
throw new HttpException({
error: {
code: 232740003,
message: 'Invalid input value',
description: 'Invalid challenge method'
}
}, HttpStatus.BAD_REQUEST);
}
const msisdnInput = challenge.inputs.find(i => i.type === 'MSISDN');
if (!msisdnInput) {
throw new HttpException({
error: {
code: 232740000,
message: 'Missing input',
description: 'MSISDN is required'
}
}, HttpStatus.BAD_REQUEST);
}
const challengeId = this.mockData.createOtpChallenge(
msisdnInput.value,
challenge.country
);
return {
challenge: {
...challenge,
result: []
},
location: `/challenge/v1/challenges/${challengeId}`
};
}
@Post('challenges/:id')
validateChallenge(
@Param('id') challengeId: string,
@Body() body: any,
) {
console.log('[MOCK] OTP Challenge validation:', { challengeId, body });
const { challenge } = body;
const otpInput = challenge.inputs.find(i => i.type === 'confirmationCode');
if (!otpInput || !otpInput.value) {
throw new HttpException({
error: {
code: 232740203,
message: 'Invalid challenge inputs',
description: 'OTP code is required'
}
}, HttpStatus.BAD_REQUEST);
}
const result = this.mockData.validateOtp(challengeId, otpInput.value);
if (!result.success) {
throw new HttpException({
error: {
code: 232740201,
message: 'Authorization denied',
description: result.error
}
}, HttpStatus.FORBIDDEN);
}
return {
challenge: {
...challenge,
result: [
{
type: 'ise2',
value: result.ise2
}
]
}
};
}
}

View File

@ -0,0 +1,52 @@
// src/controllers/sms.controller.ts
import {
Controller,
Post,
Body,
Headers,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { MockDataService } from '../services/mock-data.service';
@Controller('smsmessaging/service/mea/v1')
export class SmsController {
constructor(private mockData: MockDataService) {}
//@Post('outbound/tel:+:msisdn/requests') // Original line
@Post('/outbound/tel:msisdn/requests')
sendSms(
@Headers('x-orange-ise2') ise2: string,
@Headers('x-orange-mco') mco: string,
@Body() body: any,
) {
console.log('[MOCK] SMS MT request:', { ise2, mco, body });
const { outboundSMSMessageRequest } = body;
if (!outboundSMSMessageRequest) {
throw new HttpException(
{
requestError: {
serviceException: {
messageId: 'SVC0002',
text: 'Invalid request format',
},
},
},
HttpStatus.BAD_REQUEST,
);
}
// Simuler l'envoi du SMS
const messageId = `SMS-${Date.now()}-${Math.random().toString(36).substring(7)}`;
return {
outboundSMSMessageRequest: {
...outboundSMSMessageRequest,
clientCorrelator: messageId,
resourceURL: `http://localhost:3001/smsmessaging/v1/outbound/requests/${messageId}`,
},
};
}
}

View File

@ -0,0 +1,120 @@
// src/controllers/subscription.controller.ts
import {
Controller,
Post,
Get,
Delete,
Body,
Param,
Headers,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { MockDataService } from '../services/mock-data.service';
@Controller('payment/mea/v1')
export class SubscriptionController {
constructor(private mockData: MockDataService) {}
@Post('digipay_sub/productOrder')
createSubscription(
@Headers('x-orange-ise2') ise2: string,
@Headers('x-orange-mco') mco: string,
@Body() body: any,
) {
console.log('[MOCK] Création subscription:', { ise2, mco, body });
// Validation
if (!ise2 || !mco) {
throw new HttpException(
{
requestError: {
serviceException: {
messageId: '2001',
text: 'Missing required headers',
},
},
},
HttpStatus.BAD_REQUEST,
);
}
// Simuler une souscription existante (10% de chance)
if (Math.random() < 0.1) {
throw new HttpException(
{
requestError: {
serviceException: {
messageId: '3010',
text: 'The subscription already exists',
},
},
},
HttpStatus.CONFLICT,
);
}
const subscription = this.mockData.createSubscription(body);
// Simuler l'envoi d'une notification webhook (avec délai)
setTimeout(() => {
this.sendSubscriptionNotification(subscription, 'orderCreation');
}, 2000);
return subscription;
}
@Get('digipay_sub/productOrder/:id')
getSubscription(@Param('id') id: string) {
const subscription = this.mockData.getSubscription(id);
if (!subscription) {
throw new HttpException(
{
requestError: {
serviceException: {
messageId: '3011',
text: 'Subscription not found',
},
},
},
HttpStatus.NOT_FOUND,
);
}
return subscription;
}
@Delete('digipay_sub/productOrder/:id')
deleteSubscription(@Param('id') id: string) {
const deleted = this.mockData.deleteSubscription(id);
if (!deleted) {
throw new HttpException(
{
requestError: {
serviceException: {
messageId: '3011',
text: 'Subscription not found',
},
},
},
HttpStatus.NOT_FOUND,
);
}
// Simuler notification de désinscription
setTimeout(() => {
this.sendSubscriptionNotification({ id }, 'orderDeletion');
}, 1000);
return '';
}
private sendSubscriptionNotification(subscription: any, eventType: string) {
console.log(
`[MOCK] Notification ${eventType} pour subscription ${subscription.id}`,
);
// Dans un vrai mock, vous pourriez faire un HTTP POST vers l'URL de callback
}
}

View File

18
src/main.ts Normal file
View File

@ -0,0 +1,18 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// ⚡ Config Swagger
const config = new DocumentBuilder()
.setTitle('API DCB / SMS / Payment')
.setDescription('Documentation des endpoints mock Orange DCB')
.setVersion('1.0')
.addBearerAuth() // si tu veux tester avec un token
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api-docs', app, document);
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

View File

@ -0,0 +1,50 @@
// src/middleware/auth.middleware.ts
import {
Injectable,
NestMiddleware,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { TokenService } from '../services/token.service';
@Injectable()
export class AuthMiddleware implements NestMiddleware {
constructor(private tokenService: TokenService) {}
use(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers['authorization'];
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new HttpException(
{
requestError: {
serviceException: {
messageId: '401',
text: 'Missing or invalid authorization header',
},
},
},
HttpStatus.UNAUTHORIZED,
);
}
const token = authHeader.substring(7);
if (!this.tokenService.validateToken(token)) {
throw new HttpException(
{
requestError: {
serviceException: {
messageId: '401',
text: 'Invalid or expired token',
},
},
},
HttpStatus.UNAUTHORIZED,
);
}
next();
}
}

View File

@ -0,0 +1,157 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
// src/services/mock-data.service.ts
import { Injectable } from '@nestjs/common';
import { v4 as uuidv4 } from 'uuid';
@Injectable()
export class MockDataService {
private subscriptions = new Map();
private transactions = new Map();
private otpChallenges = new Map();
private userBalances = new Map();
constructor() {
this.initializeMockData();
}
private initializeMockData() {
// Initialiser quelques utilisateurs mock
this.userBalances.set(
'PDKSUB-200-Q82vHq0+F1WozTeNS/1wBfuULco05YBaeL4yPtJ8ktU=',
{
balance: 10000,
currency: 'CDF',
type: 'prepaid',
},
);
this.userBalances.set(
'PDKSUB-200-8Ow1iM0hLPZ+LGZ8j4uwEdQxY1hm4mVwrzTdWiUnuI=',
{
balance: 5000,
currency: 'TND',
type: 'prepaid',
},
);
}
// Génération d'ISE2 mock
generateISE2(msisdn: string, country: string): string {
const base = `PDKSUB-200-`;
const hash = Buffer.from(`${msisdn}-${country}-${Date.now()}`).toString(
'base64',
);
return base + hash.substring(0, 40);
}
// Créer une souscription
createSubscription(data: any): any {
const subscriptionId = uuidv4();
const subscription = {
id: subscriptionId,
state: 'Completed',
orderDate: new Date().toISOString(),
...data,
};
this.subscriptions.set(subscriptionId, subscription);
return subscription;
}
// Récupérer une souscription
getSubscription(id: string): any {
return this.subscriptions.get(id);
}
// Supprimer une souscription
deleteSubscription(id: string): boolean {
return this.subscriptions.delete(id);
}
// Créer une transaction de paiement
createTransaction(data: any): any {
const transactionId = uuidv4();
const userBalance = this.getUserBalance(data.endUserId);
if (userBalance && userBalance.balance >= data.amount) {
// Déduire le montant
userBalance.balance -= data.amount;
const transaction = {
serverReferenceCode: transactionId,
transactionOperationStatus: 'Charged',
...data,
};
this.transactions.set(transactionId, transaction);
return { success: true, transaction };
}
return {
success: false,
error: {
code: 'POL1000',
message: 'User has insufficient credit for transaction',
},
};
}
// Créer un challenge OTP
createOtpChallenge(msisdn: string, country: string): any {
const challengeId = uuidv4();
const otp = Math.floor(1000 + Math.random() * 9000).toString();
const challenge = {
id: challengeId,
msisdn,
country,
otp,
createdAt: Date.now(),
attempts: 0,
};
this.otpChallenges.set(challengeId, challenge);
console.log(`[MOCK] OTP généré pour ${msisdn}: ${otp}`);
return challengeId;
}
// Valider un OTP
validateOtp(challengeId: string, inputOtp: string): any {
const challenge = this.otpChallenges.get(challengeId);
if (!challenge) {
return { success: false, error: 'Challenge not found' };
}
challenge.attempts++;
if (challenge.attempts > 3) {
return { success: false, error: 'Too many retries' };
}
if (Date.now() - challenge.createdAt > 300000) {
// 5 minutes
return { success: false, error: 'OTP expired' };
}
if (challenge.otp === inputOtp) {
const ise2 = this.generateISE2(challenge.msisdn, challenge.country);
this.otpChallenges.delete(challengeId);
return { success: true, ise2 };
}
return { success: false, error: 'Invalid OTP' };
}
getUserBalance(ise2: string): any {
return (
this.userBalances.get(ise2) || {
balance: 1000,
currency: 'XOF',
type: 'prepaid',
}
);
}
}

View File

@ -0,0 +1,37 @@
// src/services/token.service.ts
import { Injectable } from '@nestjs/common';
import * as crypto from 'crypto';
@Injectable()
export class TokenService {
private tokens = new Map<string, { createdAt: number; expiresIn: number }>();
generateToken(): string {
const token = crypto.randomBytes(32).toString('hex');
this.tokens.set(token, {
createdAt: Date.now(),
expiresIn: 3600000, // 1 heure
});
return token;
}
validateToken(token: string): boolean {
const tokenData = this.tokens.get(token);
if (!tokenData) {
// Pour le mock, accepter certains tokens de test
if (token === 'test-token-valid') {
return true;
}
return false;
}
const now = Date.now();
if (now - tokenData.createdAt > tokenData.expiresIn) {
this.tokens.delete(token);
return false;
}
return true;
}
}

25
test/app.e2e-spec.ts Normal file
View File

@ -0,0 +1,25 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { App } from 'supertest/types';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication<App>;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});

9
test/jest-e2e.json Normal file
View File

@ -0,0 +1,9 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}

122
test/test-integration.ts Normal file
View File

@ -0,0 +1,122 @@
// test-integration.ts
import axios from 'axios';
const BASE_URL = 'http://localhost:3000';
let accessToken = '';
async function testOAuth() {
console.log('1. Test OAuth Token Generation...');
const response = await axios.post(`${BASE_URL}/oauth/v3/token`,
'grant_type=client_credentials',
{
headers: {
'Authorization': 'Basic ' + Buffer.from('clientId:clientSecret').toString('base64'),
'Content-Type': 'application/x-www-form-urlencoded'
}
}
);
accessToken = response.data.access_token;
console.log('✅ Token obtenu:', accessToken);
return accessToken;
}
async function testSubscription() {
console.log('\n2. Test Subscription...');
const response = await axios.post(
`${BASE_URL}/payment/mea/v1/digipay_sub/productOrder`,
{
note: { text: 'test data' },
relatedPublicKey: {
id: 'PDKSUB-200-test123',
name: 'ISE2',
},
relatedParty: [
{
id: 'TESTPARTNER',
name: 'Test Partner',
role: 'partner'
},
{
id: 'TESTRETAILER',
name: 'Test Retailer',
role: 'retailer'
}
],
orderItem: {
action: 'add',
state: 'Completed',
product: {
id: 'TEST_PRODUCT',
productCharacteristic: [
{ name: 'amount', value: '100.0' },
{ name: 'currency', value: 'XOF' },
{ name: 'periodicity', value: '86400' }
]
}
}
},
{
headers: {
'Authorization': `Bearer ${accessToken}`,
'X-Orange-ISE2': 'PDKSUB-200-test123',
'X-Orange-MCO': 'OCI',
'Content-Type': 'application/json'
}
}
);
console.log('✅ Subscription créée:', response.data.id);
return response.data.id;
}
async function testCharge() {
console.log('\n3. Test Charge...');
const response = await axios.post(
`${BASE_URL}/payment/mea/v1/acr:X-Orange-ISE2/transactions/amount`,
{
amountTransaction: {
endUserId: 'acr:X-Orange-ISE2',
paymentAmount: {
chargingInformation: {
amount: '10.0',
currency: 'XOF',
description: 'Test charge'
},
chargingMetaData: {
onBehalfOf: 'TESTRETAILER',
purchaseCategoryCode: 'VOD',
serviceId: 'TEST_SERVICE'
}
},
transactionOperationStatus: 'Charged',
referenceCode: 'ref123',
clientCorrelator: 'corr123'
}
},
{
headers: {
'Authorization': `Bearer ${accessToken}`,
'X-Orange-ISE2': 'PDKSUB-200-Q82vHq0+F1WozTeNS/1wBfuULco05YBaeL4yPtJ8ktU=',
'X-Orange-MCO': 'OCI',
'Content-Type': 'application/json'
}
}
);
console.log('✅ Charge effectuée:', response.data.amountTransaction.serverReferenceCode);
}
// Exécuter les tests
async function runTests() {
try {
await testOAuth();
const subscriptionId = await testSubscription();
await testCharge();
console.log('\n✅ Tous les tests sont passés!');
} catch (error) {
console.error('❌ Erreur:', error.response?.data || error.message);
}
}
runTests();

4
tsconfig.build.json Normal file
View File

@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

21
tsconfig.json Normal file
View File

@ -0,0 +1,21 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2021",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": false,
"strictBindCallApply": false,
"noFallthroughCasesInSwitch": false
}
}