How to integrate Stripe with Laravel Cashier + Vue Stripe

Video thumbnail

Integrating Stripe with Laravel Cashier may seem complex at first, but in reality, it's one of the safest and most flexible ways to manage payments and subscriptions in your Laravel projects.

Stripe allows us to make payments on different platforms, and for our case of interest, which is developing applications with Laravel, we can integrate it directly into our application. This way, customers can make payments and purchase products within our platform.

In this article, I explain how to do it step by step, just as I implemented it in my own environment (Laravel with Vue 3).

Furthermore, I'll tell you about some errors I encountered in production and how I solved them so you don't have to go through the same thing.

Laravel Cashier is the official way we have to integrate Stripe with Laravel—it's that simple, allowing for easy use of Stripe; moreover, we'll see how to use Vue Stripe along with Laravel.

Create a Stripe Account

The first thing you should do is create a Stripe account:

https://stripe.com/es

If you already have an account, you can log in directly; if not, follow the registration steps that Stripe indicates. Once created, you will have access to the dashboard:

Stripe Dashboard

In the dashboard, you will find everything we need for the course:

  • Test and production mode: You can switch between them.
  • Publishable and secret keys: Necessary for integrating Stripe into your project.
  • Payment options: Apple Pay, Google Pay, PayPal, and more.
  • Product catalog: Here you can create the products you will sell in your application.

It is important to keep the keys in test mode while developing and replace them with the production ones when we launch the application.

What Stripe Offers

Stripe works similarly to PayPal but offers more options:

  • One-time payments
  • Recurring payments
  • Subscriptions
  • Product and price management

To configure the products:

  • Go to Product Catalog in your dashboard.
  • Create the products you are going to sell (for example, “Shoes” or “Laravel Book”).
  • Define the prices for each product.

You can create products by category, brand, or any criterion you need.

Stripe and Laravel

Laravel offers an official integration with Stripe through the Laravel Cashier package:

Laravel Cashier

With Cashier we can:

  • Create customers and products
  • Manage one-time payments and subscriptions
  • Generate invoices
  • Handle recurring payments

Additionally, Stripe also offers its native API which can be used in any PHP project:

Stripe API

You can choose between using the official Stripe API or Laravel Cashier, depending on your needs. For example, for a simple payment, the official API may suffice; for recurring payments or subscriptions, Cashier makes the job much easier.

Installation and Configuration of Laravel Cashier

To install and configure Cashier in Laravel, you must publish the migrations and the configuration file:

$ php artisan vendor:publish --tag="cashier-migrations" $ php artisan vendor:publish --tag="cashier-config"

Once this is done, you can manage customers, subscriptions, and payments directly from your Laravel application.

Alternative with Vue

If your project uses Laravel with Inertia or Vue, you can also integrate Stripe using a Vue plugin called Vue Stripe:

https://vuestripe.com/

This plugin is very useful when you only want to sell products quickly and manage payments simply.

Summary

In summary, Stripe is a very versatile electronic wallet that we can use:

  • Directly with its native API
  • Through Laravel Cashier
  • With Vue plugins for front-end applications

What is Laravel Cashier and why use it with Stripe?

Laravel Cashier is an official package that simplifies all the handling of recurring payments and subscriptions with Stripe (and Paddle). Instead of writing custom logic, Cashier offers you ready-made methods for creating, canceling, or verifying subscriptions.

In my case, I tried integrating Stripe "bare-bones" and then with Cashier, and the difference is huge. Cashier avoids you having to deal with tokens, IDs, and webhooks directly; everything comes down to methods like newSubscription() or subscribed().

Advantages of using Cashier

  • Quick implementation without writing payment logic from scratch.
  • Automatic handling of subscriptions, cancellations, and grace periods.
  • Native compatibility with Laravel.
  • Simple integration with Stripe Checkout.

However, if your app is small and you only want to charge for a specific product once, using Cashier might be overkill. In those cases, direct Stripe Checkout may be sufficient.

Create Product ID and Price ID

Before moving on, you have to come here to the first page I showed you when I pointed you to the beginning of this section a couple of videos ago, where I indicated that when you authenticate you land on this page which is the dashboard.stripe.com:

https://dashboard.stripe.com/

So we're here, I'm going to come here to the home page, here you have to come and select "test mode" which would be the environments for performing tests, don't pay attention to this, it's something asking for additional information for my account, here you can also enable test mode.

Create Test Environment

As a first step, we must go to the following link:

https://dashboard.stripe.com/

Click on the upper right corner:


 

Option to create test environment in Stripe


 

And create the test environment:


 

Create test environment


 

Create test products

Unlike PayPal, we must create the products we are going to sell so we can reference them through our Laravel/Vue application using an identifier, as we will see later; for this, from the following link:
 

https://dashboard.stripe.com/test/products?active=true

 

We create at least one product from the "Create a product" button:

 

Example product



From the product details, in the configured price, by clicking on it, you will have the price identifier, which we will use later to be able to make a purchase of this product with the selected price:

Copy Price ID


 

From the + button in the previous image, you can create more rates/prices since a product can have 1 to n prices which are the ones we use in our application to configure payments; payments must be of the one-time type, NOT recurring ones, recurring payments are for subscriptions.

