ProductBulkPrices
Displays volume/tiered pricing for a product in a table format, showing quantity ranges alongside their corresponding unit prices.
Usage
- React
- Build Your Own
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',
}}
/>
To build a custom bulk-prices display:
-
Obtain bulk prices -- they live on
product.bulkPrices(an array ofProductPriceobjects) returned byProductService.getProduct(). IncludeuserBulkPriceProductInputfor user-specific tiers. -
Choose the price field -- use
tier.netfor VAT-inclusive prices andtier.grossfor VAT-exclusive prices. To support the global PriceToggle, readlocalStorage.getItem('price_include_tax')and listen forpriceToggleChangedevents. -
Compute quantity ranges -- iterate over the sorted tiers. For each tier, read
tier.discount?.quantityFrom(or fall back totier.quantity). The upper bound of a range is one less than the next tier's threshold. The final tier has no upper bound. -
Handle empty state -- if the array is empty or absent, render nothing.
-
Handle semi-closed portal -- if your store uses
portalMode: 'semi-closed', check for an authenticateduserbefore rendering pricing.
Configuration
- React
- Build Your Own
Required
| Prop | Type | Description |
|---|---|---|
| bulkPrices | ProductPrice[] | Bulk price tiers from product.bulkPrices |
Pricing
| Prop | Type | Default | Description |
|---|---|---|---|
| includeTax | boolean | true (or localStorage) | When true, shows tier.net (incl. VAT). When false, shows tier.gross (excl. VAT). If omitted, defers to the PriceToggle state |
| taxZone | string | 'NL' | Tax zone code passed to the product query |
Visibility
| Prop | Type | Default | Description |
|---|---|---|---|
| portalMode | string | 'open' | Set to 'semi-closed' to hide the component for anonymous users |
| user | Contact | Customer | null | null | Authenticated user object. Required when portalMode is 'semi-closed' |
Appearance
| Prop | Type | Default | Description |
|---|---|---|---|
| labels | Record<string, string> | {} | Override any UI string (see Labels section below) |
| className | string | '' | Extra CSS class applied to the root element |
Function signature
function renderBulkPrices(bulkPrices: ProductPrice[], options?: BulkPriceOptions): void
Options
| Field | Type | Default | Maps to |
|---|---|---|---|
bulkPrices | ProductPrice[] | — | bulkPrices prop |
includeTax | boolean | true | includeTax prop |
taxZone | string | 'NL' | taxZone prop |
portalMode | string | 'open' | portalMode prop |
user | Contact | Customer | null | null | user prop |
UI-only props
The following props are purely visual and have no SDK equivalent: labels, className.
Labels
- React
- Build Your Own
| Key | Default | Description |
|---|---|---|
title | Volume pricing | Table heading. Set to '' to hide it entirely |
quantityFrom | Qty from | Column header for the quantity range |
price | Price | Column header for the unit price |
inclTax | incl. VAT | Annotation shown when prices include tax |
exclTax | excl. VAT | Annotation shown when prices exclude tax |
const defaultLabels = {
title: 'Volume pricing',
quantityFrom: 'Qty from',
price: 'Price',
inclTax: 'incl. VAT',
exclTax: 'excl. VAT',
};
These are suggested defaults. Override per-key to support localization.
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
priceToggleChangedcustom event. When the user flips the toggle elsewhere on the page, this component re-renders with the updated mode. - The
includeTaxprop, 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, 100 | 10--99, 100+ |
| 1, 5, 25 | 1--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
userobject 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):
| Field | Type | Usage |
|---|---|---|
net | number | Unit price including VAT. Displayed when includeTax is true |
gross | number | Unit price excluding VAT. Displayed when includeTax is false |
quantity | number | Fallback quantity threshold when discount.quantityFrom is absent |
discount.quantityFrom | number | Primary 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).