Trolley

Ecrebo Coupons & Discounts

Promotions applied on trolley

Overview

Ecrebo is a third party receipt and coupon marketing platform and ToolStation uses it to target products and customers to apply promotional campaigns.

Important Note: The frontend doesn't communicate directly to Ecrebo APIs. ECOM backend checks for available coupons and discounts against the trolley items and applies them. Discount against products are also fetched by the ECOM backend from Ecrebo campaigns. (e.g 4 + 1 free)


Core ECOM API Endpoints

  1. Check discounts: (POST) trolleys/{trolleyId}/check-discounts
  • 200 => List of applied promo codes are returned in discount response
  • 204 => Trolley is not qualified for promotions/discounts -> undefined response
  1. Apply promo code: (POST) trolleys/{trolleyId}/promo-codes
  • 200 => Promo code is applied successfully
  • 422 => Error (e.g Promo code is already applied)
  1. Remove applied promo code: (DELETE) trolleys/promo-codes/{appliedPromoCodeId}
// ~/types/ecom/trolley/trolley.type.ts

export type TrolleyDiscountResponse = {
  expires: number;
  discount_total: number;
  line_discounts: TrolleyLineDiscount[];
  qualifying_promotions: QualifyingPromotion[];
  free_products: FreeProductsData[];
  applied_promo_codes: string[];
  messages: string[];
};

/* Item wise discount */
export type TrolleyLineDiscount = {
  grant_id: number;
  product_code: string;
  promotion_id: number;
  type: string;
  amount: number;
  staff_discount: boolean;
};

/* Valid or Applied Coupon Code */
export type QualifyingPromotion = {
  id: number;
  code: string;
  name: string;
  valid_to: Date;
};

/* Combo Gifts */
export type FreeProductsData = {
  product_code: string;
  quantity: number;
};

Core Methods

  • Actions are kept in trolleyStore for global usage across different trolley components
  • Discount response is stored in core trolley resource to be used in both trolley and checkout context
  • At the end of each operation, order-total endpoint is called to update the final order summary
// ~/stores-datalayer/trolley.datalayer.ts
async function checkDiscount() {
  const trolleyId = trolleyStore.trolley_id;

  const response = await EcomTrolleyService.getTrolleyDiscounts(
    trolleyId,
    trolleyStore.trolley_session_token
  );

  // Success case-1: response is undefined but status code is 204
  // => trolley not eligible for promotions / no valid promotion exists
  if (!response) {
    trolleyStore.trolley_discount_data = null;
    return;
  }

  // Success case-2: some promotion is applied on the trolley
  trolleyStore.trolley_discount_data = response.data as TrolleyDiscountResponse;
}

// ~/services/ecom.trolley.service.ts
async function getTrolleyDiscounts(
  trolleyId: string,
  trolleySessionToken?: string | null
) {
  const trolleyHeaders = {};
  const authStore = useAuthStore();

  if (authStore.user) {
    trolleyHeaders["X-Toolstation-Customer-Id"] = authStore.user.token;
  }
  if (trolleySessionToken)
    trolleyHeaders["X-Toolstation-Session-Id"] = trolleySessionToken;

  return await useAjaxEcom<{ data: TrolleyDiscountResponse }>(
    `/trolleys/${trolleyId}/check-discounts`,
    {
      method: "post",
      headers: trolleyHeaders,
    }
  );
}
  • applyPromoCode
// ~/stores-datalayer/trolley.datalayer.ts
export async function applyPromoCode(code: string) {
  const trolleyId = trolleyStore.trolley_id;

  // Coupon codes are case insensitive, so we convert to uppercase before sending to backend for verification
  const promoCodeToCheck = code.toUpperCase().trim();

  // STEP-1: Dispatch a POST request to apply the coupon code
  const promoCodeResponse = await EcomTrolleyService.applyPromoCode(
    trolleyId,
    promoCodeToCheck,
    trolleyStore.trolley_session_token
  );

  trolleyStore.applied_promo_codes = promoCodeResponse.data.applied_promo_codes;

  // STEP-2: Check discount after applying the promo code
  await trolleyStore.getTrolleyDiscounts();

  // STEP-3: If no discount is applied i.e promo code is invalid, remove it manually
  if (
    !trolleyStore.trolley_discount_data ||
    !trolleyStore.trolley_discount_data.applied_promo_codes.includes(
      promoCodeToCheck
    )
  ) {
    const invalidCode = trolleyStore.applied_promo_codes.find(
      (appliedPromoCode) => appliedPromoCode.code === promoCodeToCheck
    );
    if (invalidCode) {
      removeAppliedPromoCode(invalidCode.id, false);
    }
    return;
  }

  // STEP-4: Update order summary to reflect final amount
  setOrderTotals();
}

