AnhLedger commited on
Commit
f4866cb
1 Parent(s): ed20d60

create payment url

Browse files
backend/.env.example CHANGED
@@ -4,4 +4,9 @@ DB_USER='pbl6_jw8s_user'
4
  DB_PASSWORD=''
5
  DB_NAME='pbl6_jw8s'
6
  JWT_KEY= ''
7
- DB_SSL_ENABLED=true # default is true to connect with remote database
 
 
 
 
 
 
4
  DB_PASSWORD=''
5
  DB_NAME='pbl6_jw8s'
6
  JWT_KEY= ''
7
+ DB_SSL_ENABLED=true # default is true to connect with remote database
8
+ #Payment
9
+ VNP_TMNCODE = ''
10
+ VNP_HASHSECRET = ''
11
+ VNP_URL = ''
12
+ VNP_RETURNURL = ''
backend/package-lock.json CHANGED
@@ -20,6 +20,7 @@
20
  "bcrypt": "^5.1.1",
21
  "class-transformer": "^0.5.1",
22
  "class-validator": "^0.14.1",
 
23
  "dotenv": "^16.4.5",
24
  "mysql2": "^3.11.3",
25
  "nest-access-control": "^3.1.0",
@@ -36,8 +37,10 @@
36
  "@nestjs/cli": "^10.0.0",
37
  "@nestjs/schematics": "^10.0.0",
38
  "@nestjs/testing": "^10.0.0",
 
39
  "@types/express": "^4.17.17",
40
  "@types/jest": "^29.5.2",
 
41
  "@types/node": "^20.3.1",
42
  "@types/supertest": "^6.0.0",
43
  "@typescript-eslint/eslint-plugin": "^8.0.0",
@@ -2144,6 +2147,13 @@
2144
  "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==",
2145
  "dev": true
2146
  },
 
 
 
 
 
 
 
2147
  "node_modules/@types/estree": {
2148
  "version": "1.0.5",
2149
  "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
@@ -2250,6 +2260,16 @@
2250
  "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
2251
  "dev": true
2252
  },
 
 
 
 
 
 
 
 
 
 
2253
  "node_modules/@types/node": {
2254
  "version": "20.16.5",
2255
  "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz",
@@ -3933,6 +3953,15 @@
3933
  "node": ">= 8"
3934
  }
3935
  },
 
 
 
 
 
 
 
 
 
3936
  "node_modules/dayjs": {
3937
  "version": "1.11.13",
3938
  "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
 
20
  "bcrypt": "^5.1.1",
21
  "class-transformer": "^0.5.1",
22
  "class-validator": "^0.14.1",
23
+ "dateformat": "^5.0.3",
24
  "dotenv": "^16.4.5",
25
  "mysql2": "^3.11.3",
26
  "nest-access-control": "^3.1.0",
 
37
  "@nestjs/cli": "^10.0.0",
38
  "@nestjs/schematics": "^10.0.0",
39
  "@nestjs/testing": "^10.0.0",
40
+ "@types/dateformat": "^5.0.2",
41
  "@types/express": "^4.17.17",
42
  "@types/jest": "^29.5.2",
43
+ "@types/multer": "^1.4.12",
44
  "@types/node": "^20.3.1",
45
  "@types/supertest": "^6.0.0",
46
  "@typescript-eslint/eslint-plugin": "^8.0.0",
 
2147
  "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==",
2148
  "dev": true
2149
  },
2150
+ "node_modules/@types/dateformat": {
2151
+ "version": "5.0.2",
2152
+ "resolved": "https://registry.npmjs.org/@types/dateformat/-/dateformat-5.0.2.tgz",
2153
+ "integrity": "sha512-M95hNBMa/hnwErH+a+VOD/sYgTmo15OTYTM2Hr52/e0OdOuY+Crag+kd3/ioZrhg0WGbl9Sm3hR7UU+MH6rfOw==",
2154
+ "dev": true,
2155
+ "license": "MIT"
2156
+ },
2157
  "node_modules/@types/estree": {
2158
  "version": "1.0.5",
2159
  "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
 
2260
  "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
2261
  "dev": true
2262
  },
2263
+ "node_modules/@types/multer": {
2264
+ "version": "1.4.12",
2265
+ "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.12.tgz",
2266
+ "integrity": "sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==",
2267
+ "dev": true,
2268
+ "license": "MIT",
2269
+ "dependencies": {
2270
+ "@types/express": "*"
2271
+ }
2272
+ },
2273
  "node_modules/@types/node": {
2274
  "version": "20.16.5",
2275
  "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz",
 
3953
  "node": ">= 8"
3954
  }
3955
  },
