TÍCH HỢP CỔNG THANH TOÁN VNPAY VÀO DỰ ÁN LARAVEL TRONG 5 BƯỚC ĐƠN GIẢN
Chuẩn bị nguyên liệu
Trước khi bắt đầu, hãy đảm bảo bạn đã có sẵn:
- Một dự án Laravel đang chạy (Phiên bản 10.x hoặc 11.x được khuyến nghị).
- Tài khoản VNPay Sandbox (Môi trường kiểm thử) để lấy thông tin cấu hình.
Bước 1: Lấy thông tin cấu hình VNPay Sandbox
- Truy cập trang VNPay Sandbox (đăng ký nếu chưa có tài khoản): https://sandbox.vnpayment.vn/devreg/
- Bạn sẽ nhận được nhưng thông tin này từ trong Email đăng kí, lưu trữ nó
- vnp_TmnCode (Mã website).
- vnp_HashSecret (Chuỗi bí mật tạo checksum).
- vnp_Url (Đường dẫn thanh toán test).

Bước 2: Cấu hình môi trường bảo mật (.env)
VNP_TMN_CODE=Dien_Ma_Website_Cua_Ban
VNP_HASH_SECRET=Dien_Chuoi_Bi_Mat_Cua_Ban
VNP_URL=https://sandbox.vnpayment.vn/paymentv2/vpcpay.html
VNP_RETURN_URL=http://localhost:8000/vnpay-return
Lưu ý: VNP_RETURN_URL là địa chỉ mà VNPay sẽ chuyển hướng người dùng về sau khi thanh toán xong. Hãy đảm bảo nó khớp với Route bạn sẽ định nghĩa trong Laravel.
Bước 3: Xây dựng Controller xử lý logic
Tiếp theo, chúng ta cần tạo một Controller đóng vai trò điều phối: tạo URL thanh toán (kèm chữ ký bảo mật) và kiểm tra dữ liệu khi VNPay trả về.
Chạy lệnh artisan sau trong terminal:
php artisan make:controller PaymentController
Sau đó, mở file app/Http/Controllers/PaymentController.php vừa tạo và cập nhật nội dung mã nguồn sau:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class PaymentController extends Controller
{
// Phương thức hiển thị giao diện thanh toán
public function index()
{
return view('payment');
}
// Phương thức tạo URL và chuyển hướng đến VNPay
public function createPayment(Request $request)
{
$vnp_Url = env('VNP_URL');
$vnp_Returnurl = env('VNP_RETURN_URL');
$vnp_TmnCode = env('VNP_TMN_CODE');
$vnp_HashSecret = env('VNP_HASH_SECRET');
$vnp_TxnRef = rand(1, 10000); // Mã đơn hàng (nên là duy nhất)
$vnp_OrderInfo = "Thanh toan don hang test";
$vnp_OrderType = "billpayment";
$vnp_Amount = 10000 * 100; // Số tiền 10,000 VND (nhân 100 theo quy tắc VNPay)
$vnp_Locale = "vn";
$vnp_IpAddr = $_SERVER['REMOTE_ADDR'];
$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
);
// Sắp xếp dữ liệu để tạo chữ ký chuẩn
ksort($inputData);
$query = "";
$i = 0;
$hashdata = "";
foreach ($inputData as $key => $value) {
if ($i == 1) {
$hashdata .= '&' . urlencode($key) . "=" . urlencode($value);
} else {
$hashdata .= urlencode($key) . "=" . urlencode($value);
$i = 1;
}
$query .= urlencode($key) . "=" . urlencode($value) . '&';
}
$vnp_Url = $vnp_Url . "?" . $query;
if (isset($vnp_HashSecret)) {
$vnpSecureHash = hash_hmac('sha512', $hashdata, $vnp_HashSecret);
$vnp_Url .= 'vnp_SecureHash=' . $vnpSecureHash;
}
// Chuyển hướng người dùng sang VNPay
return redirect($vnp_Url);
}
// Phương thức xử lý kết quả trả về từ VNPay
public function vnpayReturn(Request $request)
{
$vnp_HashSecret = env('VNP_HASH_SECRET');
$inputData = $request->all();
$vnp_SecureHash = $inputData['vnp_SecureHash'];
// Loại bỏ các tham số hash để tính toán lại và đối chiếu
unset($inputData['vnp_SecureHash']);
unset($inputData['vnp_SecureHashType']);
ksort($inputData);
$i = 0;
$hashData = "";
foreach ($inputData as $key => $value) {
if ($i == 1) {
$hashData = $hashData . '&' . urlencode($key) . "=" . urlencode($value);
} else {
$hashData = $hashData . urlencode($key) . "=" . urlencode($value);
$i = 1;
}
}
$secureHash = hash_hmac('sha512', $hashData, $vnp_HashSecret);
if ($secureHash == $vnp_SecureHash) {
if ($request->vnp_ResponseCode == '00') {
return view('payment', ['status' => 'success', 'message' => 'Giao dịch thành công!']);
} else {
return view('payment', ['status' => 'error', 'message' => 'Giao dịch thất bại.']);
}
} else {
return view('payment', ['status' => 'error', 'message' => 'Chữ ký không hợp lệ!']);
}
}
}
Bước 4: Thiết kế giao diện người dùng (Blade View)
Chúng ta cần một giao diện đơn giản để người dùng kích hoạt thanh toán. Hãy tạo file mới tại đường dẫn resources/views/payment.blade.php. Tương tự, tôi sẽ sử dụng CSS của Bootstrap 5 để giao diện trông hiện đại hơn.
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tích hợp VNPay vào Laravel</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light py-5">
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow-sm">
<div class="card-header bg-primary text-white text-center">
<h4 class="mb-0 fw-bold">Thanh Toán Đơn Hàng</h4>
</div>
<div class="card-body p-4 text-center">
<h5 class="card-title text-muted">Tổng tiền thanh toán</h5>
<h2 class="text-primary fw-bold my-3">10,000 VND</h2>
<p class="text-secondary">Nội dung: Thanh toan don hang test</p>
<form action="{{ route('vnpay.create') }}" method="POST">
@csrf
<button type="submit" class="btn btn-success btn-lg w-100">
Thanh toán qua VNPay
</button>
</form>
<hr class="my-4">
@if(isset($status))
@if($status == 'success')
<div class="alert alert-success fw-bold">
<i class="bi bi-check-circle"></i> {{ $message }}
</div>
@else
<div class="alert alert-danger fw-bold">
<i class="bi bi-x-circle"></i> {{ $message }}
</div>
@endif
@endif
</div>
</div>
</div>
</div>
</div>
</body>
</html>
Bước 5: Khai báo Route
Cuối cùng, mở file routes/web.php và đăng ký các đường dẫn cần thiết.
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PaymentController;
// Route hiển thị giao diện thanh toán
Route::get('/payment', [PaymentController::class, 'index']);
// Route xử lý tạo thanh toán (Gửi sang VNPay)
Route::post('/vnpay-create', [PaymentController::class, 'createPayment'])->name('vnpay.create');
// Route nhận kết quả trả về từ VNPay
Route::get('/vnpay-return', [PaymentController::class, 'vnpayReturn'])->name('vnpay.return');
Thành quả và Kiểm thử
Vậy là quá trình tích hợp đã hoàn tất! Hãy khởi chạy development server:
php artisan serve
Sau khi nhập OTP thành công, bạn sẽ được đưa trở lại website với thông báo kết quả.

Lời kết
All rights reserved