+12

Xây dựng tính năng thanh toán với Braintree

Giới thiệu

Chào các bạn 👋👋, sau một số bài viết liên quan đến DevOps, Go, gRPC thì hôm nay mình sẽ quay lại với xuất phát điểm ban đầu của mình đó là PHP. Trong bài viết này mình sẽ giới thiệu với các bạn về Braintree và cách tích hợp Braintree Sandbox để xây dựng tính năng thanh toán cho ứng dụng Laravel.

Braintree

Braintree là một nền tảng thanh toán, cung cấp giải pháp thanh toán đa dạng cho các doanh nghiệp, người dùng trên khắp thế giới. Được phát triển bởi PayPal, Braintree không chỉ cung cấp các công cụ để tích hợp thanh toán một cách dễ dàng vào các ứng dụng web và di động, mà còn đảm bảo tính bảo mật cao và đem lại trải nghiệm tốt nhất cho người dùng.

Braintree cho phép doanh nghiệp hoặc nhà cung cấp chấp nhận thanh toán từ nhiều nguồn khác nhau bao gồm Debit Card và Credit Card, PayPal, Apple Pay, Android Pay và nhiều phương thức thanh toán khác. Điều này giúp tối ưu hóa quá trình thanh toán và tăng sự tiện lợi cho người dùng.

Một trong những điểm mạnh của Braintree là tính linh hoạt và dễ dàng tích hợp. Với các API và SDK, Braintree cho phép developer tùy chỉnh và tích hợp thanh toán vào các ứng dụng một cách linh hoạt và hiệu quả nhất. Bên cạnh đó, Braintree cung cấp các công cụ quản lý giao dịch, phân tích và báo cáo, giúp doanh nghiệp/nhà cung cấp hiểu rõ hơn về hoạt động thanh toán của mình và tối ưu hóa chiến lược kinh doanh.

Tích hợp Braintree vào ứng dụng Laravel

Chuẩn bị

Trước khi đi vào phần tích hợp braintree vào ứng dụng laravel các bạn cần chuẩn bị một số thứ như sau

  • Repository Laravel/ứng dụng Laravel hiện tại của bạn mà bạn muốn làm chức năng thanh toán
  • Tài khoản Braintree sandbox. Nếu các bạn gặp vấn đề về việc đăng ký tài khoản các bạn có thể theo dõi phần phía bên dưới.

Đăng ký tài khoản Braintree sanbox

login.png

  • Bước 2: Điền các thông tin cần thiết vào form đăng ký
    • Name, compane name, region: các bạn có thể điền bất kỳ tên nào mà các bạn muốn
    • Email: phần email các bạn cần điền đúng email của mình vì Braintree sẽ gửi mail xác nhận để bạn tiếp tục đăng ký tài khoản
  • Bước 3: Sau khi điền xong các bạn chọn nút Try the sandbox. Sau đó Braintree sẽ gửi mail đến email bạn đã đăng ký. Bạn vào kiểm tra mail và tiếp tục làm các bước tiếp theo.
  • Bước 4: Sau khi hoàn tất đăng ký các bạn tiến hành đăng nhập bạn sẽ thấy luôn thông tin về MERCHANT_ID, PUBLIC_KEY, PRIVATE_KEY. Những thông tin này sẽ cần sử dụng khi tích hợp Braintree vào ứng dụng.

Tích hợp Braintree vào ứng dụng Laravel

Để có thể tích hợp Braintree vào ứng dụng khác như Ruby, Python, ... các bạn có thể tham khảo ở docs. Trong phần này mình sẽ hướng dẫn tích hợp với Laravel.

Phần này mình chỉ hướng dẫn mọi người cách tích hợp Braintree vào ứng dụng thôi. Còn core business logic như mọi người lưu transaction ra sao hay xử lý thanh toán thế nào thì tùy theo ứng dụng của mình mà mọi người có logic phù hợp.

Đầu tiên ta cần bốn biến môi trường đó là:

  • BT_ENVIRONMENT: sandbox
  • BT_MERCHANT_ID: ******
  • BT_PUBLIC_KEY: ******
  • BT_PRIVATE_KEY: ******

