Versions Compared

Key

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

Questions:

...

  • Create 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

...

  • First, it will call the PriceOrder mutation on intl-gateway or intl-graphql

  • This mutation will call the backend: intl-partners-service repository.

    • on this repository, will call:

      • public async price(@Body() payload: PriceOrderRequestDto): Promise<OrderResponseDto>

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

      • after: public async price(input: IPriceOrderRequest): Promise<IOrder>

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

        • on this file is where calculate the order and is generated the rbiOrderId.

Info

Currently for some reason, the calc is wrong in 2 aspects:

  • Setting up wrong offers;

  • Missing configOffer calc;

intl-partners-service

  • When the frontend call the backend, the first method ran is:

    • public async price(@Body() payload: PriceOrderRequestDto): Promise<OrderResponseDto>

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

    • after: public async price(input: IPriceOrderRequest): Promise<IOrder>

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

      • on this file is where calculate the order and is generated the rbiOrderId.

  • On price(input: IPriceOrderRequest) on file orders.service.ts, there is a call method named this.cartTotalService.calculate(input.cart).

    • Inside this method is calc the some type offers, for example System Wide offers, but there isn’t the calc relative Offer configs.

      • The method that calc relative the system wide offer is getSystemWideOfferIncentiveDiscountAndType(offerId: string): Promise<IRewardIncentive | undefined>

        • path: @intl-sanity/loyalty/index.ts

Expand
titlegetSystemWideOfferIncentiveDiscountAndType
Code Block
languagetypescript
getSystemWideOfferIncentiveDiscountAndType(offerId) {
    return this.reader.fetch(`
  *[_type == "systemwideOffer"
    && _id == $offerId
    && defined(incentives[_type == 'offerDiscount'][0])][0] {
      incentives[0]-> {
        discountType,
        discountValue
      }
    }
  `, { offerId });
}

Info

Possible solution configOffer calc:

  • We will need to create a new method to get the config offers on intl-sanity repository:

    • path: intl-sanity/loyalty/index.ts

Expand
titlegetLoyatyConfigOfferType
Code Block
languagetypescript
getLoyatyConfigOfferType(offerId) {
  return this.reader.fetch(`
  *[_type == "configOffer"
    && _id == $offerId
    && defined(incentives[_type == 'offerDiscount'][0])][0] {
      incentives[0]-> {
        discountType,
        discountValue
      }
    }
  `, { offerId });
}
  • After it, we will need to update the intl-sanity lib on intl-partners-service to have access at this new query.

  • In the next, we can do the new method to calc this type offer that can be after getSystemWideOfferIncentiveDiscountAndType method:

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

Expand
titleCurrently code
Code Block
 /**
   * Calculates cart total.
   *
   * @param {ICart} cart Contents of the cart.
   * @returns {Promise<number>} A cart total.
   */
  public async calculate(cart: ICart): Promise<number> {
    // our base will be a simple sum of prices of all items
    let total = cart.cartEntries.reduce((sum: number, current: ICartEntry) => {
      if (current.price) {
        // even though we have price for entire item already we need to look for premium items which are not included
        const premiumItemsPrice = this.pricePremiumItems(current);
        return sum + current.price + premiumItemsPrice;
      }
      return sum + this.priceCartItem(current);
    }, 0);

    this.logger.debug({ total }, `Base total calculated`);

    // apply discount (we assume that there cannot be more than one)
    const discountEntry = cart.cartEntries.find(
      (entry: ICartEntry) => entry.type === CartEntryType.offerDiscount,
    );

    if (discountEntry) {
      const { sanityId } = discountEntry;

      const offer = await this.sanityLoyalty.getSystemWideOfferIncentiveDiscountAndType(sanityId);

      if (offer) {
        const { discountValue } = offer.incentives;
        this.logger.debug({ discountValue, sanityId }, 'Applying discount (system wide offer)');
        total = total - Math.round((total * discountValue) / 100);
      } else {
        this.logger.warn({ sanityId }, 'Offer not found in sanity');
      }
    }

    this.logger.info({ total }, 'Cart total calculated');

    return total;
  }
Expand
titlePossible calc code (POC - will need to refactor)
Code Block
languagetypescript
/**
   * Calculates cart total.
   *
   * @param {ICart} cart Contents of the cart.
   * @returns {Promise<number>} A cart total.
   */
  public async calculate(cart: ICart): Promise<ICart> {
    // apply discount (we assume that there cannot be more than one)
    const discountEntry = cart.cartEntries.find(
      (entry: ICartEntry) => entry.type === CartEntryType.offerDiscount,
    );
    
    const cartEntriesWithDiscount = cart.cartEntries.filter(
      (entry: ICartEntry) => entry.type !== CartEntryType.offerDiscount,
    );

    let offerSystemWide = {} as IRewardIncentive | undefined;
    let configOffer = {} as ILoyatyConfigOffer | undefined;

    if (discountEntry) {
      const { sanityId } = discountEntry;
      offerSystemWide = await this.sanityLoyalty.getSystemWideOfferIncentiveDiscountAndType(
        sanityId,
      );
      configOffer = await this.sanityLoyalty.getLoyatyConfigOfferType(sanityId);
    }

    const cartDiscounted = cart.cartEntries.map((cartEntrie: ICartEntry) => {
      if (cartEntrie.type === CartEntryType.offerDiscount) {
        return cartEntrie;
      }

      const discountPromo = offerSystemWide?.incentives || configOffer?.incentives;
      if (cartEntrie.price && discountPromo) {
        const { discountValue, discountType } = discountPromo;
        return {
          ...cartEntrie,
          price: this.calDiscount({
            description: offerSystemWide?.incentives
              ? '(system wide offer)'
              : configOffer?.incentives
              ? '(config offer)'
              : '',
            discountType,
            discountValue,
            length: cartEntriesWithDiscount.length,
            value: cartEntrie.price,
          }),
        };
      }
      return cartEntrie;
    });

    cart.cartEntries = cartDiscounted;

    // our base will be a simple sum of prices of all items
    const total = cart.cartEntries.reduce((sum: number, current: ICartEntry) => {
      if (current.type === CartEntryType.offerDiscount) {
        return sum;
      }

      if (current.price) {
        // even though we have price for entire item already we need to look for premium items which are not included
        const premiumItemsPrice = this.pricePremiumItems(current);
        return sum + current.price + premiumItemsPrice;
      }
      return sum + this.priceCartItem(current);
    }, 0);

    const cartUpdated = {
      ...cart,
      subTotalCents: total,
      totalCents: total,
    };

    return cartUpdated;
  }
  
  
  
  
private calDiscount({
    discountValue,
    discountType,
    value,
    length,
    description,
  }: {
    discountValue: number;
    discountType: string;
    value: number;
    length: number;
    description?: string;
  }): number {
    switch (discountType) {
      case 'amount':
        value = value - Math.round((discountValue * 100) / length);
        break;
      default:
        value = value - Math.round((value * discountValue) / 100);
        break;
    }
    this.logger.debug({ discountType, discountValue }, `Applied discount ${description}`);
    return value;
  }

...

Remembering that all these problems can occur because there is an error in the offer settings somewhere.

Screenshots

...

Screenshots

  • N/A

POC

Impact Analysis

  • N/A

Dependencies

...