ESHOPMAN Alert: Unmasking the Silent Buy-Get Promotion Bug Affecting Storefront Pricing
Introduction: The Silent Threat to Your ESHOPMAN Promotions
In the dynamic world of headless commerce with ESHOPMAN, ensuring consistent and accurate promotion application is paramount for both merchant profitability and customer satisfaction. However, a critical bug within ESHOPMAN's promotion module has been identified, causing specific 'buy-get' promotions to silently fail, leading to incorrect pricing on storefronts deployed via HubSpot CMS. This issue highlights the importance of understanding the underlying logic of your ESHOPMAN backend, built on Node.js/TypeScript, to maintain a seamless shopping experience.
The Problem: Cart Order Dictates Promotion Success
The core of the issue lies with 'buy-get' promotions where the target product (the one receiving the discount) is also included in the 'buy_rules' (the products required to trigger the promotion). In such scenarios, the promotion's application becomes entirely dependent on the order in which line items are added to the cart. When the promotion fails, there's no error message, no warning, and no adjustment – the cart simply remains at full price. This silent failure makes it incredibly difficult for merchants, support teams, and customers to identify the problem.
Reproduction Example:
Consider an ESHOPMAN 'buyget' promotion configured as follows:
type: buyget
application_method:
type: percentage
value: 61
target_type: items
allocation: across
buy_rules_min_quantity: 2
apply_to_quantity: 1
max_quantity: 1
buy_rules: [{ attribute: "items.product.id", operator: "in", values: [A, B, C] }]
target_rules: [{ attribute: "items.product.id", operator: "eq", values: [A] }]If a customer's cart contains one unit each of products A, B, and C, all at the same unit price (e.g., 125), the promotion's application varies drastically based purely on the line item order:
- A, B, C: ✅ Promotion applied
- A, C, B: ✅ Promotion applied
- B, A, C: ❌ Promotion NOT applied
- C, A, B: ❌ Promotion NOT applied
- B, C, A: ❌ Promotion NOT applied
- C, B, A: ❌ Promotion NOT applied
The expected behavior is for the promotion to apply in all cases, as a valid assignment (e.g., B and C as the buy side, A as the target) always exists.
Diving Deeper: The Technical Roots in ESHOPMAN's Core
The ESHOPMAN community identified two primary causes within the platform's Node.js/TypeScript promotion module (specifically in the logic handling promotion computations):
1. The sortByPrice Comparator Anomaly
A comparator function, sortByPrice, used in ESHOPMAN's promotion calculation logic (e.g., within utils/compute-actions/buy-get.ts and sort-by-price.ts) was found to be flawed. It never returns 0 when comparing items with equal prices:
function sortByPrice(a, b) {
return MathBN.lt(a.subtotal, b.subtotal) ? 1 : -1
}When all items in a cart have the same price, this comparator incorrectly implies a strict ordering. This leads to an implementation-defined sort result, often reversing the array order. Consequently, the order in which items are processed for promotion eligibility becomes unpredictable and non-deterministic, directly influencing whether the discount is applied.
2. Greedy Buy-Side Selection Without Backtracking
Another contributing factor is the greedy approach taken by the promotion module's preparePromotionApplicationState function. It reserves the first eligible items as the 'buy' side without any mechanism for backtracking. If this greedy selection inadvertently consumes the target item (Product A in our example), it then incorrectly determines that no applicable target quantity remains:
const applicableQuantity = MathBN.sub(availableTargetQuantityForItem, quantityUsedInBuyRules)
if (MathBN.lte(applicableQuantity, 0)) {
continue
}This means even if a valid alternative assignment exists where the target item is not consumed by the 'buy' side, the promotion logic gives up, resulting in the silent failure.
Real-World Impact: Financial Discrepancies and Customer Frustration
The impact of this silent bug is significant. Merchants using ESHOPMAN could unknowingly be charging customers full price for items that should have been discounted. This leads to lost revenue from potential sales, customer complaints, and a damaged brand reputation. The inability to attribute the failure makes troubleshooting a nightmare for support teams, as re-entering promotion codes for automatic promotions has no effect.
The ESHOPMAN Community Solution
Thanks to vigilant community members, a fix for this critical issue has been developed and is being integrated into ESHOPMAN's core. The proposed solution addresses both root causes:
- Tie-Safe Comparators: Modifying the
sortByPriceand similar comparators to return0on equality ensures a stable, deterministic order for equally priced items. - Smarter Buy-Side Selection: Implementing logic to prefer non-target-eligible items for the buy side, or retrying the allocation by excluding target items if the first pass fails, prevents the greedy selection from prematurely blocking the promotion.
Ensuring Robust ESHOPMAN Storefronts
This incident underscores the importance of staying updated with ESHOPMAN's core modules and best practices. For developers working with ESHOPMAN's Admin API and Store API, understanding these nuances in promotion logic is crucial for building resilient commerce experiences. Merchants should regularly review their promotion configurations, especially complex 'buy-get' scenarios, and monitor their storefronts deployed via HubSpot CMS for unexpected pricing discrepancies.