1 00:00:01,010 --> 00:00:03,730 And now to finish this project, 2 00:00:03,730 --> 00:00:06,593 let's finish this Stripe integration with webhooks. 3 00:00:09,310 --> 00:00:12,040 Let's start to remember how our Stripe integration 4 00:00:12,040 --> 00:00:14,210 actually works right now. 5 00:00:14,210 --> 00:00:16,750 We have this checkout-session endpoint, 6 00:00:16,750 --> 00:00:19,540 which gets called from our front-end. 7 00:00:19,540 --> 00:00:22,293 This will then call the getCheckoutSession function, 8 00:00:23,440 --> 00:00:25,100 so basically this one here, 9 00:00:25,100 --> 00:00:28,180 which will create a checkout session on the server 10 00:00:28,180 --> 00:00:30,300 using all of this information here, 11 00:00:30,300 --> 00:00:32,750 and then send it back to the client. 12 00:00:32,750 --> 00:00:35,170 Then after the processing of the payment 13 00:00:35,170 --> 00:00:37,280 is successfully done by Stripe, 14 00:00:37,280 --> 00:00:40,990 then redirect the user to this success URL, 15 00:00:40,990 --> 00:00:42,483 so this one that we created. 16 00:00:44,210 --> 00:00:48,120 Remember that onto this URL, we added the tour ID, 17 00:00:48,120 --> 00:00:50,920 the user ID, and also the price. 18 00:00:50,920 --> 00:00:55,040 We did that so that once this URL here is then called, 19 00:00:55,040 --> 00:00:57,920 our application would create a new booking document 20 00:00:57,920 --> 00:00:59,680 in our database. 21 00:00:59,680 --> 00:01:01,047 How did that work? 22 00:01:01,047 --> 00:01:04,743 In the my-tours route, we have a middleware for that. 23 00:01:06,040 --> 00:01:09,940 Remember, here in the viewRoutes, in my-tours, 24 00:01:09,940 --> 00:01:12,467 we have this createBookingCheckout. 25 00:01:14,770 --> 00:01:18,628 This function here, which basically from the query 26 00:01:18,628 --> 00:01:21,440 takes the tour, user, and price, 27 00:01:21,440 --> 00:01:25,023 and creates a entry in the database using that data. 28 00:01:26,350 --> 00:01:29,160 Basically we put this data on the URL 29 00:01:29,160 --> 00:01:32,500 whenever Stripe successfully processes a payment. 30 00:01:32,500 --> 00:01:34,990 And then this middleware function that we have here 31 00:01:34,990 --> 00:01:38,570 picks up the data and creates a new booking in our system 32 00:01:38,570 --> 00:01:39,960 using that data. 33 00:01:39,960 --> 00:01:42,790 And then after that, we basically redirect here 34 00:01:42,790 --> 00:01:45,763 onto the original URL without the query string. 35 00:01:46,770 --> 00:01:50,150 Now the problem with this was that it's not really secure. 36 00:01:50,150 --> 00:01:52,963 So, everyone who knows this URL structure, 37 00:01:54,010 --> 00:01:57,670 so this one up here, which tour, user, and price 38 00:01:57,670 --> 00:02:00,700 in the query string, can basically create a booking 39 00:02:01,761 --> 00:02:03,850 in our system without actually paying. 40 00:02:03,850 --> 00:02:07,120 So, all that we'd have to do is to open up this URL 41 00:02:07,120 --> 00:02:08,500 with some data in there 42 00:02:08,500 --> 00:02:11,680 and then from there automatically create a booking 43 00:02:11,680 --> 00:02:14,193 without going through the Stripe process. 44 00:02:15,540 --> 00:02:18,630 Remember how back then I said that we would fix this 45 00:02:18,630 --> 00:02:20,853 using something called webhooks. 46 00:02:22,090 --> 00:02:23,120 So, we do that now. 47 00:02:23,120 --> 00:02:24,090 Because for that, 48 00:02:24,090 --> 00:02:27,140 we actually need our website to be deployed. 49 00:02:27,140 --> 00:02:29,350 Now at this point, that's actually the case. 50 00:02:29,350 --> 00:02:31,833 And so, now we can implement these webhooks. 51 00:02:33,240 --> 00:02:35,663 For that, let's go to our Stripe dashboard. 52 00:02:37,400 --> 00:02:39,750 And I actually already have that opened here. 53 00:02:39,750 --> 00:02:43,903 And then go here to developers, then choose webhooks, 54 00:02:45,070 --> 00:02:47,970 and here, add a new endpoint. 55 00:02:47,970 --> 00:02:52,149 Now what is this endpoint here and this webhook? 56 00:02:52,149 --> 00:02:55,380 Basically we're gonna specify a URL here 57 00:02:55,380 --> 00:02:59,500 to which Stripe will automatically send a POST request to 58 00:02:59,500 --> 00:03:02,800 whenever a checkout session has successfully completed, 59 00:03:02,800 --> 00:03:05,740 so basically whenever a payment was successful. 60 00:03:05,740 --> 00:03:09,920 With that POST request, Stripe will then send back 61 00:03:09,920 --> 00:03:13,230 the original session data that we created in the first step 62 00:03:13,230 --> 00:03:15,623 when we created that checkout session. 63 00:03:17,540 --> 00:03:20,130 That's the reason why we actually needed our website 64 00:03:20,130 --> 00:03:23,010 to be deployed here because now we need to specify 65 00:03:23,010 --> 00:03:24,923 that real-life URL here. 66 00:03:27,170 --> 00:03:28,573 Let's grab that from here, 67 00:03:31,290 --> 00:03:34,150 and then add our route here basically. 68 00:03:34,150 --> 00:03:36,930 I'm going to call this one webhook-checkout. 69 00:03:41,620 --> 00:03:45,350 It's not in the API, and it's not inside the bookings. 70 00:03:45,350 --> 00:03:47,593 You will see in a moment why that is. 71 00:03:49,130 --> 00:03:51,210 Again, when a payment was successful, 72 00:03:51,210 --> 00:03:53,280 Stripe will then automatically post 73 00:03:53,280 --> 00:03:55,503 the original session data to this URL. 74 00:03:58,060 --> 00:04:00,380 Now we also need to select the event. 75 00:04:00,380 --> 00:04:04,740 And you see there are tons of events that we could use here. 76 00:04:04,740 --> 00:04:09,667 The one that we're using is the checkout_session_completed. 77 00:04:11,767 --> 00:04:12,650 Add that. 78 00:04:12,650 --> 00:04:15,083 Now you need to put in your password here again. 79 00:04:17,100 --> 00:04:19,110 And then there we go. 80 00:04:19,110 --> 00:04:22,665 This webhook then also has a secret here. 81 00:04:22,665 --> 00:04:25,850 This one, we will then also need in a second 82 00:04:25,850 --> 00:04:29,063 when we actually create our route for this URL here. 83 00:04:29,980 --> 00:04:32,430 Actually that's exactly what we're gonna do next. 84 00:04:33,750 --> 00:04:35,600 Basically in our system of course, 85 00:04:35,600 --> 00:04:38,840 we now need a route for this here 86 00:04:39,960 --> 00:04:43,840 so that when Stripe then posts the data to our application, 87 00:04:43,840 --> 00:04:46,233 we can then actually do something with it. 88 00:04:48,120 --> 00:04:52,233 Let's go back here and open up our application. 89 00:04:54,740 --> 00:04:57,743 We will actually add this route right here. 90 00:04:59,610 --> 00:05:03,100 Again, I will explain you why in a second. 91 00:05:03,100 --> 00:05:04,350 So, app.post 92 00:05:06,320 --> 00:05:08,850 and standard route and then of course we need 93 00:05:08,850 --> 00:05:10,810 a handler function for that. 94 00:05:10,810 --> 00:05:14,720 Let's quickly create it here in our bookingController. 95 00:05:14,720 --> 00:05:19,013 Let me call that export.webhookCheckout. 96 00:05:31,360 --> 00:05:36,360 Now I will have to import this controller into app.js. 97 00:05:39,210 --> 00:05:42,110 Let's do that right here after the bookingRouter actually, 98 00:05:45,150 --> 00:05:47,133 so this one and this one, 99 00:05:49,440 --> 00:05:51,383 controller and here also controller. 100 00:05:54,580 --> 00:05:56,050 All right. 101 00:05:56,050 --> 00:06:01,050 Now down here, that's bookingController.webhookCheckout. 102 00:06:04,800 --> 00:06:08,820 Now why do we actually define this webhook-checkout 103 00:06:08,820 --> 00:06:12,410 right here in app.js instead of doing it for example 104 00:06:12,410 --> 00:06:14,440 in the bookingRouter. 105 00:06:14,440 --> 00:06:17,950 The reason for that is that in this handler function, 106 00:06:17,950 --> 00:06:20,677 when we receive the body from Stripe, 107 00:06:20,677 --> 00:06:22,850 the Stripe function that we're then gonna use 108 00:06:22,850 --> 00:06:26,780 to actually read the body needs this body in a raw form, 109 00:06:26,780 --> 00:06:29,633 so basically as a string and not as JSON. 110 00:06:31,370 --> 00:06:34,140 Again, in this route here, we need the body 111 00:06:34,140 --> 00:06:37,555 coming with the request to be not in JSON, 112 00:06:37,555 --> 00:06:40,600 otherwise this is not going to be working at all. 113 00:06:40,600 --> 00:06:43,700 Now the thing is, that as soon as a request 114 00:06:43,700 --> 00:06:46,710 hits this middleware here, the body will be parsed 115 00:06:46,710 --> 00:06:48,563 and converted to JSON. 116 00:06:49,700 --> 00:06:54,650 It will then be put on request.body as a simple JSON object. 117 00:06:54,650 --> 00:06:57,520 Again with that, this route handler here 118 00:06:57,520 --> 00:06:59,180 would then not work. 119 00:06:59,180 --> 00:07:02,520 That's the whole reason why we need to put this route here 120 00:07:02,520 --> 00:07:04,557 before we call the body-parser. 121 00:07:05,580 --> 00:07:08,260 Now we still need to actually parse the body 122 00:07:08,260 --> 00:07:10,120 but in a so-called raw format. 123 00:07:10,120 --> 00:07:13,690 By the time I was recording this video, 124 00:07:13,690 --> 00:07:17,220 we could not do it with Express out of the box. 125 00:07:17,220 --> 00:07:21,500 And so, in this video, we download the body-parser from npm 126 00:07:21,500 --> 00:07:24,220 and use it as I show it in the video. 127 00:07:24,220 --> 00:07:28,340 However, like five days after I recorded this video, 128 00:07:28,340 --> 00:07:32,770 Express added the raw parser to Express as well. 129 00:07:32,770 --> 00:07:37,000 Now we can use express.raw instead of having to install 130 00:07:37,000 --> 00:07:39,523 the body-parser or middleware from npm. 131 00:07:40,530 --> 00:07:44,610 Again, in this video, I will now install the body-parser, 132 00:07:44,610 --> 00:07:46,440 but you don't really need to. 133 00:07:46,440 --> 00:07:49,293 You can just use express.raw instead. 134 00:07:51,590 --> 00:07:52,700 Npm install 135 00:07:54,480 --> 00:07:55,403 body-parser. 136 00:07:58,950 --> 00:08:02,120 This probably all sounds a little bit focusing, 137 00:08:02,120 --> 00:08:04,350 and I totally understand that, 138 00:08:04,350 --> 00:08:08,050 but that's just how the Stripe documentation works 139 00:08:08,890 --> 00:08:10,893 and forces us to do it, really. 140 00:08:15,210 --> 00:08:17,100 Let's go back here to our route. 141 00:08:17,100 --> 00:08:20,453 In this route, we need the body to be in a raw format. 142 00:08:21,460 --> 00:08:25,330 We can add that as a middleware here between the route 143 00:08:25,330 --> 00:08:26,673 and the final handler. 144 00:08:28,654 --> 00:08:31,013 Here we say bodyParser.raw, 145 00:08:34,830 --> 00:08:37,490 and we also need to specific here the type 146 00:08:39,450 --> 00:08:43,127 just very quick as application/json. 147 00:08:48,130 --> 00:08:52,660 We now added this body parsing as a raw body 148 00:08:52,660 --> 00:08:54,183 here in this middleware stack. 149 00:08:55,964 --> 00:08:58,150 All this will really start to come together 150 00:08:58,150 --> 00:09:00,970 once we start implementing this function. 151 00:09:00,970 --> 00:09:02,543 Actually let's do that now, 152 00:09:03,820 --> 00:09:05,210 so right here. 153 00:09:05,210 --> 00:09:07,100 But before we actually do that, 154 00:09:07,100 --> 00:09:09,780 let's get rid of all the code that we wrote 155 00:09:09,780 --> 00:09:11,680 in order to make it work right now. 156 00:09:11,680 --> 00:09:14,420 So, basically this middleware function, 157 00:09:14,420 --> 00:09:16,350 we don't need it anymore. 158 00:09:16,350 --> 00:09:18,480 Also here in the viewRoutes, 159 00:09:18,480 --> 00:09:21,980 we don't need it here anymore either. 160 00:09:21,980 --> 00:09:24,770 And then finally in the bookingController, 161 00:09:24,770 --> 00:09:28,153 let's also set our URL back to normal. 162 00:09:31,080 --> 00:09:33,180 I will just leave all of this here 163 00:09:33,180 --> 00:09:35,233 so that you can keep it as a reference. 164 00:09:37,390 --> 00:09:40,863 But now the success URL should actually just be this. 165 00:09:43,090 --> 00:09:45,400 Basically after a successful booking, 166 00:09:45,400 --> 00:09:48,090 we still want to come back to my-tours 167 00:09:48,090 --> 00:09:50,350 but without all this query parameters 168 00:09:51,350 --> 00:09:54,580 because now it's no longer this function here, 169 00:09:54,580 --> 00:09:57,430 which will take care of creating the booking 170 00:09:57,430 --> 00:09:59,770 but instead it is this function here, 171 00:09:59,770 --> 00:10:02,060 which is of course the one that gets called 172 00:10:02,060 --> 00:10:05,633 once Stripe calls our webhook. 173 00:10:07,140 --> 00:10:08,470 Let's now implement this. 174 00:10:08,470 --> 00:10:10,140 The first thing that we need to do 175 00:10:10,140 --> 00:10:13,763 is to rid this Stripe signature out of our headers, 176 00:10:15,780 --> 00:10:19,840 so signature and then request.headers 177 00:10:21,500 --> 00:10:26,373 and then from there stripe-signature. 178 00:10:28,220 --> 00:10:30,710 Basically when Stripe calls our webhook, 179 00:10:30,710 --> 00:10:32,830 it will add a header to that request 180 00:10:32,830 --> 00:10:36,280 containing a special signature for our webhook. 181 00:10:38,480 --> 00:10:40,700 If you're thinking that you're just blindly following 182 00:10:40,700 --> 00:10:42,590 what I'm doing here, well, (laughs) 183 00:10:42,590 --> 00:10:45,070 that's actually exactly what I did as well 184 00:10:45,070 --> 00:10:47,050 from the Stripe documentations. 185 00:10:47,050 --> 00:10:50,320 Again, this is really just how Stripe works, 186 00:10:50,320 --> 00:10:52,973 and there's nothing we can do against that. 187 00:10:54,350 --> 00:10:57,453 Anyway, next up, let's create a Stripe event, 188 00:10:59,310 --> 00:11:03,690 so const event equals stripe. 189 00:11:03,690 --> 00:11:07,410 For that of course, we need to the Stripe library installed, 190 00:11:07,410 --> 00:11:09,573 which we have up here. 191 00:11:12,650 --> 00:11:14,350 So, stripe.webhooks.contructEvent. 192 00:11:20,378 --> 00:11:23,210 Now here is where finally that body 193 00:11:23,210 --> 00:11:26,520 comes into play, so request.body. 194 00:11:26,520 --> 00:11:28,370 And remember that this body here 195 00:11:28,370 --> 00:11:30,220 needs to be in the raw form, 196 00:11:30,220 --> 00:11:32,083 so basically available as a string. 197 00:11:33,130 --> 00:11:36,340 Once more, that is why we put that route 198 00:11:36,340 --> 00:11:38,110 before all our other routes 199 00:11:38,110 --> 00:11:41,580 and especially before the body parser could do its job 200 00:11:41,580 --> 00:11:44,863 of converting our body into a JSON object. 201 00:11:46,170 --> 00:11:51,050 Then besides that body, for the event, we need a signature, 202 00:11:51,050 --> 00:11:53,370 so basically the signature that was sent 203 00:11:53,370 --> 00:11:56,763 along with the header, and then finally our webhook secret. 204 00:11:57,710 --> 00:12:00,653 Let's get that from here, copy it. 205 00:12:01,585 --> 00:12:05,610 Since it's a secret, we should, as always, add it here 206 00:12:05,610 --> 00:12:07,143 to our config file, 207 00:12:10,460 --> 00:12:12,737 so STRIPE_WEBHOOK_SECRET. 208 00:12:16,650 --> 00:12:19,380 And then later of course, don't forget to also add this 209 00:12:19,380 --> 00:12:21,663 to our Heroku configuration. 210 00:12:26,100 --> 00:12:27,330 Let's now use that. 211 00:12:27,330 --> 00:12:28,767 Add process.env. 212 00:12:30,330 --> 00:12:31,830 I should have just copied that 213 00:12:35,690 --> 00:12:36,573 right here. 214 00:12:37,752 --> 00:12:41,200 So, you see, all of this is really to make the process 215 00:12:41,200 --> 00:12:43,450 super, super secure. 216 00:12:43,450 --> 00:12:45,970 We need all of this data like the signature 217 00:12:45,970 --> 00:12:49,450 and also the secret in order to basically validate 218 00:12:49,450 --> 00:12:51,640 the data that comes in the body 219 00:12:51,640 --> 00:12:54,433 so that no one can actually manipulate that. 220 00:12:55,870 --> 00:12:58,050 Now during the creation of this event, 221 00:12:58,050 --> 00:12:59,280 there might be some errors, 222 00:12:59,280 --> 00:13:01,420 for example if the signature is wrong 223 00:13:01,420 --> 00:13:03,900 or if the secret is wrong. 224 00:13:03,900 --> 00:13:07,813 And so, let's wrap this into a try-catch block. 225 00:13:16,290 --> 00:13:17,850 Okay. 226 00:13:17,850 --> 00:13:19,500 Of course, we now need the catch. 227 00:13:22,150 --> 00:13:23,410 In case there is an error, 228 00:13:23,410 --> 00:13:26,053 we want to send back an error to Stripe, 229 00:13:27,880 --> 00:13:32,450 so return res.status 400 230 00:13:33,756 --> 00:13:35,657 and then just use send webhook error 231 00:13:40,140 --> 00:13:44,023 and then let's just add the error.message. 232 00:13:45,714 --> 00:13:49,220 So, it is Stripe who will receive this response here 233 00:13:49,220 --> 00:13:53,230 because again it is Stripe who will actually call the URL, 234 00:13:53,230 --> 00:13:56,603 so our webhook, which will then call this function. 235 00:13:58,520 --> 00:14:02,420 Now we need to of course also declare this event here 236 00:14:02,420 --> 00:14:04,610 outside of the try-catch block 237 00:14:04,610 --> 00:14:07,623 because otherwise we will not be able to use it down there. 238 00:14:08,660 --> 00:14:13,160 So, let event and then reassign down here 239 00:14:13,160 --> 00:14:15,430 because remember that the ES6 const 240 00:14:15,430 --> 00:14:17,450 and let are block-scoped. 241 00:14:17,450 --> 00:14:20,480 And so, this variable would not be available outside 242 00:14:20,480 --> 00:14:21,473 of this block. 243 00:14:23,180 --> 00:14:25,830 Now let's actually use that event. 244 00:14:25,830 --> 00:14:29,090 First off, we need to test if this really is the event 245 00:14:29,090 --> 00:14:29,923 that we want. 246 00:14:30,810 --> 00:14:34,240 So, we can do event.type 247 00:14:34,240 --> 00:14:38,973 is equal to checkout.session.complete. 248 00:14:42,080 --> 00:14:44,370 Remember that in our Stripe dashboard, 249 00:14:44,370 --> 00:14:48,090 that's exactly the type that we defined here. 250 00:14:48,090 --> 00:14:49,260 So, that's the event type. 251 00:14:49,260 --> 00:14:52,183 Now we're checking if that is really the event 252 00:14:52,183 --> 00:14:56,287 that we are receiving here just to be 100% sure. 253 00:14:56,287 --> 00:14:59,780 If it is, we then want to actually use the event 254 00:14:59,780 --> 00:15:02,053 to create our booking in our database. 255 00:15:03,860 --> 00:15:06,280 Actually let's do that in a separate function 256 00:15:06,280 --> 00:15:08,983 and not inside here of all of this mess. 257 00:15:10,517 --> 00:15:12,590 For that, I will create a function. 258 00:15:12,590 --> 00:15:13,640 Actually let me give it 259 00:15:13,640 --> 00:15:15,990 this exact same name, so createBookingCheckout. 260 00:15:17,487 --> 00:15:19,490 It was actually a nice name, 261 00:15:19,490 --> 00:15:21,450 but now it cannot be a middleware 262 00:15:21,450 --> 00:15:23,250 but instead just a regular function. 263 00:15:26,080 --> 00:15:28,823 This function will accept the session data. 264 00:15:31,080 --> 00:15:35,310 And remember that the session data is exactly this session 265 00:15:35,310 --> 00:15:37,513 that we created here in the first place. 266 00:15:41,404 --> 00:15:43,730 If this is the correct event, 267 00:15:43,730 --> 00:15:45,743 then let's actually call that function, 268 00:15:46,680 --> 00:15:49,500 so createBookingCheckout with the session, 269 00:15:49,500 --> 00:15:53,057 which is at event.data.object. 270 00:15:57,447 --> 00:15:58,320 And then finally, 271 00:15:58,320 --> 00:16:01,333 let's just send back some response to Stripe. 272 00:16:02,450 --> 00:16:03,840 So, status 200 273 00:16:05,780 --> 00:16:07,480 and then let's say some json 274 00:16:10,300 --> 00:16:11,823 receive set to true. 275 00:16:13,200 --> 00:16:14,033 Makes sense? 276 00:16:16,000 --> 00:16:18,490 Once more, all of this code here will run 277 00:16:18,490 --> 00:16:21,390 whenever a payment was successful. 278 00:16:21,390 --> 00:16:25,380 Stripe will then call our webhook, which is the URL, 279 00:16:25,380 --> 00:16:27,420 which is going to call this function. 280 00:16:27,420 --> 00:16:30,600 And so, this function receives a body from the request, 281 00:16:30,600 --> 00:16:34,330 and then together with the signature and/or webhook secret, 282 00:16:34,330 --> 00:16:37,110 creates an event, which will contain the session. 283 00:16:37,110 --> 00:16:39,190 And then using that session data, 284 00:16:39,190 --> 00:16:41,963 we can create our new booking in the database. 285 00:16:43,987 --> 00:16:45,660 And so, that will actually be pretty similar 286 00:16:45,660 --> 00:16:47,143 to what we had here before. 287 00:16:48,400 --> 00:16:51,790 So, we will need this line of code here again. 288 00:16:51,790 --> 00:16:53,923 So, this will also be an async function. 289 00:16:58,497 --> 00:17:00,530 And so, this is exactly the same. 290 00:17:00,530 --> 00:17:02,260 Now what we need here of course 291 00:17:02,260 --> 00:17:06,690 is to get access to the tour, user, and price. 292 00:17:06,690 --> 00:17:10,550 But that data once more is stored in that session. 293 00:17:10,550 --> 00:17:12,400 So, let's start with the tour. 294 00:17:12,400 --> 00:17:14,780 And remember how up here 295 00:17:14,780 --> 00:17:17,100 when we first created this handler function, 296 00:17:17,100 --> 00:17:20,040 I specified this client_reference_id field 297 00:17:20,040 --> 00:17:22,370 and added the tourId to that. 298 00:17:22,370 --> 00:17:23,840 Remember that? 299 00:17:23,840 --> 00:17:25,700 I did that because, at the time, 300 00:17:25,700 --> 00:17:29,840 I already knew that we would need this tour ID a bit later. 301 00:17:29,840 --> 00:17:32,490 Now it's that time where we actually need the tour ID 302 00:17:32,490 --> 00:17:35,333 in order to be able to create that new booking. 303 00:17:36,732 --> 00:17:38,490 And so, now the tour ID that we need 304 00:17:38,490 --> 00:17:41,670 is at session dot client's reference ID. 305 00:17:41,670 --> 00:17:44,770 So, let's copy this and say 306 00:17:47,870 --> 00:17:48,703 session. 307 00:17:49,660 --> 00:17:53,823 And of course, here we need to say tour. 308 00:17:55,670 --> 00:17:57,040 So, that's the tour ID. 309 00:17:57,040 --> 00:17:59,150 Next up, we need the user ID. 310 00:17:59,150 --> 00:18:01,240 Now the information that we have in our session 311 00:18:01,240 --> 00:18:03,973 about the user is the user's email. 312 00:18:05,630 --> 00:18:07,170 And so, now what we need to do 313 00:18:07,170 --> 00:18:10,500 is to basically get the user's ID. 314 00:18:10,500 --> 00:18:12,793 For that, we will query by the email. 315 00:18:13,720 --> 00:18:16,810 That's no problem because the email is also unique. 316 00:18:16,810 --> 00:18:19,353 Based on that, we can then find the unique ID. 317 00:18:20,370 --> 00:18:24,183 So, const user is await. 318 00:18:25,570 --> 00:18:27,660 And I think we already have the user here. 319 00:18:27,660 --> 00:18:28,493 No? 320 00:18:29,520 --> 00:18:30,570 No, I actually don't. 321 00:18:31,890 --> 00:18:33,290 So, let's just do that here. 322 00:18:35,490 --> 00:18:37,973 And user here as well. 323 00:18:41,070 --> 00:18:41,903 Okay. 324 00:18:41,903 --> 00:18:46,890 So, User.findOne and then query via email, 325 00:18:47,990 --> 00:18:51,330 which is in session dot, 326 00:18:51,330 --> 00:18:53,780 and I believe that's client's email or something. 327 00:18:55,200 --> 00:18:56,200 It's customer_email. 328 00:18:59,860 --> 00:19:00,693 Okay. 329 00:19:02,070 --> 00:19:04,970 But that will then return the entire document, 330 00:19:04,970 --> 00:19:06,910 but we want actually the ID. 331 00:19:06,910 --> 00:19:09,780 So, let's wrap all of this here in parenthesis 332 00:19:10,730 --> 00:19:14,743 and then call the ID on there or actually read the ID. 333 00:19:16,620 --> 00:19:17,960 So, that's it. 334 00:19:17,960 --> 00:19:19,233 And finally, the price. 335 00:19:22,350 --> 00:19:24,023 Where is the price stored? 336 00:19:25,320 --> 00:19:26,833 Well, it's here in line_items. 337 00:19:27,880 --> 00:19:30,610 That's an array, so at element zero, 338 00:19:30,610 --> 00:19:33,553 and then the amount divided by 100. 339 00:19:34,580 --> 00:19:38,210 So, we multiplied it here by 100 to get cents, 340 00:19:38,210 --> 00:19:41,590 but now of course we want it back in dollars. 341 00:19:41,590 --> 00:19:44,700 So, we need to basically divide that back. 342 00:19:44,700 --> 00:19:48,550 And so, session.line_items 343 00:19:49,460 --> 00:19:54,460 and then the first element dot amount if I'm right. 344 00:19:55,950 --> 00:19:56,783 Yeah. 345 00:19:56,783 --> 00:20:01,710 So, amount divided by 100. 346 00:20:01,710 --> 00:20:04,010 That should actually be it. 347 00:20:04,010 --> 00:20:06,630 Let's now commit our changes to the repo 348 00:20:06,630 --> 00:20:08,740 and push it to Stripe. 349 00:20:08,740 --> 00:20:12,840 So, git add all, of course, 350 00:20:12,840 --> 00:20:16,600 and then git commit message 351 00:20:18,090 --> 00:20:21,633 Improved stripe implementation, 352 00:20:24,960 --> 00:20:29,960 and then git push heroku master. 353 00:20:31,190 --> 00:20:33,273 Once more, this will take some time. 354 00:20:33,273 --> 00:20:35,263 I'll see you when that's done. 355 00:20:36,200 --> 00:20:37,033 All right. 356 00:20:37,033 --> 00:20:40,323 Now don't forget to set that new environment variable. 357 00:20:41,610 --> 00:20:46,610 So, that's heroku config colon set, 358 00:20:46,750 --> 00:20:49,433 and then simply copy it from here. 359 00:20:53,590 --> 00:20:54,720 Okay. 360 00:20:54,720 --> 00:20:56,800 That then restarts the application. 361 00:20:56,800 --> 00:20:58,173 And that's it. 362 00:20:59,570 --> 00:21:02,723 So, let's now actually go ahead and test it. 363 00:21:04,980 --> 00:21:05,813 All right. 364 00:21:07,050 --> 00:21:09,480 We are still here in our application. 365 00:21:09,480 --> 00:21:12,883 Let's see which tours Laura has already booked. 366 00:21:14,100 --> 00:21:15,370 She has the Forest Hiker. 367 00:21:15,370 --> 00:21:19,823 That booking was still done using the old method. 368 00:21:21,050 --> 00:21:24,240 But that old method now no longer works. 369 00:21:24,240 --> 00:21:27,047 Now if we do another booking and it works, 370 00:21:27,047 --> 00:21:29,490 well, then that's going to mean 371 00:21:29,490 --> 00:21:32,773 that of course our new implementation works. 372 00:21:34,730 --> 00:21:35,780 Let's test that here. 373 00:21:39,760 --> 00:21:41,493 As always, 4242. 374 00:21:50,420 --> 00:21:51,683 Now let's wait for it. 375 00:21:52,730 --> 00:21:55,740 Well, that apparently didn't go so well 376 00:21:55,740 --> 00:21:58,520 because otherwise our second new tour 377 00:21:58,520 --> 00:22:00,743 should already be here in our bookings. 378 00:22:02,230 --> 00:22:04,203 Let's see here in our dashboard. 379 00:22:05,860 --> 00:22:06,983 If we now reload this, 380 00:22:12,150 --> 00:22:15,893 then we actually see that there was a successful event. 381 00:22:17,407 --> 00:22:20,320 So, that's the event that we just created 382 00:22:20,320 --> 00:22:23,170 and which sent this body here 383 00:22:23,170 --> 00:22:25,380 and then received this response. 384 00:22:25,380 --> 00:22:27,560 So, this receive set to true 385 00:22:27,560 --> 00:22:30,663 is exactly what we did here in our code, 386 00:22:31,670 --> 00:22:32,633 so this here. 387 00:22:34,060 --> 00:22:36,000 So, that's the response that we sent 388 00:22:36,000 --> 00:22:39,770 and the body that we got in was all of this data. 389 00:22:39,770 --> 00:22:42,810 And so, we can see here the session with the price, 390 00:22:42,810 --> 00:22:46,460 with the email, with the tour. 391 00:22:46,460 --> 00:22:49,483 And so, I'm not sure why it didn't work. 392 00:22:51,000 --> 00:22:53,163 So, let's just quickly reload this here. 393 00:22:55,780 --> 00:22:59,050 So, actually our Stripe implementation should be correct, 394 00:22:59,050 --> 00:23:02,013 but, for some reason, our new booking was not created. 395 00:23:03,120 --> 00:23:05,020 Let's check that also here in Compass. 396 00:23:07,460 --> 00:23:09,970 And indeed, it's not there. 397 00:23:09,970 --> 00:23:12,123 So, let's go back to our code here. 398 00:23:13,410 --> 00:23:17,360 Oh and one error that I see right away is here. 399 00:23:17,360 --> 00:23:20,393 So, it should be completed like this. 400 00:23:22,090 --> 00:23:24,480 So, that's a stupid mistake. 401 00:23:24,480 --> 00:23:26,950 Let's just see if there might be another error 402 00:23:26,950 --> 00:23:30,050 up here in createBookingCheckout. 403 00:23:30,050 --> 00:23:30,883 Here we have 404 00:23:32,750 --> 00:23:33,583 line_items. 405 00:23:33,583 --> 00:23:35,093 Let's see if that's correct. 406 00:23:36,110 --> 00:23:38,170 And yeah, it seems to be. 407 00:23:38,170 --> 00:23:41,123 We can also confirm that here again in our Stripe. 408 00:23:43,110 --> 00:23:45,290 Actually here it's called display_items. 409 00:23:46,590 --> 00:23:47,423 That's weird. 410 00:23:48,367 --> 00:23:52,140 Just to make sure, let's also call it display_items 411 00:23:52,140 --> 00:23:54,363 here in our code right here. 412 00:23:55,980 --> 00:23:57,580 Now another thing that I noticed 413 00:23:58,750 --> 00:24:00,350 now as I took another look here 414 00:24:00,350 --> 00:24:03,510 is that we still have this image hardcoded 415 00:24:03,510 --> 00:24:05,763 to this other natours.dev. 416 00:24:07,587 --> 00:24:11,380 Now let's actually fix that because at this point of course, 417 00:24:11,380 --> 00:24:14,580 our website is already live and deployed. 418 00:24:14,580 --> 00:24:16,600 And so, we can basically replace that 419 00:24:16,600 --> 00:24:18,100 with the same as we have here. 420 00:24:20,900 --> 00:24:23,430 So, we use this part here many times. 421 00:24:23,430 --> 00:24:25,480 And so, it's time to use that again here. 422 00:24:32,672 --> 00:24:33,505 Yeah. 423 00:24:33,505 --> 00:24:35,353 Let's try to redeploy this. 424 00:24:36,380 --> 00:24:38,113 So, git add all again. 425 00:24:40,420 --> 00:24:42,070 And let's just call this here 426 00:24:42,070 --> 00:24:44,430 Improved stripe implementation two. 427 00:24:44,430 --> 00:24:47,693 And then push it again to Heroku. 428 00:24:51,580 --> 00:24:52,560 Okay. 429 00:24:52,560 --> 00:24:54,253 Let's try that one more time. 430 00:24:55,830 --> 00:24:57,023 Let's go back here. 431 00:25:00,630 --> 00:25:04,063 Now let's try to book again to Park Camper. 432 00:25:15,760 --> 00:25:16,683 All right. 433 00:25:17,920 --> 00:25:21,530 You ought to see the image popping up here on the left side. 434 00:25:21,530 --> 00:25:24,200 That means that our new image integration 435 00:25:24,200 --> 00:25:25,753 also worked just fine. 436 00:25:27,220 --> 00:25:28,283 Now it's processing. 437 00:25:29,382 --> 00:25:31,380 Ah now it is here. 438 00:25:31,380 --> 00:25:32,320 Great. 439 00:25:32,320 --> 00:25:33,533 That's beautiful. 440 00:25:34,420 --> 00:25:36,850 Now we really have a secure 441 00:25:36,850 --> 00:25:39,940 and way more professional Stripe implementation 442 00:25:39,940 --> 00:25:41,173 in our application. 443 00:25:42,070 --> 00:25:43,520 That's great. 444 00:25:43,520 --> 00:25:45,570 Of course, if you reload here, 445 00:25:45,570 --> 00:25:49,500 then you should see this new event here, 446 00:25:49,500 --> 00:25:52,050 so this new call to our webhook, 447 00:25:52,050 --> 00:25:54,593 which of course again was successful. 448 00:25:55,840 --> 00:25:57,690 That's just great. 449 00:25:57,690 --> 00:26:00,740 Now there's just one final thing that I want to do, 450 00:26:00,740 --> 00:26:04,420 which is to basically give the user some feedback 451 00:26:04,420 --> 00:26:06,980 in form of one of these green messages 452 00:26:06,980 --> 00:26:09,123 that we use also for example in the login. 453 00:26:10,650 --> 00:26:12,930 Right now our application doesn't really give 454 00:26:12,930 --> 00:26:16,476 any kind of feedback when a new tour was booked. 455 00:26:16,476 --> 00:26:18,650 Now I want to change that. 456 00:26:18,650 --> 00:26:21,900 However, doing this is not really straightforward 457 00:26:21,900 --> 00:26:23,990 because remember that these messages 458 00:26:23,990 --> 00:26:26,750 are actually displayed by JavaScript. 459 00:26:26,750 --> 00:26:30,280 So, in the other cases, we did an HTTP call to our API. 460 00:26:30,280 --> 00:26:33,070 And then when that was done, we used JavaScript 461 00:26:33,070 --> 00:26:34,840 to display some kind of message. 462 00:26:34,840 --> 00:26:36,970 But now we do not do it this way. 463 00:26:36,970 --> 00:26:40,710 And so, the message should already be somewhere in the HTML 464 00:26:40,710 --> 00:26:42,380 as soon as the page loads 465 00:26:42,380 --> 00:26:45,400 so that then our JavaScript can pick that message up 466 00:26:45,400 --> 00:26:49,070 from the HTML and display it nicely up there 467 00:26:49,070 --> 00:26:50,463 in one of these banners. 468 00:26:51,610 --> 00:26:54,510 And so, the way I'm going to put these alerts 469 00:26:54,510 --> 00:26:58,223 in the HTML is once more by using a data property. 470 00:26:59,450 --> 00:27:03,000 Let's start by implementing this feature right there 471 00:27:03,000 --> 00:27:04,363 in our main template. 472 00:27:06,610 --> 00:27:09,273 That's here in views, base. 473 00:27:11,160 --> 00:27:13,630 I will actually add that alert message 474 00:27:13,630 --> 00:27:15,663 right onto the body element. 475 00:27:17,110 --> 00:27:19,963 Here we will have a data alert property, 476 00:27:21,860 --> 00:27:24,000 which should actually only be set 477 00:27:24,000 --> 00:27:26,563 if the alert variable is available here. 478 00:27:27,480 --> 00:27:31,460 So, let's use ES6, so a template string, 479 00:27:31,460 --> 00:27:35,060 and say if there is an alert, 480 00:27:35,060 --> 00:27:38,713 then use alert here, and else, an empty string. 481 00:27:39,980 --> 00:27:43,370 And so, this alert here will be the alert message 482 00:27:43,370 --> 00:27:47,230 that JavaScript will then pick up and display on the page. 483 00:27:47,230 --> 00:27:50,230 Now how does this alert message then actually end up 484 00:27:50,230 --> 00:27:52,513 as an alert variable here in our template? 485 00:27:53,360 --> 00:27:56,448 Well, I came up with a solution that is reusable 486 00:27:56,448 --> 00:27:59,250 so that we can use all over our application. 487 00:27:59,250 --> 00:28:01,840 That is that on the query string, 488 00:28:01,840 --> 00:28:03,890 we will add some alert keyword 489 00:28:03,890 --> 00:28:05,820 and then we will have a middleware, 490 00:28:05,820 --> 00:28:08,560 which will take that keyword from the URL 491 00:28:08,560 --> 00:28:10,910 and, according to the keyword that we put there, 492 00:28:10,910 --> 00:28:15,050 will then put a whole alert message on response.locals. 493 00:28:15,050 --> 00:28:19,000 And so, remember that everything that's on response.locals 494 00:28:19,000 --> 00:28:22,483 is then available as a variable in all of our templates. 495 00:28:23,450 --> 00:28:25,630 So, we actually used that before 496 00:28:25,630 --> 00:28:27,563 in our authController, I believe. 497 00:28:29,480 --> 00:28:32,567 Very quickly, let me show that to you. 498 00:28:33,530 --> 00:28:37,060 Right here, we said response.local.user 499 00:28:37,060 --> 00:28:39,074 and put the current user there. 500 00:28:39,074 --> 00:28:41,720 Then automatically in all templates, 501 00:28:41,720 --> 00:28:44,283 we have access to that user variable. 502 00:28:47,430 --> 00:28:50,070 So, let's now implement what I just said 503 00:28:50,070 --> 00:28:52,597 and starting with the URL. 504 00:28:54,330 --> 00:28:57,540 What I'm gonna do here is to actually add that query string 505 00:28:57,540 --> 00:28:59,097 here to the success URL. 506 00:28:59,970 --> 00:29:04,573 Here, I will say alert equal booking. 507 00:29:05,970 --> 00:29:10,310 Now I could, in all other URLS, also add some alert 508 00:29:10,310 --> 00:29:12,863 and then with a different keyword here, of course. 509 00:29:14,350 --> 00:29:18,100 And we will just do it here really for this booking. 510 00:29:18,100 --> 00:29:21,793 But again I created a kind of reusable solution here. 511 00:29:23,340 --> 00:29:27,470 Anyway, now in our routes, we need basically a middleware, 512 00:29:27,470 --> 00:29:29,920 which will run for all the requests. 513 00:29:29,920 --> 00:29:32,270 And it's that middleware, which will pick up the alert 514 00:29:32,270 --> 00:29:35,240 from the query string and put a alert message 515 00:29:35,240 --> 00:29:37,453 onto our response.locals. 516 00:29:41,457 --> 00:29:42,624 So, router.use 517 00:29:45,040 --> 00:29:48,233 viewsController.alerts. 518 00:29:50,290 --> 00:29:52,320 And so, this is a middleware function, 519 00:29:52,320 --> 00:29:56,200 which will basically run for each and every single request 520 00:29:56,200 --> 00:29:58,130 that's coming into this router, 521 00:29:58,130 --> 00:30:01,063 so basically for all the requests to our website. 522 00:30:02,370 --> 00:30:04,870 Now let's actually create that middleware 523 00:30:04,870 --> 00:30:06,020 in our viewsController. 524 00:30:10,460 --> 00:30:12,380 So, exports.alerts 525 00:30:14,480 --> 00:30:17,283 request, response, and next. 526 00:30:19,650 --> 00:30:20,730 And so, the alert 527 00:30:22,760 --> 00:30:26,300 is request.query.alert. 528 00:30:26,300 --> 00:30:29,873 And so, let's just use this structuring here once more. 529 00:30:32,020 --> 00:30:36,553 And then let's say if alert is equals to booking, 530 00:30:39,030 --> 00:30:42,653 so the alert that we put right here in the query string, 531 00:30:44,670 --> 00:30:46,070 well, then in that case, 532 00:30:46,070 --> 00:30:50,970 let's say response.locals.alert 533 00:30:52,830 --> 00:30:53,780 will be 534 00:30:56,910 --> 00:30:57,970 your booking 535 00:30:59,850 --> 00:31:01,023 was successful, 536 00:31:03,790 --> 00:31:06,883 please check your email for a confirmation. 537 00:31:10,330 --> 00:31:13,090 And we should also add some other phrase, 538 00:31:13,090 --> 00:31:17,960 which is this one, if your booking doesn't, 539 00:31:24,070 --> 00:31:27,743 select this, doesn't show up here immediately, 540 00:31:33,270 --> 00:31:34,523 please come back later. 541 00:31:36,140 --> 00:31:37,230 And this last part 542 00:31:37,230 --> 00:31:39,920 is because Stripe does very specifically say 543 00:31:39,920 --> 00:31:43,620 in their documentation that sometimes the webhook is called 544 00:31:43,620 --> 00:31:46,880 a little bit after the success URL is called. 545 00:31:46,880 --> 00:31:49,810 In that case, that success URL would then show 546 00:31:49,810 --> 00:31:52,677 all of the current tours, but only after that, 547 00:31:52,677 --> 00:31:54,300 the webhook would be called 548 00:31:54,300 --> 00:31:57,270 and the tour would be created in our database. 549 00:31:57,270 --> 00:32:00,040 Therefore, the new booking would not show up right away 550 00:32:00,040 --> 00:32:01,953 on the My Bookings page. 551 00:32:02,850 --> 00:32:06,220 But of course, everything still worked well in that case. 552 00:32:06,220 --> 00:32:09,583 And so, I simply reload, but later we'll fix that problem. 553 00:32:12,340 --> 00:32:15,080 Now we just need to call the next middleware. 554 00:32:15,080 --> 00:32:17,160 And that's actually it. 555 00:32:17,160 --> 00:32:21,390 Again, we only did this here for alert equal to booking, 556 00:32:21,390 --> 00:32:24,090 but we could now use this all over the place 557 00:32:24,090 --> 00:32:27,070 in our website by setting different alert keywords 558 00:32:27,070 --> 00:32:28,982 and query strings. 559 00:32:28,982 --> 00:32:33,982 With this, we put this message here onto res.locals.alert. 560 00:32:35,600 --> 00:32:38,940 Again, our base template will then pick that up 561 00:32:38,940 --> 00:32:42,320 and display it here into this data alert property. 562 00:32:42,320 --> 00:32:46,440 And so, all that is left to do now is to go to our index.js 563 00:32:46,440 --> 00:32:49,890 and read the alert from here and then display it. 564 00:32:49,890 --> 00:32:52,100 And so, that should be fairly easy. 565 00:32:52,100 --> 00:32:56,230 Here in public, let's actually do it right in the index. 566 00:32:56,230 --> 00:33:00,260 And the first thing is that we actually need to import 567 00:33:00,260 --> 00:33:01,343 the alerts function. 568 00:33:06,480 --> 00:33:08,160 That's not an app. 569 00:33:08,160 --> 00:33:09,343 It's here in index. 570 00:33:10,920 --> 00:33:12,090 Okay. 571 00:33:12,090 --> 00:33:15,883 And then down here, let's basically read that alert. 572 00:33:17,290 --> 00:33:22,133 So, const alertMessage, let's say, 573 00:33:23,250 --> 00:33:25,320 is document.querySelector, 574 00:33:28,742 --> 00:33:31,327 then the body element, dot dataset.alert. 575 00:33:35,350 --> 00:33:37,673 And so, only if there is an alert, of course, 576 00:33:39,760 --> 00:33:42,020 then show the alert 577 00:33:43,160 --> 00:33:44,250 with success 578 00:33:45,840 --> 00:33:48,000 and the alert message. 579 00:33:48,000 --> 00:33:50,640 And now this one small thing that I want to do 580 00:33:50,640 --> 00:33:54,630 is to change a little bit this showAlert function here 581 00:33:54,630 --> 00:33:57,210 because we actually have a lot of text now. 582 00:33:57,210 --> 00:33:59,780 And the standard time that the alert is shown 583 00:33:59,780 --> 00:34:03,163 would not be enough to actually read all the text. 584 00:34:04,210 --> 00:34:06,880 So, you see here that after five seconds, 585 00:34:06,880 --> 00:34:08,373 the alert is hidden. 586 00:34:10,126 --> 00:34:11,760 Let's actually allow the user to specify 587 00:34:11,760 --> 00:34:14,253 the amount of seconds that the alert is shown. 588 00:34:16,810 --> 00:34:20,320 We will do that as a default of five seconds. 589 00:34:20,320 --> 00:34:24,810 Here, we then simply do time times 1,000 590 00:34:24,810 --> 00:34:26,483 to convert it to milliseconds. 591 00:34:27,976 --> 00:34:30,690 Like this, all the functions will work everywhere 592 00:34:30,690 --> 00:34:32,270 with five seconds. 593 00:34:32,270 --> 00:34:34,790 Let's actually make it seven seconds 594 00:34:34,790 --> 00:34:36,600 if we don't specify anything. 595 00:34:36,600 --> 00:34:39,980 But if we want, we can then override this seven. 596 00:34:39,980 --> 00:34:42,040 And so, I will that now here 597 00:34:42,040 --> 00:34:45,370 and actually put it 20 seconds on the screen. 598 00:34:45,370 --> 00:34:46,203 All right. 599 00:34:47,360 --> 00:34:49,240 I think that should be it. 600 00:34:49,240 --> 00:34:51,060 I hope that made sense. 601 00:34:51,060 --> 00:34:53,993 Let's now just very quickly compile our bundle. 602 00:34:55,360 --> 00:35:00,343 That's npm run build, then tap autocomplete. 603 00:35:03,480 --> 00:35:05,990 That takes a little bit of time as well. 604 00:35:05,990 --> 00:35:07,373 But now it's done. 605 00:35:12,030 --> 00:35:14,340 Let's now deploy it one last time 606 00:35:15,580 --> 00:35:17,083 hoping that it works actually. 607 00:35:18,250 --> 00:35:19,083 So, git commit. 608 00:35:25,840 --> 00:35:27,513 So, Stripe messages. 609 00:35:29,670 --> 00:35:34,670 And one last time, git push heroku master. 610 00:35:37,451 --> 00:35:41,403 Let's now test it by buying yet another tour here. 611 00:35:42,830 --> 00:35:44,963 Let's get the City Wanderer this time. 612 00:35:46,490 --> 00:35:49,683 Oh I just see that there is a message already here. 613 00:35:50,810 --> 00:35:51,783 That's not good. 614 00:35:54,530 --> 00:35:58,500 And you see that it disappeared after 20 seconds. 615 00:35:58,500 --> 00:36:00,240 So, it seems like now, by default, 616 00:36:00,240 --> 00:36:02,993 it will always put this alert class here. 617 00:36:06,028 --> 00:36:06,861 (laughs) 618 00:36:06,861 --> 00:36:07,694 Yeah. 619 00:36:07,694 --> 00:36:09,990 That's because here it should be alertMessage 620 00:36:09,990 --> 00:36:11,063 and not just alert. 621 00:36:12,810 --> 00:36:16,800 But anyway, let's now just test 622 00:36:16,800 --> 00:36:20,433 if the message actually is correct when we book the tour. 623 00:36:24,410 --> 00:36:25,243 Okay. 624 00:36:32,470 --> 00:36:34,880 Now let's wait for it. 625 00:36:34,880 --> 00:36:36,330 Here we go. 626 00:36:36,330 --> 00:36:39,163 Indeed, there is our message. 627 00:36:40,130 --> 00:36:41,460 So, beautiful. 628 00:36:41,460 --> 00:36:44,420 Also, our tour shows up here. 629 00:36:44,420 --> 00:36:48,510 And you see that it really stays here for a lot of time. 630 00:36:48,510 --> 00:36:49,853 So, that also works. 631 00:36:51,532 --> 00:36:52,832 Let's just very quickly... 632 00:36:55,840 --> 00:36:59,383 And first, we actually need to rebuild the bundle here. 633 00:37:03,877 --> 00:37:07,170 Then we can add everything to our staging area, 634 00:37:13,580 --> 00:37:18,490 Message alert bug fix. 635 00:37:18,490 --> 00:37:19,670 So, these are some (laughs) 636 00:37:19,670 --> 00:37:23,500 really professional-sounding messages here already. 637 00:37:23,500 --> 00:37:26,313 Now one final push to Heroku. 638 00:37:29,670 --> 00:37:32,580 Now when we load our page, 639 00:37:32,580 --> 00:37:34,740 we should see no alert message. 640 00:37:34,740 --> 00:37:37,250 And indeed, now everything is clean. 641 00:37:37,250 --> 00:37:40,470 And so, I can now say that at least for now, 642 00:37:40,470 --> 00:37:42,977 this project is really finished. 643 00:37:42,977 --> 00:37:46,490 Once more, great job, congratulations, 644 00:37:46,490 --> 00:37:51,100 and well done for probably being one of the few people 645 00:37:51,100 --> 00:37:54,350 who actually are making it all the way to the end 646 00:37:54,350 --> 00:37:58,370 of the project and really building this beautiful website 647 00:37:58,370 --> 00:38:01,780 and also API that you can now put on your portfolio 648 00:38:01,780 --> 00:38:02,923 and show the world.