Stripe handles a slightly different structure to what we are used to, for example with PayPal, where we simply set a price and that's it, here we cannot set the price or amount that we want, we have to establish a reference, a payment or a price, better said, that will be referenced here in our API, so to create that price, we first have to create a product.

https://dashboard.stripe.com/test/products

Create Product

We create at least one product from the "Create a product" button:

Recurring vs One-time Payment

Here to indicate billing periods 6 months 3 months annual weekly daily or custom, that is for the subscription part, you could say to make our lives easier, let's put "one-time" here which means it is a single payment and here you enter the amount, I'm going to enter one which is referring to one euro or one dollar, you switch it here to one-time and there you set it.

Products

Create Price

And that would be it, we click here to add product and there we have our product, now we have to select the product, let's see, this part is always a little complicated with this interface, I think it also appears here where it says prices and or rates, this would be to see the price, I think this is the one for the price, in the same way, click back here on the three dots, there's the product, yes, this would already be for the prices, look here we have copy the Price ID, since when we create a product.

⚙️ Laravel Cashier Installation and Stripe Configuration

The command composer require laravel/cashier, nothing strange, well, you put it here in your project and let it install happily:

$ composer require laravel/cashier

And we publish its configuration and migrations:

$ php artisan vendor:publish --tag="cashier-config" 
$ php artisan vendor:publish --tag="cashier-migrations" 
$ php artisan migrate

This creates three main tables (customers, subscriptions, subscription_items) and the config file config/cashier.php.

Configure Stripe keys

In your .env file, add the following variables:

STRIPE_KEY=your-public-key 
STRIPE_SECRET=your-secret-key 
STRIPE_WEBHOOK_SECRET=your-webhook-secret

“Missing API key provided.”
It turned out that I hadn't correctly published the cashier.php file in config.
So make sure you run php artisan vendor:publish --tag="cashier-config" before deploying.

Implement one-time payments with Stripe Checkout

Stripe Checkout allows you to redirect the user to a secure payment page, without storing sensitive data.

In your Laravel controller:

use Laravel\Cashier\Checkout;
use Illuminate\Http\Request;
function createSession(string $priceId)
{
   $session = Checkout::guest()->create($priceId, [
       'mode' => 'payment',
       'success_url' => url('/success').'?session_id={CHECKOUT_SESSION_ID}',
       'cancel_url' => url('/cancel'),
   ]);
   return $session->id;
}

This method creates a Checkout Session and returns its ID, which you can use from the frontend with sessionId; if you return the object:

return $session

Laravel automatically performs the redirection to the Stripe website to make the payment.

 

I prefer this approach because it redirects the user to Stripe (like PayPal does).
This way you avoid handling cards within your app and build more trust.

Recurring Subscriptions with Laravel Cashier

To enable subscriptions:

Create a recurring Price ID from the Stripe dashboard.

Use the newSubscription() method in your backend:

Route::post('/user/subscribe', function (Request $request) {
   $request->user()->newSubscription('default', 'price_monthly')
       ->create($request->paymentMethodId);
});

We indicate what the plan and price would be, and for the rest, we create it here using some payment method.

From here we have a lot of additional information. For example, we can set trial subscriptions, defining the days the trial will last.

This section also includes options related to billing, although I won't really cover that part in this course.

Coupons and discounts

Regarding coupons and discounts, I usually manage them directly from the application, but you can also create or manage them from Stripe.

In my case, if I need to apply a discount, I do it before setting it in Stripe, based on some internal condition.

So there you have the part about coupons, and also an option to configure the trial, although I don't remember exactly where it is located within the panel.

Subscription verification

Another interesting aspect is the possibility of verifying if a user is already subscribed, which is very useful for controlling access or avoiding duplicating plans.

In summary, with this we already have practically everything configured to manage plans, prices, coupons and trial periods in Stripe.

To verify if a user is subscribed:

