Spaces:
Sleeping
Sleeping
Trần Viết Sơn
commited on
Commit
•
463569a
1
Parent(s):
069905f
feat: order
Browse files- backend/src/common/enums/ReceiptType.enum.ts +4 -0
- backend/src/modules/order/dto/update-order.dto.ts +19 -1
- backend/src/modules/order/order.controller.ts +3 -0
- backend/src/modules/order/order.module.ts +4 -2
- backend/src/modules/order/order.service.ts +113 -2
- backend/src/payment/payment.module.ts +6 -3
- backend/src/payment/payment.service.ts +44 -36
backend/src/common/enums/ReceiptType.enum.ts
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export enum ReceiptType {
|
2 |
+
ORDER = 0,
|
3 |
+
PAYMENT = 1,
|
4 |
+
}
|
backend/src/modules/order/dto/update-order.dto.ts
CHANGED
@@ -1,4 +1,10 @@
|
|
1 |
-
import {
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
import { OrderItemsDto } from './order-items.dto.js';
|
3 |
import { Type } from 'class-transformer';
|
4 |
|
@@ -11,4 +17,16 @@ export class UpdateOrderDto {
|
|
11 |
@ValidateNested()
|
12 |
@Type(() => OrderItemsDto)
|
13 |
order_items: OrderItemsDto[];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
}
|
|
|
1 |
+
import {
|
2 |
+
IsArray,
|
3 |
+
IsNumber,
|
4 |
+
IsOptional,
|
5 |
+
IsString,
|
6 |
+
ValidateNested,
|
7 |
+
} from 'class-validator';
|
8 |
import { OrderItemsDto } from './order-items.dto.js';
|
9 |
import { Type } from 'class-transformer';
|
10 |
|
|
|
17 |
@ValidateNested()
|
18 |
@Type(() => OrderItemsDto)
|
19 |
order_items: OrderItemsDto[];
|
20 |
+
|
21 |
+
@IsOptional()
|
22 |
+
@IsNumber()
|
23 |
+
order_status?: number;
|
24 |
+
|
25 |
+
@IsOptional()
|
26 |
+
@IsNumber()
|
27 |
+
rating?: number;
|
28 |
+
|
29 |
+
@IsOptional()
|
30 |
+
@IsString()
|
31 |
+
note?: string;
|
32 |
}
|
backend/src/modules/order/order.controller.ts
CHANGED
@@ -14,6 +14,7 @@ import { Role } from '../../common/enums/role.enum.js';
|
|
14 |
import { paginate, Paginate, PaginateQuery } from 'nestjs-paginate';
|
15 |
import { Roles } from '../authentication/authorization/roles.decorator.js';
|
16 |
import { UpdateOrderDto } from './dto/update-order.dto.js';
|
|
|
17 |
|
18 |
@Controller('branchs/:branchId/orders')
|
19 |
export class BranchOrderController {
|
@@ -25,6 +26,7 @@ export class BranchOrderController {
|
|
25 |
@Req() req: Request,
|
26 |
@Body() createOrderDto: CreateOrderDto,
|
27 |
) {
|
|
|
28 |
const userId = req['user'].sub;
|
29 |
const role = req['user'].role;
|
30 |
console.log(req['user']);
|
@@ -34,6 +36,7 @@ export class BranchOrderController {
|
|
34 |
branchId,
|
35 |
userId,
|
36 |
createOrderDto,
|
|
|
37 |
);
|
38 |
} else
|
39 |
return this.orderService.createFromStaff(
|
|
|
14 |
import { paginate, Paginate, PaginateQuery } from 'nestjs-paginate';
|
15 |
import { Roles } from '../authentication/authorization/roles.decorator.js';
|
16 |
import { UpdateOrderDto } from './dto/update-order.dto.js';
|
17 |
+
import { Request } from 'express';
|
18 |
|
19 |
@Controller('branchs/:branchId/orders')
|
20 |
export class BranchOrderController {
|
|
|
26 |
@Req() req: Request,
|
27 |
@Body() createOrderDto: CreateOrderDto,
|
28 |
) {
|
29 |
+
const ipAddr = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
|
30 |
const userId = req['user'].sub;
|
31 |
const role = req['user'].role;
|
32 |
console.log(req['user']);
|
|
|
36 |
branchId,
|
37 |
userId,
|
38 |
createOrderDto,
|
39 |
+
ipAddr as string,
|
40 |
);
|
41 |
} else
|
42 |
return this.orderService.createFromStaff(
|
backend/src/modules/order/order.module.ts
CHANGED
@@ -1,13 +1,15 @@
|
|
1 |
-
import { Module } from '@nestjs/common';
|
2 |
import { OrderService } from './order.service.js';
|
3 |
import { BranchOrderController, OrderController } from './order.controller.js';
|
4 |
import { BranchModule } from '../branch/branch.module.js';
|
5 |
import { BranchMenusModule } from '../branch-menus/branch-menus.module.js';
|
|
|
|
|
6 |
|
7 |
@Module({
|
8 |
imports: [BranchModule, BranchMenusModule],
|
9 |
controllers: [BranchOrderController, OrderController],
|
10 |
-
providers: [OrderService],
|
11 |
exports: [OrderService],
|
12 |
})
|
13 |
export class OrderModule {}
|
|
|
1 |
+
import { forwardRef, Module } from '@nestjs/common';
|
2 |
import { OrderService } from './order.service.js';
|
3 |
import { BranchOrderController, OrderController } from './order.controller.js';
|
4 |
import { BranchModule } from '../branch/branch.module.js';
|
5 |
import { BranchMenusModule } from '../branch-menus/branch-menus.module.js';
|
6 |
+
import { PaymentModule } from '../../payment/payment.module.js';
|
7 |
+
import { PaymentService } from '../../payment/payment.service.js';
|
8 |
|
9 |
@Module({
|
10 |
imports: [BranchModule, BranchMenusModule],
|
11 |
controllers: [BranchOrderController, OrderController],
|
12 |
+
providers: [OrderService, PaymentService],
|
13 |
exports: [OrderService],
|
14 |
})
|
15 |
export class OrderModule {}
|
backend/src/modules/order/order.service.ts
CHANGED
@@ -18,21 +18,99 @@ import {
|
|
18 |
PaginateConfig,
|
19 |
PaginateQuery,
|
20 |
} from 'nestjs-paginate';
|
|
|
|
|
21 |
import { UpdateOrderDto } from './dto/update-order.dto.js';
|
22 |
import { DataSource } from 'typeorm';
|
|
|
|
|
|
|
|
|
23 |
|
24 |
@Injectable()
|
25 |
export class OrderService {
|
26 |
constructor(
|
|
|
|
|
27 |
private readonly branchService: BranchService,
|
28 |
private readonly datasource: DataSource,
|
|
|
29 |
) {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
async createFromCustomer(
|
31 |
branchId: string,
|
32 |
userId: string,
|
33 |
createOrderDto: CreateOrderDto,
|
|
|
34 |
) {
|
35 |
-
//
|
36 |
if (createOrderDto.order_type != OrderType.ONLINE) {
|
37 |
throw new BadRequestException('customer cannot create offline order');
|
38 |
}
|
@@ -93,8 +171,18 @@ export class OrderService {
|
|
93 |
|
94 |
await queryRunner.commitTransaction();
|
95 |
await queryRunner.release();
|
96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
97 |
} catch (err) {
|
|
|
98 |
await queryRunner.rollbackTransaction();
|
99 |
await queryRunner.release();
|
100 |
throw err;
|
@@ -197,6 +285,29 @@ export class OrderService {
|
|
197 |
// Cập nhật trạng thái
|
198 |
// Cập nhật rating
|
199 |
// Cập nhật note
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
200 |
}
|
201 |
|
202 |
async updateOrderPayment(id: number, paymentId: number) {
|
|
|
18 |
PaginateConfig,
|
19 |
PaginateQuery,
|
20 |
} from 'nestjs-paginate';
|
21 |
+
import * as querystring from 'qs';
|
22 |
+
import * as crypto from 'crypto';
|
23 |
import { UpdateOrderDto } from './dto/update-order.dto.js';
|
24 |
import { DataSource } from 'typeorm';
|
25 |
+
import { PaymentService } from '../../payment/payment.service.js';
|
26 |
+
import { ConfigService } from '@nestjs/config';
|
27 |
+
import { ReceiptEntity } from '../../entities/receipt.entity.js';
|
28 |
+
import { ReceiptType } from '../../common/enums/ReceiptType.enum.js';
|
29 |
|
30 |
@Injectable()
|
31 |
export class OrderService {
|
32 |
constructor(
|
33 |
+
// @Inject(forwardRef(() => PaymentService))
|
34 |
+
// private readonly paymentService: PaymentService,
|
35 |
private readonly branchService: BranchService,
|
36 |
private readonly datasource: DataSource,
|
37 |
+
private readonly configService: ConfigService,
|
38 |
) {}
|
39 |
+
formatDate(date: Date, format: string): string {
|
40 |
+
const yyyymmdd = date.toISOString().slice(0, 10).replace(/-/g, ''); // YYYYMMDD
|
41 |
+
const hhmmss = date.toTimeString().slice(0, 8).replace(/:/g, ''); // HHMMSS
|
42 |
+
return format === 'yyyymmddHHmmss' ? yyyymmdd + hhmmss : hhmmss;
|
43 |
+
}
|
44 |
+
|
45 |
+
sortObject(obj) {
|
46 |
+
let sorted = {};
|
47 |
+
let str = [];
|
48 |
+
let key;
|
49 |
+
for (key in obj) {
|
50 |
+
if (obj.hasOwnProperty(key)) {
|
51 |
+
str.push(encodeURIComponent(key));
|
52 |
+
}
|
53 |
+
}
|
54 |
+
str.sort();
|
55 |
+
for (key = 0; key < str.length; key++) {
|
56 |
+
sorted[str[key]] = encodeURIComponent(obj[str[key]]).replace(/%20/g, '+');
|
57 |
+
}
|
58 |
+
return sorted;
|
59 |
+
}
|
60 |
+
async createPaymentUrl(
|
61 |
+
amount: number,
|
62 |
+
orderId: string,
|
63 |
+
orderDescription: string,
|
64 |
+
orderType: string,
|
65 |
+
language: string,
|
66 |
+
ipAddr: string,
|
67 |
+
) {
|
68 |
+
const tmnCode = this.configService.get<string>('vnp_TmnCode');
|
69 |
+
const secretKey = this.configService.get<string>('vnp_HashSecret');
|
70 |
+
const vnpUrl = this.configService.get<string>('vnp_Url');
|
71 |
+
const returnUrl = this.configService.get<string>('vnp_ReturnUrl');
|
72 |
+
|
73 |
+
const date = new Date();
|
74 |
+
const createDate = this.formatDate(date, 'yyyymmddHHmmss');
|
75 |
+
const locale = language || 'vn';
|
76 |
+
const currCode = 'VND';
|
77 |
+
|
78 |
+
const vnp_Params: Record<string, string> = {
|
79 |
+
vnp_Version: '2.1.0',
|
80 |
+
vnp_Command: 'pay',
|
81 |
+
vnp_TmnCode: tmnCode,
|
82 |
+
vnp_Locale: locale,
|
83 |
+
vnp_CurrCode: currCode,
|
84 |
+
vnp_TxnRef: orderId,
|
85 |
+
vnp_OrderInfo: orderDescription,
|
86 |
+
vnp_OrderType: orderType,
|
87 |
+
vnp_Amount: (amount * 100).toString(),
|
88 |
+
vnp_ReturnUrl: returnUrl,
|
89 |
+
vnp_IpAddr: ipAddr,
|
90 |
+
vnp_CreateDate: createDate,
|
91 |
+
};
|
92 |
+
console.log('3');
|
93 |
+
|
94 |
+
const sortedParams = this.sortObject(vnp_Params);
|
95 |
+
|
96 |
+
// Sign the data
|
97 |
+
const signData = querystring.stringify(sortedParams, { encode: false });
|
98 |
+
const hmac = crypto.createHmac('sha512', secretKey);
|
99 |
+
const signed = hmac.update(Buffer.from(signData, 'utf-8')).digest('hex');
|
100 |
+
sortedParams['vnp_SecureHash'] = signed;
|
101 |
+
|
102 |
+
// Create the URL
|
103 |
+
const res = `${vnpUrl}?${querystring.stringify(sortedParams, { encode: false })}`;
|
104 |
+
|
105 |
+
return res;
|
106 |
+
}
|
107 |
async createFromCustomer(
|
108 |
branchId: string,
|
109 |
userId: string,
|
110 |
createOrderDto: CreateOrderDto,
|
111 |
+
ipAddr: string,
|
112 |
) {
|
113 |
+
// Tạo order đơn.
|
114 |
if (createOrderDto.order_type != OrderType.ONLINE) {
|
115 |
throw new BadRequestException('customer cannot create offline order');
|
116 |
}
|
|
|
171 |
|
172 |
await queryRunner.commitTransaction();
|
173 |
await queryRunner.release();
|
174 |
+
// hoàn thành tạo order, lấy url thanh toán
|
175 |
+
const paymentUrl = await this.createPaymentUrl(
|
176 |
+
order.total_value,
|
177 |
+
'1731148499',
|
178 |
+
order.note || 'what',
|
179 |
+
'other',
|
180 |
+
'vn',
|
181 |
+
ipAddr,
|
182 |
+
);
|
183 |
+
return { ...order, order_items: orderItems, payment_url: paymentUrl };
|
184 |
} catch (err) {
|
185 |
+
console.log(err);
|
186 |
await queryRunner.rollbackTransaction();
|
187 |
await queryRunner.release();
|
188 |
throw err;
|
|
|
285 |
// Cập nhật trạng thái
|
286 |
// Cập nhật rating
|
287 |
// Cập nhật note
|
288 |
+
const order = await this.findOrderOrError(id);
|
289 |
+
if (updateOrderDto.table_number) {
|
290 |
+
order.table_number = updateOrderDto.table_number;
|
291 |
+
}
|
292 |
+
if (updateOrderDto.note) {
|
293 |
+
order.note = updateOrderDto.note;
|
294 |
+
}
|
295 |
+
if (updateOrderDto.order_status) {
|
296 |
+
const st = updateOrderDto.order_status;
|
297 |
+
if (st == OrderStatus.DONE) {
|
298 |
+
const receipt = await ReceiptEntity.create({
|
299 |
+
branch_id: order.branch_id,
|
300 |
+
income: order.total_value,
|
301 |
+
type: ReceiptType.ORDER,
|
302 |
+
sender_id: order.customer_id,
|
303 |
+
receiver_id: order.staff_id,
|
304 |
+
}).save();
|
305 |
+
}
|
306 |
+
order.order_status = updateOrderDto.order_status;
|
307 |
+
}
|
308 |
+
if (updateOrderDto.rating) {
|
309 |
+
order.rating = updateOrderDto.rating;
|
310 |
+
}
|
311 |
}
|
312 |
|
313 |
async updateOrderPayment(id: number, paymentId: number) {
|
backend/src/payment/payment.module.ts
CHANGED
@@ -1,11 +1,14 @@
|
|
1 |
-
import { Module } from '@nestjs/common';
|
2 |
import { PaymentService } from './payment.service.js';
|
3 |
import { PaymentController } from './payment.controller.js';
|
|
|
|
|
4 |
import { OrderService } from '../modules/order/order.service.js';
|
5 |
-
import { BranchService } from '../modules/branch/branch.service.js';
|
6 |
|
7 |
@Module({
|
|
|
8 |
controllers: [PaymentController],
|
9 |
-
providers: [PaymentService, OrderService
|
|
|
10 |
})
|
11 |
export class PaymentModule {}
|
|
|
1 |
+
import { forwardRef, Module } from '@nestjs/common';
|
2 |
import { PaymentService } from './payment.service.js';
|
3 |
import { PaymentController } from './payment.controller.js';
|
4 |
+
import { OrderModule } from '../modules/order/order.module.js';
|
5 |
+
import { BranchModule } from '../modules/branch/branch.module.js';
|
6 |
import { OrderService } from '../modules/order/order.service.js';
|
|
|
7 |
|
8 |
@Module({
|
9 |
+
imports: [BranchModule],
|
10 |
controllers: [PaymentController],
|
11 |
+
providers: [PaymentService, OrderService],
|
12 |
+
exports: [PaymentService],
|
13 |
})
|
14 |
export class PaymentModule {}
|
backend/src/payment/payment.service.ts
CHANGED
@@ -1,23 +1,29 @@
|
|
1 |
// payment.service.ts
|
2 |
-
import { HttpStatus, Injectable } from '@nestjs/common';
|
3 |
import { ConfigService } from '@nestjs/config';
|
4 |
import * as querystring from 'qs';
|
5 |
import * as crypto from 'crypto';
|
6 |
import { OrderService } from '../modules/order/order.service.js';
|
7 |
import { PaymentEntity } from '../entities/payment.entity.js';
|
8 |
-
import { CreatePaymentUrlDto } from './dto/create-payment-url.dto.js';
|
9 |
import { CreatePaymentDto } from './dto/create-payment.dto..js';
|
10 |
import { VnpCardType } from '../common/enums/VnpCardType.enum.js';
|
11 |
|
12 |
@Injectable()
|
13 |
export class PaymentService {
|
14 |
constructor(
|
|
|
|
|
15 |
private readonly configService: ConfigService,
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
|
|
|
|
|
|
|
|
|
|
21 |
const tmnCode = this.configService.get<string>('vnp_TmnCode');
|
22 |
const secretKey = this.configService.get<string>('vnp_HashSecret');
|
23 |
const vnpUrl = this.configService.get<string>('vnp_Url');
|
@@ -42,7 +48,7 @@ export class PaymentService {
|
|
42 |
vnp_IpAddr: ipAddr,
|
43 |
vnp_CreateDate: createDate,
|
44 |
};
|
45 |
-
console.log(
|
46 |
|
47 |
const sortedParams = this.sortObject(vnp_Params);
|
48 |
|
@@ -58,9 +64,8 @@ export class PaymentService {
|
|
58 |
return res;
|
59 |
}
|
60 |
|
61 |
-
|
62 |
async vnpayIpn(reqQuery) {
|
63 |
-
console.log(
|
64 |
let vnp_Params = reqQuery;
|
65 |
let secureHash = vnp_Params['vnp_SecureHash'];
|
66 |
|
@@ -73,8 +78,8 @@ export class PaymentService {
|
|
73 |
vnp_Params = this.sortObject(vnp_Params);
|
74 |
let secretKey = this.configService.get('vnp_HashSecret');
|
75 |
let signData = querystring.stringify(vnp_Params, { encode: false });
|
76 |
-
let hmac = crypto.createHmac(
|
77 |
-
let signed = hmac.update(Buffer.from(signData, 'utf-8')).digest(
|
78 |
|
79 |
let paymentStatus = '0'; // Giả sử '0' là trạng thái khởi tạo giao dịch, chưa có IPN. Trạng thái này được lưu khi yêu cầu thanh toán chuyển hướng sang Cổng thanh toán VNPAY tại đầu khởi tạo đơn hàng.
|
80 |
//let paymentStatus = '1'; // Giả sử '1' là trạng thái thành công bạn cập nhật sau IPN được gọi và trả kết quả về nó
|
@@ -83,31 +88,37 @@ export class PaymentService {
|
|
83 |
let checkOrderId = true;
|
84 |
let order;
|
85 |
try {
|
86 |
-
order = await this.orderService.findOne(orderId)
|
87 |
} catch (error) {
|
88 |
return {
|
89 |
statusCode: HttpStatus.OK,
|
90 |
message: 'Order not found',
|
91 |
};
|
92 |
}
|
93 |
-
console.log(
|
94 |
-
console.log(
|
95 |
// Kiểm tra số tiền "giá trị của vnp_Amout/100" trùng khớp với số tiền của đơn hàng trong CSDL của bạn
|
96 |
-
let checkAmount =
|
97 |
-
|
|
|
|
|
98 |
if (checkOrderId) {
|
99 |
if (checkAmount) {
|
100 |
-
if (paymentStatus ==
|
101 |
-
|
|
|
102 |
//thanh cong
|
103 |
//paymentStatus = '1'
|
104 |
// Ở đây cập nhật trạng thái giao dịch thanh toán thành công vào CSDL của bạn
|
105 |
const payment = await this.create({
|
106 |
payment_method: 2,
|
107 |
-
vnp_amount: parseFloat(vnp_Params[
|
108 |
vnp_bank_code: vnp_Params['vnp_BankCode'],
|
109 |
vnp_bank_tran_no: vnp_Params['vnp_BankTranNo'],
|
110 |
-
vnp_card_type:
|
|
|
|
|
|
|
111 |
vnp_order_info: vnp_Params['vnp_BankTranNo'],
|
112 |
vnp_paydate: vnp_Params['vnp_PayDate'],
|
113 |
vnp_response_code: vnp_Params['vnp_ResponseCode'],
|
@@ -115,13 +126,12 @@ export class PaymentService {
|
|
115 |
vnp_transaction_status: vnp_Params['vnp_TransactionStatus'],
|
116 |
});
|
117 |
await PaymentEntity.save(payment);
|
118 |
-
this.orderService.updateOrderPayment(orderId, payment.id)
|
119 |
return {
|
120 |
statusCode: HttpStatus.OK,
|
121 |
message: 'Thành công!',
|
122 |
};
|
123 |
-
}
|
124 |
-
else {
|
125 |
//that bai
|
126 |
//paymentStatus = '2'
|
127 |
// Ở đây cập nhật trạng thái giao dịch thanh toán thất bại vào CSDL của bạn
|
@@ -130,29 +140,25 @@ export class PaymentService {
|
|
130 |
message: 'Thất bại',
|
131 |
};
|
132 |
}
|
133 |
-
}
|
134 |
-
else {
|
135 |
return {
|
136 |
statusCode: HttpStatus.OK,
|
137 |
message: 'This order has been updated to the payment status',
|
138 |
};
|
139 |
}
|
140 |
-
}
|
141 |
-
else {
|
142 |
return {
|
143 |
statusCode: HttpStatus.OK,
|
144 |
message: 'Amount invalid',
|
145 |
};
|
146 |
}
|
147 |
-
}
|
148 |
-
else {
|
149 |
return {
|
150 |
statusCode: HttpStatus.OK,
|
151 |
message: 'Order not found',
|
152 |
};
|
153 |
}
|
154 |
-
}
|
155 |
-
else {
|
156 |
return {
|
157 |
statusCode: HttpStatus.OK,
|
158 |
message: 'Checksum failed!',
|
@@ -178,12 +184,14 @@ export class PaymentService {
|
|
178 |
}
|
179 |
str.sort();
|
180 |
for (key = 0; key < str.length; key++) {
|
181 |
-
sorted[str[key]] = encodeURIComponent(obj[str[key]]).replace(/%20/g,
|
182 |
}
|
183 |
return sorted;
|
184 |
}
|
185 |
|
186 |
-
async create(
|
|
|
|
|
187 |
return PaymentEntity.create({
|
188 |
payment_method: 2,
|
189 |
vnp_amount: createPaymentDto.vnp_amount,
|
@@ -194,7 +202,7 @@ export class PaymentService {
|
|
194 |
vnp_paydate: createPaymentDto.vnp_paydate,
|
195 |
vnp_response_code: createPaymentDto.vnp_response_code,
|
196 |
vnp_transaction_no: createPaymentDto.vnp_transaction_no,
|
197 |
-
vnp_transaction_status: createPaymentDto.vnp_transaction_status
|
198 |
-
})
|
199 |
}
|
200 |
}
|
|
|
1 |
// payment.service.ts
|
2 |
+
import { forwardRef, HttpStatus, Inject, Injectable } from '@nestjs/common';
|
3 |
import { ConfigService } from '@nestjs/config';
|
4 |
import * as querystring from 'qs';
|
5 |
import * as crypto from 'crypto';
|
6 |
import { OrderService } from '../modules/order/order.service.js';
|
7 |
import { PaymentEntity } from '../entities/payment.entity.js';
|
|
|
8 |
import { CreatePaymentDto } from './dto/create-payment.dto..js';
|
9 |
import { VnpCardType } from '../common/enums/VnpCardType.enum.js';
|
10 |
|
11 |
@Injectable()
|
12 |
export class PaymentService {
|
13 |
constructor(
|
14 |
+
@Inject(forwardRef(() => OrderService))
|
15 |
+
private readonly orderService: OrderService,
|
16 |
private readonly configService: ConfigService,
|
17 |
+
) {}
|
18 |
+
|
19 |
+
async createPaymentUrl(
|
20 |
+
amount: number,
|
21 |
+
orderId: string,
|
22 |
+
orderDescription: string,
|
23 |
+
orderType: string,
|
24 |
+
language: string,
|
25 |
+
ipAddr: string,
|
26 |
+
) {
|
27 |
const tmnCode = this.configService.get<string>('vnp_TmnCode');
|
28 |
const secretKey = this.configService.get<string>('vnp_HashSecret');
|
29 |
const vnpUrl = this.configService.get<string>('vnp_Url');
|
|
|
48 |
vnp_IpAddr: ipAddr,
|
49 |
vnp_CreateDate: createDate,
|
50 |
};
|
51 |
+
console.log('3');
|
52 |
|
53 |
const sortedParams = this.sortObject(vnp_Params);
|
54 |
|
|
|
64 |
return res;
|
65 |
}
|
66 |
|
|
|
67 |
async vnpayIpn(reqQuery) {
|
68 |
+
console.log('helloooo');
|
69 |
let vnp_Params = reqQuery;
|
70 |
let secureHash = vnp_Params['vnp_SecureHash'];
|
71 |
|
|
|
78 |
vnp_Params = this.sortObject(vnp_Params);
|
79 |
let secretKey = this.configService.get('vnp_HashSecret');
|
80 |
let signData = querystring.stringify(vnp_Params, { encode: false });
|
81 |
+
let hmac = crypto.createHmac('sha512', secretKey);
|
82 |
+
let signed = hmac.update(Buffer.from(signData, 'utf-8')).digest('hex');
|
83 |
|
84 |
let paymentStatus = '0'; // Giả sử '0' là trạng thái khởi tạo giao dịch, chưa có IPN. Trạng thái này được lưu khi yêu cầu thanh toán chuyển hướng sang Cổng thanh toán VNPAY tại đầu khởi tạo đơn hàng.
|
85 |
//let paymentStatus = '1'; // Giả sử '1' là trạng thái thành công bạn cập nhật sau IPN được gọi và trả kết quả về nó
|
|
|
88 |
let checkOrderId = true;
|
89 |
let order;
|
90 |
try {
|
91 |
+
order = await this.orderService.findOne(orderId);
|
92 |
} catch (error) {
|
93 |
return {
|
94 |
statusCode: HttpStatus.OK,
|
95 |
message: 'Order not found',
|
96 |
};
|
97 |
}
|
98 |
+
console.log('order = ', order);
|
99 |
+
console.log('order total value ', order.total_value);
|
100 |
// Kiểm tra số tiền "giá trị của vnp_Amout/100" trùng khớp với số tiền của đơn hàng trong CSDL của bạn
|
101 |
+
let checkAmount =
|
102 |
+
order.total_value == parseFloat(vnp_Params['vnp_Amount']) / 100;
|
103 |
+
if (secureHash === signed) {
|
104 |
+
//kiểm tra checksum
|
105 |
if (checkOrderId) {
|
106 |
if (checkAmount) {
|
107 |
+
if (paymentStatus == '0') {
|
108 |
+
//kiểm tra tình trạng giao dịch trước khi cập nhật tình trạng thanh toán
|
109 |
+
if (rspCode == '00') {
|
110 |
//thanh cong
|
111 |
//paymentStatus = '1'
|
112 |
// Ở đây cập nhật trạng thái giao dịch thanh toán thành công vào CSDL của bạn
|
113 |
const payment = await this.create({
|
114 |
payment_method: 2,
|
115 |
+
vnp_amount: parseFloat(vnp_Params['vnp_Amount']) / 100,
|
116 |
vnp_bank_code: vnp_Params['vnp_BankCode'],
|
117 |
vnp_bank_tran_no: vnp_Params['vnp_BankTranNo'],
|
118 |
+
vnp_card_type:
|
119 |
+
VnpCardType[
|
120 |
+
vnp_Params['vnp_CardType'] as keyof typeof VnpCardType
|
121 |
+
],
|
122 |
vnp_order_info: vnp_Params['vnp_BankTranNo'],
|
123 |
vnp_paydate: vnp_Params['vnp_PayDate'],
|
124 |
vnp_response_code: vnp_Params['vnp_ResponseCode'],
|
|
|
126 |
vnp_transaction_status: vnp_Params['vnp_TransactionStatus'],
|
127 |
});
|
128 |
await PaymentEntity.save(payment);
|
129 |
+
this.orderService.updateOrderPayment(orderId, payment.id);
|
130 |
return {
|
131 |
statusCode: HttpStatus.OK,
|
132 |
message: 'Thành công!',
|
133 |
};
|
134 |
+
} else {
|
|
|
135 |
//that bai
|
136 |
//paymentStatus = '2'
|
137 |
// Ở đây cập nhật trạng thái giao dịch thanh toán thất bại vào CSDL của bạn
|
|
|
140 |
message: 'Thất bại',
|
141 |
};
|
142 |
}
|
143 |
+
} else {
|
|
|
144 |
return {
|
145 |
statusCode: HttpStatus.OK,
|
146 |
message: 'This order has been updated to the payment status',
|
147 |
};
|
148 |
}
|
149 |
+
} else {
|
|
|
150 |
return {
|
151 |
statusCode: HttpStatus.OK,
|
152 |
message: 'Amount invalid',
|
153 |
};
|
154 |
}
|
155 |
+
} else {
|
|
|
156 |
return {
|
157 |
statusCode: HttpStatus.OK,
|
158 |
message: 'Order not found',
|
159 |
};
|
160 |
}
|
161 |
+
} else {
|
|
|
162 |
return {
|
163 |
statusCode: HttpStatus.OK,
|
164 |
message: 'Checksum failed!',
|
|
|
184 |
}
|
185 |
str.sort();
|
186 |
for (key = 0; key < str.length; key++) {
|
187 |
+
sorted[str[key]] = encodeURIComponent(obj[str[key]]).replace(/%20/g, '+');
|
188 |
}
|
189 |
return sorted;
|
190 |
}
|
191 |
|
192 |
+
async create(
|
193 |
+
createPaymentDto: CreatePaymentDto,
|
194 |
+
): Promise<PaymentEntity | undefined> {
|
195 |
return PaymentEntity.create({
|
196 |
payment_method: 2,
|
197 |
vnp_amount: createPaymentDto.vnp_amount,
|
|
|
202 |
vnp_paydate: createPaymentDto.vnp_paydate,
|
203 |
vnp_response_code: createPaymentDto.vnp_response_code,
|
204 |
vnp_transaction_no: createPaymentDto.vnp_transaction_no,
|
205 |
+
vnp_transaction_status: createPaymentDto.vnp_transaction_status,
|
206 |
+
});
|
207 |
}
|
208 |
}
|