Đối với biến BT_ENVIRONMENT thì mọi người để giá trị là sandbox vì đây là môi trường dev. Còn ba giá trị là BT_MERCHANT_ID, BT_PUBLIC_KEY, BT_PRIVATE_KEY thì mọi người lấy value trên tràn của Braintree nhé vì ngay khi đăng nhập thành công nó đã show cho mọi người thông tin này rồi.

Sau khi đã thêm được bốn biến môi trường của Braintree thì ta đưa nó vào config để sử dụng. Trong file services.php ta thêm config như sau.

// services.php
<?php

return [

    ...

    'braintree' => [
        'environment' => env('BT_ENVIRONMENT', 'sandbox'),
        'merchantId' => env('BT_MERCHANT_ID'),
        'publicKey' => env('BT_PUBLIC_KEY'),
        'privateKey' => env('BT_PRIVATE_KEY'),
    ],

];

Vì nội dung chính trong bài viết này là hướng dẫn tích hợp nên mình sẽ dựng lên một form request đơn giản để mọi người thực hiện thanh toán. Trong thư mục views mọi người thêm file payment.blade.php có nội dung sau.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    
    <!-- custom css file link  -->
    <link href="{{ asset('css/style.css') }}" rel="stylesheet">

</head>
<body>

<div class="container">

    @if (session('success_message'))
        <div class="alert alert-success">
            {{ session('success_message') }}
        </div>
    @endif

    @if(count($errors) > 0)
        <div class="alert alert-danger">
            <ul>
                @foreach ($errors->all() as $error)
                    <li>{{ $error }}</li>
                @endforeach
            </ul>
        </div>
    @endif

    <form method="post" id="payment-form" action="{{ url('/payment/checkout') }}">
        @csrf
        <div class="row">

            <div class="col">

                <h3 class="title">billing address</h3>

                <div class="inputBox">
                    <span>full name :</span>
                    <input type="text" placeholder="Simon">
                </div>
                <div class="inputBox">
                    <span>email :</span>
                    <input type="email" placeholder="Example@example.com">
                </div>
                <div class="inputBox">
                    <span>address :</span>
                    <input type="text" placeholder="Home Number - Street - Locality">
                </div>
                <div class="inputBox">
                    <span>city :</span>
                    <input type="text" placeholder="Hanoi">
                </div>

            </div>

            <div class="col">

                <h3 class="title">payment</h3>

                <div class="inputBox">
                    <span>Total in $</span>
                    <input id="amount" name="amount" type="tel" min="1" placeholder="Amount" value="10">
                </div>

                <div id="dropin-wrapper">
                    <div id="checkout-message"></div>
                    <div id="dropin-container"></div>
                </div>

            </div>
    
        </div>

        <input id="nonce" name="payment_method_nonce" type="hidden" />
        <button id="submit-button" class="submit-btn" type="submit" >Submit payment</button>

    </form>

</div>
</body>
</html>

File style.css.

@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@100;300;400;500;600&display=swap');

*{
  font-family: 'Poppins', sans-serif;
  margin:0; padding:0;
  box-sizing: border-box;
  outline: none; border:none;
  text-transform: capitalize;
  transition: all .2s linear;
}

