0

TÍCH HỢP CỔNG THANH TOÁN VNPAY VÀO DỰ ÁN LARAVEL TRONG 5 BƯỚC ĐƠN GIẢN

💡 Thanh toán trực tuyến là "mạch máu" của mọi hệ thống thương mại điện tử hiện đại. Bạn là một lập trình viên Laravel và muốn tích hợp cổng thanh toán quốc dân VNPay vào dự án của mình? Bài viết này sẽ hướng dẫn bạn cách xây dựng luồng thanh toán VNPay chuẩn chỉ, bảo mật và cực kỳ nhanh chóng mà không cần phụ thuộc vào các package quá phức tạp. 🎉
Trong bài hướng dẫn thực hành này, chúng ta sẽ cùng nhau xây dựng một chức năng thanh toán đơn giản: Người dùng nhấn nút "Thanh toán", hệ thống chuyển hướng sang cổng VNPay để nhập thẻ, và cuối cùng quay trở lại website Laravel để hiển thị kết quả giao dịch.

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

Để ứng dụng Laravel của bạn có thể kết nối với cổng thanh toán, bạn cần các thông số định danh từ VNPay. Chúng ta sẽ sử dụng môi trường Sandbox để test thoải mái mà không mất tiền thật.
  1. Truy cập trang VNPay Sandbox (đăng ký nếu chưa có tài khoản): https://sandbox.vnpayment.vn/devreg/
  2. 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).

image.png

Hình 1: Giao diện đăng kí key test VNPay Sandbox.

Bước 2: Cấu hình môi trường bảo mật (.env)

Chúng ta tuyệt đối không nên viết trực tiếp (hard-code) Secret Key vào trong mã nguồn vì lý do bảo mật. Thay vào đó, hãy sử dụng file biến môi trường .env của Laravel. Mở file .env trong thư mục gốc dự án và thêm các dòng cấu hình sau:
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

Truy cập vào địa chỉ http://localhost:8000/payment. Nhấn nút "Thanh toán qua VNPay", bạn sẽ được chuyển hướng sang cổng Sandbox. Tại đây, chọn ngân hàng NCB và sử dụng thông tin thẻ test (Số thẻ: 9704198526191432198 - OTP: 123456).

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ả.

image.png

Hình 2: Giao diện cổng thanh toán VNPay khi người dùng được chuyển hướng.

Lời kết

Như vậy, bạn đã hoàn thành việc tích hợp cổng thanh toán VNPay vào Laravel một cách thủ công để hiểu rõ bản chất của quá trình xác thực và mã hóa dữ liệu. Tuy nhiên, để hệ thống hoàn thiện hơn cho môi trường Production, bạn nên tìm hiểu thêm về kỹ thuật IPN (Instant Payment Notification) để cập nhật trạng thái đơn hàng ngầm (Server-to-Server), đảm bảo đơn hàng không bị mất trạng thái nếu người dùng tắt trình duyệt sớm. Chúc các bạn thành công!

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí