Spaces:
Sleeping
Sleeping
change amount in payment service
Browse files- backend/src/modules/authentication/authentication.service.ts +1 -1
- backend/src/payment/dto/{create-payment.dto.ts → create-payment-url.dto.ts} +4 -1
- backend/src/payment/dto/create-payment.dto..ts +38 -0
- backend/src/payment/dto/update-payment.dto.ts +0 -4
- backend/src/payment/payment.controller.ts +3 -3
- backend/src/payment/payment.module.ts +3 -1
- backend/src/payment/payment.service.ts +105 -71
backend/src/modules/authentication/authentication.service.ts
CHANGED
@@ -28,7 +28,7 @@ export class AuthenticationService {
|
|
28 |
if (!compare) {
|
29 |
throw new UnauthorizedException("Wrong password");
|
30 |
}
|
31 |
-
const payload = { sub: user.id, username: user.full_name,
|
32 |
return {
|
33 |
access_token: await this.jwtService.signAsync(payload),
|
34 |
};
|
|
|
28 |
if (!compare) {
|
29 |
throw new UnauthorizedException("Wrong password");
|
30 |
}
|
31 |
+
const payload = { sub: user.id, username: user.full_name, role: user.role};
|
32 |
return {
|
33 |
access_token: await this.jwtService.signAsync(payload),
|
34 |
};
|
backend/src/payment/dto/{create-payment.dto.ts → create-payment-url.dto.ts}
RENAMED
@@ -1,6 +1,6 @@
|
|
1 |
import { IsNumber, IsOptional, IsString } from "class-validator"
|
2 |
|
3 |
-
export class
|
4 |
@IsNumber()
|
5 |
amount: number
|
6 |
|
@@ -8,6 +8,9 @@ export class CreatePaymentDto {
|
|
8 |
@IsString()
|
9 |
bankCode: string
|
10 |
|
|
|
|
|
|
|
11 |
@IsString()
|
12 |
orderDescription: string
|
13 |
|
|
|
1 |
import { IsNumber, IsOptional, IsString } from "class-validator"
|
2 |
|
3 |
+
export class CreatePaymentUrlDto {
|
4 |
@IsNumber()
|
5 |
amount: number
|
6 |
|
|
|
8 |
@IsString()
|
9 |
bankCode: string
|
10 |
|
11 |
+
@IsString()
|
12 |
+
orderId: string
|
13 |
+
|
14 |
@IsString()
|
15 |
orderDescription: string
|
16 |
|
backend/src/payment/dto/create-payment.dto..ts
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { IsNumber, IsOptional, IsString } from 'class-validator';
|
2 |
+
|
3 |
+
export class CreatePaymentDto {
|
4 |
+
@IsNumber()
|
5 |
+
payment_method: number;
|
6 |
+
|
7 |
+
@IsNumber()
|
8 |
+
vnp_amount: number
|
9 |
+
|
10 |
+
@IsString()
|
11 |
+
vnp_bank_code: string
|
12 |
+
|
13 |
+
@IsString()
|
14 |
+
vnp_bank_tran_no: string
|
15 |
+
|
16 |
+
@IsNumber()
|
17 |
+
vnp_card_type: number
|
18 |
+
|
19 |
+
@IsString()
|
20 |
+
@IsOptional()
|
21 |
+
vnp_order_info: string //Nội dung giao dịch
|
22 |
+
|
23 |
+
@IsString()
|
24 |
+
@IsOptional()
|
25 |
+
vnp_paydate: string
|
26 |
+
|
27 |
+
@IsString()
|
28 |
+
@IsOptional()
|
29 |
+
vnp_response_code: number
|
30 |
+
|
31 |
+
@IsString()
|
32 |
+
@IsOptional()
|
33 |
+
vnp_transaction_no: string
|
34 |
+
|
35 |
+
@IsString()
|
36 |
+
@IsOptional()
|
37 |
+
vnp_transaction_status: number
|
38 |
+
}
|
backend/src/payment/dto/update-payment.dto.ts
DELETED
@@ -1,4 +0,0 @@
|
|
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/payment.controller.ts
CHANGED
@@ -3,7 +3,7 @@ import { Controller, Post, Body, Req, Res, Get } 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 |
-
import {
|
7 |
|
8 |
@Controller('payment')
|
9 |
export class PaymentController {
|
@@ -11,7 +11,7 @@ export class PaymentController {
|
|
11 |
|
12 |
@Public()
|
13 |
@Post('create_payment_url')
|
14 |
-
async createPaymentUrl(@Req() req: Request, @Body() body:
|
15 |
|
16 |
const ipAddr =
|
17 |
req.headers['x-forwarded-for'] ||
|
@@ -20,7 +20,7 @@ export class PaymentController {
|
|
20 |
|
21 |
return await this.paymentService.createPaymentUrl(
|
22 |
body.amount,
|
23 |
-
body.
|
24 |
body.orderDescription,
|
25 |
body.orderType,
|
26 |
body.language,
|
|
|
3 |
import { PaymentService } from './payment.service.js';
|
4 |
import { Request, Response } from 'express';
|
5 |
import { Public } from '../modules/authentication/authentication.decorator.js';
|
6 |
+
import { CreatePaymentUrlDto } from './dto/create-payment-url.dto.js';
|
7 |
|
8 |
@Controller('payment')
|
9 |
export class PaymentController {
|
|
|
11 |
|
12 |
@Public()
|
13 |
@Post('create_payment_url')
|
14 |
+
async createPaymentUrl(@Req() req: Request, @Body() body: CreatePaymentUrlDto) {
|
15 |
|
16 |
const ipAddr =
|
17 |
req.headers['x-forwarded-for'] ||
|
|
|
20 |
|
21 |
return await this.paymentService.createPaymentUrl(
|
22 |
body.amount,
|
23 |
+
body.orderId,
|
24 |
body.orderDescription,
|
25 |
body.orderType,
|
26 |
body.language,
|
backend/src/payment/payment.module.ts
CHANGED
@@ -1,9 +1,11 @@
|
|
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 {}
|
|
|
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, BranchService],
|
10 |
})
|
11 |
export class PaymentModule {}
|
backend/src/payment/payment.service.ts
CHANGED
@@ -3,12 +3,20 @@ 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 |
|
7 |
@Injectable()
|
8 |
export class PaymentService {
|
9 |
-
constructor(
|
|
|
|
|
|
|
10 |
|
11 |
-
async createPaymentUrl(amount: number,
|
12 |
|
13 |
const tmnCode = this.configService.get<string>('vnp_TmnCode');
|
14 |
const secretKey = this.configService.get<string>('vnp_HashSecret');
|
@@ -17,7 +25,6 @@ export class PaymentService {
|
|
17 |
|
18 |
const date = new Date();
|
19 |
const createDate = this.formatDate(date, 'yyyymmddHHmmss');
|
20 |
-
const orderId = Date.now().toString();
|
21 |
const locale = language || 'vn';
|
22 |
const currCode = 'VND';
|
23 |
|
@@ -36,9 +43,6 @@ export class PaymentService {
|
|
36 |
vnp_CreateDate: createDate,
|
37 |
};
|
38 |
console.log("3")
|
39 |
-
if (bankCode) {
|
40 |
-
vnp_Params['vnp_BankCode'] = bankCode;
|
41 |
-
}
|
42 |
|
43 |
const sortedParams = this.sortObject(vnp_Params);
|
44 |
|
@@ -55,11 +59,11 @@ export class PaymentService {
|
|
55 |
}
|
56 |
|
57 |
|
58 |
-
vnpayIpn(reqQuery){
|
59 |
console.log("helloooo")
|
60 |
let vnp_Params = reqQuery;
|
61 |
let secureHash = vnp_Params['vnp_SecureHash'];
|
62 |
-
|
63 |
let orderId = vnp_Params['vnp_TxnRef'];
|
64 |
let rspCode = vnp_Params['vnp_ResponseCode'];
|
65 |
|
@@ -70,92 +74,122 @@ export class PaymentService {
|
|
70 |
let secretKey = this.configService.get('vnp_HashSecret');
|
71 |
let signData = querystring.stringify(vnp_Params, { encode: false });
|
72 |
let hmac = crypto.createHmac("sha512", secretKey);
|
73 |
-
let signed = hmac.update(Buffer.from(signData, 'utf-8')).digest("hex");
|
74 |
-
|
75 |
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.
|
76 |
//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ó
|
77 |
//let paymentStatus = '2'; // Giả sử '2' là trạng thái thất bại bạn cập nhật sau IPN được gọi và trả kết quả về nó
|
78 |
-
|
79 |
-
let checkOrderId = true;
|
80 |
-
let
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
|
|
113 |
}
|
114 |
-
else{
|
|
|
|
|
|
|
115 |
return {
|
116 |
statusCode: HttpStatus.OK,
|
117 |
-
message: '
|
118 |
-
data: { /* dữ liệu trả về nếu có */ },
|
119 |
};
|
120 |
}
|
121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
122 |
else {
|
123 |
return {
|
124 |
statusCode: HttpStatus.OK,
|
125 |
-
message: '
|
126 |
-
data: { /* dữ liệu trả về nếu có */ },
|
127 |
};
|
128 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
129 |
}
|
130 |
else {
|
131 |
return {
|
132 |
statusCode: HttpStatus.OK,
|
133 |
message: 'Checksum failed!',
|
134 |
-
data: { /* dữ liệu trả về nếu có */ },
|
135 |
};
|
136 |
}
|
137 |
}
|
138 |
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
str.push(encodeURIComponent(key));
|
153 |
-
}
|
154 |
}
|
155 |
-
str.sort();
|
156 |
-
for (key = 0; key < str.length; key++) {
|
157 |
-
sorted[str[key]] = encodeURIComponent(obj[str[key]]).replace(/%20/g, "+");
|
158 |
-
}
|
159 |
-
return sorted;
|
160 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
161 |
}
|
|
|
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 |
+
private readonly orderService: OrderService
|
17 |
+
) { }
|
18 |
|
19 |
+
async createPaymentUrl(amount: number, orderId: string, orderDescription: string, orderType: string, language: string, ipAddr: string) {
|
20 |
|
21 |
const tmnCode = this.configService.get<string>('vnp_TmnCode');
|
22 |
const secretKey = this.configService.get<string>('vnp_HashSecret');
|
|
|
25 |
|
26 |
const date = new Date();
|
27 |
const createDate = this.formatDate(date, 'yyyymmddHHmmss');
|
|
|
28 |
const locale = language || 'vn';
|
29 |
const currCode = 'VND';
|
30 |
|
|
|
43 |
vnp_CreateDate: createDate,
|
44 |
};
|
45 |
console.log("3")
|
|
|
|
|
|
|
46 |
|
47 |
const sortedParams = this.sortObject(vnp_Params);
|
48 |
|
|
|
59 |
}
|
60 |
|
61 |
|
62 |
+
async vnpayIpn(reqQuery) {
|
63 |
console.log("helloooo")
|
64 |
let vnp_Params = reqQuery;
|
65 |
let secureHash = vnp_Params['vnp_SecureHash'];
|
66 |
+
|
67 |
let orderId = vnp_Params['vnp_TxnRef'];
|
68 |
let rspCode = vnp_Params['vnp_ResponseCode'];
|
69 |
|
|
|
74 |
let secretKey = this.configService.get('vnp_HashSecret');
|
75 |
let signData = querystring.stringify(vnp_Params, { encode: false });
|
76 |
let hmac = crypto.createHmac("sha512", secretKey);
|
77 |
+
let signed = hmac.update(Buffer.from(signData, 'utf-8')).digest("hex");
|
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ó
|
81 |
//let paymentStatus = '2'; // Giả sử '2' là trạng thái thất bại bạn cập nhật sau IPN được gọi và trả kết quả về nó
|
82 |
+
//Kiểm tra có đúng order id không
|
83 |
+
let checkOrderId = true;
|
84 |
+
let order;
|
85 |
+
try {
|
86 |
+
order = await this.orderService.findOne(orderId)
|
87 |
+
} catch (error) {
|
88 |
+
checkOrderId = false;
|
89 |
+
}
|
90 |
+
// 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
|
91 |
+
let checkAmount = order.total_value == parseFloat(vnp_Params["vnp_Amount"])/100;
|
92 |
+
if (secureHash === signed) { //kiểm tra checksum
|
93 |
+
if (checkOrderId) {
|
94 |
+
if (checkAmount) {
|
95 |
+
if (paymentStatus == "0") { //kiểm tra tình trạng giao dịch trước khi cập nhật tình trạng thanh toán
|
96 |
+
if (rspCode == "00") {
|
97 |
+
//thanh cong
|
98 |
+
//paymentStatus = '1'
|
99 |
+
// Ở đâ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
|
100 |
+
const payment = await this.create({
|
101 |
+
payment_method: 2,
|
102 |
+
vnp_amount: parseFloat(vnp_Params["vnp_Amount"])/100,
|
103 |
+
vnp_bank_code: vnp_Params['vnp_BankCode'],
|
104 |
+
vnp_bank_tran_no: vnp_Params['vnp_BankTranNo'],
|
105 |
+
vnp_card_type: VnpCardType[vnp_Params['vnp_CardType'] as keyof typeof VnpCardType],
|
106 |
+
vnp_order_info: vnp_Params['vnp_BankTranNo'],
|
107 |
+
vnp_paydate: vnp_Params['vnp_PayDate'],
|
108 |
+
vnp_response_code: vnp_Params['vnp_ResponseCode'],
|
109 |
+
vnp_transaction_no: vnp_Params['vnp_TransactionNo'],
|
110 |
+
vnp_transaction_status: vnp_Params['vnp_TransactionStatus'],
|
111 |
+
});
|
112 |
+
await PaymentEntity.save(payment);
|
113 |
+
this.orderService.updateOrderPayment(orderId, payment.id)
|
114 |
+
return {
|
115 |
+
statusCode: HttpStatus.OK,
|
116 |
+
message: 'Thành công!',
|
117 |
+
};
|
118 |
}
|
119 |
+
else {
|
120 |
+
//that bai
|
121 |
+
//paymentStatus = '2'
|
122 |
+
// Ở đâ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
|
123 |
return {
|
124 |
statusCode: HttpStatus.OK,
|
125 |
+
message: 'Thất bại',
|
|
|
126 |
};
|
127 |
}
|
128 |
+
}
|
129 |
+
else {
|
130 |
+
return {
|
131 |
+
statusCode: HttpStatus.OK,
|
132 |
+
message: 'This order has been updated to the payment status',
|
133 |
+
};
|
134 |
+
}
|
135 |
+
}
|
136 |
else {
|
137 |
return {
|
138 |
statusCode: HttpStatus.OK,
|
139 |
+
message: 'Amount invalid',
|
|
|
140 |
};
|
141 |
}
|
142 |
+
}
|
143 |
+
else {
|
144 |
+
return {
|
145 |
+
statusCode: HttpStatus.OK,
|
146 |
+
message: 'Order not found',
|
147 |
+
};
|
148 |
+
}
|
149 |
}
|
150 |
else {
|
151 |
return {
|
152 |
statusCode: HttpStatus.OK,
|
153 |
message: 'Checksum failed!',
|
|
|
154 |
};
|
155 |
}
|
156 |
}
|
157 |
|
158 |
+
// Format date helper function
|
159 |
+
formatDate(date: Date, format: string): string {
|
160 |
+
const yyyymmdd = date.toISOString().slice(0, 10).replace(/-/g, ''); // YYYYMMDD
|
161 |
+
const hhmmss = date.toTimeString().slice(0, 8).replace(/:/g, ''); // HHMMSS
|
162 |
+
return format === 'yyyymmddHHmmss' ? yyyymmdd + hhmmss : hhmmss;
|
163 |
+
}
|
164 |
+
|
165 |
+
sortObject(obj) {
|
166 |
+
let sorted = {};
|
167 |
+
let str = [];
|
168 |
+
let key;
|
169 |
+
for (key in obj) {
|
170 |
+
if (obj.hasOwnProperty(key)) {
|
171 |
str.push(encodeURIComponent(key));
|
|
|
172 |
}
|
|
|
|
|
|
|
|
|
|
|
173 |
}
|
174 |
+
str.sort();
|
175 |
+
for (key = 0; key < str.length; key++) {
|
176 |
+
sorted[str[key]] = encodeURIComponent(obj[str[key]]).replace(/%20/g, "+");
|
177 |
+
}
|
178 |
+
return sorted;
|
179 |
+
}
|
180 |
+
|
181 |
+
async create(createPaymentDto: CreatePaymentDto): Promise<PaymentEntity | undefined> {
|
182 |
+
return PaymentEntity.create({
|
183 |
+
payment_method: 2,
|
184 |
+
vnp_amount: createPaymentDto.vnp_amount,
|
185 |
+
vnp_bank_code: createPaymentDto.vnp_bank_code,
|
186 |
+
vnp_bank_tran_no: createPaymentDto.vnp_bank_tran_no,
|
187 |
+
vnp_card_type: createPaymentDto.vnp_card_type,
|
188 |
+
vnp_order_info: createPaymentDto.vnp_order_info,
|
189 |
+
vnp_paydate: createPaymentDto.vnp_paydate,
|
190 |
+
vnp_response_code: createPaymentDto.vnp_response_code,
|
191 |
+
vnp_transaction_no: createPaymentDto.vnp_transaction_no,
|
192 |
+
vnp_transaction_status: createPaymentDto.vnp_transaction_status
|
193 |
+
})
|
194 |
+
}
|
195 |
}
|