File size: 7,766 Bytes
f4866cb
463569a
f4866cb
 
 
ca1cc57
 
 
 
f4866cb
 
 
ca1cc57
463569a
 
ca1cc57
463569a
 
 
 
 
 
 
 
 
 
f4866cb
 
 
 
d18f023
f4866cb
 
 
 
d18f023
f4866cb
 
 
 
 
 
 
 
 
 
 
 
 
 
463569a
f4866cb
 
d18f023
f4866cb
 
 
 
 
d18f023
f4866cb
 
d18f023
f4866cb
 
6b95a9f
ca1cc57
463569a
6b95a9f
 
ca1cc57
6b95a9f
 
 
 
 
 
 
 
 
463569a
 
ca1cc57
6b95a9f
 
 
ca1cc57
 
 
 
463569a
ca1cc57
af4a850
 
 
 
ca1cc57
463569a
 
ca1cc57
463569a
 
 
 
ca1cc57
 
463569a
 
 
ca1cc57
 
 
 
 
463569a
ca1cc57
 
463569a
 
 
 
ca1cc57
 
 
 
 
 
 
463569a
ca1cc57
 
 
 
463569a
ca1cc57
 
 
6b95a9f
 
ca1cc57
6b95a9f
 
463569a
ca1cc57
 
 
 
 
463569a
6b95a9f
 
ca1cc57
6b95a9f
 
463569a
ca1cc57
 
 
 
 
463569a
6b95a9f
 
 
 
 
f4866cb
 
ca1cc57
 
 
 
 
 
 
 
 
 
 
 
 
6b95a9f
f4866cb
6b95a9f
ca1cc57
 
463569a
ca1cc57
 
 
 
463569a
 
 
ca1cc57
 
 
 
 
 
 
 
 
 
463569a
 
