Saturday, November 14, 2020

Database Transactions in Laravel

 

Database Transactions in Laravel

Laravel's documentation on Database Transactions describes wrapping our database calls within a closure. What if we need more power? Let's dig in to see what's going on behind the scenes, and what tools we have to work with Database Transactions in Laravel.

What are Database Transactions?

You may already know what a transaction is. However, let's review! A transaction gives you the ability to safely perform a set of data-modifying SQL queries (such as insertions, deletions or updates). This is made safe because you can choose to rollback all queries made within the transaction at any time.

For example, let's pretend we have an application which allows the creation of accounts. Each account can have one or more users associated with it. If this application creates an account and the first user at the same time, you need to handle what happens when the account was created successfuly, but the user is not.

In this sample code:

// Create Account
$newAcct = Account::create([
    'accountname' => Input::get('accountname'),
]);

// Create User
$newUser = User::create([
    'username' => Input::get('username'),
    'account_id' => $newAcct->id,
]);

Two situations can cause issues:

Account was not created.

If the account was not created, there's no id available to pass to the user for its account_id field. In this scenario, the account and user will fail to be created, so there isn't necessarily disparate data in the database. We just need to handle that situation in code (not shown above).

User was not created.

If, however, the account was created, but the user was not, then we run into issues. You now have an account with no available users, and there is disparity in the database data. You can either code around that, and every other possible data disparity edge-case in your application, or you can wrap this in a transaction and be done with it!

Our Transactional Toolset

Database transactions consist of three possible "tools":

  1. Creating a transaction - Letting the database know that next queries on a connection should be considered part of a transaction
  2. Rolling back a transaction - Cancelling all queries within the transaction, ending the transactional state
  3. Committing a transaction - Committing all queries within the transaction, ending the transactional state. No data if affected until the transaction is committed.

Table and/or row locking is important to know about as well, especially on high-traffic sites. However, I won't cover that here. See MySQL Transactional Locking with InnoDB and/or PostgreSQL transaction isolation. Perhaps read on about ACID and Concurrency Control.

The previous sample code can be pseudo-coded with transactions as such:

// Start transaction
beginTransaction();

// Run Queries
$acct = createAccount();
$user = createUser();

// If there's an error
//    or queries don't do their job,
//    rollback!
if( !$acct || !$user )
{
    rollbackTransaction();
} else {
    // Else commit the queries
    commitTransaction();
}

Basic Transactions in Laravel

The first way to run a transaction within Laravel is to put your queries within a closure passed to the DB::transaction() method:

DB::transaction(function()
{
    $newAcct = Account::create([
        'accountname' => Input::get('accountname')
    ]);

    $newUser = User::create([
        'username' => Input::get('username'),
        'account_id' => $newAcct->id,
    ]);
});

One thing that's not evident is the answer to this question: How does this code know to rollback or commit the transaction?

We can find out by looking at the code behind the scenes:

    public function transaction(Closure $callback)
    {
            $this->beginTransaction();

            // We'll simply execute the given callback within a try / catch block
            // and if we catch any exception we can rollback the transaction
            // so that none of the changes are persisted to the database.
            try
            {
                    $result = $callback($this);

                    $this->commit();
            }

            // If we catch an exception, we will roll back so nothing gets messed
            // up in the database. Then we'll re-throw the exception so it can
            // be handled how the developer sees fit for their applications.
            catch (\Exception $e)
            {
                    $this->rollBack();

                    throw $e;
            }

            return $result;
    }
// try...catch try { // Transaction $exception = DB::transaction(function() { // Do your SQL here }); if(is_null($exception)) { return true; } else { throw new Exception; } } catch(Exception $e) { return false; }

Very simply, if an Exception of any kind is thrown within the closure, then the transaction is rolled back. This means that if there's a SQL error (one that would not normally fail silently), then the transaction is rolled back. More powerfully, however, this means that we can throw our own exceptions in order to rollback a transaction. Something like this:

DB::transaction(function()
{
    $newAcct = Account::create([
        'accountname' => Input::get('accountname')
    ]);

    $newUser = User::create([
        'username' => Input::get('username'),
        'account_id' => $newAcct->id,
    ]);

    if( !$newUser )
    {
        throw new \Exception('User not created for account');
    }
});

