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
- 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
- Apply promo code: (POST)
trolleys/{trolleyId}/promo-codes
- 200 => Promo code is applied successfully
- 422 => Error (e.g Promo code is already applied)
- Remove applied promo code: (DELETE)
trolleys/promo-codes/{appliedPromoCodeId}
Discount Related Types
// ~/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
trolleyStorefor global usage across different trolley components - Discount response is stored in
core trolley resourceto be used in both trolley and checkout context - At the end of each operation,
order-totalendpoint 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
checkDiscountmethod 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
applyPromoCodemethod 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
removePromoCodeByIdmethod 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
/ordersendpoint POST as part of the parameters - Discount response is sent in
promotionskey 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;
}