article

Friday, September 30, 2022

Laravel 9 Cashier Stripe Subscription

Laravel 9 Cashier Stripe Subscription

Download Laravel App

composer create-project --prefer-dist laravel/laravel my-app
C:\xampp\htdocs\laravel>composer create-project --prefer-dist laravel/laravel my-app

Connecting our Database

open .env file root directory.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laraveldb
DB_USERNAME=root
DB_PASSWORD=

Install Auth Scaffold
composer require laravel/ui
C:\xampp\htdocs\laravel\my-app>composer require laravel/ui

generate auth scaffold with bootstrap
php artisan ui bootstrap --auth
C:\xampp\htdocs\laravel\my-app>php artisan ui bootstrap --auth

install npm packages
npm install
C:\xampp\htdocs\laravel\my-app>npm install

built bootstrap CSS
npm run build
C:\xampp\htdocs\laravel\my-app>npm run build

Install Cashier Package
Laravel Cashier (Stripe)
https://laravel.com/docs/9.x/billing
composer require laravel/cashier
C:\xampp\htdocs\laravel\my-app>composer require laravel/cashier

publish cashier migration for creating tables
php artisan vendor:publish --tag="cashier-migrations"
C:\xampp\htdocs\laravel\my-app>php artisan vendor:publish --tag="cashier-migrations"

run the migration
C:\xampp\htdocs\laravel\my-app>php artisan migrate

login stripe account stripe.com test click Developers and get API keys 
add API Kyes to .env 

STRIPE_KEY=pk_test_secretkeyxxx
STRIPE_SECRET=sk_test_secretkeyxxxx

click Products tab and create plans
BUSINESS PLAN and PREMIUM and copy the API ID 

add Cashier to User Model 
Laravel\Cashier\Billable
app/Models/User.php
//
<?php

namespace App\Models;

// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use Laravel\Cashier\Billable;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable, Billable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
}
Model Plan
C:\xampp\htdocs\laravel\my-app>php artisan make:migration create_plans_table
database/migrations/create_plan_table.php
//database/migrations/create_plan_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up()
    {
        Schema::create('plans', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('slug');
            $table->string('stripe_plan');
            $table->integer('price');
            $table->string('description');
            $table->timestamps();
        });
    }
    public function down()
    {
        Schema::dropIfExists('plans');
    }
};
run this migration
C:\xampp\htdocs\laravel\my-app>php artisan migrate

create model plan
php artisan make:model Plan
C:\xampp\htdocs\laravel\my-app>php artisan make:model Plan
app/Models/Plan.php
//app/Models/Plan.php
<?php
  
namespace App\Models;
  
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
  
class Plan extends Model
{
    use HasFactory;
  
    protected $fillable = [
        'name',
        'slug',
        'stripe_plan',
        'price',
        'description',
    ];
  
    public function getRouteKeyName()
    {
        return 'slug';
    }
}
Create Controller
C:\xampp\htdocs\laravel\my-app>php artisan make:controller PlanController
app/Http/Controllers/PlanController.php
//app/Http/Controllers/PlanController.php
<?php
  
namespace App\Http\Controllers;
  
use Illuminate\Http\Request;
use App\Models\Plan;
use Illuminate\Support\Facades\Auth;
use App\Models\User;

class PlanController extends Controller
{
    /**
     * Write code on Method
     *
     * @return response()
     */
    public function index()
    {
        $plans = Plan::get();
  
        return view("plans", compact("plans"));
    }  
  
    /**
     * Write code on Method
     *
     * @return response()
     */
    public function show(Plan $plan, Request $request)
    {
        $intent = auth()->user()->createSetupIntent();
  
        return view("subscription", compact("plan", "intent"));
    }
    /**
     * Write code on Method
     *
     * @return response()
     */
    public function subscription(Request $request)
    {
        $plan = Plan::find($request->plan);
  
        $subscription = $request->user()->newSubscription($request->plan, $plan->stripe_plan)
                        ->create($request->token);
  
        return view("subscription_success");
    }
}
Create Routes laravelproject\routes\web.php
//laravelproject\routes\web.php
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PlanController;

Route::get('/', function () {
    return view('welcome');
});


Auth::routes();

Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');

Route::middleware("auth")->group(function () {
    Route::get('plans', [PlanController::class, 'index']);
    Route::get('plans/{plan}', [PlanController::class, 'show'])->name("plans.show");
    Route::post('subscription', [PlanController::class, 'subscription'])->name("subscription.create");
});
Create View Blade File

