1. Giới thiệu

Với các website thương mại điện tử, mua bán online việc quan trọng nhất là phải tích hợp được phương thức thanh toán online để tạo sự tiện lợi, tiết kiệm thời gian cho khách hàng. Ngày nay, có rất nhiều cổng thanh toán đã được đưa vào sử dụng trong những năm gần đây: Momo, OnePay, VNpay, PayPal,… Trong đó, cổng thanh toán VNpay hiện tại vẫn là cổng thanh toán được sử dụng nhiều nhất trên các trang web bán hàng online, thương mại điện tử.
Để tích hợp VNpay vào tài liệu bạn đọc có thể tìm hiểu thêm và chi tiết tại đậy:“https://sandbox.vnpayment.vn/apis/”;. Bài viết sẽ hướng dãn tích hợp code của VNPay với tài khoản Sanbox của VnPay. Để kiểm thử và dùng thử với tài khoản thật.

2. Luồng thực hiện giao dịch khi thực hiện thanh toán qua Vnpay

Luồng thanh toán VNpay

Bước 1: Khách hàng thực hiện Thanh toán trực tuyến trên Website.
Bước 2: Website tạo thông tin thanh toán dưới dạng URL mang thông tin thanh toán và chuyển hướng khách hàng sang Cổng thanh toán VNPAY. Cổng thanh toán VNPAY xử lý yêu cầu Thanh toán đó. Khách hàng nhập các thông tin được yêu cầu để thực hiện việc Thanh toán.
Bước 3,4: Khách hàng nhập thông tin để xác minh tài khoản Ngân hàng của khách hàng và xác thực giao dịch.
Bước 5: Giao dịch thành công tại Ngân hàng, VNPAY tiến hành: Chuyển hướng khách hàng về Website hiển thị cho người dùng (vnp_ReturnUrl) và thông báo cho Website TMĐT kết quả thanh toán của client thông qua vnp_IpnURL.
Bước 6: Hiển thị kết quả giao dịch tới khách hàng.

3. Cấu hình VNPay trong Laravel project

1. Thêm các cấu hình sau trong file .env <-- File này ko được tải lên github

    VNP_TMN_CODE= // vnp_TmnCode được cung cấp 
    VNP_HASH_SECRET=  // vnp_HashSecret được cung cấp
    VNP_URL=  //VNP_URL địa chỉ cổng thanh toán vd: https://sandbox.vnpayment.vn/paymentv2/vpcpay.html
    VNP_RETURN_URL= //URL mà VNPay sẽ chuyển hướng về sau khi thanh toán hoàn tất url web của bạn 

2. Tạo file config/vnpay.php để quản lý các thông tin cấu hình:

 return [
        'vnp_TmnCode' => env('VNP_TMN_CODE'),
        'vnp_HashSecret' => env('VNP_HASH_SECRET'),
        'vnp_Url' => env('VNP_URL'),
        'vnp_Returnurl' => env('VNP_RETURN_URL'),
    ];

3. Tạo Route, Controller cho thanh toán và xử lý Callback

php artisan make:controller Payment/VnPayController

   // routes/web.php
   use App\Http\Controllers\Payment\VnPayController;
      
   Route::get('/payment', [VnPayController::class, 'createPayment'])->name('payment.create');
   Route::get('/vnpay-return', [VnPayController::class, 'vnpayReturn'])->name('vnpay.return');

