Blog Single

Hướng Dẫn Tích Hợp Cổng Thanh Toán VNPay Với Laravel

Trong thời đại thương mại điện tử bùng nổ, việc cung cấp đa dạng phương thức thanh toán là yếu tố sống còn để tăng tỷ lệ chuyển đổi. VNPay hiện đang là một trong những cổng thanh toán phổ biến và uy tín nhất tại Việt Nam.

Hôm nay, Techblog.vn sẽ hướng dẫn bạn cách tích hợp cổng thanh toán VNPay với Laravel một cách chuẩn xác, bảo mật và nhanh chóng nhất. Bài viết này dành cho các lập trình viên đang xây dựng website bán hàng hoặc dịch vụ bằng framework Laravel.

1. Chuẩn bị trước khi tích hợp VNPay vào Laravel

Để quá trình tích hợp diễn ra suôn sẻ, bạn cần chuẩn bị các yếu tố sau:

  • Một Project Laravel đã được cài đặt và đang hoạt động (phiên bản Laravel 8, 9, 10 hoặc 11 đều áp dụng được).

  • Tài khoản môi trường thử nghiệm (Sandbox) của VNPay. Bạn cần đăng ký trên trang chủ VNPay Sandbox để lấy 3 thông tin quan trọng:

    • vnp_TmnCode: Mã website của bạn tại VNPay.

    • vnp_HashSecret: Chuỗi bí mật dùng để tạo checksum (đảm bảo tính toàn vẹn dữ liệu).

    • vnp_Url: URL thanh toán của VNPay.

2. Các bước tích hợp VNPay với Laravel

Bước 1: Cấu hình môi trường (.env)

Mở file .env ở thư mục gốc của project Laravel và thêm các thông tin bạn vừa lấy được từ VNPay:

Đoạn mã

VNPAY_TMN_CODE=Mã_TmnCode_Của_Bạn
VNPAY_HASH_SECRET=Chuỗi_HashSecret_Của_Bạn
VNPAY_URL=https://sandbox.vnpayment.vn/paymentv2/vpcpay.html
VNPAY_RETURN_URL="${APP_URL}/vnpay/return"

Tiếp theo, để dễ dàng gọi các biến này trong code, hãy khai báo chúng trong file config/services.php:

PHP

'vnpay' => [
    'tmn_code' => env('VNPAY_TMN_CODE'),
    'hash_secret' => env('VNPAY_HASH_SECRET'),
    'url' => env('VNPAY_URL'),
    'return_url' => env('VNPAY_RETURN_URL'),
],

Bước 2: Tạo Controller xử lý thanh toán VNPay

Bạn chạy lệnh sau trong terminal để tạo Controller: php artisan make:controller VNPayController

Trong VNPayController, chúng ta sẽ cần 2 hàm chính: một hàm để tạo URL thanh toán và chuyển hướng người dùng, một hàm để xử lý kết quả trả về (Return URL).

PHP

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class VNPayController extends Controller
{
    // Hàm tạo URL và chuyển hướng đến VNPay
    public function createPayment(Request $request)
    {
        $vnp_Url = config('services.vnpay.url');
        $vnp_Returnurl = config('services.vnpay.return_url');
        $vnp_TmnCode = config('services.vnpay.tmn_code');
        $vnp_HashSecret = config('services.vnpay.hash_secret');

        $vnp_TxnRef = time(); // Mã đơn hàng (có thể thay bằng mã ID đơn hàng thật)
        $vnp_OrderInfo = "Thanh toán đơn hàng #" . $vnp_TxnRef;
        $vnp_OrderType = 'billpayment';
        $vnp_Amount = $request->amount * 100; // Số tiền thanh toán (VNĐ) nhân 100
        $vnp_Locale = 'vn';
        $vnp_IpAddr = $request->ip();

        $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
        );

        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;
        }

        return redirect()->away($vnp_Url);
    }

    // Hàm xử lý kết quả trả về từ VNPay
    public function returnPayment(Request $request)
    {
        $vnp_HashSecret = config('services.vnpay.hash_secret');
        $inputData = array();
        foreach ($request->all() as $key => $value) {
            if (substr($key, 0, 4) == "vnp_") {
                $inputData[$key] = $value;
            }
        }
        
        $vnp_SecureHash = $inputData['vnp_SecureHash'];
        unset($inputData['vnp_SecureHash']);
        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') {
                // Giao dịch thành công
                // Cập nhật trạng thái đơn hàng vào database tại đây
                return view('payment.success', ['message' => 'Thanh toán thành công!']);
            } else {
                // Giao dịch lỗi
                return view('payment.error', ['message' => 'Giao dịch bị hủy hoặc lỗi!']);
            }
        } else {
            // Chữ ký không hợp lệ
            return view('payment.error', ['message' => 'Chữ ký không hợp lệ!']);
        }
    }
}

Bước 3: Thiết lập Route

Mở file routes/web.php và định tuyến cho 2 hàm chúng ta vừa tạo:

PHP

use App\Http\Controllers\VNPayController;

// Route xử lý form gửi đi
Route::post('/vnpay/payment', [VNPayController::class, 'createPayment'])->name('vnpay.payment');

// Route nhận kết quả trả về
Route::get('/vnpay/return', [VNPayController::class, 'returnPayment'])->name('vnpay.return');

Bước 4: Tạo View nút thanh toán (Blade Template)

Tạo một form HTML cơ bản để người dùng nhập số tiền và bấm nút thanh toán:

HTML

<form action="{{ route('vnpay.payment') }}" method="POST">
    @csrf
    <div class="form-group">
        <label for="amount">Số tiền cần thanh toán (VNĐ)</label>
        <input type="number" name="amount" class="form-control" required>
    </div>
    <button type="submit" class="btn btn-primary mt-3">Thanh toán qua VNPay</button>
</form>

3. Lưu ý quan trọng khi đưa lên môi trường thật (Production)

  • Sử dụng IPN (Instant Payment Notification): Hàm returnPayment ở trên chỉ để hiển thị cho người dùng. Để đảm bảo tính chính xác tuyệt đối (tránh trường hợp người dùng tắt trình duyệt khi đang load), bạn phải cấu hình thêm URL IPN. VNPay sẽ gọi ngầm vào URL IPN này để cập nhật trạng thái đơn hàng vào Database của bạn.

  • Thay đổi thông tin Cấu hình: Khi golive, bạn phải thay đổi vnp_TmnCode, vnp_HashSecretVNPAY_URL thành thông tin môi trường thật do VNPay cấp.

Kết Luận

Việc tích hợp cổng thanh toán VNPay với Laravel không quá phức tạp nếu bạn làm đúng theo tài liệu chuẩn. Hy vọng bài viết này của Techblog.vn đã giúp bạn cấu hình thành công và tối ưu hóa hệ thống thanh toán cho dự án của mình.

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *