Build a shopping cart with Laravel Cashier and Vue

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

Hey everyone, Andrew here!

I've been asked a few times what a simple e-commerce application could look like, using Vue as the frontend and Cashier to process credit card payments with Stripe. So, here it is!

This is my longest video to date, and it's a pretty lengthy one at just under an hour. I'm not a fan of multi-part videos, so I thought I'd give it to you in one complete package. If you check out the description, there are skip markers if you'd like to navigate directly to a portion that you'd find helpful (e.g. when I actually start using Stripe).

Let me know what you think, and if you have any questions!

👍︎︎ 8 👤︎︎ u/aschmelyun 📅︎︎ Dec 30 2020 🗫︎ replies

Good stuff as always, Andrew

👍︎︎ 2 👤︎︎ u/lyotox 📅︎︎ Dec 31 2020 🗫︎ replies

Thanks for this.

👍︎︎ 2 👤︎︎ u/sajjadalis 📅︎︎ Dec 31 2020 🗫︎ replies

This is a really good guide. Well done.

👍︎︎ 2 👤︎︎ u/FDeschanel 📅︎︎ Dec 31 2020 🗫︎ replies

Dope

👍︎︎ 1 👤︎︎ u/NoDadYouShutUp 📅︎︎ Dec 30 2020 🗫︎ replies

Saved. Thanks for the effort

👍︎︎ 1 👤︎︎ u/JapanEngineer 📅︎︎ Dec 31 2020 🗫︎ replies

Nice work, u/aschmelyun! I like your screencast style :)