4. Viết logic xử lý trong VnPayController

  • Create Payment method
   namespace App\Http\Controllers\Payment;
   
   use Illuminate\Http\Request;
   class VnPayController extends Controller
   {
       public function createPayment(Request $request){
           // Lấy thông tin config: 
           $vnp_TmnCode = config('vnpay.vnp_TmnCode'); // Mã website của bạn tại VNPAY 
           $vnp_HashSecret = config('vnpay.vnp_HashSecret'); // Chuỗi bí mật
           $vnp_Url = config('vnpay.vnp_Url'); // URL thanh toán của VNPAY
           $vnp_ReturnUrl = config('vnpay.vnp_Returnurl'); // URL nhận kết quả trả về

          // Lấy thông tin từ đơn hàng phục vụ thanh toán 
          // Dưới đây là thông tin giả định, bạn có thể lấy thông tin đơn hàng của bạn  để thay thế
          $order = (object)[
             "code" => 'ORDER' . rand(100000, 999999),  // Mã đơn hàng
             "total" => 100000, // Số tiền cần thanh toán (VND)
             "bankCode" => 'NCB',   // Mã ngân hàng
             "type" => "billpayment" // Loại đơn hàng
             "info" => "Thanh toán đơn hàng" // Thông tin đơn hàng
          ]
         
            // Thông tin đơn hàng, thanh toán
           $vnp_TxnRef = $order->code;
           $vnp_OrderInfo = $order->info;
           $vnp_OrderType =  $order->type;
           $vnp_Amount = $order->total * 100; 
           $vnp_Locale = 'vn';
           $vnp_BankCode = $order->bankCode;  // Mã ngân hàng
           $vnp_IpAddr = $request->ip(); // Địa chỉ IP

           // Tạo input data để gửi sang VNPay server
           $inputData = array(
               "vnp_Version" => "2.1.0",
               "vnp_TmnCode" => $vnp_TmnCode,
               "vnp_Amount" => $vnp_Amount,
               "vnp_Command" => "pay",
               "vnp_CreateDate" => date('YmdHis'),
               "vnp_CurrCode" => "VND",
               "vnp_IpAddr" => $vnp_IpAddr,
               "vnp_Locale" => $vnp_Locale,
               "vnp_OrderInfo" => $vnp_OrderInfo,
               "vnp_OrderType" => $vnp_OrderType,
               "vnp_ReturnUrl" => $vnp_ReturnUrl,
               "vnp_TxnRef" => $vnp_TxnRef,
            );
            // Kiểm tra nếu mã ngân hàng đã được thiết lập và không rỗng
            if (isset($vnp_BankCode) && $vnp_BankCode != "") {
               $inputData['vnp_BankCode'] = $vnp_BankCode;
            }
          
            // Kiểm tra nếu thông tin tỉnh/thành phố hóa đơn đã được thiết lập và không rỗng
            if (isset($vnp_Bill_State) && $vnp_Bill_State != "") {
                $inputData['vnp_Bill_State'] = $vnp_Bill_State; // Gán thông tin tỉnh/thành phố hóa đơn vào mảng dữ liệu input
            }

            // Sắp xếp mảng dữ liệu input theo thứ tự bảng chữ cái của key
            ksort($inputData);
          
            $query = ""; // Biến lưu trữ chuỗi truy vấn (query string)
            $i = 0; // Biến đếm để kiểm tra lần đầu tiên
            $hashdata = ""; // Biến lưu trữ dữ liệu để tạo mã băm (hash data)

            // Duyệt qua từng phần tử trong mảng dữ liệu input
            foreach ($inputData as $key => $value) {
                if ($i == 1) {
                    // Nếu không phải lần đầu tiên, thêm ký tự '&' trước mỗi cặp key=value
                    $hashdata .= '&' . urlencode($key) . "=" . urlencode($value);
                } else {
                    // Nếu là lần đầu tiên, không thêm ký tự '&'
                    $hashdata .= urlencode($key) . "=" . urlencode($value);
                    $i = 1; // Đánh dấu đã qua lần đầu tiên
                }
                // Xây dựng chuỗi truy vấn
                $query .= urlencode($key) . "=" . urlencode($value) . '&';
            }
             
            // Gán chuỗi truy vấn vào URL của VNPay
            $vnp_Url = $vnp_Url . "?" . $query;

            // Kiểm tra nếu chuỗi bí mật hash secret đã được thiết lập
            if (isset($vnp_HashSecret)) {
                // Tạo mã băm bảo mật (Secure Hash) bằng cách sử dụng thuật toán SHA-512 với hash secret
                $vnpSecureHash = hash_hmac('sha512', $hashdata, $vnp_HashSecret);
                // Thêm mã băm bảo mật vào URL để đảm bảo tính toàn vẹn của dữ liệu
                $vnp_Url .= 'vnp_SecureHash=' . $vnpSecureHash;
            }
            
             return redirect($vnp_Url);
       }
   }
  • vnpayReturn()
   namespace App\Http\Controllers\Payment;
   
   use Illuminate\Http\Request;
   class VnPayController extends Controller
   {
     public function vnpayReturn(Request $request)
       {
           $vnp_SecureHash = $request->vnp_SecureHash;
           $inputData = $request->all();
   
           unset($inputData['vnp_SecureHash']);
           ksort($inputData);
           $hashData = "";
           foreach ($inputData as $key => $value) {
               $hashData .= urlencode($key) . "=" . urlencode($value) . '&';
           }
           $hashData = rtrim($hashData, '&');
   
           $secureHash = hash_hmac('sha512', $hashData, config('vnpay.vnp_HashSecret'));
   
           if ($secureHash === $vnp_SecureHash) {
               if ($request->vnp_ResponseCode == '00') {
                   // Thanh toán thành công
                   return view('payment_success', compact('inputData'));
               } else {
                   // Thanh toán không thành công
                   return view('payment_failed');
               }
           } else {
               // Dữ liệu không hợp lệ
               return view('payment_failed');
           }
       }
   }

5. Tạo giao diện thanh toán

  • nút thanh toán với Vnpay
   <a href="{{ route('payment.create') }}" class="btn btn-primary">Thanh toán qua VNPay</a>
  • View payment_success.blade.php
<h1>Thanh toán thành công!</h1>
<p>Mã giao dịch: {{ $inputData['vnp_TxnRef'] }}</p>
<p>Số tiền: {{ number_format($inputData['vnp_Amount'] / 100) }} VND</p>
  • View payment_failed.blade.php
<h1>Thanh toán thất bi!</h1>
<p>Vui lòng thử lại.</p>