Jaime Forcada, Software Engineer
The checkout had to be extensible and configurable enough to fulfill the following requirements:
The Order is a simple, persistent container into which we place the input the user has chosen through the checkout. In the Order, we keep a record of the data plan chosen, the initial top-up, shipping information, etc. An Order is identified by its OrderId.
The StateMachine is a state machine that drives the checkout process from the beginning to end. Its responsibility is to define a flow of steps, so that the front-end can request the next step of the checkout in a generic way, which the StateMachine will provide. A StateMachine is identified by the FlowId.
When we have a step that depends on those that come before it, the StateMachine is able to resolve it through guard conditions. For example, if the user chooses neither a an initial top-up amount nor a data bundle, the machine won’t provide the payment step.
The relation between the two is that an Order references a StateMachine, so an order can only be processed by a single StateMachine.
![]()
For example, if we’re doing an A/B/C testing, we define 3 different state machines.
![]()
![]()
Although the checkout happy case is to go forward until the end, we had to make it possible to go back to edit steps. Having implemented a state machine, the natural move was to just define edit transitions between steps. These edit transitions go backwards in the StateMachine.
Implementing the step editing possibility as a transition not only removes code complexity and looks like the natural behaviour of the state machine, but it also makes it clear to us the from which states users can edit others (for example, if the user has already gone through the payment step, we shouldn’t allow him/her to change the initial top-up).
Being that a main goal is to make finishing the checkout as fast as possible, we didn’t want our customers to repeat the steps they already completed, even if that only means clicking and submitting the pre-filled data again. This can be done both when editing a previous step and after a full reload.
Thus, we make the user jump from the editing step when submitting it to where s/he was before editing (that is, forward in the state machine). The initial implementation was to navigate automatically through the original transitions, calculating them given the previously introduced data (automatically resolving branching, like postpay/prepay), until we got to the point we where we could not transition further because of lack of data: That’s the state of the user before, so we would stop the machine there.
This initial implementation of these auto transitions was quickly shown to be incorrect when we needed to add callbacks to the transitions, such as sending a request to our third party ID validation system, sending an email of a certain event to the user, etc. These actions took place every time the transition was traversed, which was an undesirable behaviour.
In the end, we started coding the auto transitions as if they were independent transitions, without any triggers. Like with the edit transitions, this gave us the power to choose which transitions could be automatic and which can’t.
In summary, every StateMachine is defined by its transitions, which are specified in 3 groups:![]()
![]()
We added a new layer to decouple the rendering. With it, we are able to control the kind of views we are using for this flow, that is, it allow us to perform A/B testing against the look and feel of the checkout.
![]()
From the client side, if a step has a special need, we subscribe a step initializer function name. It will be called by our JavaScript page coordinator when the step is loaded. For example, if the shipping information step needs to build a city autofill, that step will specify the JavaScript function to do so.
The entry point of the checkout (usually an agent) is the one who defines the flow (StateMachine) and the renderer of the process for this user. Our A/B testing API chooses both of them, which are stored in the Order (that is recovered by session), so if the user reloads the page or revisits it after several days, they still see and continue the same process.
We feel as though we developed more a checkout framework than the product itself, something that exceeded our original estimations. However, we are confident that it will pay off, because we had total flexibility to change any aspect of the process in an easy way, as long as we have the power to do A/B testing against any side of the checkout and, of course, build more specific flows.
Requirements and Motivation
The team in charge of the Mobile Virtual Network Operator (hereafter MVNO) at Tuenti was asked to build a new checkout for the store. For legal and business reasons, a checkout for an MVNO has to comprise steps such as shipping information, billing information, customer ID check, etc. Our main goal was to find the best checkout that minimizes users abandoning the process.The checkout had to be extensible and configurable enough to fulfill the following requirements:
- To be able to perform A/B testing on the order of the steps.
- To be able to perform A/B testing on the specific view of a step.
Back-end
There are two main entities in the back-end to model our checkout framework: the Order and the StateMachine.The Order is a simple, persistent container into which we place the input the user has chosen through the checkout. In the Order, we keep a record of the data plan chosen, the initial top-up, shipping information, etc. An Order is identified by its OrderId.
The StateMachine is a state machine that drives the checkout process from the beginning to end. Its responsibility is to define a flow of steps, so that the front-end can request the next step of the checkout in a generic way, which the StateMachine will provide. A StateMachine is identified by the FlowId.
When we have a step that depends on those that come before it, the StateMachine is able to resolve it through guard conditions. For example, if the user chooses neither a an initial top-up amount nor a data bundle, the machine won’t provide the payment step.
The relation between the two is that an Order references a StateMachine, so an order can only be processed by a single StateMachine.