Advanced Transactions in Laravel

I recently found myself needing more control over handling transaction. My create() methods also handled validation by throwing a custom ValidationException if there was a validation issue. If this exception was caught, the server responded by redirecting the user with the error messages.

try {
    // Validate, then create if valid
    $newAcct = Account::create( ['accountname' => Input::get('accountname')] );
} catch(ValidationException $e)
{
    // Back to form with errors
    return Redirect::to('/form')
        ->withErrors( $e->getErrors() )
        ->withInput();
}

try {
    // Validate, then create if valid
    $newUser = User::create([
        'username' => Input::get('username'),
        'account_id' => $newAcct->id
    ]);
} catch(ValidationException $e)
{
    // Back to form with errors
    return Redirect::to('/form')
        ->withErrors( $e->getErrors() )
        ->withInput();
}

Conversations about this use of Exceptions aside, how would I put this in a transaction if the ValidationExceptions were always caught? Simply putting this inside of a DB::transaction() call would guarantee it would never trigger a rollback if the validation failed on the creation of either account or user.

Looking more closely at the database code, however, we can see that we can manually call beginTransactionrollback and commit! Putting the above code into a transaction was then as simple as:

// Start transaction!
DB::beginTransaction();

try {
    // Validate, then create if valid
    $newAcct = Account::create( ['accountname' => Input::get('accountname')] );
} catch(ValidationException $e)
{
    // Rollback and then redirect
    // back to form with errors
    DB::rollback();
    return Redirect::to('/form')
        ->withErrors( $e->getErrors() )
        ->withInput();
} catch(\Exception $e)
{
    DB::rollback();
    throw $e;
}

try {
    // Validate, then create if valid
    $newUser = User::create([
        'username' => Input::get('username'),
        'account_id' => $newAcct->id
    ]);
} catch(ValidationException $e)
{
    // Rollback and then redirect
    // back to form with errors
    DB::rollback();
    return Redirect::to('/form')
        ->withErrors( $e->getErrors() )
        ->withInput();
} catch(\Exception $e)
{
    DB::rollback();
    throw $e;
}

// If we reach here, then
// data is valid and working.
// Commit the queries!
DB::commit();

Note that I also catch a generic Exception as a last-ditch maneuver to ensure data integrity, just in case any other exception other than a ValidationException is thrown. Because this strategy costs us our previously discusssed automatic protection against exceptions, it's prudent to add in this precaution.

That's it! We have full control over database transactions within Laravel!

Laravel stripe payment gateway integration with Laravel 7

Today, I want to share with you how to integrate stripe payment gateway in Laravel 7. we will use stripe/stripe-php composer library for stripe payment gateway. we will make RS100 charge using Stripe payment gateway Integration.

Stripe is a US company allowing businesses to accept payments over the Internet. It operates in 25 countries and powers 100,000+ businesses.Stripe payments allow you to get paid with all major cards from customers around the world on the web or in mobile apps.

I will give you an example from scratch to implement a stripe payment gateway in Laravel 7 applications. In this tutorial, we are going to use stripe js v3 and stripe intent to make the payment. You just need to follow each and every step to get paid.

Requirments

  • Stripe Account
  • A system with installed composer
  • Basic idea of PHP and Laravel

STEP – 1 Install fresh Laravel Project by using the below command just open the command prompt and copy paste the below code.

composer create-project --prefer-dist laravel/laravel Stripe

STEP – 2 Install stripe via composer again go to your command and write the below code

composer require stripe/stripe-php

STEP – 3 Create your stripe Account

Stripe Account Dashboard

STEP- 4 Get your stripe key and secret you will find those things in your stripe dashboard, Just copy the publishable key and secret key and paste to the safe place we will use those later.

Stripe Dashboard

STEP – 5 Create a CheckoutController Using the following Command.

php artisan make:controller CheckoutController

STEP – 6 Go to web.php and the following code in that file

Route::get('checkout','CheckoutController@checkout');
Route::post('checkout','CheckoutController@afterpayment')->name('checkout.credit-card');

