Skip to main content

Sale Type

This document covers the product-level Sale Type classification - the umbrella concept over the existing dual-sale logic plus the single-sale variants (SELL_THRU_RTS, MOD_ONLY).

Overviewโ€‹

Sale Type is a product-level classification that determines two things at once:

  1. Whether the PDP shows the Estimated Arrival radio selector
  2. Whether a rush fee line is added to the cart when Rush Production is the active variant

The classification lives on a single metafield (custom.sale_type) and is read at both render time (PDP and add-on UI) and add-to-cart time (line item attributes).

Metafieldsโ€‹

custom.sale_type (Product Level)โ€‹

Single-line text enum, defaults to DUAL_SALE when unset, empty, or unrecognized.

ValuePDP BehaviorCart BehaviorRush Fee
DUAL_SALE (default)Standard / Rush radio selector + shipping alert render as todayRTS or MTO attributes based on the user's selection; rush fee line added when Rush is selectedCharged via separate rush fee line item
SELL_THRU_RTSSelector hidden; heading shows Rush timeline when any Rush variant has stock, else Standard date rangeVariant resolved by inventory: Rush while in stock, Standard once Rush sells out - attributes match the resolved variantSuppressed
MOD_ONLYSelector hidden; heading always shows the Standard date rangeResolved variant is always Standard Production; MTO attributesSuppressed

custom.rush_fee (Companion Rule)โ€‹

Single-sale products must leave custom.rush_fee unset - not $0.

The $0 GID convention is a dual-sale-only signal that hides Standard and forces free Rush (see Rush Production). On a single-sale product, the explicit absence of any rush fee variant communicates "no rush fee path exists at all." Setting it to $0 on a single-sale product would be ambiguous and is treated as a merch configuration error.

PDP Behaviorโ€‹

Dual sale (DUAL_SALE)โ€‹

No change from prior behavior. The Estimated Arrival fieldset and the gray "Shipping rates still applyโ€ฆ" alert both render. Customers pick Standard or Rush, and the cart line picks up the corresponding attributes.

Single sale (SELL_THRU_RTS and MOD_ONLY)โ€‹

The Estimated Arrival selector fieldset and the shipping alert are both hidden. The "Estimated Arrival: {date}" heading and its info tooltip continue to render - the heading reads from the resolved variant, so customers still see the right date / business-day copy.

For products that don't carry an Estimated Arrival option at all (single-variant accessories such as scarves or ties), the cart treats them the same as today's RTS default - no MTO attributes, no rush fee.

Add-on QuickViewโ€‹

For single-sale add-on products opened in the QuickView drawer:

  • The Estimated Arrival option list is filtered out of the rendered options
  • selectedOptions is pre-populated with the resolved Estimated Arrival value so the variant resolves correctly when the user saves
  • The shipping notice (tooltip + alert) reads off the resolved variant

Add-on Upsell tileโ€‹

The "You May Also Need" tile shows a "Get it ..." delivery line only for single-sale add-ons:

  • MOD_ONLY -> Standard date range (e.g., Get it Apr 14 - Apr 21)
  • SELL_THRU_RTS with any Rush variant in stock -> Rush copy (e.g., Get it in ~5 business days)
  • SELL_THRU_RTS with no Rush variants in stock -> Standard date range
  • DUAL_SALE -> no copy on the tile (unchanged)

Known limitation: the Rush check on the tile is aggregate - it asks "does any Rush variant have stock?" If only some sizes have Rush stock, the tile may advertise the Rush timeline while the resolver later assigns Standard for the size the customer picks. The QuickView and final cart line always show the correct per-size timeline.

Inventory Cutover (SELL_THRU_RTS)โ€‹

The cutover from Rush -> Standard happens per variant, not per product.

For a given (size, color) combination, the resolver assigns the Rush Production variant while it has stock (availableForSale && quantityAvailable > 0). When that specific Rush variant goes out of stock, the resolver falls back to the Standard Production variant for the same combination. Other (size, color) combinations keep their own Rush/Standard state independently.

This means it's normal for a SELL_THRU_RTS product to be partially Rush and partially Standard across sizes during a sell-through.

Cart Attributesโ€‹

Cart attributes are written by the same helpers that handle dual-sale today - prepareReadyToShipProperty (RTS) and prepareMTOProperties (MTO) - but selected by sale type instead of by user choice. For attribute key/value details, see:

The difference for single-sale products is purely in what triggers each branch:

  • MOD_ONLY -> always MTO attrs
  • SELL_THRU_RTS with Rush variant resolved -> RTS attrs
  • SELL_THRU_RTS with Standard fallback (Rush OOS) -> MTO attrs
  • No rush fee line item is created for any single-sale variant
  • No _rush_fee_amount attribute is added to the main line for any single-sale variant

Resolution Logicโ€‹

The sale type and the resolved variant are computed by two pure helpers in app/lib/saleType.ts:

// Reads custom.sale_type, defaults to DUAL_SALE when missing/unrecognized
getSaleType(product) -> SaleType

// True for MOD_ONLY and SELL_THRU_RTS, false for DUAL_SALE
isSingleSale(saleType) -> boolean

// Picks the variant for a single-sale product:
// MOD_ONLY -> Standard variant matching selectedOptions
// SELL_THRU_RTS -> Rush if in stock, else Standard fallback
// DUAL_SALE -> undefined (caller picks)
resolveSingleSaleVariant({ variants, selectedOptions, saleType }) -> variant | undefined

The PDP's EstimatedArrivalOptionGroup calls resolveSingleSaleVariant() to compute the right Estimated Arrival value and dispatches it through the reducer when the customer picks size. The cart-side logic in addToCart.component.tsx then reads the resolved variant's options to pick MTO vs RTS attributes and to skip the rush fee path.

The Add to Bag click handler in addToCart.component.tsx also calls resolveSingleSaleVariant() directly as a fallback, in case the PDP's useEffect hasn't finished dispatching the resolved arrival into state by the time the customer clicks. This keeps the click self-sufficient and prevents an undefined variant from reaching the cart line builder.

Hydrogen Storefrontโ€‹

  • app/lib/saleType.ts - getSaleType, isSingleSale, resolveSingleSaleVariant, hasInStockRushVariant
  • app/components/EstimatedArrivalOptionGroup/estimatedArrivalOptionGroup.component.tsx - Hides selector + alert for single-sale; dispatches resolved arrival
  • app/components/AddToCart/addToCart.component.tsx - Skips rush fee path for single-sale; routes MTO vs RTS per resolved variant
  • app/components/AddOnUpsell/addOnUpsell.component.tsx - Renders "Get it ..." line on the upsell tile for single-sale add-ons
  • app/components/AddOnQuickView/addOnQuickView.component.tsx - Filters Estimated Arrival option list and pre-fills selectedOptions for single-sale add-ons
  • app/components/ProductOptions/utils.tsx - resolveArrivalDates shared helper
  • app/graphql/storefront/ProductQuery.ts - saleType metafield queried on PRODUCT_BASIC_FRAGMENT
  • app/types/pdp.d.ts - SaleType union type

Builder.io Site Settingsโ€‹

Same defaults as dual-sale - see Standard Production and Rush Production. The single-sale paths reuse siteSettings.dualSale.standardProductionDays, mtoDeliveryWindow, productionDays, and productionWeeks for date copy.