تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Writing webhook feature tests0:00

Now that we have already made sure that we can pick one of our courses and trigger this checkout overlay by Paddle, it's a good idea to start with the backend side of things. So after purchasing a course through this checkout, we're going to get a request by Paddle itself, which we want to store and handle. So let's see how we can do this. Back inside our application, let's create now a new feature test, WebhookPaddlePurchaseTest. All right, so what are the things that we want to test here now? First, I want to make sure that all the requests are stored. So it stores a PaddlePurchaseRequest, but I also want to make sure that not all requests

First, I want to make sure that all the requests are stored. So it stores a PaddlePurchaseRequest, but I also want to make sure that not all requests are stored, because PaddleRequest comes with a specific signature, so we want to make sure that we only store valid requests. So let's add another test. It does not store invalid PaddlePurchaseRequest. All right, so these are the two main things that I want to start with. Again, we haven't implemented anything yet, we're just thinking about how this could work. So here, I want to make sure that after a valid purchase request by Paddle, that we have a new request being stored, a new Webhook request being stored inside our database.

So here, I want to make sure that after a valid purchase request by Paddle, that we have a new request being stored, a new Webhook request being stored inside our database. So this means at the beginning, I want to make sure that this table is empty, assertDatabaseCount, we want to deal with a Webhook model, which we don't have yet, and we want to make sure it's zero. I don't think there's an empty method, assertDatabaseEmpty, nope, maybe I'm going to add a pull request. All right, Webhook call, I think it will be called, should be empty. Then we're going to act, this time we're actually needing act and assert separated, and we're going to act by making a POST request.

Then we're going to act, this time we're actually needing act and assert separated, and we're going to act by making a POST request. And we're making the POST request to a Webhooks endpoint, which of course we don't have yet, and then we want to make sure that if we add some specific data, that this is being sent to the Webhooks endpoint. And before I add the data here, let's finish the test here, and then we want to make sure that we now have one entry inside our WebhookCall table. All right, so what are we going to send to our Webhooks endpoint? So this is what we're going to get sent by Paddle itself. And inside the Paddle account, let me show this to you, there is the events page under

Inspecting Paddle webhook payload2:34

So this is what we're going to get sent by Paddle itself. And inside the Paddle account, let me show this to you, there is the events page under developer tools, and here you can simulate a Webhook. And here you can just define what you want to send, then you can define the URL where you want to send it, and then the data. So if we're working locally, you need to create some kind of tunnel to send this to your local environment. And this is what I have already done. And this is very important because all the data that you define here, then also a specific signature is being added to the request.

And this is very important because all the data that you define here, then also a specific signature is being added to the request. And this looks like that. We now have a lot of data which we get from Paddle itself, like earnings, order ID, Paddle fee, the price, the product ID, which is very important, and much more. And very important, here at the end, we're going to get the signature. This is now a signature being created from all the data being sent here. So this way we can make sure that the data we get here is being valid by later checking it against the signature. Okay, this is the data which we sent here.

Installing webhook client package3:40

it against the signature. Okay, this is the data which we sent here. Let's call now test, let's run it, and let's see, general error, no such table called webhook call. Yes, that's true, we haven't created yet. And I'm also not going to create it myself, because again, there is a nice package by Sparcy already out there to do that for us. And this one is called, and it's called laravel-webhook-client to receive webhooks inside your Laravel application. Let's start by installing it.

your level application. Let's start by installing it. Here we go. What else do we need? This time we need to publish the config file. Let's place it in here. And now this file has been copied to the config/webhook-client.php file. And let's see if there's anything else. Let's go through it here together, make this a little bit bigger. So we have here a few things that we can change, like there is a specific class which is going

Let's go through it here together, make this a little bit bigger. So we have here a few things that we can change, like there is a specific class which is going to validate the request. There is a specific response, and there's also a specific model, and this is the model which comes from this package, and is also what we're going to use ourselves. So let's try to import this now. Yes, phpStorm does find it now. And let's try it again. And it's still failing. No such table webhook_calls.

And it's still failing. No such table webhook_calls. And that's also because we haven't run any migrations yet. Let's see, this should be somewhere in here. Yes. We're going to need to copy this here to create a new migration. And let's see. Well, this is now about failing. Asserting table webhook_calls matches entries count of one because zero found. Yeah.

Adding webhooks endpoint5:35

Asserting table webhook calls matches entries count of one because zero found. Yeah. Okay. This is what we have already expected because we haven't done anything yet. We also need now this webhooks route, which we have tried to access inside our test. And now with this package comes a new method called webhooks. And I'm going to define here a name, an endpoint, and I just leave it with webhooks for now. But also what we have to do is verify the token we need now to add an exception for our new webhooks endpoint. Because here we have a different way of checking this request.

our new webhooks endpoint. Because here we have a different way of checking this request. All right. Inside our test, let's see, is there anything changing? No, I don't think so. No, the empty table is what we still have. And let's check the config again. It's here and here we have a validator, which is being used by default. And it's checking for a specific signature inside the request. And if it's not given, it's going to return false.

Creating signature validator6:39

And it's checking for a specific signature inside the request. And if it's not given, it's going to return false. And I think this is where we're currently going to end up here. So let's create now our new custom webhook validator. For now, I'm just creating here under app, a new class, and we're going to call it PedalSignatureValidator. Like this. And then inside the config, we can now define that we want to use our own validator. Like this. And if we create a validator, we also want to make sure that we think we're going to

