Lecturer: And now to finish this project,
let's finish this Stripe integration with webhooks.
Let's start to remember how our Stripe integration
actually works right now.
We have this checkout-session endpoint,
which gets called from our front-end.
This will then call the getCheckoutSession function,
so basically this one here,
which will create a checkout session on the server
using all of this information here,
and then send it back to the client.
Then after the processing of the payment
is successfully done by Stripe,
then redirect the user to this success URL,
so this one that we created.
Remember that onto this URL, we added the tour ID,
the user ID, and also the price.
We did that so that once this URL here is then called,
our application would create a new booking document
in our database.
How did that work?
In the my-tours route, we have a middleware for that.
Remember, here in the viewRoutes, in my-tours,
we have this createBookingCheckout.
This function here, which basically from the query
takes the tour, user, and price,
and creates a entry in the database using that data.
Basically we put this data on the URL
whenever Stripe successfully processes a payment.
And then this middleware function that we have here
picks up the data and creates a new booking in our system
using that data.
And then after that, we basically redirect here
onto the original URL without the query string.
Now the problem with this was that it's not really secure.
So, everyone who knows this URL structure,
so this one up here, which tour, user, and price
in the query string, can basically create a booking
in our system without actually paying.
So, all that we'd have to do is to open up this URL
with some data in there
and then from there automatically create a booking
without going through the Stripe process.
Remember how back then I said that we would fix this
using something called webhooks.
So, we do that now.
Because for that,
we actually need our website to be deployed.
Now at this point, that's actually the case.
And so, now we can implement these webhooks.
For that, let's go to our Stripe dashboard.
And I actually already have that opened here.
And then go here to developers, then choose webhooks,
and here, add a new endpoint.
Now what is this endpoint here and this webhook?
Basically we're gonna specify a URL here
to which Stripe will automatically send a POST request to
whenever a checkout session has successfully completed,
so basically whenever a payment was successful.
With that POST request, Stripe will then send back
the original session data that we created in the first step
when we created that checkout session.
That's the reason why we actually needed our website
to be deployed here because now we need to specify
that real-life URL here.
Let's grab that from here,
and then add our route here basically.
I'm going to call this one webhook-checkout.
It's not in the API, and it's not inside the bookings.
You will see in a moment why that is.
Again, when a payment was successful,
Stripe will then automatically post
the original session data to this URL.
Now we also need to select the event.
And you see there are tons of events that we could use here.
The one that we're using is the checkout_session_completed.
Add that.
Now you need to put in your password here again.
And then there we go.
This webhook then also has a secret here.
This one, we will then also need in a second
when we actually create our route for this URL here.
Actually that's exactly what we're gonna do next.
Basically in our system of course,
we now need a route for this here
so that when Stripe then posts the data to our application,
we can then actually do something with it.
Let's go back here and open up our application.
We will actually add this route right here.
Again, I will explain you why in a second.
So, app.post
and standard route and then of course we need
a handler function for that.
Let's quickly create it here in our bookingController.
Let me call that export.webhookCheckout.
Now I will have to import this controller into app.js.
Let's do that right here after the bookingRouter actually,
so this one and this one,
controller and here also controller.
All right.
Now down here, that's bookingController.webhookCheckout.
Now why do we actually define this webhook-checkout
right here in app.js instead of doing it for example
in the bookingRouter.
The reason for that is that in this handler function,
when we receive the body from Stripe,
the Stripe function that we're then gonna use
to actually read the body needs this body in a raw form,
so basically as a string and not as JSON.
Again, in this route here, we need the body
coming with the request to be not in JSON,
otherwise this is not going to be working at all.
Now the thing is, that as soon as a request
hits this middleware here, the body will be parsed
and converted to JSON.
It will then be put on request.body as a simple JSON object.
Again with that, this route handler here
would then not work.
That's the whole reason why we need to put this route here
before we call the body-parser.
Now we still need to actually parse the body
but in a so-called raw format.
By the time I was recording this video,
we could not do it with Express out of the box.
And so, in this video, we download the body-parser from npm
and use it as I show it in the video.
However, like five days after I recorded this video,
Express added the raw parser to Express as well.
Now we can use express.raw instead of having to install
the body-parser or middleware from npm.
Again, in this video, I will now install the body-parser,
but you don't really need to.
You can just use express.raw instead.
Npm install
body-parser.
This probably all sounds a little bit focusing,
and I totally understand that,
but that's just how the Stripe documentation works
and forces us to do it, really.
Let's go back here to our route.
In this route, we need the body to be in a raw format.
We can add that as a middleware here between the route
and the final handler.
Here we say bodyParser.raw,
and we also need to specific here the type
just very quick as application/json.
We now added this body parsing as a raw body
here in this middleware stack.
All this will really start to come together
once we start implementing this function.
Actually let's do that now,
so right here.
But before we actually do that,
let's get rid of all the code that we wrote
in order to make it work right now.
So, basically this middleware function,
we don't need it anymore.
Also here in the viewRoutes,
we don't need it here anymore either.
And then finally in the bookingController,
let's also set our URL back to normal.
I will just leave all of this here
so that you can keep it as a reference.
But now the success URL should actually just be this.
Basically after a successful booking,
we still want to come back to my-tours
but without all this query parameters
because now it's no longer this function here,
which will take care of creating the booking
but instead it is this function here,
which is of course the one that gets called
once Stripe calls our webhook.
Let's now implement this.
The first thing that we need to do
is to rid this Stripe signature out of our headers,
so signature and then request.headers
and then from there stripe-signature.
Basically when Stripe calls our webhook,
it will add a header to that request
containing a special signature for our webhook.
If you're thinking that you're just blindly following
what I'm doing here, well, (laughs)
that's actually exactly what I did as well
from the Stripe documentations.
Again, this is really just how Stripe works,
and there's nothing we can do against that.
Anyway, next up, let's create a Stripe event,
so const event equals stripe.
For that of course, we need to the Stripe library installed,
which we have up here.
So, stripe.webhooks.contructEvent.
Now here is where finally that body
comes into play, so request.body.
And remember that this body here
needs to be in the raw form,
so basically available as a string.
Once more, that is why we put that route
before all our other routes
and especially before the body parser could do its job
of converting our body into a JSON object.
Then besides that body, for the event, we need a signature,
so basically the signature that was sent
along with the header, and then finally our webhook secret.
Let's get that from here, copy it.
Since it's a secret, we should, as always, add it here
to our config file,
so STRIPE_WEBHOOK_SECRET.
And then later of course, don't forget to also add this
to our Heroku configuration.
Let's now use that.
Add process.env.
I should have just copied that
right here.
So, you see, all of this is really to make the process
super, super secure.
We need all of this data like the signature
and also the secret in order to basically validate
the data that comes in the body
so that no one can actually manipulate that.
Now during the creation of this event,
there might be some errors,
for example if the signature is wrong
or if the secret is wrong.
And so, let's wrap this into a try-catch block.
Okay.
Of course, we now need the catch.
In case there is an error,
we want to send back an error to Stripe,
so return res.status 400
and then just use send webhook error
and then let's just add the error.message.
So, it is Stripe who will receive this response here
because again it is Stripe who will actually call the URL,
so our webhook, which will then call this function.
Now we need to of course also declare this event here
outside of the try-catch block
because otherwise we will not be able to use it down there.
So, let event and then reassign down here
because remember that the ES6 const
and let are block-scoped.
And so, this variable would not be available outside
of this block.
Now let's actually use that event.
First off, we need to test if this really is the event
that we want.
So, we can do event.type
is equal to checkout.session.complete.
Remember that in our Stripe dashboard,
that's exactly the type that we defined here.
So, that's the event type.
Now we're checking if that is really the event
that we are receiving here just to be 100% sure.
If it is, we then want to actually use the event
to create our booking in our database.
Actually let's do that in a separate function
and not inside here of all of this mess.
For that, I will create a function.
Actually let me give it
this exact same name, so createBookingCheckout.
It was actually a nice name,
but now it cannot be a middleware
but instead just a regular function.
This function will accept the session data.
And remember that the session data is exactly this session
that we created here in the first place.
If this is the correct event,
then let's actually call that function,
so createBookingCheckout with the session,
which is at event.data.object.
And then finally,
let's just send back some response to Stripe.
So, status 200
and then let's say some json
receive set to true.
Makes sense?
Once more, all of this code here will run
whenever a payment was successful.
Stripe will then call our webhook, which is the URL,
which is going to call this function.
And so, this function receives a body from the request,
and then together with the signature and/or webhook secret,
creates an event, which will contain the session.
And then using that session data,
we can create our new booking in the database.
And so, that will actually be pretty similar
to what we had here before.
So, we will need this line of code here again.
So, this will also be an async function.
And so, this is exactly the same.
Now what we need here of course
is to get access to the tour, user, and price.
But that data once more is stored in that session.
So, let's start with the tour.
And remember how up here
when we first created this handler function,
I specified this client_reference_id field
and added the tourId to that.
Remember that?
I did that because, at the time,
I already knew that we would need this tour ID a bit later.
Now it's that time where we actually need the tour ID
in order to be able to create that new booking.
And so, now the tour ID that we need
is at session dot client's reference ID.
So, let's copy this and say
session.
And of course, here we need to say tour.
So, that's the tour ID.
Next up, we need the user ID.
Now the information that we have in our session
about the user is the user's email.
And so, now what we need to do
is to basically get the user's ID.
For that, we will query by the email.
That's no problem because the email is also unique.
Based on that, we can then find the unique ID.
So, const user is await.
And I think we already have the user here.
No?
No, I actually don't.
So, let's just do that here.
And user here as well.
Okay.
So, User.findOne and then query via email,
which is in session dot,
and I believe that's client's email or something.
It's customer_email.
Okay.
But that will then return the entire document,
but we want actually the ID.
So, let's wrap all of this here in parenthesis
and then call the ID on there or actually read the ID.
So, that's it.
And finally, the price.
Where is the price stored?
Well, it's here in line_items.
That's an array, so at element zero,
and then the amount divided by 100.
So, we multiplied it here by 100 to get cents,
but now of course we want it back in dollars.
So, we need to basically divide that back.
And so, session.line_items
and then the first element dot amount if I'm right.
Yeah.
So, amount divided by 100.
That should actually be it.
Let's now commit our changes to the repo
and push it to Stripe.
So, git add all, of course,
and then git commit message
Improved stripe implementation,
and then git push heroku master.
Once more, this will take some time.
I'll see you when that's done.
All right.
Now don't forget to set that new environment variable.
So, that's heroku config colon set,
and then simply copy it from here.
Okay.
That then restarts the application.
And that's it.
So, let's now actually go ahead and test it.
All right.
We are still here in our application.
Let's see which tours Laura has already booked.
She has the Forest Hiker.
That booking was still done using the old method.
But that old method now no longer works.
Now if we do another booking and it works,
well, then that's going to mean
that of course our new implementation works.
Let's test that here.
As always, 4242.
Now let's wait for it.
Well, that apparently didn't go so well
because otherwise our second new tour
should already be here in our bookings.
Let's see here in our dashboard.
If we now reload this,
then we actually see that there was a successful event.
So, that's the event that we just created
and which sent this body here
and then received this response.
So, this receive set to true
is exactly what we did here in our code,
so this here.
So, that's the response that we sent
and the body that we got in was all of this data.
And so, we can see here the session with the price,
with the email, with the tour.
And so, I'm not sure why it didn't work.
So, let's just quickly reload this here.
So, actually our Stripe implementation should be correct,
but, for some reason, our new booking was not created.
Let's check that also here in Compass.
And indeed, it's not there.
So, let's go back to our code here.
Oh and one error that I see right away is here.
So, it should be completed like this.
So, that's a stupid mistake.
Let's just see if there might be another error
up here in createBookingCheckout.
Here we have
line_items.
Let's see if that's correct.
And yeah, it seems to be.
We can also confirm that here again in our Stripe.
Actually here it's called display_items.
That's weird.
Just to make sure, let's also call it display_items
here in our code right here.
Now another thing that I noticed
now as I took another look here
is that we still have this image hardcoded
to this other natours.dev.
Now let's actually fix that because at this point of course,
our website is already live and deployed.
And so, we can basically replace that
with the same as we have here.
So, we use this part here many times.
And so, it's time to use that again here.
Yeah.
Let's try to redeploy this.
So, git add all again.
And let's just call this here
Improved stripe implementation two.
And then push it again to Heroku.
Okay.
Let's try that one more time.
Let's go back here.
Now let's try to book again to Park Camper.
All right.
You ought to see the image popping up here on the left side.
That means that our new image integration
also worked just fine.
Now it's processing.
Ah now it is here.
Great.
That's beautiful.
Now we really have a secure
and way more professional Stripe implementation
in our application.
That's great.
Of course, if you reload here,
then you should see this new event here,
so this new call to our webhook,
which of course again was successful.
That's just great.
Now there's just one final thing that I want to do,
which is to basically give the user some feedback
in form of one of these green messages
that we use also for example in the login.
Right now our application doesn't really give
any kind of feedback when a new tour was booked.
Now I want to change that.
However, doing this is not really straightforward
because remember that these messages
are actually displayed by JavaScript.
So, in the other cases, we did an HTTP call to our API.
And then when that was done, we used JavaScript
to display some kind of message.
But now we do not do it this way.
And so, the message should already be somewhere in the HTML
as soon as the page loads
so that then our JavaScript can pick that message up
from the HTML and display it nicely up there
in one of these banners.
And so, the way I'm going to put these alerts
in the HTML is once more by using a data property.
Let's start by implementing this feature right there
in our main template.
That's here in views, base.
I will actually add that alert message
right onto the body element.
Here we will have a data alert property,
which should actually only be set
if the alert variable is available here.
So, let's use ES6, so a template string,
and say if there is an alert,
then use alert here, and else, an empty string.
And so, this alert here will be the alert message
that JavaScript will then pick up and display on the page.
Now how does this alert message then actually end up
as an alert variable here in our template?
Well, I came up with a solution that is reusable
so that we can use all over our application.
That is that on the query string,
we will add some alert keyword
and then we will have a middleware,
which will take that keyword from the URL
and, according to the keyword that we put there,
will then put a whole alert message on response.locals.
And so, remember that everything that's on response.locals
is then available as a variable in all of our templates.
So, we actually used that before
in our authController, I believe.
Very quickly, let me show that to you.
Right here, we said response.local.user
and put the current user there.
Then automatically in all templates,
we have access to that user variable.
So, let's now implement what I just said
and starting with the URL.
What I'm gonna do here is to actually add that query string
here to the success URL.
Here, I will say alert equal booking.
Now I could, in all other URLS, also add some alert
and then with a different keyword here, of course.
And we will just do it here really for this booking.
But again I created a kind of reusable solution here.
Anyway, now in our routes, we need basically a middleware,
which will run for all the requests.
And it's that middleware, which will pick up the alert
from the query string and put a alert message
onto our response.locals.
So, router.use
viewsController.alerts.
And so, this is a middleware function,
which will basically run for each and every single request
that's coming into this router,
so basically for all the requests to our website.
Now let's actually create that middleware
in our viewsController.
So, exports.alerts
request, response, and next.
And so, the alert
is request.query.alert.
And so, let's just use this structuring here once more.
And then let's say if alert is equals to booking,
so the alert that we put right here in the query string,
well, then in that case,
let's say response.locals.alert
will be
your booking
was successful,
please check your email for a confirmation.
And we should also add some other phrase,
which is this one, if your booking doesn't,
select this, doesn't show up here immediately,
please come back later.
And this last part
is because Stripe does very specifically say
in their documentation that sometimes the webhook is called
a little bit after the success URL is called.
In that case, that success URL would then show
all of the current tours, but only after that,
the webhook would be called
and the tour would be created in our database.
Therefore, the new booking would not show up right away
on the My Bookings page.
But of course, everything still worked well in that case.
And so, I simply reload, but later we'll fix that problem.
Now we just need to call the next middleware.
And that's actually it.
Again, we only did this here for alert equal to booking,
but we could now use this all over the place
in our website by setting different alert keywords
and query strings.
With this, we put this message here onto res.locals.alert.
Again, our base template will then pick that up
and display it here into this data alert property.
And so, all that is left to do now is to go to our index.js
and read the alert from here and then display it.
And so, that should be fairly easy.
Here in public, let's actually do it right in the index.
And the first thing is that we actually need to import
the alerts function.
That's not an app.
It's here in index.
Okay.
And then down here, let's basically read that alert.
So, const alertMessage, let's say,
is document.querySelector,
then the body element, dot dataset.alert.
And so, only if there is an alert, of course,
then show the alert
with success
and the alert message.
And now this one small thing that I want to do
is to change a little bit this showAlert function here
because we actually have a lot of text now.
And the standard time that the alert is shown
would not be enough to actually read all the text.
So, you see here that after five seconds,
the alert is hidden.
Let's actually allow the user to specify
the amount of seconds that the alert is shown.
We will do that as a default of five seconds.
Here, we then simply do time times 1,000
to convert it to milliseconds.
Like this, all the functions will work everywhere
with five seconds.
Let's actually make it seven seconds
if we don't specify anything.
But if we want, we can then override this seven.
And so, I will that now here
and actually put it 20 seconds on the screen.
All right.
I think that should be it.
I hope that made sense.
Let's now just very quickly compile our bundle.
That's npm run build, then tap autocomplete.
That takes a little bit of time as well.
But now it's done.
Let's now deploy it one last time
hoping that it works actually.
So, git commit.
So, Stripe messages.
And one last time, git push heroku master.
Let's now test it by buying yet another tour here.
Let's get the City Wanderer this time.
Oh I just see that there is a message already here.
That's not good.
And you see that it disappeared after 20 seconds.
So, it seems like now, by default,
it will always put this alert class here.
(laughs)
Yeah.
That's because here it should be alertMessage
and not just alert.
But anyway, let's now just test
if the message actually is correct when we book the tour.
Okay.
Now let's wait for it.
Here we go.
Indeed, there is our message.
So, beautiful.
Also, our tour shows up here.
And you see that it really stays here for a lot of time.
So, that also works.
Let's just very quickly...
And first, we actually need to rebuild the bundle here.
Then we can add everything to our staging area,
Message alert bug fix.
So, these are some (laughs)
really professional-sounding messages here already.
Now one final push to Heroku.
Now when we load our page,
we should see no alert message.
And indeed, now everything is clean.
And so, I can now say that at least for now,
this project is really finished.
Once more, great job, congratulations,
and well done for probably being one of the few people
who actually are making it all the way to the end
of the project and really building this beautiful website
and also API that you can now put on your portfolio
and show the world.