Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Questions:

  •  N/D

Technical Refinement

...

Expand
title1 - TASK: Apply a promo code flow for 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 the 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 Where the new logic can be (two options):

    • 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

Code Block
languagetypescript
const onSubmitLoyaltyPromoCode = useCallback(async () => {
    setPromoCodeValidationLoading(true);

    // const loyaltyId = 'ec5cec01-b41b-509b-9111-310ab5a18154';

    let event = createRbiApplyPromoCodeEvent(promoCodeInput, 'Successful');
    const personalizedOffer = await redeemMutation(user?.loyaltyId || '', promoCodeInput)
      .catch((e: PromoCodeError) => {
        const reason = buildErrorMessageFromPromoCodeError(e);
        setPromoCodeErrorMessageId(
          (reason as TPromoCodeErrorMessageIds) || PromoCodeErrorMessageIds.default
        );

        logger.error(`Error validating promo code ${e}`);
        event = createRbiApplyPromoCodeEvent(promoCodeInput, 'Failed', e.message);
      })
      .finally(() => {
        setPromoCodeValidationLoading(false);
      });

    trackEvent(event);

    if (personalizedOffer) {
      await handleRedemption(personalizedOffer);

      // clear promo code input & error message
      setPromoCodeErrorMessageId(null);
      setPromoCodeInput('');

      toast.success(formatMessage({ id: 'offerAddedToCart' }));
    }
  }, [
    formatMessage,
    handleRedemption,
    promoCodeInput,
    redeemMutation,
    trackEvent,
    user?.loyaltyId,
  ]);
  
  
   useEffect(() => {
    if (appliedOfferPromoCode?.loyaltyEngineId) {
      const standardOffersLimit =
        earningCalculationData?.EarningCalculation?.offerRedemptionLimits?.standardOffersLimit || 1;

      dispatch(actions.loyalty.setSelectedOffer(appliedOfferPromoCode));
      dispatch(actions.loyalty.setAppliedOffers([appliedOfferPromoCode]));

      if (isDiscountLoyaltyOffer(appliedOfferPromoCode)) {
        // If limit of offers reached remove the first one
        if (appliedOffers?.length >= standardOffersLimit) {
          removeFromCart({ cartId: appliedOffers[0].cartId });
        }

        //Discount offers should not show menu item details
        dispatch(
          actions.loyalty.applyOffer({
            id: appliedOfferPromoCode.loyaltyEngineId,
            type: OfferType.GLOBAL,
            isStackable: appliedOfferPromoCode.isStackable,
            isSurprise: isSurpriseOffer(appliedOfferPromoCode),
            cmsId: appliedOfferPromoCode._id,
            cartId: 'discount-offer',
          })
        );
      }

      dispatch(actions.loyalty.setCmsOffers([appliedOfferPromoCode]));
      return;
    }

    dispatch(actions.loyalty.setSelectedOffer(null));
    dispatch(actions.loyalty.setAppliedOffers([]));
    dispatch(actions.loyalty.setCmsOffers([]));

    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [appliedOfferPromoCode]);

The code above was developed in the PoC and is a new code.

Attention points:

  1. We need to apply (dispatch) the personalised offer on some contexts:

    1. actions.loyalty.setSelectedOffer(personalizedOffer)

    2. actions.loyalty.setAppliedOffers

    3. actions.loyalty.applyOffer

    4. actions.loyalty.setCmsOffers

OBS.: It’s possible we need to apply the offer in another context too.

  1. When we click on remove in offer info (after applying the offer), we need to remove the offer in all contexts.

  2. When the customers reload the page, the offer must continue applied.

  3. Verify the need for other validations

With this update, the frontend is prepared to apply the promo code to loyalty.

...

Expand
title4 - 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;

  • When have the promo code information on file:

    • path: intl-whitelabel-app/workspaces/frontend/src/pages/loyalty/loyalty-offers/loyalty-offers.tsx

      Code Block
      languagetypescript
      const { sortedTilesAndOffers, marketingTileTop, marketingTileBottom } =
        useOffersPageMarketingTiles({
          offers,
          offersPageMarketingTiles: marketingTiles,
          selectedOffer: selectedIncentive || null,
        });
    • On object sortedTilesAndOffers

Important: We don’t have the POC to this task

Expand
title5 - 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

Code Block
languagetypescript
useEffect(() => {
  if (!loading && !orderErrors) {
    dispatch(actions.loyalty.setSelectedOffer(null));
    dispatch(actions.loyalty.setAppliedOffers([]));
    dispatch(actions.loyalty.setCmsOffers([]));
  }
}, [dispatch, loading, orderErrors]);

  • After the order is finished, we need to apply the sanity rules too, for example:

    • If the number used voucher is 1, after the order, the offer should be removed from the offer page

    • we can use the same logic on the component OfferRedemptionModal

      • path: intl-whitelabel-app/workspaces/frontend/src/components/offer-redemption-modal/index.tsx

      • When we click on Redeem in Restaurant

      • Will show the modal and if we click on the close button, the offers will removed on the offer page. So we can use the same logic:

    • OBS: This relation between offer x customer, probably is saved on dynamoBD

      • We can find the answer on intl-promotion-service repository, from redeemCoupon method flow;

intl-partners-service (backend)

...

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 by getOffers(sanityId: string)

      • path: intl-partners-service/src/modules/orders/cart-total.service.ts

  • We will add a new attribute orderDiscounts on the webhook IWebhookPriceOrder payload;

Acceptance criteria

  • We can’t impact the other markets;

  • If there is an offer applied on the “input cart“, we need to send the discount to Winrest;

Tasks:

Expand
title6 - TASK: Send to orderDiscount to Winrest
  • Create a feature flag;

  • Update mapOrderDiscounts method to return the offer discount also.

    • path: intl-partners-service/src/modules/orders/orders.service.ts

    • 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 to public;

    • After that, we will send the discount to Winrest via IWebhookPriceOrder adding the orderDiscounts;

  • Payload will be:

Code Block
languagejson
{
  "callbackUrl": "https://euw3-dev-plk-partners-api.rbictg.com/api/v1/orders/84b5f1df-28df-4e6f-a871-a2783602436f/price/callback",
  "cart": {
    "menuSelections": [
      {
        "externalReferenceId": "800853",
        "id": "f9f5b02e-bee8-4dd1-a5fa-222b79516a98",
        "menuSelections": [
          {
            "externalReferenceId": "800852",
            "id": "item_67167",
            "menuSelections": [],
            "price": {
              "amount": 679,
              "currency": "EUR"
            },
            "quantity": 1
          }
        ],
        "price": {
          "amount": 1100,
          "currency": "EUR"
        },
        "quantity": 1
      }
    ]
  },
  "channel": "WHITELABEL_IN_STORE",
  "fees": [],
  "id": "84b5f1df-28df-4e6f-a871-a2783602436f",
  "instructions": [],
  "serviceMode": "TAKEOUT",
  "storeId": "1111",
  "orderDiscounts": [
    {
      "type": "percentage", // or amount
      "value": 50
    }
  ]
}

...