.container{
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding:25px;
  min-height: 100vh;
  background-image: linear-gradient(to bottom right, #FDFCFB, #E2D1C3);
}

.container .alert{
  padding: 20px;
  width: 700px;
  margin-bottom: 50px;
  box-shadow: 0 5px 10px rgba(0, 0, 0, .1);
  font-size: 17px;
  color: #333;
}

.container .alert.alert-success{
  background-image: linear-gradient(to bottom right, #72FFB6, #10D164);
}

.container .alert.alert-danger{
  background-image: linear-gradient(to bottom right, #FF512F, #DD2476);
}

.container form{
  padding: 20px;
  width: 700px;
  background: #fff;
  box-shadow: 0 5px 10px rgba(0, 0, 0, .1);
}

.container form .row{
  display: flex;
  flex-wrap: wrap;
  gap: 15px;
}

.container form .row .col{
  flex: 1 1 250px;
}

.container form .row .col .title{
  font-size: 20px;
  color: #333;
  padding-bottom: 5px;
  text-transform: uppercase;
}

.container form .row .col .inputBox{
  margin: 15px 0;
}

.container form .row .col .inputBox span{
  margin-bottom: 10px;
  display: block;
}

.container form .row .col .inputBox input{
  width: 100%;
  border: 1px solid #ccc;
  padding: 10px 15px;
  font-size: 15px;
  text-transform: none;
}

.container form .row .col .inputBox input:focus{
  border: 1px solid #000;
}

.container form .row .col .flex{
  display: flex;
  gap: 15px;
}

.container form .row .col .flex .inputBox{
  margin-top: 5px;
}

.container form .row .col .inputBox img{
  height: 34px;
  margin-top: 5px;
  filter: drop-shadow(0 0 1px #000);
}

.container form .submit-btn{
  width: 100%;
  padding: 12px;
  font-size: 17px;
  background: radial-gradient(circle at 4.3% 10.7%, rgb(138, 118, 249) 13.6%, rgb(75, 252, 235) 100.7%);
  color: #fff;
  margin-top: 5px;
  cursor: pointer;
}

.container form .submit-btn:hover{
  background: #2ecc71;
}

Sau khi thêm hai file này vào ứng dụng ta sẽ có giao diện.

image.png

Giao diện này sẽ bao gồm 1 form để người dùng có thể gửi thông tin thanh toán và hiển thị trạng thái của việc thanh toán là thành công hay thất bại.

Để có thể tương tác với API của Braintree ta sẽ sử dụng package braintree_php. Các bạn cài thêm package này bằng lệnh.

composer require braintree/braintree_php

Sau đó tiến hành tạo PaymentController.php và thêm route cho phần thanh toán như sau.

// routes.php

...

Route::get('/payment', 'PaymentController@index')->name('payment');
Route::post('/payment/checkout', 'PaymentController@checkout')->name('checkout');
// PaymentController.php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class PaymentController extends Controller
{
    public function index()
    {
        $gateway = new \Braintree\Gateway([
            'environment' => config('services.braintree.environment'),
            'merchantId' => config('services.braintree.merchantId'),
            'publicKey' => config('services.braintree.publicKey'),
            'privateKey' => config('services.braintree.privateKey')
        ]);
    
        $token = $gateway->ClientToken()->generate();
    
        return view('payment', compact('token'));
    }

    public function checkout(Request $request)
    {
        $gateway = new \Braintree\Gateway([
            'environment' => config('services.braintree.environment'),
            'merchantId' => config('services.braintree.merchantId'),
            'publicKey' => config('services.braintree.publicKey'),
            'privateKey' => config('services.braintree.privateKey')
        ]);

        $amount = $request->amount;
        $nonce = $request->payment_method_nonce;

        $result = $gateway->transaction()->sale([
            'amount' => $amount,
            'paymentMethodNonce' => $nonce,
            'customer' => [
                'firstName' => 'Simon',
                'lastName' => 'Nguyen',
                'email' => 'testing-user@gmail.com',
            ],
            'options' => [
                'submitForSettlement' => true
            ]
        ]);

        if ($result->success) {
            $transaction = $result->transaction;
    
            return back()->with('success_message', 'Transaction successful. The ID is: '. $transaction->id);
        } else {
            $errorString = "";
    
            foreach ($result->errors->deepAll() as $error) {
                $errorString .= 'Error: ' . $error->code . ": " . $error->message . "\n";
            }
    
            return back()->withErrors('An error occurred with the message: '.$result->message);
        }
    }
}

Trong file routes.php mình thêm hai routes mới với một method là GET dùng để hiện thị form mà minh đã code ở trên và một method là POST dùng để xử lý logic khi users tiến hành thanh toán. Trong file PaymentController có hai hàm là index()checkout(Request $request).

  • index(): dùng để hiển thị form thanh toán cho người dùng và tạo token của Braintree dùng để tạo instance Braintree phía client sau này
  • checkout(Request $request): dùng để xử lý cho việc thanh toán với Braintree. Đầu tiên mình sẽ tạo gateway của Braintree, sau đó sẽ thêm transaction cho lần thanh toán đó của users. Ở đây mình sẽ hash code user như trong ví dụ là Simon. Và cuối cùng là hiển thị kết quả của lần thanh toán này là thành công hay thất bại.

Sau khi đã setup xong phía server có đầy đủ logic rồi thì ta sẽ tiến hành thêm Braintree instance cho phía client. Ở đây mình sẽ sử dụng drop-ui. Như file blade mình đã đề cập ở trên thì ta có một chỗ đang chưa được sử dụng đó là đoạn.

<div id="dropin-wrapper">
    <div id="checkout-message"></div>
    <div id="dropin-container"></div>
</div>

Thì chính phần container này sẽ được sử dụng cho phần drop-ui mà mình sẽ thêm ở đây. Để có thể sử dụng được SDK này đầu tiên các bạn thêm dòng script xuống cuối của phần body.

<script src="https://js.braintreegateway.com/web/dropin/1.42.0/js/dropin.min.js"></script>

Sau khi đã thêm thành công SDK của Braintree ta sẽ tiến hành viết script để xử lý gửi request lên trên server. Ta thêm đoạn logic sau.

<script type="text/javascript">
    const form = document.querySelector('#payment-form');
    var client_token = "{{ $token }}";

    braintree.dropin.create({
        authorization: client_token,
        container: '#dropin-container'
    }, function (createErr, instance) {
        if (createErr) {
            console.log('Create Error', createErr);
            return;
        }

        form.addEventListener('submit', function (event) {
            event.preventDefault();

            instance.requestPaymentMethod(function (err, payload) {
              if (err) {
                console.log('Payment Method Error', err);
                return;
              }

              // Add the nonce to the form and submit
              document.getElementById('nonce').value = payload.nonce;
              form.submit();
            });
        });
    });
</script>

Đầu tiên ta sẽ lấy ra form submit bằng cách query ra id #payment-form, tạo biến để lưu trữ token của Braintree như mình đã đề cập trong hàm index() ở trên. Sau đó ta sẽ tiến hành tạo instance dropin với SDK. Hàm create sẽ nhận config của instance và một callback. Trong callback này mình sẽ kiểm tra xem instance đã được tạo thành công hay chưa và log ra lỗi ở console, thêm event submit cho form để khi user nhấn submit sẽ gửi request lên trên server. Trong hàm xử lý phần submit form mình sẽ sử dụng hàm requestPaymentMethod để tạo giá trị nonce sau đó điền giá trị này vào input đã được hidden ở trên form để gửi lên server.

Sau khi đã có đầy đủ logic như trên rồi thì giao diện ta sẽ có thêm phần thanh toán cho thẻ của Braintree.

image.png

Ta sẽ tiến hành điền một vài thông tin vào form để kiểm tra lại chức năng thanh toán xem nó đã hoạt động hay chưa.

image.png

Sau khi điền thông tin như hình và bấm submit ta sẽ có thông báo như sau.

image.png

Vậy là transaction đã thành công. Để kiểm tra lại phần này ta sẽ lên lại trang web sandbox của Braintree và vào phần Transactions (lưu ý các bạn cần đăng nhập đúng tài khoản có config với Laravel nhé). Trong trang Transactions sẽ cho phép ta tra cứu lại các giao dịch theo điều kiện để kiểm tra nhanh gọn các bạn chỉ cần kéo xuống dưới cùng và chọn Search, các bạn sẽ thấy lịch sử vừa thành công của mình.

image.png

Các bạn có thể nhấn vào ID của transaction đó để xem chi tiết hơn về giao dịch, ngoài ra trong màn chi tiết ta còn có thể in hóa đơn, refund.

Tổng kết

Qua bài viết này mình đã giới thiệu Braintree và hướng dẫn tích hợp Braintree vào ứng dụng Laravel. Braintree còn rất nhiều tiệc ích khác (thanh toán Paypal, ApplePay, ...) mà trong bài viết mình không đề cập đến vì nội dụng của bài viết đã khá dài rồi. Hy vọng bài viết có thể giúp ích được các bạn phần nào. Hẹn gặp lại các bạn ở những bài viết khác và cảm ơn các bạn đã theo dõi đến hết bài viết ❤️.


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í