// ~/services/ecom.trolley.service.ts
async function applyPromoCode(
  trolleyId: string,
  code: string,
  trolleySessionToken?: string
) {
  const trolleyHeaders {};
  const authStore = useAuthStore();

  // Apply ECOM headers
  if (authStore.user) {
    trolleyHeaders["X-Toolstation-Customer-Id"] = (authStore.user as any).token;
  }

  if (trolleySessionToken)
    trolleyHeaders["X-Toolstation-Session-Id"] = trolleySessionToken;

  return await useAjaxEcom<{ data: TrolleyResponse }>(`/trolleys/${trolleyId}/promo-codes`, {
    method: "post",
    headers: trolleyHeaders,
    params: {
      code,
    },
  });
}
  • removePromoCodeById
// ~/stores-datalayer/trolley.datalayer.ts
export async function removePromoCodeById(promoCodeId: number) {
  const trolleyId = trolleyStore.trolley_id;
  const trolleySessionToken = trolleyStore.trolley_session_token;

  // STEP-1: Dispatch a DELETE request to remove the coupon code
  const response = await EcomTrolleyService.removePromoCodeById(
    trolleyId,
    promoCodeId,
    trolleySessionToken
  );

  trolleyStore.applied_promo_codes = response.data.applied_promo_codes;

  // STEP-2: Update order summary to reflect final amount
  setOrderTotals();
}

// ~/services/ecom.trolley.service.ts
async function removePromoCodeById(
  trolleyId: string,
  appliedPromoCodeId: number,
  trolleySessionToken?: string
) {
  const trolleyHeaders = {};
  const authStore = useAuthStore();

  // Apply ECOM headers
  if (authStore.user) {
    trolleyHeaders["X-Toolstation-Customer-Id"] = (authStore.user as any).token;
  }

  if (trolleySessionToken)
    trolleyHeaders["X-Toolstation-Session-Id"] = trolleySessionToken;

  return await useAjaxEcom<{ data: TrolleyResponse }>(
    `/trolleys/promo-codes/${appliedPromoCodeId}`,
    {
      method: "delete",
      headers: trolleyHeaders,
    }
  );
}

1. Check Discount

  • checkDiscount method is called when user visits /trolley or /checkout page
  • It checks for available discounts against the trolley items
  • If a discount is found in Ecrebo, it is applied to the trolley by the backend

Trolley Discount Check On Mount: (combo discount)

2. Apply Promo Code

  • applyPromoCode method is called when user enters a promo code
  • It checks if the coupon code is valid and eligible for the trolley
  • If eligibility is validated by Ecrebo, discount data is applied to the trolley by the backend

Note: Invalid promo codes also appear in trolley resource but not in discount response, So they are manually removed from the trolley resource by making an extra request

Coupon Apply:

3. Remove Promo Code

  • removePromoCodeById method is called when user removes a promo code by clicking on the cross icon against applied code
  • It removes the discount applied by that promo code from the trolley
  • If a trolley item loses its eligibility for the discount either by decreasing the quantity or removing the item, the discount is removed from the trolley

Demo:


Checkout - Order Place

  • Finally, during order place, the associated promotional data is sent to the backend to the /orders endpoint POST as part of the parameters
  • Discount response is sent in promotions key of the request body
  • Read Order Place section for more details
const createOrderRequest = {
  trolley_id: trolleyStore.trolley_id,
  order_total: trolleyStore.final_order_total_amount,
  billing_address_id: checkoutStore.billing_address?.id,
  payment_type: type,
  payment_method: method,
  payment_nonce: nonce,
  payment_device_data: checkoutStore.device_data,
};

// Send promotions data received from `/check-discount` endpoint
if (trolleyStore.trolley_discount_data) {
  createOrderRequest.promotions = trolleyStore.trolley_discount_data;
}

References


Copyright © 2026