👍︎︎ 1 👤︎︎ u/cjav_dev 📅︎︎ Jan 05 2021 🗫︎ replies
Captions
hey everyone andrew here i've been asked before about using vue with laravel and stripe specifically with the cashier package in order to process transactions or fulfill a payment i've also been asked to show more complete projects that use the full front and back and stack from start to finish so that's what i'll be showing off i'm going to be creating a very simple ecommerce application that will have a variety of items let you add them to a cart display your total in that cart and take a credit card payment that will be processed through stripe all of this will be powered by a view front-end spa and a laravel back-end api all right let's get started what we have here is a blank laravel 8 application right out of the box the first thing we'll need to do is create our models and migrations for the data that we'll be saving in the database since this is an ecommerce application we'll need a product model and passing in the migration flag here we'll create our migration files using this single artisan command so we don't have to go back and create those manually we could use a category for each of the products too and an order model which will store and track completed purchases let's open up our migrations and start generating the data structure that we want first the users table this one is included by default in new laravel installations and comes with a basic useful table layout we're going to modify this slightly however let's take out the email verified.attribute we won't be using that and instead let's add in fields for the user's address city state and zip code all right that looks good let's move on to the products table and think about what data we'll need for each product probably a name and a slug this way we can reference a url friendly name when navigating the site instead of just an id a description for the product and of course a price i'm storing this as an integer for two reasons one i just like dealing with it better and two stripe takes whole integers as the price when dealing with their charge api so it'll be easier just to keep it the same here as well okay moving on categories this should be much simpler i think we'll just save a name and a slug lastly the orders table first each order is going to belong to a particular user so let's add in that user underscore id column we'll also want to store the transaction id from stripe in case we need to provide the user this for reference i'll also want to store the entire purchase amount here as an integer okay those are our tables all set up well almost all set up these are just our model tables but some of these models have relationships that need intermediary pivot tables for instance multiple products will belong to multiple categories and vice versa same with orders and products we can create these tables with artisan make migration create order product table passing in the order underscore product table attribute and create category product table passing in the table name category underscore product let's open these up and add in the data for them ah it looks like i've made a small mistake in the command line earlier i should have passed in create instead of table oh well we can just swap out this schema table call for a schema create and move on so for this order underscore product table we'll need an order id a product id and also a quantity we'll need to know how many of a product someone purchased not just what product they purchased we'll also have to modify the down method to use schema drop if exists okay on to category product for this table we just need a category id and a product id okay our migrations are all set let's open up our models and make some changes starting from the top our model needs to have a protected guarded attribute set to an empty array i'm going to do this on all of the models all of them can have each field mass filled in as for the relationships each category is going to have multiple products attached to it and we can specify that with the belongs to many method passing in the product model class next up order again the guarded attribute set to an empty array each order belongs to a user and each order also belongs to many products we can attach the with pivot method to this return here and specify the quantity column this means that when the product's relationship is called from an order model that extra column that we added in the order underscore product pivot table will be attached and included in the return on to the product model guarded attribute set as an empty array and each product belongs to many categories it also belongs to many orders as well for the user model let's get rid of this fillable boilerplate and add in our guarded attribute with the empty array we can also get rid of this cast attribute since we're not using email verification instead we can add in our orders relationship a user has many orders all right models done next i'd like to talk about what it takes to pre-populate our application's database with some test models i could just use a mysql gui program to go in and manually create some test orders or products but i'm instead going to use larval's built-in factories and seeders to make creating test data super easy if we head to the database factories directory in our app we can see that laravel already has a user factory ready to go factory classes are super simple they have a model class attribute and a public definition method that returns an array of key value pairs corresponding to the columns in the database the data for those columns can either be hard coded in or generated with something like this included faker library for the user model we can see that faker is generating a name and a unique email address let's get rid of the email verified out column since we're not using that and instead add in the additional columns that we added to our migration we can use the faker library to generate test values for each of them too there's address city state and zip code now if we use artisan tinker to get a shell into our larval app we can try out our factory and see what a test model looks like all we have to do is call our user model classes factory method and then chain on make this will return back a user model object with the generated attributes that we specified in the definition method of our factory using the make method doesn't actually save it in the database either in order to do that we'd have to use the create method instead alright so now that we know how factories work let's create some for the rest of our application php artisan make factory product factory and then we can autofill in the model class by using the model option product let's create one for our category model as well and open up our new factory to start scaffolding out the generated data we only need two attributes for categories name and slug the slug is actually going to be a modified version of the name string so we should generate that value separate from the return i think for the name i'm going to use faker's job title attribute and since that could consist of multiple words together and i really only want one word for a category i'm going to create an array out of potential phrases and just store the first word available all right testing that out and we have our category factory working as expected onto the product factory again we'll need a name and a slug as well as a description which we'll use the faker real text method for setting it to a max length of 320 characters uh-oh i just realized i specified the wrong model for this it should be product not factory okay moving on the last column we need to generate data for is the price in that case let's use faker's number between method and have the min be 10 000 and the max 100 000. this will generate a price anywhere between 100 and 1000 some pretty expensive stuff in our store for the name i think the best option is probably a fake company we'll use that in combination with a random suffix like sweater pants shirt etc and testing that out our product factory is working just as expected so now we have to actually get these faked items into our database and in order to do that we can use the database seeder open up that class located in the database cders directory there's a single function in here run under this is where we'll specify what factories to use to generate our seated data we could split this out into separate cder classes for each individual model and then just call them all in here but for simplicity's sake i'm going to just directly run the factories in this main database cedar product class factory method and then chain on count providing how many of the models you'd like to seed then the create method finalizes the seed and stores the objects in the database so we have our fake products and categories created but we also need to generate relationships between them or it's not that useful in order to do this i'm just going to get all of the categories into an array and run a for each loop on the product models with the each method passing in that categories array since each product has a categories many-to-many relationship we can use the attach method and pass in an array of ids to attach specified categories to that product we'll use the random method on the categories array to pull out just two and get their ids okay this is our setup now let's actually get the database structures up and seated in our terminal art is in migrate and then artisan db seed you'll see that i'm using dcr here instead of php by the way it's just an alias for docker compose run since my local environment uses docker perfect both went through successfully we can run tinker and in the shell get all of the products to see if our seating went through and you can see that it definitely did we have 20 products in our database running the same command and attaching the categories relationship to the products we can see that displaying as expected too okay we have our database structure and data now we need an api to access this let's open up the routes api file and scaffold something out first a products route this will return a list of all of our products and their categories compiled through a product controller's index method we also need a route to post to that will complete a purchase i'm thinking just slash purchase and have that use the user controller's purchase method let's go ahead and create those respective controllers with artisan and open them up the user controller's purchase method is going to run when we make a purchase and for now let's set it aside the product controller's index method is going to return back all the products as json that's accomplished pretty easily just return product all actually product with categories so we can display which category a product is attached to on the front end for something like this we can also specify which attributes we want to return in that relationship by using a colon and a comma separated list afterwards so for this categories but only the id and name attributes of that category object let's open up our browser and see if this works navigating to slash api slash products we get back json containing each of our 20 products with the categories they're associated with and those categories only have the id and name attributes we have our super basic api setup now it's time to get a front end together for our customers to see opening up the routes web file let's replace the call to the welcome view and return back a view called app instead this is going to be a view spa and running in history mode meaning that it will take control of the history and url bar fully in order to make that compliant with a laravel application we have to add in an additional route call at the bottom of our routes web file route any slash any and a closure function returning the same view app then chaining on the where method where any evaluates as period asterisk what this acts as is a catch-all any route name or combination in the url bar will return back the app view and will draw the view spa alright let's create that view now i'm just going to scaffold in a basic template here okay we have our app element which will eventually be the anchor for our view application a script file and a style sheet i want to use tailwind for this project so i'm going to set it up now in our app.css file we just have to add three lines tailwind base tailwind components and tailwind utilities next opening up our webpack.mix.js file in this empty array we added a call to require the tailwind css package this method shown was introduced in larval 8. if you're using an earlier version you may have to get tailwind installed a different way all we'll have to do now is actually install tailwind css to our app's dependencies using npm in our package.json file let's add a line for tailwind css under dev dependencies and at the time of this video using this value is a fix for post css requirement compatibility now we just have to run npm install and npm run dev to compile our assets refreshing our browser and we're seeing a blank page which is expected since we don't have any text or images added into our front end but if we inspect we can see our app's template and we can see that tailwind is pulling in through the styles inspector now that we have our styles added in let's start working on our script we're using view and we need a store and router as well so let's use npm to install view view x and view router once that's done let's start scaffolding out our application's main javascript file app.js import view import and use view x import and use view router create our router object history mode as we discussed earlier and instead of writing out all of the routes in this file we'll abstract those out into a separate routes.js file create our store object with a default state products is a blank array cart is a blank array and order is an empty object we'll also need to add some mutations or methods that will affect and modify our state attributes update products we'll take an array of products and use them for our state's products array add to cart we'll take a single product object and push it into our cart array but what if we added the same product twice we wouldn't want that to be a duplicate entry instead we should update something like a quantity attribute on that product object first let's find the index of the product in the card if it already exists let's increment the quantity by 1 and return early and if it doesn't let's set that quantity to 1 and push it to our card array okay removing an item from a cart that should take an index and splice the cart array removing just the item at the index update order should take an order object and use it in place of the state's order and finally update cart should take a whole cart array and replace the state's cart with that it'll be used whenever a purchase is complete to clear out the cart next we have our store's actions these are methods that perform a task and then make commits to the mutations that we created above for instance git products this should fetch our products from the api that we made earlier and add them to the state axios get slash api products and then with the response commit update products and pass through the whole response data object if an error crops up let's just alert it directly to the user this is a test app after all we could also use a method called clearcart which will just commit an empty array to the update cart mutation okay our store is looking good let's finalize this script by initializing our view app pass through our router store the app element and using the created lifecycle event run the get products action it's a promise so we'll make sure to handle that accordingly oh we're forgetting our routes file that we specified earlier let's get that set up module exports an array of route objects for our first one it's the landing page so just a slash for the path and we'll name it products index the component will reside at routes products index.view next an individual product at slash product slug which will use the product's slug attribute to fill in that space after slash product it'll be called products show and use the routes products showed up view component we'll need a checkout route as well called order checkout following the same convention using the routes order checkout.view component and finally a summary name order summary and using the routes order summary.view component let's go ahead and create these components now and scaffold out a super basic view template in each of them something i forgot to mention the method that i'm using to import these route components lazy loads them and code splits them into multiple smaller javascript files it helps to cut down on large initial downloads when your app boots up and can make user interaction happen faster for users with slower internet speeds let's add in some test text to our landing routes template also add in the router view element under our app's mount element compile our assets and refresh our browser perfect we can see our test text here at our landing route okay now it's time to get some actual markup and products on our site i'm going to scaffold out a basic layout with tailwind to use as our products index view let's use a computed products method that will return the current array of products in our ux state and if there's no products yet we'll be displaying a loading notice using v else we'll actually display our products for product in products using the id as the key attribute we'll have this link out to the individual product page using router link and the name of our route as the to attribute this params object takes a list of attributes corresponding to fillable areas in our route definition in this case just slug and it'll be filled in with the product slug let's display the categories and the name of the products as well as the price well we can't just dump the price in here we are storing it as an integer instead we'll create a method called format currency passing in that product's price and create that method definition below we'll take the price and divide it by 100 to get our decimal value returning it as a currency string using the javascript method to locale string it'll be in usd currency but there are a variety of values that you can choose from okay recompiling our assets and refreshing our browser window we can see our loading element displaying as expected and our products are showing up perfect the price is displaying how we want it to and clicking on a product's image takes us to that page although it's currently blank because we don't have anything built for it let's change that opening up our product showdown view component i'm going to scaffold out a basic tailwind template for this view just like our index view we have a computed products method returning the current products array in the state but we'll also return back the product of the page that we're currently on to do that we just need a single line return this products find where the product slug matches the current routes slug parameter since we are displaying a price let's also add in that same format currency method from earlier too we are displaying the categories name and price but also the description this time and we'll need a button to add this product to our cart the click action for this should just be commit add to cart passing in the whole product object all right let's see how this looks clicking on a product and we're brought to our product page we can see the title description and our add to cart button clicking it doesn't look like it does anything but if we open up our dev console and go to the view tab we can see the current state and that our cart has this product in it with a quantity of 3 since that's how many times i clicked the button we should have somewhere on this app to show how many items are in your cart let's head to our app.blade file and add in a header block above our router view element i just pasted this prepared block in here but essentially what we have is a header element with a logo a link to our main products page and a button that shows how many items are currently in our cart using the length of the card array in the view x state it also links to the checkout route that we specified earlier refreshing and that looks perfect it's staying on every view since it's outside of the router view element and as we click on a product's add to cart button the number updates accordingly to the variety of products that we have in the card let's get that checkout section working now opening up the component i'm going to scaffold out a basic checkout view we'll need to store some form data so let's create a data object for this component a customer object will contain the customer's first name last name email and address info we'll also need access to the vuex state card array so a computed property returning that will be helpful additionally the total quantity of cart items and the total price of all of the items in the card those will both be displayed on the page so a computed property for each generated with the reduce method will come in handy for our carts table let's show the name quantity and price of each product that the customer wants and a button in case the user wants to remove a particular product from their card we can just call a commit to the remove from cart mutation that we created in the state earlier passing in the index of that item to splice it from the card array i also reference this cart line total method earlier let's actually add that into our component it'll just return a price that's calculated based on the product's price times the quantity selected okay time to see this in action let's add a few items to our cart and then click checkout perfect we are seeing two separate products here with different quantities and prices that reflect that clicking remove on one of them removes it from the cart and updates our total just as expected now we need to get the customer's information so we can handle the actual purchase let's start by adding in the form fields attached to the attributes of the customer object in our components data and we'll also add this payment processing boolean data point that way as the transaction is going through we can disable all the form fields on the page to prevent anything being changed or double-clicked during it alright all that's missing is a credit card field now i think it's time we talk about stripe if you happen to be unfamiliar with it stripe is a payment gateway that lets you accept credit cards debit cards or other forms of payment on your website a lot of developers tend to migrate toward this platform because of its robust documentation and ease of integration with a variety of different frameworks in backend languages laravel itself has a package called cashier which as it states provides an expressive fluent interface to stripes subscription billing services however we're going to be using it for one-off purchases instead of subscription charges but i still prefer using this package over stripes provided php library so first things first we'll need to actually install the package into our laravel app using composer in the terminal from our project root run composer require laravel cashier once that's done we'll need to run artisan migrate to pull in the tables and columns for the cashier package then we just need to add this billable trait to our user model and finally update our env file with some keys from stripe let's head over to stripe so we can get those values after signing into my account i'm going to create or navigate to the account that i'll be using for this app then i'll navigate to the developer section on the left sidebar and click api keys that will bring me to a page where i can see the public publishable key and the secret key let's copy over both of these to their respective places in our larval apps.env file okay now that we have our prerequisites all installed and set up we can start actually implementing stripe for this we need two separate parts that work together we need the front end javascript portion that generates and validates a credit card and the backend php code that will actually charge the card with the generated token that stripe provides let's get the front end portion added in first in our terminal at the project root we'll use npm to install the stripe.js library and once that's done let's add an element to our checkout view that will hold our card input it will have the id card elements we also have our button with a view action of process payment let's go ahead and create that method now this is what is actually going to send the payment information to laravel and stripe through our applications back end before we can actually see our credit card form up here we need to initialize the stripe library and tell it where to build that form at the top of our component data object we're going to add a couple of new attributes a stripe object and a card element object and then using the async version of the mounted lifecycle event we're going to wait for the stripe library to load in and attach it to that stripe data object almost forgot we'll have to actually import the load stripe method from the stripe javascript library all right once that's available we'll generate our card element using elements.create a type of card and passing through an options object that will allow us to style the credit card form just like our other inputs after that element is ready we can mount it using this card element mount and pass through our card element id for the div that we created earlier for our base css classes i'm just copy pasting in the classes that are also on our other form inputs the last piece we need for this is our public stripe key passed into the load stripe method here now we could just copy and paste this value from our env file but what if we need to update that in the future because we're using laravel mix to compile these assets we can actually use the dot env values directly in our view components with the process.env object however there's a caveat they have to be prefixed with mix underscore so mix stripe key in our dot env file we could just create that mixed stripe key attribute and copy in our public key but again i'm not a huge fan of multiple lines that need to be updated for single things like keys or secrets instead we can actually reference the above stripe key attribute in our mixed stripe key attribute by using quotes and then wrapping the value in a dollar sign in brackets when referenced the mix stripe key value will be the same as a stripe key value above it okay let's compile our assets and see if everything is working as expected let's add a few items to our cart head to the checkout and perfect our credit card form is appearing asking for a card number expiration and cvc there's currently no functionality behind it though so let's head back into our checkout view component and work on the process payment method first let's set the payment processing data attribute to true so that all of our form fields and buttons are disabled then we're going to call the create payment method function on our stripe object which will validate the card information from stripe and return to us either a payment method object or an error because we are awaiting an async method we'll have to update our method with async as well inside the createpayment method it's expecting a card argument the element that the card information is stored in which we use the data attribute for and an object containing additional information like billing details we'll add in the name email and address which is an object containing line one or the street address city state and postal code or zip code okay after we get back a return let's determine if an error is present if so let's stop the payment processing and just alert that error out back to our customer however if there's no error the payment method is valid and we can continue with the charge let's set some additional attributes on our customer object payment underscore method underscore id will be the payment method object's id amount is the total full integer price of everything in the customer's cart calculated by using reduce on the card array and we'll also pass in the whole cart array by converting the data attribute to json all right after that let's use axios to create a post request to our api purchase endpoint that we created and we're just going to use our whole customer data object to pass through everything to our back end whenever a response comes back let's update the payment processing attribute to false so that our form fields aren't disabled anymore and commit an update order to the vuex store passing through our response data object finally dispatch a clear cart to remove everything that the customer had in their cart and navigate them to the order summary view if there's an error caught we're simply going to turn off the payment processing flag and alert the error out to our customer all right our front-end checkout looks done now we just have to work on our back-end api purchase endpoint let's open up our user controller file where we added this purchase method in earlier and get to work we are going to be creating this purchase with the help of the cashier package and in order to do that we'll need to generate a user first using the first or create method we can first pass in an array of values that will be used to determine if a user exists or not in our application in this case i'm just using the email address of our customer and for the second argument another array of values that will be used to create the user if one doesn't exist for the email passed through let's auto-generate a password for our example app with str random and hash make pass in the name address city state and zip code all using attributes from our original customer object generated in the front end also in a production application we should be validating these inputs both in the front and side and in the back end but this is an example application so let's just blindly trust anything coming through now that we have our user object let's wrap the next part in a try block payment equals user charge and that expects two arguments a whole number amount and a payment method id from stripe which we stored as payment underscore method underscore id i'll also need some attributes back from stripe for our order so i'm going to get that by attaching this as stripe paymentintent method to our payment object returned now let's create our order we can use the relationship orders on our user model and create passing in an array of values that our model expects transaction id is available through our payment object charges the data's first child and id for the total payment charges data's first child and amount our order is created but we need to attach what products we purchased and what quantities to it we pass in the full card array and we can loop over that by running json decode on the input cart item as item for each of the cart items we're going to attach the product's relationship to our user and run attach this expects either ids as an array or we can pass through a single id with the item's id attribute for the second argument we can fill in any additional columns that are in the pivot table our pivot table contains a quantity integer so let's update that with the amount purchased under the item's quantity attribute and finally we're going to return the whole order object along with the products lazy loaded into it via the load method finishing up our try block we'll catch any exceptions and just return them as a json response the message from the exception and a 500 code which will cause the axios call to throw an error instead of proceeding as intended let's head back to our browser and test out everything that we just added put a few items in our cart fill out our name and address put in card information courtesy of stripes test card number and hit pay now our payment button and form fields are disabled and the payment is processing and we're at a blank screen which is good if we look at the url we've passed through to the summary screen let's take a look at our stripe dashboard and see if our payment got processed successfully and it looks like it did we have a brand new payment of just over two thousand dollars for our items that our customer purchased one thing that i forgot to add in our try catch block however was to generate a stripe customer for our user using this create or get stripe customer method it will either return back or create and return a stripe customer meaning that our user's information will also be added into the stripe dashboard as well as our actual applications database alright let's move on to our last view the summary component i'm going to copy and paste a lot of this from the order page since my goal here is just to display a table of the products quantities and prices that were purchased i'm just replacing the cart data attribute with an order object using a computed property to return this object from the vuex state that was originally committed after the checkout return sent back from our larval api one thing to keep in mind is that the quantity for our products isn't kept in the actual product object but is instead stored in reference in the pivot object attached to it since it resides in the pivot table between products and orders okay let's test out our application from start to finish one last time adding in some items to our cart going to the checkout page adding in our customers details and credit card information let's pay now and perfect we are on our summary page displaying the total list of items that we purchased their quantities and prices as well as the return to stripe transaction id in case our customer needs to reference the actual purchase in stripe we can see the actual purchase that took place and the customer's details that are associated with the purchase our order has been successfully submitted to stripe and saved in our database attached to our particular user either found or created products for that order have been attached as relationships inside of a pivot table along with their quantities purchase well that's about it for this video you've learned how to create a basic api with laravel and seed a database with generated products and categories we've created a basic ecommerce front end using vue spa architecture and allowed a customer to progress through multiple screens selecting products purchasing them and viewing their final order and finally we've been able to process credit card payments for orders with a stripe api courtesy of the cashier package that we installed and configured in our laravel application thank you so much to my github sponsors and everybody else who continues to support these videos as always if you have any questions about the topics in this video or web development in general please feel free to reach out to me in the comments or on my twitter linked below thanks for watching
Info
Channel: Andrew Schmelyun
Views: 42,761
Rating: undefined out of 5
Keywords: laravel, vue, e-commerce, stripe, cashier, laravel cashier, web development, php develeopment, web development tutorial
Id: gvnxd1xne7Q
Channel Id: undefined
Length: 55min 7sec (3307 seconds)
Published: Wed Dec 30 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.