Categories Games

How to Add Stripe One Time Payment Form to Laravel Project – Quick Admin Panel


Payments are one of the most common elements of any web project, and Stripe is a payment provider that is very easy to install in Laravel projects. In this article, we will add a payment form to the page.

As an example, we’ll take the Product Show page from our QuickAdminPanel Product Management module, but you can follow the same instructions and add a Stripe form to ANY Laravel project page.

The plan will consist of 8 steps:

  1. Install Laravel Cashier
  2. Run Cashier migration
  3. Stripe credentials in .env
  4. User Model must be Billable
  5. Controller: Payment Intent Form
  6. Blade Pages: Form, Style, and Script
  7. Controller: Post Payment Processing
  8. After Successful Purchase: Ship the Product

Let’s start!


1. Install Laravel Cashier

Run this command:


composer require laravel/cashier

See: Currently the latest version of Kasir is v12. If you are reading this article when the latest version is available, please read the upgrade guide. But personally, I doubt that any fundamentals will change.


2. Run Cashier migration

The cashier package lists its own database migration directory, so remember to migrate your database after installing the package:


php artisan migrate

The migration was not carried out database/migration folder, they are inside /seller. Here are the contents.

1. Four new columns to user table:


Schema::table('users', function (Blueprint $table) {
    $table->string('stripe_id')->nullable()->index();
    $table->string('card_brand')->nullable();
    $table->string('card_last_four', 4)->nullable();
    $table->timestamp('trial_ends_at')->nullable();
});

2. New table subscriber:


Schema::create('subscriptions', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->unsignedBigInteger('user_id');
    $table->string('name');
    $table->string('stripe_id');
    $table->string('stripe_status');
    $table->string('stripe_plan')->nullable();
    $table->integer('quantity')->nullable();
    $table->timestamp('trial_ends_at')->nullable();
    $table->timestamp('ends_at')->nullable();
    $table->timestamps();

    $table->index(['user_id', 'stripe_status']);
});

3. New table subscription_item:


Schema::create('subscription_items', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->unsignedBigInteger('subscription_id');
    $table->string('stripe_id')->index();
    $table->string('stripe_plan');
    $table->integer('quantity');
    $table->timestamps();

    $table->unique(['subscription_id', 'stripe_plan']);
});

3. Stripe credentials in .env

There are two Stripe credentials you need to add in the file .env submit:


STRIPE_KEY=pk_test_xxxxxxxxx
STRIPE_SECRET=sk_test_xxxxxxxxx

Where to get the “keys” and “secrets”? In your Stripe Dashboard:

Laravel Key Dashboard Stripe env

Keep in mind, there are two Stripe button “modes”: testing And life key. When on a local server or test server, please remember to use the TESTING key, you can see it by enabling “View Test Data” in the left menu:

Line display test data

Another way to tell if you are using a test/live key: the test key starts with sk_test_ And pk_test_and the direct key starts with sk_live_ And pk_live_. Additionally, direct keys will not work without an SSL certificate enabled.

See: if you work in a team, when you add a new variable, it is a very good practice to add it also with an empty value in .env.example. Then your teammates will know what variables are needed on their server. Read more here.


4. User Model must be Billable

Simple steps: in your User model, add Billable Characteristics of Cashier:

app/Models/User.php:


// ...
use Laravel\Cashier\Billable;

class User extends Authenticatable
{
    use HasFactory, Billable;

5. Controller: Payment Intent Form

To enable the Stripe payment form, we need to create something called a “payment intent” and pass it to Blade.

In this case, we will add it Product Controller method show():


class ProductController extends Controller
{
    // ...

    public function show(Product $product)
    {
        $intent = auth()->user()->createSetupIntent();

        return view('frontend.coupons.show', compact('product', 'intent'));
    }

Method createSetupIntent() came from Billable the trait we added right above User model.


6. Blade Pages: Shape, Style, and Script

This is the form we’ll add from Stripe, with the cardholder’s name, card number, expiration month/year, CVV code, and zip code.

Stripe payment form in Laravel

Luckily, Stripe’s documentation tells us exactly what HTML/JavaScript/CSS code to add.

So, on to us show.blade.phpwe added this:


<form method="POST" action="{{ route('products.purchase', $product->id) }}" class="card-form mt-3 mb-3">
    @csrf
    <input type="hidden" name="payment_method" class="payment-method">
    <input class="StripeElement mb-3" name="card_holder_name" placeholder="Card holder name" required>
    <div class="col-lg-4 col-md-6">
        <div id="card-element"></div>
    </div>
    <div id="card-errors" role="alert"></div>
    <div class="form-group mt-3">
        <button type="submit" class="btn btn-primary pay">
            Purchase
        </button>
    </div>
</form>

All input variables are exactly as Stripe suggests, the only elements you need to change are routewhere the form will be posted, so this:


route('products.purchase', $product->id)

We will create the Controller routes and methods in the next step.

Meanwhile, we also need to include Stripe Style And JavaScript.
Just imagine in your main Blade file you have @produce section for styles and scripts, like this:


<!DOCTYPE html>
<html>

<head>
    ...

