Skip to main content

ProductBulkPrices

Displays volume/tiered pricing for a product in a table format, showing quantity ranges alongside their corresponding unit prices.

Usage

Minimal

<ProductBulkPrices bulkPrices={product?.bulkPrices || []} />

With VAT explicitly set

<ProductBulkPrices
bulkPrices={product?.bulkPrices || []}
includeTax={false}
/>

Without title

<ProductBulkPrices
bulkPrices={product?.bulkPrices || []}
labels={{ title: '' }}
/>

Semi-closed portal (hidden for anonymous users)

<ProductBulkPrices
bulkPrices={product?.bulkPrices || []}
portalMode="semi-closed"
user={authState.user}
/>

Fully customized labels

<ProductBulkPrices
bulkPrices={product?.bulkPrices || []}
portalMode="open"
user={authState.user}
className="my-4"
labels={{
title: 'Staffelprijzen',
quantityFrom: 'Aantal vanaf',
price: 'Prijs',
inclTax: 'incl. BTW',
exclTax: 'excl. BTW',
}}
/>

Configuration

Required

PropTypeDescription
bulkPricesProductPrice[]Bulk price tiers from product.bulkPrices

Pricing

PropTypeDefaultDescription
includeTaxbooleantrue (or localStorage)When true, shows tier.net (incl. VAT). When false, shows tier.gross (excl. VAT). If omitted, defers to the PriceToggle state
taxZonestring'NL'Tax zone code passed to the product query

Visibility

PropTypeDefaultDescription
portalModestring'open'Set to 'semi-closed' to hide the component for anonymous users
userContact | Customer | nullnullAuthenticated user object. Required when portalMode is 'semi-closed'

Appearance

PropTypeDefaultDescription
labelsRecord<string, string>{}Override any UI string (see Labels section below)
classNamestring''Extra CSS class applied to the root element

Labels

KeyDefaultDescription
titleVolume pricingTable heading. Set to '' to hide it entirely
quantityFromQty fromColumn header for the quantity range
pricePriceColumn header for the unit price
inclTaxincl. VATAnnotation shown when prices include tax
exclTaxexcl. VATAnnotation shown when prices exclude tax

Behavior

Price toggle (VAT switch)

The component integrates with the global PriceToggle mechanism:

  • On mount it reads localStorage.getItem('price_include_tax') to determine the initial VAT mode (default: true, meaning prices include VAT).
  • It listens for the priceToggleChanged custom event. When the user flips the toggle elsewhere on the page, this component re-renders with the updated mode.
  • The includeTax prop, when explicitly passed, takes precedence over the localStorage / event-driven value.
  • The price column header dynamically shows "(incl. VAT)" or "(excl. VAT)" to reflect the active mode.

Tier pricing display

Quantity ranges are computed automatically from adjacent tiers using discount.quantityFrom (falling back to tier.quantity):

Tiers (quantityFrom)Displayed ranges
10, 10010--99, 100+
1, 5, 251--4, 5--24, 25+
50 (single tier)50+

Each tier's upper bound is one less than the next tier's quantityFrom. The last tier always displays with a + suffix.

Prices are formatted as EUR with two decimal places (e.g., EUR 12.50).

Visibility rules

  • No bulk prices: The component renders nothing.
  • Semi-closed portal + no user: The component is hidden. Pass a user object to make it visible.

SDK Services

ProductBulkPrices is a display-only component -- it does not call any SDK service itself. It receives data that was already fetched as part of a product query.

Product fields consumed

The component reads from ProductPrice objects (the items inside product.bulkPrices):

FieldTypeUsage
netnumberUnit price including VAT. Displayed when includeTax is true
grossnumberUnit price excluding VAT. Displayed when includeTax is false
quantitynumberFallback quantity threshold when discount.quantityFrom is absent
discount.quantityFromnumberPrimary quantity threshold used to compute the range label

Fetching bulk prices

Bulk prices are returned as part of the product query. For correct per-user quantity thresholds, pass userBulkPriceProductInput:

service.getProduct({
productId: 123,
userBulkPriceProductInput: {
taxZone: 'NL',
companyId: user?.company?.companyId,
contactId: user?.contactId,
},
});

Without userBulkPriceProductInput, the API returns default quantity values (typically 1 for every tier).