resources/views/plans.blade.php
resources/views/subscription.blade.php
resources/views/subscription_success.blade.php

resources/views/plans.blade.php
//resources/views/plans.blade.php
@extends('layouts.app')
  
@section('content')
<div class="container">
<section>
  <div class="container py-5">
    
  <header class="text-center mb-5 text-white">
      <div class="row">
        <div class="col-lg-12 mx-auto">
          <h1>Laravel 9 Cashier Stripe Subscription</h1>
          <h3>PRICING</h3>
        </div>
      </div>
    </header>

    <div class="row text-center align-items-end">
        <div class="col-lg-4 mb-5 mb-lg-0">
            <div class="bg-white p-5 rounded-lg shadow">
            <h1 class="h6 text-uppercase font-weight-bold mb-4">FREE</h1>
            <h2 class="h1 font-weight-bold">$0<span class="text-small font-weight-normal ml-2">/ free</span></h2>

            <div class="custom-separator my-4 mx-auto bg-primary"></div>

            <ul class="list-unstyled my-5 text-small text-left">
                <li class="mb-3">
                <i class="fa fa-check mr-2 text-primary"></i> Lorem ipsum dolor sit amet</li>
                <li class="mb-3">
                <i class="fa fa-check mr-2 text-primary"></i> Sed ut perspiciatis</li>
                <li class="mb-3">
                <i class="fa fa-check mr-2 text-primary"></i> At vero eos et accusamus</li>
                <li class="mb-3 text-muted">
                <i class="fa fa-times mr-2"></i>
                <del>Nam libero tempore</del>
                </li>
                <li class="mb-3 text-muted">
                <i class="fa fa-times mr-2"></i>
                <del>Sed ut perspiciatis</del>
                </li>
                <li class="mb-3 text-muted">
                <i class="fa fa-times mr-2"></i>
                <del>Sed ut perspiciatis</del>
                </li>
            </ul>
            <a href="#" class="btn btn-primary btn-block shadow rounded-pill">Buy Now</a>
            </div>
        </div>

        @foreach($plans as $plan)
        <div class="col-lg-4 mb-5 mb-lg-0">
            <div class="bg-white p-5 rounded-lg shadow">
            <h1 class="h6 text-uppercase font-weight-bold mb-4">{{ $plan->name }}</h1>
            <h2 class="h1 font-weight-bold">${{ $plan->price }}<span class="text-small font-weight-normal ml-2">/ month</span></h2>

            <div class="custom-separator my-4 mx-auto bg-primary"></div>

            <ul class="list-unstyled my-5 text-small text-left font-weight-normal">
                <li class="mb-3">
                <i class="fa fa-check mr-2 text-primary"></i> Lorem ipsum dolor sit amet</li>
                <li class="mb-3">
                <i class="fa fa-check mr-2 text-primary"></i> Sed ut perspiciatis</li>
                <li class="mb-3">
                <i class="fa fa-check mr-2 text-primary"></i> At vero eos et accusamus</li>
                <li class="mb-3">
                <i class="fa fa-check mr-2 text-primary"></i> Nam libero tempore</li>
                <li class="mb-3">
                <i class="fa fa-check mr-2 text-primary"></i> Sed ut perspiciatis</li>
                <li class="mb-3 text-muted">
                <i class="fa fa-times mr-2"></i>
                <del>Sed ut perspiciatis</del>
                </li>
            </ul>
            <a href="{{ route('plans.show', $plan->slug) }}" class="btn btn-primary btn-block shadow rounded-pill">Buy Now</a>
            </div>
        </div>
        @endforeach
    </div>
  </div>
</section>

</div>
@endsection
resources/views/subscription.blade.php
//resources/views/subscription.blade.php
@extends('layouts.app')
    
