← Files WixARCHIVED FILE
skills/wix-headless/references/astro/templates/ecom/discounts.ts
2.75 KB · Oct 4, 2026 · 12:02 UTC
// Live discount-rule fetch + per-product matching for storefront indicators.
// Owned by the ecom pack because Wix Discount Rules is an @wix/ecom primitive.
// Consumers: stores pack (ProductCard ribbon, product-detail offer callout,
// home featured grid). Future ecom-using verticals (bookings, events) can
// reuse the same util without change.
//
// Gotchas encoded here — do not remove:
// 1. queryDiscountRules needs ECOM.DISCOUNT_RULES_READ — must be called via
// auth.elevate(), otherwise a visitor client returns no rules silently.
// 2. Scope objects are returned with `_id` (underscore), not `id`. Check both.
// 3. "All products" scope shape: { _id: "all_<appDefId>", catalogItemFilter:
// { catalogAppId, catalogItemIds: [] } }. Empty array + app id = applies
// to every product.
import { discountRules } from "@wix/ecom";
import { auth } from "@wix/essentials";
export interface DiscountOffer {
id: string;
name: string;
offer: string;
targetType: string;
}
function applies(scopes: any[] | undefined, productId: string): boolean {
if (!scopes || scopes.length === 0) return false;
for (const s of scopes) {
const scopeId = s?._id ?? s?.id;
if (typeof scopeId === "string" && scopeId.startsWith("all_")) return true;
const ids = s?.catalogItemFilter?.catalogItemIds;
if (Array.isArray(ids) && ids.length === 0 && s?.catalogItemFilter?.catalogAppId) return true;
if (Array.isArray(ids) && ids.includes(productId)) return true;
}
return false;
}
function ruleApplies(rule: any, productId: string): boolean {
const values = rule?.discounts?.values ?? [];
for (const v of values) {
if (v?.targetType === "SPECIFIC_ITEMS") {
if (applies(v?.specificItemsInfo?.scopes, productId)) return true;
} else if (v?.targetType === "BUY_X_GET_Y") {
if (applies(v?.buyXGetYInfo?.customerBuys?.scopes, productId)) return true;
if (applies(v?.buyXGetYInfo?.customerGets?.scopes, productId)) return true;
}
}
return false;
}
export async function fetchLiveOffers(): Promise<any[]> {
try {
const elevated = auth.elevate(discountRules.queryDiscountRules);
const res: any = await elevated({ paging: { limit: 50 } } as any);
const list = res?.discountRules ?? res?.items ?? [];
return list.filter((r: any) => r?.active !== false && r?.status !== "EXPIRED");
} catch (err) {
console.error("[discounts] queryDiscountRules failed:", err);
return [];
}
}
export function offersForProduct(rules: any[], productId: string): DiscountOffer[] {
return rules
.filter((r) => ruleApplies(r, productId))
.map((r) => ({
id: r._id ?? r.id,
name: r.name ?? "",
offer: (r.offer ?? r.name ?? "").toString(),
targetType: r?.discounts?.values?.[0]?.targetType ?? "",
}));
}
SHA-256: ec39fb8935d77833b26c8d71d3c9e13ac97d5223e039bd526122dd96de48f718