dinoperovic / django-salesman

Headless e-commerce framework for Django and Wagtail.
https://django-salesman.rtfd.io
BSD 3-Clause "New" or "Revised" License
384 stars 46 forks source link

Multiple checkout steps - A checkout model #31

Closed Timusan closed 1 year ago

Timusan commented 1 year ago

Currently, a checkout is a transition from a Basket to an Order via a single API call and returns either a redirect URL to a payment method processing page or a custom URL (such as a thank you page).

However, I think there are use cases where a checkout can be multiple steps. For example, during checkout you define the shipping address, yet the shipping cost (which you define as a modifier on your Basket) could be variable depending on your address - it is not always a flat fee. Currently we would have to add the shipping address to the basket to be able to add a variable shipping cost modifier ... and then again to the checkout to store the shipping address on the order. This feels hacky.

Having multiple steps during checkout would allow to set Basket modifiers (or Checkout modifiers in that case?) which would add extra lines to the Basket/Order depending on data that is given during the checkout.

This would probably require a checkout to become its own model which sits between the basket and the final order and to which different data is applied during various steps. I imagine API routes such as:

Via various calls to the PUT route one could implement multiple steps, adding more data to the checkout as they go along. The final POST would act similar to the current checkout, as it would return a URL to redirect to (external payment method, thank you page, ...). Checkout models would also contain a configurable status object depicting the various steps such as "define shipping", "define contact data", "define gift wrapping", ... .

Having a checkout being a separate model and giving it its own status could also provide some more statistical insight in the backend, as we would have an admin showing the various checkouts and where the user might have jumped off, aka. on which status a checkout is left.

I think that currently the idea is that the basket is used for all of the above, as in: we patch the basket during "checkout" until we are ready to commit this to an Order. But on the other hand only the checkout route contains crucial data such as the shipping address which is needed to calculate the shipping basket modifier.

Semantically I feel as if a basket is simply your shopping cart, the checkout is its own thing where your patch in various items such as addresses, contact info, gift wrapping, ... and an order is all of that finalized.

dinoperovic commented 1 year ago

You are right in that the Basket was designed to handle all those use-cases. Storing addresses (and any other extra data) can be done on the basket using the /api/basket/extra/ endpoint. This allows for basket modifiers to use all that data and modify the basket or its items accordingly, before it gets converted into an order.

You can even store a "step" progress on the basket "extra", or whatever seems necessary for your project.

Adding a Checkout model would only mean copying all that functionality, making it complex, and doesn't seem to add any extra value other than semantics around which endpoint we're using -- or am I missing something?

I would see the case for it if you would expect to have multiple "Checkout" objects for a single Basket, but that scenario doesn't really make sense in the e-commerce flow.

Timusan commented 1 year ago

Hmmm, I see what you mean (and excuse my late reply). I guess at heart this is purely a semantics thing. In other setups I was quite used to seeing a basket, a checkout and an order as three separate entities. The checkout entity would mainly be used for two things: configuration of the checkout flow and checkout storage for statistical use.

The latter one is simply seeing where a customer might have run stuck during checkout, purely for conversion measuring. This can be done in a myriad of other ways of course and is probably too high-level for Salesman.

The former sits more at the configuration end of the e-commerce setup and allows you to configure a single or multiple checkout flows (certain items in the cart, certain addresses, ... could trigger a certain checkout flow) - it would allow for you to compose the different steps of each flow and choose the rules on which to trigger each one. A site administrator could compose such a checkout in the admin, without code.

But, on the other hand ... this also comes from less programmatic frameworks and frameworks which were not headless. With the rise of front-end frameworks, these flows will most likely be programmed at that end and the setup that the basket currently gives you is open enough to implement any kind of logic in the front.

I guess that any backend logic (if a sites requires an admin to compose checkout flows or you simply want server side validation as well on top of your front-end framework rules) can be made using custom models you program besides what Salesman offers.

The question is a bit: Where does the Salesman functionality stop and custom coding takes over. Should a checkout (with definitions of what checkouts there are, the steps on them and when to trigger each) be more explicit in Salesman? I'm inclined to say yes ... but that, again, stems from more lower-code and non-headless frameworks.

dinoperovic commented 1 year ago

I see what you mean, having multiple checkout flows that store some state could be useful in some cases, but that would also mean we have to re-implement all basket functionality as well, to generate an order in the end.

I do believe having multiple checkout flows, and an UI to handle them is "custom" enough use-case to be left to the developer to implement. What salesman offers is a "payment method" interface where a simple form of this is ment to be implemented in. Based on the items/address/etc you can decide which payment methods are available and which one to select. Payment methods are where you can provide any custom flows of converting a basket into an order. If you decide to store a "Checkout" model here and have multiple steps, that would be a valid decision.

Another way of having a multi-step process is in order models. Your order can have custom statuses and status transitions where you could trigger different workflows. You can even use some sort of FSM to handle them.

My goal for Salesman is to try to keep this as simple as possible for the newer devs, but also provide interfaces for it to be extended to some advanced use-cases. If we were to implement some custom checkout model, it would still need to be basic but extendable. I would prefer to see this in action before deciding on adding it, as it would be adding complexity.

I am open to suggestions on how to potentially implement this and will think about it some more, thanks! 👍🏻