@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">
                    You will be charged ${{ number_format($plan->price, 2) }} for {{ $plan->name }} Plan
                </div>
  
                <div class="card-body">
  
                    <form id="payment-form" action="{{ route('subscription.create') }}" method="POST">
                        @csrf
                        <input type="hidden" name="plan" id="plan" value="{{ $plan->id }}">
  
                        <div class="row">
                            <div class="col-xl-4 col-lg-4">
                                <div class="form-group">
                                    <label for="">Name</label>
                                    <input type="text" name="name" id="card-holder-name" class="form-control" value="" placeholder="Name on the card">
                                </div>
                            </div>
                        </div>
  
                        <div class="row">
                            <div class="col-xl-4 col-lg-4">
                                <div class="form-group">
                                    <label for="">Card details</label>
                                    <div id="card-element"></div>
                                </div>
                            </div>
                            <div class="col-xl-12 col-lg-12">
                            <hr>
                                <button type="submit" class="btn btn-primary" id="card-button" data-secret="{{ $intent->client_secret }}">Purchase</button>
                            </div>
                        </div>
  
                    </form>
  
                </div>
            </div>
        </div>
    </div>
</div>
  
<script src="https://js.stripe.com/v3/"></script>
<script>
    const stripe = Stripe('{{ env('STRIPE_KEY') }}')
  
    const elements = stripe.elements()
    const cardElement = elements.create('card')
  
    cardElement.mount('#card-element')
  
    const form = document.getElementById('payment-form')
    const cardBtn = document.getElementById('card-button')
    const cardHolderName = document.getElementById('card-holder-name')
  
    form.addEventListener('submit', async (e) => {
        e.preventDefault()
  
        cardBtn.disabled = true
        const { setupIntent, error } = await stripe.confirmCardSetup(
            cardBtn.dataset.secret, {
                payment_method: {
                    card: cardElement,
                    billing_details: {
                        name: cardHolderName.value
                    }   
                }
            }
        )
  
        if(error) {
            cardBtn.disable = false
        } else {
            let token = document.createElement('input')
            token.setAttribute('type', 'hidden')
            token.setAttribute('name', 'token')
            token.setAttribute('value', setupIntent.payment_method)
            form.appendChild(token)
            form.submit();
        }
    })
</script>
@endsection
resources/views/subscription_success.blade.php
//resources/views/subscription_success.blade.php
@extends('layouts.app')
  
@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
  
                <div class="card-body">
  
                    <div class="alert alert-success">
                        Subscription purchase successfully!
                    </div>
  
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
add css to laravel\my-app\resources\view\layouts\app.blade.php 

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" />
<link href="{{ asset('css/app.css') }}" rel="stylesheet" type="text/css" >

my-app/public/css/app.css
//my-app/public/css/app.css
body {
    background: #00B4DB;
    background: -webkit-linear-gradient(to right, #0083B0, #00B4DB);
    background: linear-gradient(to right, #0083B0, #00B4DB);
    color: #514B64;
    min-height: 100vh;
}
.rounded-lg {
    border-radius: 1rem !important;
  }
  
  .text-small {
    font-size: 0.9rem !important;
  }
  
  .custom-separator {
    width: 5rem;
    height: 6px;
    border-radius: 1rem;
  }
  
  .text-uppercase {
    letter-spacing: 0.2em;
  }
  
  body {
    background: #00B4DB;
    background: -webkit-linear-gradient(to right, #0083B0, #00B4DB);
    background: linear-gradient(to right, #0083B0, #00B4DB);
    color: #514B64;
    min-height: 100vh;
  }
Create Seeder For Plans
php artisan make:seeder PlanSeeder
C:\xampp\htdocs\laravel\my-app>php artisan make:seeder PlanSeeder

database/seeders/PlanSeeder.php
//database/seeders/PlanSeeder.php
<?php
namespace Database\Seeders;
  
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use App\Models\Plan;
  
class PlanSeeder extends Seeder
{
    public function run()
    {
        $plans = [
            [
                'name' => 'Business Plan', 
                'slug' => 'business-plan', 
                'stripe_plan' => 'price_1LmB1SCXbPPKAWayGsb1DAkq', 
                'price' => 10, 
                'description' => 'Business Plan'
            ],
            [
                'name' => 'Premium', 
                'slug' => 'premium', 
                'stripe_plan' => 'price_1LnwbTCXbPPKAWay3ByKpcfq', 
                'price' => 20, 
                'description' => 'Premium'
            ]
        ];
  
        foreach ($plans as $plan) {
            Plan::create($plan);
        }
    }
}
run PermissionTableSeeder seeder
php artisan db:seed --class=PlanSeeder
C:\xampp\htdocs\laravel\my-app>php artisan db:seed --class=PlanSeeder

check database plan table
Run C:\xampp\htdocs\laravel\my-app>php artisan serve
Starting Laravel development server: http://127.0.0.1:8000

http://localhost:8000/login
After, login
http://localhost:8000/plans

Related Post