if ($user->subscribed('default')) { // Access allowed }

Create a recurring type Price in the Stripe dashboard

Remember that you must establish a recurring payment and not a one-time payment.

You can do it directly from the Stripe panel:

https://dashboard.stripe.com/dashboard

Once the price is created, copy the generated Price ID.

Example of subscription in Laravel

Here is an example of how to create a new subscription from your Laravel application using the obtained Price ID:

Route::get('/stripe/new-subscription', function () {
    $user = User::find(1);
    dd(
        $user->newSubscription(
            'default',
            'YOUR_RECURRENT_PRICE_ID'
        )->create('<USER_PAYMENT_METHOD_ID>')
    );
});

The tag 'default' can be replaced by any other name, as it simply works as an internal label used by the system.

Other important methods
 

Stripe handles what is known as a grace period, meaning that if the user has already paid for the subscription, they can continue to enjoy the service until the paid time ends.

You can verify if the user is still within this period with the following method:

$user->subscription('default')->onGracePeriod();

Even if the subscription was canceled, the user will still have access during the remaining days they already paid for.

Verify if the subscription is active


To check if the user's subscription is still active or recurring, use the following method:
 

$user->subscription('default')->recurring();

Vue Stripe Integration in Laravel

If you use Vue 3 on the frontend, you can integrate Vue Stripe to manage payments and subscriptions from components.

$ npm install @vue-stripe/vue-stripe

Then, create a simple component:

<template>
 <div>
   <stripe-checkout
     ref="checkoutRef"
     mode="payment"
     :pk="publishableKey"
     :line-items="lineItems"
     :success-url="successURL"
     :cancel-url="cancelURL"
     @loading="v => loading = v"
   />
   <button :disabled="loading" @click="submit">Pay now!</button>
 </div>
</template>
<script>
import { StripeCheckout } from '@vue-stripe/vue-stripe';
export default {
 components: { StripeCheckout },
 data() {
   this.publishableKey = 'pk_test_...';
   return {
     loading: false,
     lineItems: [{ price: 'price_1234', quantity: 1 }],
     successURL: 'http://tuapp.test/success',
     cancelURL: 'http://tuapp.test/cancel',
   };
 },
 methods: {
   submit() {
     this.$refs.checkoutRef.redirectToCheckout();
   },
 },
};
</script>

Parameters you can configure:

  • pk (string - required): Stripe publishable key.
  • sessionId (string - Not required): The ID of the payment session that is used in the client-server integration.
  • lineItems (array[object] - Not required): An array of objects representing the items the customer wishes to purchase, we must include the priceId(s) configured for the product(s) we created in the Stripe dashboard.
  • mode (string - Not required): The mode of the payment session, either payment or subscription.
  • successUrl (string - Not required): The URL Stripe should send customers to when the payment is complete.
  • cancelUrl (string - Not required): The URL Stripe should send customers to when the payment is cancelled.

Here the success and cancel components, the component really doesn't matter to me, obviously I would have to create one for Stripe but it doesn't matter much to us right now, so here we have it:

router.js

import { createRouter, createWebHistory } from "vue-router";
import { useCookies } from 'vue3-cookies'
const { cookies } = useCookies()
import List from './componets/ListComponent.vue'
import OnePayment from "./componets/stripe/OnePayment.vue";
const routes = [
    ***
    {
        name: 'stripe',
        path: '/vue/stripe/one-payment',
        component: OnePayment
    },
    {
        name: 'success',
        path: '/vue/stripe/success',
        component: List
    },
    {
        name: 'cancel',
        path: '/vue/stripe/cancel',
        component: List
    },
]
const router = createRouter({
    history: createWebHistory(),
    routes: routes
})
export default router

⚙️ Tip:
Only the public key is used from the client (Vue).
The secret key must always be kept on the server.

Client-Only Integration, Risks and Limitations in Using Vue Stripe

Video thumbnail

If we check the developer console, we'll see a message like the following:

v3:1 Uncaught (in promise) IntegrationError: The Checkout client-only integration is not enabled. Enable it in the Dashboard at https://dashboard.stripe.com/account/checkout/settings. at Sl (v3:1:461068) at e._handleMessage (v3:1:469393) at e._handleMessage (v3:1:85275)

️ Public and Secret Keys: Security in Transactions

In these types of services, a private key or secret key must always exist. Practically all payment services work this way. The secret key is usually used on the server side to ensure that the operation is being processed correctly and securely.

It's a process similar to what we did with PayPal: on the client, we set up the plugin with the public key, but on the server, once the order was created, we approved that order using the secret key. This adds an extra layer of security.

The public key and secret key function analogously to a username and password (where the public key is the username and the private key is the password). In the current Stripe configuration, we are only working with the public key, which makes the operation much more insecure.

⚠️ Security Risks When Using Only the Public Key

It is a significant risk: an attacker could intercept the request and change your public key for theirs. If this public key were persistent, for example, in a database, and they intercede and inject theirs, all payments would fall into their account. Hence the importance of the secret key.

For this reason, the "Enable client-only integration" option is obfuscated by default, as it is a less secure operation than the traditional method.

Once we allow this integration (by clicking), the process should work correctly:

The Main Problem: Product Assignment Logic

The key problem with the current configuration is the lack of application logic after payment.

If we are selling something (a course, tickets, a book, etc.), it is assumed that when the user makes the payment, we assign that record or product to them. With the current client-only configuration, it is not possible to perform these types of operations.

Therefore, this implementation is nothing more than a simple "quick access" for people to pay us to an account configured only with the public key.

Summary of Limitations

In summary, the limitations of the current configuration are two:

  • Insecurity: It is more insecure because only the public key is used to match the account. This, depending on the integration, can make it more susceptible to hacking and the diversion of funds to another account.
  • Inability to Assign Products: It is not possible, at least with this configuration, to implement the necessary logic so that, once the payment is made, our application can release or assign the products the user has just purchased.
  • We will solve this in the following videos, but it is important that you understand these limitations beforehand.

I agree to receive announcements of interest about this Blog.

Learn step-by-step how to create a recurring price in Stripe and link it to your Laravel application using Cashier. Discover how to manage subscriptions, check grace periods, and allow users to easily cancel or reactivate their plan.

| 👤 Andrés Cruz

🇪🇸 En español