A/B Testing on the Order of the Steps
The design described above allows us to perform the A/B testing on the order of the steps simply defining another StateMachine, which is no more than a declaration of steps and transitions.For example, if we’re doing an A/B/C testing, we define 3 different state machines.

StateMachine, Auto and Edit Transitions
Any transition of the StateMachine has two attributes:- The guard condition decides which step will follow the current one depending on the return value of the guard function.
- Callback is the function to be executed when the transition takes place.

Although the checkout happy case is to go forward until the end, we had to make it possible to go back to edit steps. Having implemented a state machine, the natural move was to just define edit transitions between steps. These edit transitions go backwards in the StateMachine.
Implementing the step editing possibility as a transition not only removes code complexity and looks like the natural behaviour of the state machine, but it also makes it clear to us the from which states users can edit others (for example, if the user has already gone through the payment step, we shouldn’t allow him/her to change the initial top-up).
Being that a main goal is to make finishing the checkout as fast as possible, we didn’t want our customers to repeat the steps they already completed, even if that only means clicking and submitting the pre-filled data again. This can be done both when editing a previous step and after a full reload.
Thus, we make the user jump from the editing step when submitting it to where s/he was before editing (that is, forward in the state machine). The initial implementation was to navigate automatically through the original transitions, calculating them given the previously introduced data (automatically resolving branching, like postpay/prepay), until we got to the point we where we could not transition further because of lack of data: That’s the state of the user before, so we would stop the machine there.
This initial implementation of these auto transitions was quickly shown to be incorrect when we needed to add callbacks to the transitions, such as sending a request to our third party ID validation system, sending an email of a certain event to the user, etc. These actions took place every time the transition was traversed, which was an undesirable behaviour.
In the end, we started coding the auto transitions as if they were independent transitions, without any triggers. Like with the edit transitions, this gave us the power to choose which transitions could be automatic and which can’t.
In summary, every StateMachine is defined by its transitions, which are specified in 3 groups:
- Forward transitions (Grey)
- Edit transitions (Orange)
- Auto transitions (Green)

Front-end
The front-end side of the project is based on our in-house framework. The typical architecture of a product based on this framework consists of an Agent that receives the http request parameters and returns a response with rendered content and data to the client-side.
We added a new layer to decouple the rendering. With it, we are able to control the kind of views we are using for this flow, that is, it allow us to perform A/B testing against the look and feel of the checkout.

From the client side, if a step has a special need, we subscribe a step initializer function name. It will be called by our JavaScript page coordinator when the step is loaded. For example, if the shipping information step needs to build a city autofill, that step will specify the JavaScript function to do so.
A/B Testing on the View of the Steps
With the architecture presented previously, the testing a step is as easy as creating a new renderer inheriting from the original, and overriding the method in charge of render the step we want to change.The entry point of the checkout (usually an agent) is the one who defines the flow (StateMachine) and the renderer of the process for this user. Our A/B testing API chooses both of them, which are stored in the Order (that is recovered by session), so if the user reloads the page or revisits it after several days, they still see and continue the same process.
Conclusions
The whole design and implementation has been a challenge for us. Making something as sensitive as a MVNO checkout that was configurable was a very ambitious project.We feel as though we developed more a checkout framework than the product itself, something that exceeded our original estimations. However, we are confident that it will pay off, because we had total flexibility to change any aspect of the process in an easy way, as long as we have the power to do A/B testing against any side of the checkout and, of course, build more specific flows.