Okay.
Our next task is to create a controller that's going to contain an endpoint so that our clients can
effectively create a payment intent, and that will be returned with the basket to the client.
So inside the controllers folder let's create a new file.
It's going to be a class and we'll call it payments controller.
And inside here we'll derive from the base API controller.
And we're going to inject two things in here.
We're going to have our payment service and call it payment service and our store context and call it
context.
And inside here we'll have an HTTP post for our endpoint.
And we'll make it public async task of action result.
And we're going to return a basket DTO from this and we'll call it create or updates payment intent
as the name of the method.
And because we're returning a basket DTO, let's go and check that and make sure that we add the new
properties that we have here.
So we're going to have a prop that's a string that's a client secrets.
And we'll make that optional because of course we don't have it when they first start adding things
to their baskets.
And we'll also have the string for the payment intent ID as well.
And I'm just going to go to the basket and make sure that is what I've called both of those properties.
And that is indeed the case.
Okay.
So back to our payments controller.
And first of all we'll get the basket.
So we'll say var basket equals await and context dot baskets.
And we need to get the basket with the items.
We're going to need something similar to what we've used before.
And if we go to our basket controller, let's take a look at what we did to go and get the basket.
We've got this retrieve basket method.
And if we go and take a look at this, do we have an opportunity to make this reusable.
So it's not just usable inside our basket controller, but we can use it elsewhere as well.
Of course we do.
And do we have an extension method for anything related to our basket?
At the moment we do.
We've got a basket extensions class.
So let's make use of this.
I'll go to our basket controller and let's copy this code where we're returning this.
And we could create an extension method on the iqueryable of basket for instance, that includes this
code to make it easier for us to go and get the basket.
So I'll go to the basket.
I'll just copy this into my clipboard and go to the basket extensions.
And let's create below this extension method.
Let's create another extension method and we'll use public static async task that returns a basket.
And we'll call it get basket with items.
And we'll use this extension on the item variable of basket.
So we'll use item variable of type basket and specify query.
And we'll also pass in the string and make this string optional for the basket ID and open curly brackets
in there.
Let's just bring this down because I've gone off the edge of the screen there.
And inside here I'll just paste in what I have inside my clipboard.
But instead of awaiting context dot baskets, we'll just use return await query.
And then let's use the quick fix on the include so that we can bring in using Microsoft Entity Framework
Core.
And instead of the request cookies basket ID, we'll just use the basket ID that we're passing in as
a parameter.
Now we're getting a warning here because we've said that we are returning a basket, not basket or null.
We're not making this optional.
We only want this query to return the basket.
We don't want it to return anything else.
So we will to the right of the closing parenthesis here we'll add double question mark.
So if this query is going to return null then we'll execute what's to the right of the double question
mark.
And in this case we're just throw a new exception and just say cannot get baskets as the message inside
here.
Now exceptions continue to be exceptional.
So this is fine to use inside here as it is.
And it guarantees in our code that when we make this request, we ensure that if that request is successful,
then we definitely are working with a basket.
So we don't need to be defensive in our code where we're using it.
So let's go back to our payments controller, and now we have access to our new get basket with items
method.
And inside here we can pass in the request dot cookies and the basket ID.
And if we hover over the baskets then technically it still could be null.
Okay, so we will be defensive.
My prediction that we didn't need to be defensive was a bit short lived, because with this method,
it's still suggesting that it could be undefined.
And that's because of the string I think here.
Regardless, we'll just be defensive here.
We'll check to see if the basket is equal to null, and if it is, then we'll just return a bad request
and say problem with the basket.
I'll be vague inside there, and I wouldn't expect to see that anyway based on how this endpoint is
going to be used.
But for our compiler, we need to be defensive so that we don't get these nasty warnings inside our
code.
So next we're going to create our new intent.
So we'll save our intent equals await.
And we'll use our payment service.
And we'll use the create or update payment intent.
And we'll pass through our basket to that method.
We'll check to see if we have the intent.
Because if we hover over the intent then that will also potentially be null.
We can see that question mark there.
So again we'll check to see if the intent is null here.
And if it is we're going to return a bad request and just say problem creating payment intent.
And then we can update our basket with the new information.
So the basket payment intent ID.
And because we're using this for create or update then this might not be null.
So we can use another operator.
Here we'll use a double question mark which represents null.
If it is null, then we're going to use equals after this, which is going to assign it to, in this
case the intents id.
And then we'll do the same for the basket clientsecret.
Once again we'll check to see if it's null.
If it is, then we're going to assign this to the intents dot clientsecret that we get from stripe.
Following this we can then specify var results equals await and use our context and save changes async
because we're updating our basket.
And we'll check to see if the number of changes is greater than zero.
If the not results then that means there was nothing saved into our database.
So we'll again we'll return a bad request at this point and say in the message problem updating baskets
with intents.
And these error messages are really more for us as it's going to tell us which stage it failed if it
did fail at all.
And then we can return the basket.
And we've got a two DTO method we can use here to return our basket as well, so we can take advantage
of that.
Now, I didn't update the basket controller with our extension method.
I'll leave that to you if you wish to do that, because we don't need this retrieve basket method in
here.
Now we can use the extension method.
But to save a bit of time, I'm not going to demonstrate that.
I'll just leave that up to you.
If you do want to update your basket controller to use that as well, it should work just fine.
And one more thing I want to check before we test this functionality back in our payments controller.
Did we make this authorised?
We did not.
So I'm just going to make this an authorised endpoint.
So the user would need to be authenticated before they can create this payment intent.
But where we're going to use this is inside our checkout.
So we're not letting the user get to the checkout before they're authenticated.
So we'll just make this endpoint authenticated as well.
So now we're in a position where we can go and test this.
So I'll just go back to the terminal and I'll just execute Netwatch to restart the API.
Because I did stop it when I created a new migration.
So we'll make sure this starts correctly.
And that looks good.
So we'll head back to postman to test this specific functionality.
So let's just double check.
Actually if I just click on the login and click send and log in as Bob at test.com, click send and
just double check the cookies and see if we've got a basket cookie inside here I do not.
I only have the authentication cookie.
In fact, I do have a basket cookie.
Where did that come from?
No idea.
But I'm going to delete this basket ID because I want to make sure I'm creating a clean basket, in
this case because I have dropped my database previously, I've got no idea if that's a valid basket.
And I'm going to click on Get Basket to see what I get back from the API In this next request, I'll
click send and I get the 204 no content, which effectively means I do not have a basket.
So I'll add an item to the basket which will have the effect of creating a basket in our API.
So I'll click this one.
Just adding a product with the id of two and a quantity of one.
I'll click send and this gets created.
I now have a basket with a basket ID and I should have the cookie for this.
Although postman being weird seems to need me to click that button twice so that I can see the basket
ID which should match what I got returned in that request.
So starts 070, and if I take a look at this then that looks great.
Okay.
And if I get the basket and click send, that should confirm that everything is working as it should.
So now we get to take a look at creating our payment ID.
Now this part is going to go out to stripe.
And we don't send anything in the body of this request.
I don't have anything there.
And we are going to send up with the authorization.
I'll just use inherit for parent.
It should send up the cookie with the request anyway because I have it inside postman.
And we'll click send on this.
So let's click send.
And it looks like we've got a 200 okay.
And annoyingly we've got no client secret or payment intent ID inside here.
I've just remembered what I didn't do, which is update our extension method in the basket extensions.
We didn't update this to also return the client secrets and the payment intent ID from our basket,
which we should have when we execute this method.
So just inside here I'm going to specify the client secrets and say basket dot client secret and also
the payment intent ID and say basket dot payment intent ID.
Now can we update our payment intent ID And even though we didn't have this information inside our baskets.
Well, if we think about how we're getting our basket on the back end, all we need is our basket ID
and it's in our basket in the database that should have this populated.
And if we just double check that theory, we'll open up the solution explorer.
Let's just click on store DB and see what we have inside our basket.
And let's click on this.
And this is the basket we're working with.
We start 070.
And I can see that I've got the client secret and the payment intent ID populated inside there.
What that means is I should be able to update my basket.
And one thing before we do do that, let's go and take a look at the stripe dashboard inside the transactions.
What we should see is that we do have a payment intent created $150 with the payment intent ID, and
that's the one that would have just been created there.
So our first transaction is incomplete because it hasn't been paid for yet.
But this is a payment intent that's now recorded inside stripe.
So what we should be able to do is update our basket and update our payment intent based on the fact
that we have this payment intent ID in our basket as well.
So let's go take a look and I'll add another item to the basket.
In fact I'll just increase the quantity.
So I'll send this request again.
Click send.
Now we've got two quantity of this product which should meet or should have a price now of $300.
So if I create a payment intent and click send then I've got a problem updating basket with intent.
Okay I think this is about the point where I'm prepared to say, maybe it's time we took a look in the
debugger and see what's going on as we go throughout the process of creating this intent, and it will
help us solve this problem if there is one.
And it's not just related to hot reload, but whatever, we'll take a look through the debugger in the
next lesson at what's going on here.