Reducing Coupling in PurchaseItems0:00
Welcome back to another lesson. What we currently have on the PurchaseItems class is pretty good. It's much better than what we started with. However, we are still touching quite a lot of modules. Let's see what we can do to make this a little bit more loosely coupled and talk to those other modules in a more stable way. Well, the first thing that I can see is that we have two payment-related things right here. We have the actual paymentCharge, but we also have the Payment model itself, which relies on the paymentCharge. I think we can group that together in the Payment module.
Creating Payment Action Command0:31
which relies on the payment charge. I think we can group that together in the payment module. So just like we created a command for the order module, let's go to the payment module. We're also going to create an actions directory, which, again, is a self-handling command. And we're going to create a command. Let's call it createPaymentForOrder. All right. We're going to create a handle method. And let's think, what do we need here?
We're going to create a handle method. And let's think, what do we need here? First, we need the orderId. That's obvious. We also need a paymentGateway. And for now, we're going to expect PayBuddy here. So let's say that we expect a PayBuddy instance. Well, we also need a userId. So let's add this userId right here. And we also need an orderTotal in cents, or rather paymentTotal. Just call it totalInCents.
And we also need an orderTotal in cents, or rather paymentTotal. Just call it totalInCents. And the reason we're not passing the cartItemCollection here is because this piece of code specifically is not really interested in the items. It isn't going to do anything with that. It is interested in the total of how much it has to charge a given cart. Oh, we also need a paymentToken. So let's add that. Let me format this. Okay. Let's take a look at what we have here.
Let me format this. Okay. Let's take a look at what we have here. The first thing I'm going to do is to inject this on the constructor. And again, use Laravel's container to inject the dependency for us. And I just noticed I had a typo here. So let's rename this. CreatePaymentForOrder. Not for order. CreatePaymentForOrder. There we go.
Create Payment for Order. There we go. So to create a Payment, we need to first have an Order. That means that we'll have to move this charge piece a little down further. Let's commence by getting rid of this from this class and putting this here. The payment provider is going to be passed as an argument, and for now it's PayBuddy. And the order total in cents is just called totalInCents here. Let's import this Exception and also import this Exception. Okay. I noticed that we're still passing the paymentId here,
Let's import this Exception and also import this Exception. Okay. I noticed that we're still passing the paymentId here, and we don't need this anymore, so we can get rid of this. We now have that on the Payment model. Let's see what else we have related to payments. And we have the actual payment creation. So let's also remove this from here and put it on this class. Obviously, we don't have an Order object, so we'll have to create a Payment object by itself. Let's import this class and call the create method.
so we'll have to create a Payment object by itself. Let's import this class and call the create method. This is called total in cents. We have the charge. We have the userId. We also have to pass the orderId, which we also have as an argument to this create method. And we can just return this. Let me add the create method here. Like I said, I like to see it. Now we can add a return type. We're going to return a Payment for now.
Now we can add a return type. We're going to return a payment for now. All right. Let's go back here. Let's rerun our tasks. They should fail. Okay. We have two tasks failing. Attempting to read status unknown on line 56. So let's see what that is. 56. It is the payment assertions.
56. It is the payment assertions. Obviously, those are going to fail. And then the second failure is that we got a 201 on this other task. And that's obvious because we're not actually doing anything payment-related, nor are we trying to charge the card, so there isn't something to fail. All right. Let's reimplement this. Let's go back to purchaseItems. Since we have the order at this point,
Wiring Payment Into Flow4:02
Let's go back to purchase items. Since we have the order at this point, we can now call our new dependency, createPaymentForOrder, and let's pass what we need. So we have the order and its ID. We have the userID. We have the orderTotal in cents. We have the paymentProvider, and we have the paymentToken. There we go. So now we have an action calling a non-action.
There we go. So now we have an action calling a non-action. That's really cool. You can compose actions. Let's rerun our tasks. Okay. They're passing. Even though they're passing, the second task is wrong. And the reason is, remember, we were previously creating, or rather charging the card at the beginning of the flow,
Finding Late-Failure Issue4:40
we were previously creating, or rather charging the card at the beginning of the flow, which means that if the token was invalid, it was going to fail very early. Now that we moved this down below, it is going to fail very late on the flow, which means that we are creating an Order, we are creating the OrderLines, and we're not creating the payment because it's failing. So if we go back to this task,
and we're not creating the payment because it's failing. So if we go back to this task, and we add a couple more assertions, for example, we can safely expect the User not to have any Order. Let's say that we want to assert that the number of Order, or rather orders, the User has is zero. Or even better, we can just assert that the system doesn't have any Order,
Or even better, we can just assert that the system doesn't have any Order, like this. Let's rerun this and see what happens. Okay. Now we have a failing task. Failed asserting that one matches expected zero. That's because we are creating the Order. At this point, we can use a transaction to ensure this all happens during an atomic operation.
Wrapping Flow in Transaction5:38
we can use a transaction to ensure this all happens during an atomic operation, and that if the payment happens to fail, the entire operation is going to be reverted. So on our PurchaseItems class, there are two things you can do here. You can either use the DB facade, or you can inject the database manager. And that's what I'm going to do. DatabaseManager,
And that's what I'm going to do. Database manager, and then we can call this within a transaction. Let's pass a closure. Let's copy all of this. There we go. Let's format this. Now the scenario we'll have to add those variables to the closure's use statement. So let's import all of that.
to the closure's use statement. So let's import all of that. I don't like this, but it's what we have. And now if we run the task, let's try it. Now we have two tasks failing, which is even worse. We're calling URL on no, and that's because we have a closure,
We're calling URL on no, and that's because we have a closure, and we have to return order, and we're not doing that. So let's return order right here. Finally, we also have to import this, and import this. Okay, we should have everything now. Let's rerun this. And now we have both tasks passing.
Let's rerun this. And now we have both tasks passing. So if something in this process happens to fail, and it's usually going to be the payment, maybe it could also be maybe inserting a record into the database, or maybe decriminating the stock of a product. Those could fail. And if those fail for some reason, the entire operation is going to be reverted.
And if those fail for some reason, the entire operation is going to be reverted. We can get rid of this variable as well, and phpStorm will do it automatically for us. Let's rerun our tasks, and they're both passing. So now we can ensure that if something goes wrong during this operation, it will be reverted, and the user will not have an order.
it will be reverted, and the user will not have an order without paying for it. That's it for this lesson. See you on the next one.