    @yield('styles')
</head>

<body>
    ...

    
    @yield('scripts')
</body>
</html>

Then, on to us show.blade.phpwe can fill in those parts, with code from Stripe:


@section('styles')
<style>
    .StripeElement {
        box-sizing: border-box;
        height: 40px;
        padding: 10px 12px;
        border: 1px solid transparent;
        border-radius: 4px;
        background-color: white;
        box-shadow: 0 1px 3px 0 #e6ebf1;
        -webkit-transition: box-shadow 150ms ease;
        transition: box-shadow 150ms ease;
    }
    .StripeElement--focus {
        box-shadow: 0 1px 3px 0 #cfd7df;
    }
    .StripeElement--invalid {
        border-color: #fa755a;
    }
    .StripeElement--webkit-autofill {
        background-color: #fefde5 !important;
    }
</style>
@endsection

@section('scripts')
<script src="
<script>
    let stripe = Stripe("{{ env('STRIPE_KEY') }}")
    let elements = stripe.elements()
    let style = {
        base: {
            color: '#32325d',
            fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
            fontSmoothing: 'antialiased',
            fontSize: '16px',
            '::placeholder': {
                color: '#aab7c4'
            }
        },
        invalid: {
            color: '#fa755a',
            iconColor: '#fa755a'
        }
    }
    let card = elements.create('card', {style: style})
    card.mount('#card-element')
    let paymentMethod = null
    $('.card-form').on('submit', function (e) {
        $('button.pay').attr('disabled', true)
        if (paymentMethod) {
            return true
        }
        stripe.confirmCardSetup(
            "{{ $intent->client_secret }}",
            {
                payment_method: {
                    card: card,
                    billing_details: {name: $('.card_holder_name').val()}
                }
            }
        ).then(function (result) {
            if (result.error) {
                $('#card-errors').text(result.error.message)
                $('button.pay').removeAttr('disabled')
            } else {
                paymentMethod = result.setupIntent.payment_method
                $('.payment-method').val(paymentMethod)
                $('.card-form').submit()
            }
        })
        return false
    })
</script>

Inside the section, we add two variables from the back-end:

env('STRIPE_KEY')

And

$intent->client_secret

So make sure you add it in the previous step.


7. Controller: Post Payment Processing

Remember the route we called in the previous step? It’s time to make it.

In the routes/web.phpadd this:


Route::post('products/{id}/purchase', 'ProductController@purchase')->name('products.purchase');

Then, let’s create a method in it Product Controller:


public function purchase(Request $request, Product $product)
{
    $user          = $request->user();
    $paymentMethod = $request->input('payment_method');

    try {
        $user->createOrGetStripeCustomer();
        $user->updateDefaultPaymentMethod($paymentMethod);
        $user->charge($product->price * 100, $paymentMethod);        
    } catch (\Exception $exception) {
        return back()->with('error', $exception->getMessage());
    }

    return back()->with('message', 'Product purchased successfully!');
}

So what’s going on here?

1. We get payment_method from the form (Stripe handles it in the background for us)
2. Then we call the Cashier method to get/create the customer, set their payment method, and bill them.
3. Finally, we redirect again with successful results
3b. If an error occurs, the try/catch block will handle it and redirect with the error.

See: variable $product->price is the price of your product, and we need to multiply it by 100 because Stripe billing is occurring in cents.

To display success or error messages, in your Blade file you need to add something like this:


@if(session('message'))
    <div class="alert alert-success" role="alert">{{ session('message') }}</div>
@endif
@if(session('error'))
    <div class="alert alert-danger" role="alert">{{ session('error') }}</div>
@endif

8. After Successful Purchase: Ship the Product

Once the customer pays for the product, you must ship the order. Of course, it depends on what they buy and the code is individual, but I’ll show you where to put it.

Actually there are two ways. Easier but less safe, or harder and safer.

Option 1. Fulfill Orders in ProductController

You can do it directly in the same way:


public function purchase(Request $request, Product $product)
{
    $user          = $request->user();
    $paymentMethod = $request->input('payment_method');

    try {
        $user->createOrGetStripeCustomer();
        $user->updateDefaultPaymentMethod($paymentMethod);
        $user->charge($product->price * 100, $paymentMethod);        
    } catch (\Exception $exception) {
        return back()->with('error', $exception->getMessage());
    }

    // Here, complete the order, like, send a notification email
    $user->notify(new OrderProcessed($product)); 

    return back()->with('message', 'Product purchased successfully!');
}

Easy, right? The problem with that method is that it happens synchronously, which means it does $user->cost() may not have been successfully completed by the time you fulfill the order. In theorythis may result in the delivery of fraudulent orders with unsuccessful invoices.

Option 2. Stripe Webhooks

Alternatively, a more reliable method is to capture what Stripe calls a Webhook. They ensure that the charge happens successfully, in the right way. Every time something happens in Stripe, they send a POST request to your server URL that you provide in the Stripe dashboard.

You can watch many events from Stripe, and one of them is cost.success.

For that, I would recommend using a package called Laravel Stripe Webhooks, I’ve recorded a separate video about it:

So if you want to capture more events, and not just bill for success, I recommend you to use Stripe Webhook. Keep in mind that they won’t (easily) work on your local machine, you need to set up a real domain that Stripe will call.


Just that! May you receive many successful payments in your projects.



Teknologi Terkini

Agen Togel Terpercaya

Bandar Togel

Sabung Ayam Online

Berita Terkini

Artikel Terbaru

Berita Terbaru

Penerbangan

Berita Politik

Berita Politik

Software

Software Download

Download Aplikasi

Berita Terkini

News

Jasa PBN

Jasa Artikel

News

Breaking News

Berita

More From Author