Checkout Completed Action0:00
So now that we have an Idea on how the payload looks like, we can move on and implement the logic where we'll take care of creating the necessary database records in order to track our order, our order items, to keep our customer informed. So let's start off by creating a new action, and we'll call this HandleCheckoutSessionCompleted. Let's define a new public method, and we'll pass the sessionId as the parameter. So now if we go to our EventServiceProvider and next into our StripeEventListener, we can call this action. So it's a new HandleCheckoutSessionCompleted, and we'll pass the sessionId from our payload. And if we look at our JSON file, you can see that all the data is located inside the data array, followed by objects, and in this case we are looking for the sessionId.
Retrieve Stripe Session0:53
And if we look at our JSON file, you can see that all the data is located inside the data array, followed by objects, and in this case we are looking for the session ID. So that matches up with the following. Okay, so the first thing that we want to do is to retrieve the current session. So we can use the CachierFacade and call Stripe. This will return the Stripe API client. Next, we can say CheckoutSessions and retrieve our session based on the session ID. Now in this case, we want to create an order, and the order is attached to our User. And the first thing we need to do is to figure out what the User is that is associated with this session. Now, if we look again at our payload example, you can see that the email is sent along, so we could use this to match up our User. But in some cases, maybe the User has,
Attach User Metadata1:45
session. Now, if we look again at our payload example, you can see that the email is sent along, so we could use this to match up our User. But in some cases, maybe the User has, in the meantime, updated their email address. If a job, for example, fails and you might execute this later on, it would fail because the User no longer exists with this email address. So what we could do is to send along metadata. So if we head over to our CreateStripeCheckoutSession, you recall that we can provide additional information when we create a session. So we're going to add a new key value array that will contain our metadata and pass along the userID to which this card is associated. So now if we head back to our HandleCheckoutComplete action, we can fetch the User based on this metadata. We can say $user is equal to User::find($session->metadata->userID);
Create Order Migrations2:36
action, we can fetch the User based on this metadata. We can say $user is equal to $user, and say find, and we can get the session metadata, and we'll get the user ID. So now that we have this information, we can move on and create our order. Now, we haven't have an Order model at this time. We don't have any order items. So let's create some new migrations and models. Let's open up our terminal and let's run php artisan make:model Order -m to create a migration. While we're here, let's create an OrderItem as well. Let's head into our CreateOrderTableMigration, and let's define our columns. First, let's make sure that we store our Stripe Checkout ID for later reference as well. Next, we want to store information like the amount, like shipping, discount, tax, subtotal, and total, which we can all find
our Stripe Checkout ID for later reference as well. Next, we want to store information like the amount, like shipping, discount, tax, subtotal, and total, which we can all find on our example payload, and we'll store this in cents, so we'll do an integer, the default of zero. Next, we'll have amount, discount, followed by amount tax, subtotal, and finally total. Next, we get the billing and shipping information from Stripe as well. So we'll create a JSON column that will contain our billing address, and another one that will contain our shipping address. So I think this should cover the basics for now, otherwise we'll get back to this migration later on if we need any additional columns. Let's head over to our CreateOrderItemsMigration. That is going to have an unsignedInteger, which is going to be our orderId. Next, we'll also want to store our productVariantId, so we'll add a name column.
CreateOrderItemsMigration. That is going to have an unsigned integer, which is going to be our orderId. Next, we'll also want to store our productVariantId, so we'll add a name column and a description column because I want our orderItems table to be somewhat immutable. Our productVariantId might not always exist. For example, maybe this variant is deleted in the future, and that would mess up the orderItems column, so that would mess up the orderItems relationships between the productVariant and the orderItem, and at the same time, it will break the interface, for example, where a User could look into his or her place orders, and by storing the actual name and description, we don't have to worry about this. So next, we can move on, and we'll have our price, quantity, and the same columns that we have on our order table, but this in this case, per item, we'll have amount discount, because discounts could be applied to a specific
and we'll have our price, quantity, and the same columns that we have on our order table, but this in this case, per item, we'll have amount, discount, because discounts could be applied to a specific product, a total, tax, and total. So I think this should cover it for now, and again, we'll get back to this migration in case we've missed any columns. Let's make sure that the relationship is set up, so let's head over to our User model, and define a new public method called orders, which is going to be a hasMany relationship, and we of course need to make sure that we have a user_id column on our orders table, otherwise this won't work for us. Okay, next we'll head over to our Orders model, and we'll add a public method called items, which will return a hasMany relationship to our OrderItem. Okay, now let's define our casts. So we want to make sure that our billing_address and our
Create Orders and Items5:59
add a public method called items, which will return a hasMany relationship to our OrderItem. Okay, now let's define our casts. So we want to make sure that our billingAddress and our shippingAddress is going to be cast to a Collection automatically whenever we access this on our model. So here we can say billingAddress, enter it into Collection, and let's do the same for shippingAddress. So let's continue, and let's migrate our database. So now that we have set everything up, we can move on and create the actual Order and the OrderItems. So let's create a new Order, and let's use our session information to fill out each of these columns. So this is all the information related to the amount that was charged, and the subtotal, tax, discount, shipping cost, etc. Next, let's store the billingAddress. Now we can simply duplicate this array, rename this to shippingAddress,
and the subtotal, tax, discount, shipping cost, etc. Next, let's store the billing address. Now we can simply duplicate this array, rename this to shippingAddress, and update this reference from customerDetails to shippingDetails. Okay, so we create a new Order that is going to be associated with a User, we store all the necessary session information, and now that we have our order, we can continue storing our orderItem information. First, we need to retrieve all the line items from Stripe. So we can use the cashier helper again, use the Stripe API to fetch all line items from our Stripe session. This will return an array containing all of our line items. So now we can map over each of these items, and prepare it to be inserted into our database. So we want to store our productIdentity. Now this is not part of the orderItems,
map over each of these items, and prepare it to be inserted into our database. So we want to store our product identity. Now this is not part of the order items, so we need to fetch this separately. So we can do cashier, Stripe, and retrieve a product, and fetch the product variance ID from our metadata. Because if you recall, when we create our checkout, we add our product and variance ID to the metadata of our product. And now we can fetch this by retrieving our product, and getting the metadata object or array which contains our product variance ID. We can actually do this to stay consistent. Next, we have our price, our quantity. Okay, and of course, let's make sure that we actually return an order item. So we can say orderItems, saveMany, and pass our orderItems. I noticed I made a little mistake.
Okay, and of course, let's make sure that we actually return an orderItem. So we can say orderItems, saveMany, and pass our orderItems. I noticed I made a little mistake in regards to our billing and shipping address. So the name lives on the customerDetails object, but our address information is on an additional level. So we just have to add address here. And we'll have to do the same for our shipping address, and that should do the trick. And this is incorrect as well. These two values are on the session object and not on the totalDetails object. And I also made a little mistake with our stripeCheckout column. This has to be stripeCheckoutSessionId. And finally, on our orderItem, we have a name and description that we want to add as well.
Wrap in Transaction9:50
Stripe checkout session ID. And finally, on our orderItem, we have a name and description that we want to add as well. So let's make sure that we reference our columns and associate the product data. So this was going to be named like blanket, and our description is going to be equal to the size and the color that we defined when we created our Stripe checkout session. Finally, we want to wrap this entire call in a database transaction. So in case something goes wrong, all the changes are rolled back. We can use the database facade and call transaction. Let's make sure that we pass on the sessionID, and let's remove all our code inside the transaction.
Let's make sure that we pass on the sessionId, and let's remove all our code inside the transaction. So using database transaction helps us prevent to have an inconsistent database state. Let's say, for example, the creation of orders was successful, but only one out of five order items was able to successfully be inserted into the database. This would lead into an inconsistent state. And when we use database transaction, everything is rolled back in case something in the closure fails. So this is especially useful when dealing with money or with these kinds of orders, because you don't want to, I don't know, ship items twice to the same customer or charge them multiple times. So yeah, use database transactions whenever you're using.
because you don't want to, I don't know, ship items twice to the same customer or charge them multiple times. So yeah, use database transactions whenever you're using important database logic and you want to ensure that you don't have inconsistent data in your database.
