Would you like to inspect the original subtitles? These are the user uploaded subtitles that are being translated:
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,742
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,126
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,099
when we first created this handler function,
296
00:17:17,099 --> 00:17:20,040
I specified this client_reference_id field
297
00:17:20,040 --> 00:17:22,369
and added the tourId to that.
298
00:17:22,369 --> 00:17:23,839
Remember that?
299
00:17:23,839 --> 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,239
I think that should be it.
600
00:34:49,239 --> 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.
49757
Can't find what you're looking for?
Get subtitles in any language from opensubtitles.com, and translate them here.