# Checkout/finance calculator refactor presentation
## Introduction
The docs is very thick (misses a few things that will be added later) so I'd like everyone to take the time to read it (as it's more detailed than this presentation and the presentation itself isn't enough to be able to work with the new code).
Any questions or suggestions for improvements are more than welcome. The docs should be a long lasting Notion page for new and existing employees so it's easier to implement features and work with checkout.
Thank you! :heart:
## Step 1: general explanation
### Single source of truth
Before, we used to split our data between multiple sources (local state, session storage, etc.). This led to issues where we could have f.e. discrepancies between our data if it wasn't synced properly.
Now, the data is centralized and managed via our [alveron](https://alveron.vercel.app/intro/overview) store. That way, we make sure that all part of the code using the data uses/update the same data.
This is what is called a single source of truth:
> Single source of truth definition: in IT, a single source of truth (or single point of truth) architecture, is the practice of structuring information models and associated data schemas such that every data element is mastered (or edited) in only one place.
## Step 2: Alveron's case
In the case of Alveron, this translates by the fact that we have a `store` object (the `alveron` store), which in turns contains 3 different fields:
- `state`: this is where our data is basically stored. We'll go into details later.
- `actions`: this is a set of functions that allow you to update the `state` as you wish. Same, details about this later.
- `effects`: those are also functions but that contains side effect (like fetching data from an API) and so they are split from `actions`. Details later!
### `state` field
The `state` field contains all our data. This data is structured in a way that makes it easier for us devs to understand, via some form of namespaces as follow:
- `contact`: for all user contact data
- `options`: for any selected options in checkout
- `financing`: for all the financing data
- `order`: for all order related data (order ID, payment ID, etc)
- `navigation` (only in checkout!): to store all navigation related data (the step we are on and which step we completed last)
All of those data can be used like a regular React state in a `useEffect` or anything you might need:
```js
// useEffect
useEffect(() => {
console.log(`Kind has changed, it's now ${store.state.contact.kind}`)
}, [store.state.contact.kind])
// in a component return value
<div>{store.state.contact.kind}</div>
// or anywhere you want :P
```
### `actions` field
As stated before, `actions` allow us to update our `state` data the way we want it. Those `actions` are defined by the developer when implementing the `alveron` store and in our case we basically use the following: each `state` field (or namespace, depending how you see it) has its setter and its updater.
For example, `contact` in `state` has 2 actions associated:
- `updateContact`: simply update the value of contact with the provided payload (object)
- `setContact`: sets the value of `contact` to the provided payload (object)
Illustration:
```js
// initial state
state = {
contact: {
kind: 'B2B'
}
}
// update state
updateContact({kind: 'B2C', firstname: 'John', lastname: 'Doe'})
// new state after update
state = {
contact: {
kind: 'B2C',
firstname: 'John',
lastname: 'Doe',
}
}
// set state
setContact({kind: 'B2B'})
// new state after update
state = {
contact: {
kind: 'B2B'
}
}
```
> All `actions` acts in the same way as a React state setter. This means that the state update happens asynchronously and is batched by React.
> React batching: the action of grouping state update into a single operation when they happen during the same execution call
### `effects` field
As explained before, `effects` are pretty much `actions` (in the sense that they call `actions` under the hood to update the state) but they usually do API calls or any other side effects.
In our case, we only have a single effect: `loadStorage`.
This effect is simply used for loading persisted data (see details afterwards) into our store so that we can populate `state` with anything that was persisted by the store.
### The true, enriched alveron store...
Besides the basic `state` & `actions`, we also provide some extra method to our actual store (done via our implementation) to allow us to persist our data in our session storage and also reset it.
Those functions are accessible under:
- `store.persistData`: this function basically writes the `state` object in the session storage so that it can be persisted
- `store.resetPersistedData`: this function removes any persisted data and resets the store back to its initial data
## Step 3: Data loading & persistence
In some apps, we might be using a global data store that handles all the data for the application.
However, here we decided to go with a "local" store that is only initialized and used in PDP & checkout.
> **Important:** PDP & checkout do not share the same store! They both run alveron, they both use the same hook to initialize the store but those 2 entities are distinct.
The way it works is as follow:
1. when we call `useStore` hook (the hook that creates our store), a default `alveron` store is created with a default set of data based on the car data (f.e. kind is B2C, we set that the delivery address is the same as billing, and we copy all car pricing data into the `financing` `state` field)
2. once this is done, we call our `store.effects.loadStorage` in order to load any persisted data from session storage (if nothing is there, then `state` is left untouched and stays with the default values)
3. once we have our populated store, we can start working with it!
The way data is then shared between PDP & checkout is by calling `store.persistData` when the user clicks on the buy button on PDP, which in turn will write `state` into the session storage. And when the app navigates to checkout, it'll load the data from storage hence look like we have the same store.
> More details in docs!
## Step 4: data flow
The way the data is being updated depends a bit on the page or the checkout step you're on.
### Case 1: checkout (but not financing form!)
For all checkout steps besides financing, the store data is:
- **updated** when the user clicks on the next button. The payload used for the update is basically the form data
- **persisted** when the user clicks on the next button. This happens ofc after we updated the state
### Case 2: checkout financing & PDP
For checkout financing step and PDP this works a bit differently. The store data is:
- **updated** when the user interacts with the financing calculator. **Attention:** the data isn't updated for every form change. The update is actually debounced (100ms? Can't remember) and only happens if no change was detected in the last 100ms. This way, we improve the performance of the app and also avoid race conditions where multiple updates would occur in parallel of more changes coming in from the form.
- **persisted** when the user clicks on the buy button to navigate to checkout. That way we can carry our data to checkout in order to keep using them
In the specific case of the financing step: the data is persisted when the user clicks on the next button. We also only persist the relevant data for the selected payment method, in order to clean up the `financing` data and not carry over some financing calculation data when a user would have selected cash at the end.
## Bonus content!
### Parent-child component data flow
During the refactor, we also reviewed the way the data flows between parent and child component (f.e. between PDP and finance calculator).
Before, we used to have a big `useFinanceCalculator` hook that contained a variety of stuff: computation, fields, state setters, etc.
In order to break this down and avoid entangling our code with cross-dependencies, we did multiple major changes:
1. `useFinanceCalculator` became `useFinancing` and is now only responsible for calculating financing data (loan cost, effective rate, etc) based on a set of data (coming from financing calculator)
2. `useFinancing` is no more used at multiple level of the app but almost always at the top most level of the app: our page. The only edge case to this is for the `FinancingExampleModal` because it doesn't always receive computed data so it has to compute it itself (f.e. on the landing page)
3. the data flow follow a more vanilla approach where we:
- propagate form update from financing calculator via an `onChange` attribute (similar to how `onChange` attribute works on `input` elements)
- update the source data for the financing calculator via a `data` attribute
| See graphical example in docs: https://www.notion.so/Checkout-flow-0024311da59e4f489ad4e00d6cad9c5a#612db48a78b14c9ba2ef4a5b63d7c5d0
In other words, we now have:
- a clear flow for our data
- no more mix of presentational & logical data from a single hook
- no more use of a hook accross the code base for different purpose (it is always used for computation and computation only)
And this logic (especially the parent-child data flow) was applied to a few other components too: `KindSelector` (formerly `KindButton`), `CarSummary`, etc.
### Financing calculator impact
The reason why the logic is slightly different between PDP/financing step and the rest of the checkout is because of the computated financing data and how it's reused by other part of the page where the financing calculator is.
When the user interacts with the financing calculator, the computed financing data changes. The issue is that this data needs to be shared with multiple components (`FinanceCalculator`, `FinancingExampleModal`, `CarSummary`/`CartSummary`, etc.).
In order to do this, those component will read the state from our store. But to always have the lastest data, the store has to be updated. This is the reason why PDP & the financing step update the `financing` field for every change that happens on the finance calculator form.
### Financing calculator fields dependencies & interaction
The financing calculator has quite a few dependencies and interactions with multiple set of data. Among those are the following:
- the monthly payment f.e. depends on:
- the loan period
- the down payment
- the residual value
- the car price
- if the car is applicable for tax deduction and in turn, if the customer is a business or not
- the down payment depends on:
- the customer type (can't be lower than 20% of the car price for a private customer f.e.)
- the retail price (there is a set maximum that depends on the car price but also the customer type)
- the residual value depends on:
- the loan period (for each period, there is a set maximum)
- the down payment (down payment + residual value cannot exceed 90% of the car price)
This leads to a lot of constraint and interaction between the different fields and the data. For example, when we move the down payment slider, the residual value slider needs to update accordingly to prevent user to input more than 90% car price cumulated value with those 2 fields.
## Demo time!
1. start localhost
2. go to landing page
3. scroll down, click one of the available car in carousel
4. play around with financing calculator to show how changing each fields impact the other fields and the values (monthly payment)
- change loan period -> show how it changes residual value maximum + monthly payment
- change down payment -> show how it changes residual value maximum (when we exceed 90%) + monthly payment
- change residual value -> show how it changes the monthly payment
- change kind (B2C/B2B) and show how the down payment percentage is maintained while the value itself changes (because retail price has changed since the car is hopefully applicable for tax deduction, else well, tough luck!)
5. click on buy button
6. press F12
7. go into `Application` tab
8. go into session storage
9. show that the data is now persisted (under the `checkout:{car_id}:data` key)
10. fill contact form
11. show how the persisted data updated (we now have proper `contact`)
12. uncheck the option (for the show)
13. show how the persisted data updated (we now have proper `options` with the insurance being `false`)
14. select financing method
15. play around again with calculator
16. switch to cash
17. click on next
18. show how the persisted data updated (we now have proper `financing` with only cash related data)
19. show summary
20. click on next
21. buy car
22. show confirmation
23. go to BO and cancel the order (can show the order in BO, not sure it's relevant)
> Know bug (I think): I don't think that the payment method is persisted from PDP (not sure anymore) so maybe when we land on financing we won't automatically have the calculator with the persisted value but instead everything is back to default. I think there is a ticket for that in improvement board