ca1cc57
f4866cb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
// payment.service.ts
import { forwardRef, HttpStatus, Inject, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as querystring from 'qs';
import * as crypto from 'crypto';
import { OrderService } from '../modules/order/order.service.js';
import { PaymentEntity } from '../entities/payment.entity.js';
import { CreatePaymentDto } from './dto/create-payment.dto..js';
import { VnpCardType } from '../common/enums/VnpCardType.enum.js';

@Injectable()
export class PaymentService {
  constructor(
    @Inject(forwardRef(() => OrderService))
    private readonly orderService: OrderService,
    private readonly configService: ConfigService,
  ) {}

  async createPaymentUrl(
    amount: number,
    orderId: string,
    orderDescription: string,
    orderType: string,
    language: string,
    ipAddr: string,
  ) {
    const tmnCode = this.configService.get<string>('vnp_TmnCode');
    const secretKey = this.configService.get<string>('vnp_HashSecret');
    const vnpUrl = this.configService.get<string>('vnp_Url');
    const returnUrl = this.configService.get<string>('vnp_ReturnUrl');

    const date = new Date();
    const createDate = this.formatDate(date, 'yyyymmddHHmmss');
    const locale = language || 'vn';
    const currCode = 'VND';

    const vnp_Params: Record<string, string> = {
      vnp_Version: '2.1.0',
      vnp_Command: 'pay',
      vnp_TmnCode: tmnCode,
      vnp_Locale: locale,
      vnp_CurrCode: currCode,
      vnp_TxnRef: orderId,
      vnp_OrderInfo: orderDescription,
      vnp_OrderType: orderType,
      vnp_Amount: (amount * 100).toString(),
      vnp_ReturnUrl: returnUrl,
      vnp_IpAddr: ipAddr,
      vnp_CreateDate: createDate,
    };
    console.log('3');

    const sortedParams = this.sortObject(vnp_Params);

    // Sign the data
    const signData = querystring.stringify(sortedParams, { encode: false });
    const hmac = crypto.createHmac('sha512', secretKey);
    const signed = hmac.update(Buffer.from(signData, 'utf-8')).digest('hex');
    sortedParams['vnp_SecureHash'] = signed;

    // Create the URL
    const res = `${vnpUrl}?${querystring.stringify(sortedParams, { encode: false })}`;

    return res;
  }

  async vnpayIpn(reqQuery) {
    console.log('helloooo');
    let vnp_Params = reqQuery;
    let secureHash = vnp_Params['vnp_SecureHash'];

    let orderId = vnp_Params['vnp_TxnRef'];
    let rspCode = vnp_Params['vnp_ResponseCode'];

    delete vnp_Params['vnp_SecureHash'];
    delete vnp_Params['vnp_SecureHashType'];

    vnp_Params = this.sortObject(vnp_Params);
    let secretKey = this.configService.get('vnp_HashSecret');
    let signData = querystring.stringify(vnp_Params, { encode: false });
    let hmac = crypto.createHmac('sha512', secretKey);
    let signed = hmac.update(Buffer.from(signData, 'utf-8')).digest('hex');

    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.
    //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ó
    //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ó
    //Kiểm tra có đúng order id không
    let checkOrderId = true;
    let order;
    try {
      order = await this.orderService.findOne(orderId);
    } catch (error) {
      return {
        statusCode: HttpStatus.OK,
        message: 'Order not found',
      };
    }
    console.log('order = ', order);
    console.log('order total value ', order.total_value);
    // 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
    let checkAmount =
      order.total_value == parseFloat(vnp_Params['vnp_Amount']) / 100;
    if (secureHash === signed) {
      //kiểm tra checksum
      if (checkOrderId) {
        if (checkAmount) {
          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
            if (rspCode == '00') {
              //thanh cong
              //paymentStatus = '1'
              // Ở đâ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
              const payment = await this.create({
                payment_method: 2,
                vnp_amount: parseFloat(vnp_Params['vnp_Amount']) / 100,
                vnp_bank_code: vnp_Params['vnp_BankCode'],
                vnp_bank_tran_no: vnp_Params['vnp_BankTranNo'],
                vnp_card_type:
                  VnpCardType[
                    vnp_Params['vnp_CardType'] as keyof typeof VnpCardType
                  ],
                vnp_order_info: vnp_Params['vnp_BankTranNo'],
                vnp_paydate: vnp_Params['vnp_PayDate'],
                vnp_response_code: vnp_Params['vnp_ResponseCode'],
                vnp_transaction_no: vnp_Params['vnp_TransactionNo'],
                vnp_transaction_status: vnp_Params['vnp_TransactionStatus'],
              });
              await PaymentEntity.save(payment);
              this.orderService.updateOrderPayment(orderId, payment.id);
              return {
                statusCode: HttpStatus.OK,
                message: 'Thành công!',
              };
            } else {
              //that bai
              //paymentStatus = '2'
              // Ở đâ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
              return {
                statusCode: HttpStatus.OK,
                message: 'Thất bại',
              };
            }
          } else {
            return {
              statusCode: HttpStatus.OK,
              message: 'This order has been updated to the payment status',
            };
          }
        } else {
          return {
            statusCode: HttpStatus.OK,
            message: 'Amount invalid',
          };
        }
      } else {
        return {
          statusCode: HttpStatus.OK,
          message: 'Order not found',
        };
      }
    } else {
      return {
        statusCode: HttpStatus.OK,
        message: 'Checksum failed!',
      };
    }
  }

  // Format date helper function
  formatDate(date: Date, format: string): string {
    const yyyymmdd = date.toISOString().slice(0, 10).replace(/-/g, ''); // YYYYMMDD
    const hhmmss = date.toTimeString().slice(0, 8).replace(/:/g, ''); // HHMMSS
    return format === 'yyyymmddHHmmss' ? yyyymmdd + hhmmss : hhmmss;
  }

  sortObject(obj) {
    let sorted = {};
    let str = [];
    let key;
    for (key in obj) {
      if (obj.hasOwnProperty(key)) {
        str.push(encodeURIComponent(key));
      }
    }
    str.sort();
    for (key = 0; key < str.length; key++) {
      sorted[str[key]] = encodeURIComponent(obj[str[key]]).replace(/%20/g, '+');
    }
    return sorted;
  }

  async create(
    createPaymentDto: CreatePaymentDto,
  ): Promise<PaymentEntity | undefined> {
    return PaymentEntity.create({
      payment_method: 2,
      vnp_amount: createPaymentDto.vnp_amount,
      vnp_bank_code: createPaymentDto.vnp_bank_code,
      vnp_bank_tran_no: createPaymentDto.vnp_bank_tran_no,
      vnp_card_type: createPaymentDto.vnp_card_type,
      vnp_order_info: createPaymentDto.vnp_order_info,
      vnp_paydate: createPaymentDto.vnp_paydate,
      vnp_response_code: createPaymentDto.vnp_response_code,
      vnp_transaction_no: createPaymentDto.vnp_transaction_no,
      vnp_transaction_status: createPaymentDto.vnp_transaction_status,
    });
  }
}