Questions:
N/D
Technical Refinement
Goal
The main objective is to allow the customers to use promo codes at checkout to receive benefits coming from offers.
Front-end refinement
From what we understand the current feature at checkout allow promo code just to type CBA offers and this feature is deprecated. More infos: https://rbidigital.slack.com/archives/C04FZ5HTH35/p1693855082030399.
As CBA feature is deprecated we need to change the promo code field to accept loyalty promo codes. (here more details on how to configure the promo code on sanity and voucherify: /wiki/spaces/CG/pages/984973325- OBS.: The documentation explain how configure promo codes to CBA offers and Loyalty Offers, So follow just the loyalty steps.)
After the config, we will have the config offer like this:
OBS.: On In-App Benefits there is just one item. If there are more itens, the discount will be the first item.
intl-whitelabel-app
1 - TASK: Create new promo code flow to loyalty
Create a feature flag;
The first step to use the loyalty promo code at checkout will be to change the validation promo code flow.
On promo code component field, we will need to add a feature flag:
When this flag is enabled: The promo code flow will be LOYALTY OFFER
When this flag is disabled: The promo code flow will be CBA OFFER (current flow)
We will create a new hook to contain all the rules of this flow
We can use the same hook used to offer page flow.
intl-whitelabel-app/workspaces/frontend/src/state/loyalty/hooks/use-redeem-promo-codes.ts
Attention points:
We need to apply (dispatch) the personalised offer on some contexts:
actions.loyalty.setSelectedOffer(personalizedOffer)
actions.loyalty.setAppliedOffers
actions.loyalty.applyOffer
actions.loyalty.setCmsOffers
OBS.: It’s possible we need to apply the offer in another contexts too.
When we click on remove in offer info (after applied the offer), we need to remove the offer on all contexts.
When the customers reload the page, the offer must continue applied.
Verify need for other validations
With this update, the frontend is prepared to apply the promo code to loyalty.
2 - TASK: Updated action Remove button
We will need to update the button action to remove the applied offer on
loyalty.offers.cmsOffers
path:
intl-whitelabel-app/workspaces/frontend/src/state/global-state/models/loyalty/offers/offers.slice.ts
removeCmsOffers: (state, { payload }: PayloadAction<LoyaltyOffer>) => { state.cmsOffers = state.cmsOffers.filter(offer => offer._id !== payload._id); },
path:
intl-whitelabel-app/workspaces/frontend/src/pages/cart/your-cart/cart-offer.tsx
const onRemove = () => { ... dispatch(actions.loyalty.removeCmsOffers(selectedLoyaltyOffer)); };
OBS.: Change also:
path:
intl-whitelabel-app/workspaces/frontend/src/state/global-state/models/loyalty/loyalty.actions.ts
3 - TASK: Hide CBA Options
We will need to hide the CBA option to ensure that anything CBA option shows.
Then, we will create a new attribute to hide this option;
This attribute value can be the opposite of the value of feature flag (flag created on task 1)
4 - TASK: Apply discount again if not used
Currently, we don’t have the validated voucher flow on whitelabel-app and voucherify, Just the burn voucher flow when we applied the promo code.
When we applied the promo code, the offer get saved on the offers page.
So, We need to validate the offers saved and compare the promo code added on the field to offers saved before validating the promo code on voucherify.
If we have a promo code saved, we will apply the offer without validating voucherify.
If we don’t have one, we will validate the promo code with voucherify;
5 - TASK: Clean the applied offer when finished the order
When finish the order, we need to clean the offers on cookies, sessions, etc.
path:
intl-whitelabel-app/workspaces/frontend/src/pages/order-confirmation/order-confirmation.tsx
Back-end
After applying the Whitelabel will do a call to the backend to calculate the discount value:
How is the calculation done?
So basically, we need to send (via slack) to Winrest the offers sanityId.
They register these sanityId on their system and after that, always we use some offers with these sanityId registered, Winrest will return the calculation offers correctly.
Example:
To use the offer Test: Test Discount Offer (Sanity DEV)
We will need to send the sanityId:
a6f3eace-435e-49ea-9a79-e173f6172dcc
to Winrest via some means of communication this information;They will register this ID on their system;
When the intl-partners-service calls the Winrest API, the integration will know how much will be the discount;
The discount is always on the total value of the cart;
Solution proposal
Currently, we already sent the sanityId offer to the backend (
intl-partners-service
);Then, we need to send the discount information to Winrest;
With the
sanityId
that we have, we will find the discount values bygetOffers(sanityId: string)
path:
intl-partners-service/src/modules/orders/cart-total.service.ts
We will add a new attribute
orderDiscounts
on the webhook payload;
We will send it to Winrest always in percentage (requested to Winrest, 'cause will be easier for them);
The
orderDiscounts
object would be in each cart object;If the discount is
amount
type, we will transform it topercentage
type;E.g: If the total cart is 10 euros and
amount
discount is 5 euros;We will calculate the discount:
discount = (100 * 5) / 100; discount = 50%;
1 - TASK: Create a method to calculate the percentage offer;
Create a feature flag;
Create a method to calculate the percentage offer.
path:
intl-partners-service/src/modules/orders/orders.service.ts
We will send the discount values per cart item to Winrest, but the calculation of the discount percentage will be based on the cart total, which will be easier to calculate and in the end will have the same result if we calculate the discount per item.
The method will receive two params:
appliedOffers: IAppliedOffer[] | null | undefined
Offer applied on the cart;
total: number | undefined
total cart value;
The method return will be:
Promise<IOrderDiscounts[] | undefined>
Where the
IOrderDiscounts
.type will be always a percentage;
To discover the discount values we will need to use the method
getOffers
path:
intl-partners-service/src/modules/orders/cart-total.service.ts
This method is
private
, so we will update it topublic
;
If the discount offer is a type
amount
we will calculate the percentage over the total cart value;E.g: If the total cart is 10 euros and
amount
discount is 5 euros;We will calculate the discount:
discount = Math.round((5 * 100) / 10) discount = 50%;
So the payload will be:
"orderDiscounts": [ { "type": "percentage", "value": 50 } ]
If the discount offer is a type
percentage
we will return the percentage value;The method example is on branch poc: https://github.com/rbilabs/intl-partners-service/tree/poc/IBFEC-934-send-discount-offers-to-winrest
path:
intl-partners-service/src/modules/orders/orders.service.ts
method name:
mapOffersDiscounts
2 - TASK: Update the calculated total card;
Currently, the total cart is calculated the all item values including the offers configs, so we need to remove the discount offers config this calc;
path:
intl-partners-service/src/modules/orders/cart-total.service.ts
method:
calculate(cart: ICart)
We will refactor this method abstracting the logic into two parts because we will need just the part of the method code to this flow. But we can’t change the
calculate(cart: ICart)
because it is used in other flows. Then, we will create a new method.As the POC method
public calculateTotalCart(cart: ICart)
path:
intl-partners-service/src/modules/orders/cart-total.service.ts
POC: https://github.com/rbilabs/intl-partners-service/tree/poc/IBFEC-934-send-discount-offers-to-winrest
3 - TASK: Add the discount on the order;
We will use all methods created above (
mapOffersDiscounts
andcalculateTotalCart
):path:
intl-partners-service/src/modules/orders/orders.service.ts
On method
public async price(input: IPriceOrderRequest):
We will import the attribute
appliedOffers
on objectinput.cart
;We will protect the code with feature flag here;
We will calculate the total cart without possible discounts (
calculateTotalCart
) and use the methodmapOffersDiscounts
to create the object with percentage discount:let discountsOffer; if (FEATURE_FLAG && appliedOffers?.length) { const totalCart = this.cartTotalService.calculateTotalCart(input.cart); discountsOffer = await this.mapOffersDiscounts(appliedOffers, totalCart); }
We will add the variable
orderDiscounts
on attributeorderDiscounts
fromnewOrder
object;
4 - TASK: Send the new discount object to Winrest
We will need to send the new discount attribute to Winrest by webhook:
Then, we will add the new attribute on mapper
mapRbiCart
;So we will add the variable
discountsOffer
tomapRbiCart
:mapRbiCart(pricingCart, discountsOffer)
;In this method, we will add a new condition to send the discount values:
if (FEATURE_FLAG && orderDiscounts?.length && verifyDiscountTypes(cartEntry)) { result.orderDiscounts = orderDiscounts; }
Where, the
verifyDiscountTypes
method verify item type of the cart that there will be a discount because the types (CartEntryType.offerDiscount
,CartEntryType.offerCombo
,CartEntryType.offerItem
) are already the offer of an item;const verifyDiscountTypes = (cartEntry: ICartEntry): boolean => { return ( cartEntry.type !== CartEntryType.offerDiscount && cartEntry.type !== CartEntryType.offerCombo && cartEntry.type !== CartEntryType.offerItem ); };
Screenshots
N/A
POC
FRONTEND (WHITELABEL)
BACKEND (INTL-PARTNERS-SERVICE)
Impact Analysis
N/A
Dependencies
N/A
Unit Test
N/A
Useful Links
N/A
Add Comment