How to modularize my payment platform for selling courses/books with Stripe/Paypal on web and mobile

I'll tell you how I managed to modularize my payment platform in Laravel to make it as easily scalable as possible.

I wanted to show you the structure that I have implemented here to handle payments, which in this case, at least to date, I have PayPal or I have Stripe, both to buy a course or to buy what you are seeing here or to buy a book. Note that it is exactly the same plugin as you can see, so I consider this important since I think this can be one of the most complicated things that can be implemented in our web application and above all that it can easily get out of control since the reality is always to create implementations that are easily scalable or at least that are as easy to scale as possible and that are quite understandable and that when we want to change them they are not so prone to errors, but in this case for payments, as I tell you, it is always a bit complicated, especially when we go to production and we have to work with real money. So things can get quite complicated, so again that is why it is important to always create a fairly modular structure that can be easily scaled, just as I did in this case when I implemented the PayPal one, then I decided to add the Stripe one.

Two main methods to modularize API requests

So the whole end is based on the same core, since in a payment we have to process two things. On the one hand, do the whole process that the platform, for example PayPal or Stripe, asks us here, which we have to implement at the level of our application in stores: install the plugin, create the object, call some method in the Stripe API, PayPal, etc., etc., etc., and on the other hand, once we have obtained the satisfactory response from this API, we do the process ourselves. Therefore, on the one hand, we have a method that is independent of the structure or the payment method, and on the other hand, we have a common method. So from the beginning, we always have to start to modularize all this so that it doesn't eat us alive.

Showing my implementation

So I'm going to show you a little bit of my structure. Obviously it can be improved, but I'm gradually getting there, right to the end:

protected function sendRequestAPI(string $orderId, string $type)
{
    if ($type == 'stripe-session-id') {
        // stripe app web
        $this->responseAPI = $this->checkPayedSession(request("stripe_session_id"));
        if ($this->responseAPI->payment_status == 'paid') {
            $this->status = 'COMPLETED';
            $this->idAPI = request("stripe_session_id");
            $this->payment = 'stripe';
            $this->price = intdiv($this->responseAPI->amount_total, 100);
        } else {
            // respuesta error stripe excepcion
            return $this->errorResponse("", 202, json_encode($this->responseAPI));
        }
    } else if ($type == 'stripe-payment-intent') {
        // stripe app flutter
        $this->responseAPI = $this->checkPaymentIntentById($orderId);
        if ($this->responseAPI->status == 'succeeded') {
            $this->status = 'COMPLETED';
            $this->idAPI = $orderId;
            $this->payment = 'stripe';
            $this->price = intdiv($this->responseAPI->amount, 100);
        } else {
            // respuesta error stripe excepcion
            return $this->errorResponse("", 202, json_encode($this->responseAPI));
        }
    } ***
 
}

Recommendation 1: Use a Polymorphic relationship

As I told you, I have two types of entities, one is books and the other is tutorials, here I am already making a criticism since, for example, for books, yes, I did make books, it is of the fileable type, that is, it is of a polymorphic type, something that I had to implement from day one, I do not know if the development of my website has followed a little with me, but I started by selling courses and later, recently, I added the book sales part, therefore, it was a later change, so what this implied was that obviously it is a different structure and therefore it has a different management since the information or the course entity is not the same as that of books, which could have been if from day one I had created something that I can do, but I have to make those changes, the tutorials that were also of a polymorphic type and then easily, in quotes, I could have created the polymorphic part, that is, a new relationship for simply Boot, so that is the first recommendation for haa, whatever you want to sell, try to create there and manage from that day one a type that is of a polymorphic type and That's why I have two files here because if not I could have handled it perfectly from a single controller and simply by means of a conditional ask if it is books and register or do the additional process for the books and do or if it is tutorials do the additional process for the tutorials. In the same way I'm going to show you a little bit of the implementation here then.

Payment methods on 2 different platforms = 4 Payment platforms...

In the end, what is also another problem that I have that I am not going to open but I also have a mobile application, that is, for Android to date and therefore I also offer the same payment methods, but what is the problem here that the same changes, the implementations change, on the one hand, that of PayPal, if you have followed my Arabic course or the book and you have to know that on the one hand we make an implementation in the client, that is, we create the order that is like the payment intention and on the other hand, that object is passed to the server to then be processed, that is, two steps are taken, but Flutter, which is the mobile application, works in a single step since from the same client both the creation of the order and its processing are carried out, therefore it is not necessary to go through the server and I think it makes all the sense in the world since the idea of ​​​​a mobile application is that it can make the payment without having to be connecting to an apid, a third party, which in this case would be my Academia web application and Stripe, more or less the same, therefore there would be four types of payment methods simply for the same application. just as you can see, imagine

Worst implementation, 1 driver for each platform

If you want to add another one or if you want to scale later it becomes a nightmare, then you can see that some problems that we could have in the implementation before I handle a controller for what was the web application and another controller for the mobile application. But in the end it is all the same. Since what we want is to do the process that the AP requests, whether it is PayPal or stripe, and on the other hand, then there is the process that we require to register the resource and you understand the book or the tutorial. But you can see that there are already many little things. On the one hand, we have the implementations for the tutorials. On the other hand, for the books, on the other hand, if we are using PayPal or stripe here on the web and on the other hand, if you are using eh or you are comparing from the mobile application, then there are already like eight different scenarios and therefore it can get quite confusing. So here it starts to work.