STEP – 7 Go CheckoutController.php add the below code

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class CheckoutController extends Controller
{
    public function checkout()
    {   
        // Enter Your Stripe Secret
        \Stripe\Stripe::setApiKey('use-your-stripe-key-here');
        		
		$amount = 100;
		$amount *= 100;
        $amount = (int) $amount;
        
        $payment_intent = \Stripe\PaymentIntent::create([
			'description' => 'Stripe Test Payment',
			'amount' => $amount,
			'currency' => 'INR',
			'description' => 'Payment From Codehunger',
			'payment_method_types' => ['card'],
		]);
		$intent = $payment_intent->client_secret;

		return view('checkout.credit-card',compact('intent'));

    }

    public function afterPayment()
    {
        echo 'Payment Has been Received';
    }
}

STEP – 8 Create blade file credit-card.blade.php under resources/views/checkout/credit-card.blade.php and add the below code.

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Stripe Payment</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</head>
<body>
    @php
        $stripe_key = 'put your publishable key here';
    @endphp
    <div class="container" style="margin-top:10%;margin-bottom:10%">
        <div class="row justify-content-center">
            <div class="col-md-12">
                <div class="">
                    <p>You will be charged rs 100</p>
                </div>
                <div class="card">
                    <form action="{{route('checkout.credit-card')}}"  method="post" id="payment-form">
                        @csrf                    
                        <div class="form-group">
                            <div class="card-header">
                                <label for="card-element">
                                    Enter your credit card information
                                </label>
                            </div>
                            <div class="card-body">
                                <div id="card-element">
                                <!-- A Stripe Element will be inserted here. -->
                                </div>
                                <!-- Used to display form errors. -->
                                <div id="card-errors" role="alert"></div>
                                <input type="hidden" name="plan" value="" />
                            </div>
                        </div>
                        <div class="card-footer">
                          <button
                          id="card-button"
                          class="btn btn-dark"
                          type="submit"
                          data-secret="{{ $intent }}"
                        > Pay </button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
    <script src="https://js.stripe.com/v3/"></script>
    <script>
        // Custom styling can be passed to options when creating an Element.
        // (Note that this demo uses a wider set of styles than the guide below.)

        var style = {
            base: {
                color: '#32325d',
                lineHeight: '18px',
                fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
                fontSmoothing: 'antialiased',
                fontSize: '16px',
                '::placeholder': {
                    color: '#aab7c4'
                }
            },
            invalid: {
                color: '#fa755a',
                iconColor: '#fa755a'
            }
        };
    
        const stripe = Stripe('{{ $stripe_key }}', { locale: 'en' }); // Create a Stripe client.
        const elements = stripe.elements(); // Create an instance of Elements.
        const cardElement = elements.create('card', { style: style }); // Create an instance of the card Element.
        const cardButton = document.getElementById('card-button');
        const clientSecret = cardButton.dataset.secret;
    
        cardElement.mount('#card-element'); // Add an instance of the card Element into the `card-element` <div>.
    
        // Handle real-time validation errors from the card Element.
        cardElement.addEventListener('change', function(event) {
            var displayError = document.getElementById('card-errors');
            if (event.error) {
                displayError.textContent = event.error.message;
            } else {
                displayError.textContent = '';
            }
        });
    
        // Handle form submission.
        var form = document.getElementById('payment-form');
    
        form.addEventListener('submit', function(event) {
            event.preventDefault();
    
        stripe.handleCardPayment(clientSecret, cardElement, {
                payment_method_data: {
                    //billing_details: { name: cardHolderName.value }
                }
            })
            .then(function(result) {
                console.log(result);
                if (result.error) {
                    // Inform the user if there was an error.
                    var errorElement = document.getElementById('card-errors');
                    errorElement.textContent = result.error.message;
                } else {
                    console.log(result);
                    form.submit();
                }
            });
        });
    </script>
</body>
</html>

STEP – 9 visit this URL from your system – http://localhost/crud/public/checkout you will get view something like below.

Payment-view
Payment View

STEP – 10 Enter the below test card detail to make a test payment

Card Number - 4242 4242 4242 4242 EXP - 12/32 CVV - 123 ZIP - 12345

STEP – 11 After the successful payment you can check your stripe dashboard for the payment confirmation.

payment-confirmation-dashboard

Payment Confirmation Dashboard


 

https://codehunger.in/laravel-stripe-payment-gateway-integration-with-laravel-7/