Like this. And if we create a validator, we also want to make sure that we think we're going to need to implement a specific interface. Let's see. Create your own signature validator. And we're going to need to extend the SignatureValidatorInterface and then an isValid method. And then we need to add the isValid method. And here for now, we're going to return true. All right. So this means now when a request comes in to the specific endpoint webhooks, this validator

All right. So this means now when a request comes in to the specific endpoint webhooks, this validator is going to check it because we have now defined it inside our config file. And I think this also means now that this test should not work for the first time. And it does not. All right. Let's see what else we have to do. And by the way, did I just say false? No, I said it's true. Is valid true.

No, I said it's true. Is valid true. Okay. Let's also make sure if we get even in here some good and old dump and die approach here to make sure what is going to happen. And it seems we're not getting in here, but also always when the test is not working as expected, it's a good idea to try to end disabling. So without exception handling, because maybe there is already an error, which we don't see yet. And yeah, it seems we have a different error saying that we have an invalid config because

see yet. And yeah, it seems we have a different error saying that we have an invalid config because nothing or empty string is not a valid processWebhookJob class. Because a valid class should implement the ProcessWebhookJob. Yeah. So the way that this package works is if we're going to validate a specific request. So if this returns true, then another job is going to be triggered that runs some specific tasks. So let's take a look at our config again. And down here we see processWebhookJob.

Adding webhook processing job9:14

So let's take a look at our config again. And down here we see ProcessWebhookJob. So we need to provide a job that is going then to handle the request. So let's just create a new job and we're going to call it HandlePedalPurchaseJob. Yes. And I think that's not it. We also have to, yeah, it's a job that needs to extend the ProcessWebhookJob class. So let's do this as well here. And we need to put this in here first. And we haven't found it yet here.

And we need to put this in here first. And we haven't found it yet here. It's this one. I think we don't need the constructor here anymore. All right. So now that we have this class, we need to provide it to the config. And we'll pedal purchase job. And I think we're going to provide the class here. And I now believe that this test should work because I think we should now store the request and we do.

And I now believe that this test should work because I think we should now store the request and we do. It doesn't matter that we now have a job, which is now going to handle the request, which is being empty because at this point, we were only interested in that we're going to store the request, which is always a good idea. If you receive some requests from a payment service provider so that you can always check what you got into application, you don't have to lock it and then search for your lock files. All right. This also means we can get rid of this helper here. And now this test is working.

This also means we can get rid of this helper here. And now this test is working. Perfect. What about the next one? Let's copy the whole content of the first test and let's bring this in here. And now instead of providing here a valid request, I want to provide an invalid array of data. So here we're providing just nothing. And this now should still be zero. And now what do you think will happen now?

And this now should still be zero. And now what do you think will happen now? So this should fail because it was still going to store this request because we, inside our validator, we're just returning true every time. Yes. So it fails. Matches expected entries of zero because we found one. All right. So this means now we have to make sure that our validator is really working. So how are we going to check now that a request really comes from Pell itself?

Implementing Paddle signature verification11:36

So this means now we have to make sure that our validator is really working. So how are we going to check now that a request really comes from Pell itself? And of course they have already documented this for themselves, how we can verify webhooks. And you'll find quite some information about how to do this, what is needed, and also code examples here. And here's the one for php. It's not the most prettiest one, but it will work. And let me bring this in here. I have already a little bit prepared this one and cleaned this up just a bit. So let's take a look how we're going to do this.

I have already a little bit prepared this one and cleaned this up just a bit. So let's take a look how we're going to do this. First, we're going to need the public pedal key, which here I'm going to call through my config. So this means you probably need to add this there as well. Then we're going to decode the signature from the request. We have already seen this. It's quite a long string. Here we have it just so that we can make sure that nobody changed the data which we received from the request.

Here we have it just so that we can make sure that nobody changed the data which we received from the request. Then here we're going to get all the request fields here. Then we're going to unset the one for the signature. And then we're looping over them. And this is how at the end we're going to verify that the signature is true by running the openssl_verify method with the data, the signature, the key, and which algorithm we're going to use. So let's start first by adding the public key to our config on the services, public key.

So let's start first by adding the publicKey to our config on the services, publicKey. And again, we're going to load it from the .env file and we're going to call it pedalPublicKey. All right. And this we're also going to need now here. Going to add it to the .env file. And again, as always, we're going to add it to the example as well, but we also know in a brand new application without an .env file that we need to have this there. And then in order to get the publicKey, you need to go to pedal to your settings and

a brand new application without an .env file that we need to have this there. And then in order to get the public key, you need to go to pedal to your settings and there you will find this. So I've just added my public key to my .env file. I don't think you need to see it yourself, but you know that you can find it inside your pedal settings. All right. So now we should get something back here when we call this. And then what else do we get here? We still have a request here.

And then what else do we get here? We still have a request here. So I think this should work as well. So yeah, let's give this a try. And the first way we can try this is inside our test here where we make sure that it does not store invalid pedalPurchase requests. So this is now working and this is now more easy part because if anything goes wrong with checking the signature, it's going to fail. More interesting is if this one is still working and it does not. All right.

More interesting is if this one is still working and it does not. All right. Let's see what we still need to do here. All right. I just checked my .env file. I think it was just about formatting the public key, which is quite a long string. So let's try this one again. And yes, now it passes. And this also means our two new tests are also parsing and we now we are able to validate a request coming from pedal.

And this also means our two new tests are also parsing and we now we are able to validate a request coming from pedal.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