Modularize the app, trace, ID, amount, status and type

Here again what we have to think about is what the hell can we modularize so that everything is a single payment, then I'm going to start by showing you a little bit here the method that is in charge of making this discrimination, what is it that we need to register. On the one hand, it would be good to have the entire response from the Api, that is to say, that always when the payment is made, it always throws us an immense object that has information, sometimes it gives us the email in the case of PayPal of the person who paid, also the date, also the status if it was completed, eh, a lot of information that transfers, it is always good to save it in case a dispute problem occurs. So now you have all that information there, although it is not really going to be used, you convert it to Jason and he is happy, what else are we going to need? The identifier, obviously, that is to say, if they are going to make a payment to you through PayPal and suppose a problem occurs in your application, then it is recommended that you verify it manually. And for that, the person has to pass the identifier, whether from PayPal or stripe, and you verify it manually from your module or whatever, and there you register it directly. The other thing is the type Obviously if you are paying by PayPal you are paying by bank, you are paying by strike, you are paying by I don't know, Carrier Pigeon, whatever, there you put the type perfectly, it can be a numbered type but in this case I handled it this way simply with a token but surely later I will mimic it as I told you this structure can still be improved but I have a bit of the idea of ​​​​talking about that in the rest is the status as I was telling you that we also have to unify this since for example in the case of PayPal and ex obviously they are different in the case of PayPal it is complete I think it was the status so we have to unify it so that through the same implementation we can register different payment methods and that is why I have this intermediate method since notice that it is not final because it is protected so what does this method do:

public function processTutorial(Tutorial $tutorial, string $orderId, string $type = 'paypal-order-id')
{
    $user = $this->getUser();

    if ($user == null) {
        return $this->errorResponse("", 202, 'Usuario no encontrado, no se hizo ningun cargo a tu cuenta');
    }

    //*** curso pago
    $this->sendRequestAPI($orderId, $type);

    //*** registra el recurso si la orden esta ok
    if ($this->status == 'COMPLETED') {
        // respuesta exito paypal
        return $this->registerTutorialToUser(
            ***
        );
    } else {
        // respuesta error paypal excepcion
        return $this->errorResponse("", 202, "Ha ocurrido un problema con su orden, el estatus es " . $this->status . " y el ID es " . $this->idAPI . " La respuesta es: " . json_encode($this->responseAPI));
    }
}

The order, that is to say the perfect identifier, I could have put ID, the ses ID, which would be in strike, or the ID order is what it would be in PayPal, I put the ID order and it doesn't matter to me, and here's a guy to tell me what the hell is calling him. So here you can see that I started to discriminate a bit. And from here and continuing a bit with the video that also spoke to you about the importance of using class properties at the controller level since in the end we can forget that controllers are classes and with this we can enjoy all the characteristics that we have of the classes, as in this case they are the properties, I simply fill in the properties that are going to be common based on what was explained above, the price is also another important one, as you can see here and like who says so, we create a generic structure regardless of what the hell we are using. So for example in the case of strip it has the Mania that if you pass it a it is really 100:

$this->price = intdiv($this->responseAPI->amount_total, 100);

So here we have to divide it by 100 Because I am saving it in the real amount, that is to say if it is a dollar that the product costs, I save that as one, so that is why here I divide it by 100, for the rest, here I put a common status, here I get the identifier. That depends a little on how the app works, since notice that for example the identifier in the case of strip with the session ID works like this, but in the case of strike Up floater I have it here directly, I can also pass it here directly, which is what I have here, and in the case of PayPal I have it from the response, it seems to me, or from, or I pass it to you to see. Oh no, also established in this way, but here is the important thing, that is, you see where the hell you get it, since no matter what, there are many ways to obtain that information and here we are unifying to work with common patterns, which in this case is what we are going to need, giving a small summary again, what we are going to require, for example, here I am seeing that I have an error since this It should be like this since I am equalizing it here. So I'm going to need the complete trace, the status to know if the price was paid or not, and also the ID, the status, so you could do without it, but I manage it mostly internally, so if an error occurs, then an error page is displayed, but what we're going to record in the database would be the price, the ID, and also the trace. So, as I was saying:

Intermediate method to process ALL purchase requests and modularize the app using properties

I have four platforms to date and they all work differently, Stripe and PayPal but one for mobile and one for the web, so there are two and the same with Stripe and here what I do is set the properties so that they have the value regardless of the origin. So this intermediate method is called directly from the controllers, which again is only one for the web application and for the Flutter application. What we do here is get the user. And here we also have another novel because with Laravel we have two ways, it would be through tokens, which I have enabled here as well in the case of the mobile application:

$user = $this->getUser();

Later I will also implement it for the web application and through a session. Because it is an application in Larabel, also web, then independently of where I get it from and now I have the user and if it returns a null, then we have problems because I have no one to register the resource, which also causes problems, for example, in the PayPal part, for example, with the one that does the entire operation from the client. Because if you do the entire operation from the client, that is, if you do not block the PayPal plugin from the client for an unauthenticated user, then obviously I have no one to register the course with and well, the thing exploded, but oh well.

So I hope it has become a little clear and this helps you for future implementations of your applications.

- Andrés Cruz

En español

Andrés Cruz

Develop with Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz In Udemy

I agree to receive announcements of interest about this Blog.