3956
+ "node_modules/dateformat": {
3957
+ "version": "5.0.3",
3958
+ "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-5.0.3.tgz",
3959
+ "integrity": "sha512-Kvr6HmPXUMerlLcLF+Pwq3K7apHpYmGDVqrxcDasBg86UcKeTSNWbEzU8bwdXnxnR44FtMhJAxI4Bov6Y/KUfA==",
3960
+ "license": "MIT",
3961
+ "engines": {
3962
+ "node": ">=12.20"
3963
+ }
3964
+ },
3965
  "node_modules/dayjs": {
3966
  "version": "1.11.13",
3967
  "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
backend/package.json CHANGED
@@ -36,6 +36,7 @@
36
  "bcrypt": "^5.1.1",
37
  "class-transformer": "^0.5.1",
38
  "class-validator": "^0.14.1",
 
39
  "dotenv": "^16.4.5",
40
  "mysql2": "^3.11.3",
41
  "nest-access-control": "^3.1.0",
@@ -52,8 +53,10 @@
52
  "@nestjs/cli": "^10.0.0",
53
  "@nestjs/schematics": "^10.0.0",
54
  "@nestjs/testing": "^10.0.0",
 
55
  "@types/express": "^4.17.17",
56
  "@types/jest": "^29.5.2",
 
57
  "@types/node": "^20.3.1",
58
  "@types/supertest": "^6.0.0",
59
  "@typescript-eslint/eslint-plugin": "^8.0.0",
 
36
  "bcrypt": "^5.1.1",
37
  "class-transformer": "^0.5.1",
38
  "class-validator": "^0.14.1",
39
+ "dateformat": "^5.0.3",
40
  "dotenv": "^16.4.5",
41
  "mysql2": "^3.11.3",
42
  "nest-access-control": "^3.1.0",
 
53
  "@nestjs/cli": "^10.0.0",
54
  "@nestjs/schematics": "^10.0.0",
55
  "@nestjs/testing": "^10.0.0",
56
+ "@types/dateformat": "^5.0.2",
57
  "@types/express": "^4.17.17",
58
  "@types/jest": "^29.5.2",
59
+ "@types/multer": "^1.4.12",
60
  "@types/node": "^20.3.1",
61
  "@types/supertest": "^6.0.0",
62
  "@typescript-eslint/eslint-plugin": "^8.0.0",
backend/src/config/config.ts CHANGED
@@ -8,5 +8,10 @@ export const configuration = () => {
8
  'db.name': process.env.DB_NAME,
9
  'db.slow_limit': Number(process.env.DB_SLOW_LIMIT) || 500, // ms
10
  'db.ssl_enabled': process.env.DB_SSL_ENABLED,
 
 
 
 
 
11
  };
12
  };
 
8
  'db.name': process.env.DB_NAME,
9
  'db.slow_limit': Number(process.env.DB_SLOW_LIMIT) || 500, // ms
10
  'db.ssl_enabled': process.env.DB_SSL_ENABLED,
11
+ // payment config
12
+ 'vnp_TmnCode': process.env.VNP_TMNCODE,
13
+ 'vnp_HashSecret': process.env.VNP_HASHSECRET,
14
+ 'vnp_Url': process.env.VNP_URL,
15
+ 'vnp_ReturnUrl': process.env.VNP_RETURNURL,
16
  };
17
  };
backend/src/payment/dto/create-payment.dto.ts ADDED
@@ -0,0 +1 @@
 
 
1
+ export class CreatePaymentDto {}
backend/src/payment/dto/update-payment.dto.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ import { PartialType } from '@nestjs/mapped-types';
2
+ import { CreatePaymentDto } from './create-payment.dto';
3
+
4
+ export class UpdatePaymentDto extends PartialType(CreatePaymentDto) {}
backend/src/payment/entities/payment.entity.ts ADDED
@@ -0,0 +1 @@
 
 
1
+ export class Payment {}
backend/src/payment/payment.controller.spec.ts ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { PaymentController } from './payment.controller';
3
+ import { PaymentService } from './payment.service';
4
+
5
+ describe('PaymentController', () => {
6
+ let controller: PaymentController;
7
+
8
+ beforeEach(async () => {
9
+ const module: TestingModule = await Test.createTestingModule({
10
+ controllers: [PaymentController],
11
+ providers: [PaymentService],
12
+ }).compile();
13
+
14
+ controller = module.get<PaymentController>(PaymentController);
15
+ });
16
+
17
+ it('should be defined', () => {
18
+ expect(controller).toBeDefined();
19
+ });
20
+ });
backend/src/payment/payment.controller.ts ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // payment.controller.ts
2
+ import { Controller, Post, Body, Req, Res } from '@nestjs/common';
3
+ import { PaymentService } from './payment.service.js';
4
+ import { Request, Response } from 'express';
5
+ import { Public } from '../modules/authentication/authentication.decorator.js';
6
+
7
+ @Controller('payment')
8
+ export class PaymentController {
9
+ constructor(private readonly paymentService: PaymentService) {}
10
+
11
+ @Public()
12
+ @Post('create_payment_url')
13
+ async createPaymentUrl(@Req() req: Request, @Body() body: any) {
14
+ console.log("hello")
15
+ const ipAddr =
16
+ req.headers['x-forwarded-for'] ||
17
+ req.socket.remoteAddress ||
18
+ req.socket?.remoteAddress;
19
+ console.log(ipAddr);
20
+ return await this.paymentService.createPaymentUrl(
21
+ body.amount,
22
+ body.bankCode,
23
+ body.orderDescription,
24
+ body.orderType,
25
+ body.language,
26
+ ipAddr as string,
27
+ );
28
+ }
29
+ }
backend/src/payment/payment.module.ts ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import { Module } from '@nestjs/common';
2
+ import { PaymentService } from './payment.service.js';
3
+ import { PaymentController } from './payment.controller.js';
4
+
5
+ @Module({
6
+ controllers: [PaymentController],
7
+ providers: [PaymentService],
8
+ })
9
+ export class PaymentModule {}
backend/src/payment/payment.service.spec.ts ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { PaymentService } from './payment.service';
3
+
4
+ describe('PaymentService', () => {
5
+ let service: PaymentService;
6
+
7
+ beforeEach(async () => {
8
+ const module: TestingModule = await Test.createTestingModule({
9
+ providers: [PaymentService],
10
+ }).compile();
11
+
12
+ service = module.get<PaymentService>(PaymentService);
13
+ });
14
+
15
+ it('should be defined', () => {
16
+ expect(service).toBeDefined();
17
+ });
18
+ });
backend/src/payment/payment.service.ts ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // payment.service.ts
2
+ import { Injectable } from '@nestjs/common';
3
+ import { ConfigService } from '@nestjs/config';
4
+ import * as querystring from 'qs';
5
+ import * as crypto from 'crypto';
6
+
7
+ @Injectable()
8
+ export class PaymentService {
9
+ constructor(private readonly configService: ConfigService) {}
10
+
11
+ async createPaymentUrl(amount: number, bankCode: string, orderDescription: string, orderType: string, language: string, ipAddr: string) {
12
+ console.log("hi")
13
+ const tmnCode = this.configService.get<string>('vnp_TmnCode');
14
+ const secretKey = this.configService.get<string>('vnp_HashSecret');
15
+ const vnpUrl = this.configService.get<string>('vnp_Url');
16
+ const returnUrl = this.configService.get<string>('vnp_ReturnUrl');
17
+ console.log("1")
18
+ const date = new Date();
19
+ const createDate = this.formatDate(date, 'yyyymmddHHmmss');
20
+ const orderId = "5638"
21
+ const locale = language || 'vn';
22
+ const currCode = 'VND';
23
+ console.log("2")
24
+ const vnp_Params: Record<string, string> = {
25
+ vnp_Version: '2.1.0',
26
+ vnp_Command: 'pay',
27
+ vnp_TmnCode: tmnCode,
28
+ vnp_Locale: locale,
29
+ vnp_CurrCode: currCode,
30
+ vnp_TxnRef: orderId,
31
+ vnp_OrderInfo: orderDescription,
32
+ vnp_OrderType: orderType,
33
+ vnp_Amount: (amount * 100).toString(),
34
+ vnp_ReturnUrl: returnUrl,
35
+ vnp_IpAddr: ipAddr,
36
+ vnp_CreateDate: createDate,
37
+ };
38
+ console.log("3")
39
+ if (bankCode) {
40
+ vnp_Params['vnp_BankCode'] = bankCode;
41
+ }
42
+
43
+ // Sort the parameters
44
+ // const sortedParams = Object.keys(vnp_Params)
45
+ // .sort()
46
+ // .reduce((acc, key) => {
47
+ // acc[key] = vnp_Params[key];
48
+ // return acc;
49
+ // }, {} as Record<string, string>);
50
+ const sortedParams = this.sortObject(vnp_Params);
51
+ console.log("4")
52
+ // Sign the data
53
+ const signData = querystring.stringify(sortedParams, { encode: false });
54
+ const hmac = crypto.createHmac('sha512', secretKey);
55
+ const signed = hmac.update(Buffer.from(signData, 'utf-8')).digest('hex');
56
+ sortedParams['vnp_SecureHash'] = signed;
57
+ console.log("5")
58
+ // Create the URL
59
+ const res = `${vnpUrl}?${querystring.stringify(sortedParams, { encode: false })}`;
60
+ console.log("6");
61
+ return res;
62
+ }
63
+ // Format date helper function
64
+ formatDate(date: Date, format: string): string {
65
+ const yyyymmdd = date.toISOString().slice(0, 10).replace(/-/g, ''); // YYYYMMDD
66
+ const hhmmss = date.toTimeString().slice(0, 8).replace(/:/g, ''); // HHMMSS
67
+ return format === 'yyyymmddHHmmss' ? yyyymmdd + hhmmss : hhmmss;
68
+ }
69
+
70
+ sortObject(obj) {
71
+ let sorted = {};
72
+ let str = [];
73
+ let key;
74
+ for (key in obj){
75
+ if (obj.hasOwnProperty(key)) {
76
+ str.push(encodeURIComponent(key));
77
+ }
78
+ }
79
+ str.sort();
80
+ for (key = 0; key < str.length; key++) {
81
+ sorted[str[key]] = encodeURIComponent(obj[str[key]]).replace(/%20/g, "+");
82
+ }
83
+ return sorted;
84
+ }
85
+ }