Pricing Storage & Display Reference
This page is the authoritative reference for how product pricing is calculated, stored, and displayed across every layer of the Dealers Site โ from WooCommerce product meta through to the Vue widgets and WP Admin order screens.
Overview: Where Pricing Livesโ
nFusion API
โ
โผ
spot_price_<metal> โโโโโ WordPress options (updated by nFusion sync)
spot_price_modifier_<metal>
โ
โโโโถ class-metal-pricing.php โโโ _markup_rate, _markup_mode, _metal_weight (product meta)
โ โ
โ โโโโถ Algolia index (weight, markup_rate, markup_mode, price โ for catalog browse)
โ โ
โ โโโโถ Cart REST API (unit_price, premium, spot_price passed to Vue)
โ โ
โ โโโโถ Order line item meta (_unit_price, _premium, _spot_price, _metal_weight, _markup_mode)
โ
โโโโถ Vue widgets (calculate and display premium badge, tier chart, line totals)
Spot Pricesโ
Spot prices are pulled from the nFusion feed via:
herd php artisan nfusion:sync
They are stored as WordPress options and are never hardcoded:
| Option key | Description |
|---|---|
spot_price_gold | Current gold spot price (USD per troy oz) |
spot_price_silver | Current silver spot price (USD per troy oz) |
spot_price_platinum | Current platinum spot price (USD per troy oz) |
spot_price_palladium | Current palladium spot price (USD per troy oz) |
spot_price_copper | Current copper spot price (USD per troy oz) |
spot_price_modifier_gold | Dollar adjustment applied to gold spot before pricing |
spot_price_modifier_silver | Dollar adjustment applied to silver spot before pricing |
spot_price_modifier_platinum | (same pattern) |
spot_price_modifier_palladium | (same pattern) |
spot_price_modifier_copper | (same pattern) |
class-metal-pricing.php โ get_spot_price( $metal ) reads these options and caches the result per-request.
Product Meta Fieldsโ
All pricing configuration for a product lives in WooCommerce product meta:
| Meta key | Type | Description |
|---|---|---|
_metal_type | string | gold, silver, platinum, palladium, copper. Empty = non-metal product. |
_metal_weight | float | Weight in troy ounces. Defaults to 1 if empty. |
_metal_weight_unit | string | Usually oz. |
_markup_mode | string | Determines how _markup_rate is interpreted. See Markup Modes below. |
_markup_rate | float | Base markup value. Meaning depends on _markup_mode. |
_markup_rate_2 | float | Secondary markup rate (used for sale / volume tier overrides). |
_markup_sale_price | float | Optional override sale price. |
_volume_pricing | JSON | Array of tier breakpoints: [{"qty": 1, "markup": 2.05}, ...] |
These fields are managed via YITH WooCommerce Bulk Edit Pro and through the WP Admin product edit screen.
Markup Modesโ
The _markup_mode meta key controls how _markup_rate is interpreted by class-metal-pricing.php and by the Vue frontend.
| Mode | _markup_mode value | _markup_rate interpretation | Unit price formula |
|---|---|---|---|
| Weight Fixed (default) | `` (empty) | Per-oz dollar premium | (spot + modifier + markup_rate) ร weight |
| Per Piece Fixed | each_fixed | Per-piece flat dollar premium โ not per-oz | See below |
| Weight Percent | weight_percent | Percentage of spot (e.g. 5 = 5%) | (spot ร (1 + rate/100)) ร weight |
| Spot Only | spot | No premium โ pure spot pricing | spot ร weight |
| Fixed Price | (non-metal) | _markup_rate is the final price | _markup_rate directly |
each_fixed in Detailโ
each_fixed means the markup is stored as a flat per-piece dollar amount, regardless of the product's weight.
- A 1 oz coin with
_markup_rate = 2.05โ per-oz premium = $2.05 โ - A 10 oz bar with
_markup_rate = 20.50โ per-oz premium = $2.05 (= 20.50 รท 10) โ
The unit price formula for each_fixed:
unit_price = (spot_per_oz ร metal_weight) + markup_rate
So for the 10 oz bar at spot $75.524:
unit_price = (75.524 ร 10) + 20.50 = $775.74
_markup_rate for each_fixed products is always stored as a per-piece total โ even for multi-oz products. The per-oz conversion only happens in the display layer. Never change the stored value.
Per-Oz vs Per-Piece Premium Ruleโ
This rule governs every display context โ Vue badges, tier charts, cart totals, and admin columns.
| Product weight | Display rule |
|---|---|
>= 1 oz and each_fixed | Divide markup_rate by weight โ show per-oz premium |
< 1 oz and each_fixed | Show markup_rate as-is โ per-piece total |
| Any other mode | Show markup_rate as-is |
In code (Vue):
const weight = product.weight > 0 ? product.weight : 1;
const perOzMarkup = (markupMode === 'each_fixed' && weight >= 1)
? markup.value / weight
: markup.value;
In PHP (admin display):
$display_premium = ( $markup_mode === 'each_fixed' && $metal_weight >= 1.0 )
? $premium / $metal_weight
: $premium;
Algolia Index Attributes (Pricing)โ
When products are indexed by class-algolia.php, the following pricing attributes are written to each Algolia record. The Vue catalog widget reads these directly via InstantSearch / Algolia hits.
| Algolia attribute | Source | Notes |
|---|---|---|
weight | _metal_weight | Defaults to 1 if meta is empty |
weight_unit | _metal_weight_unit | Usually oz |
markup_rate | _markup_rate | Always the raw per-piece value for each_fixed |
markup_mode | _markup_mode | Used by Vue to select the display formula |
metal | _metal_type | gold, silver, etc. |
price | Calculated live unit price | Recalculated on each nFusion sync |
tiers | _volume_pricing | JSON tier array |
product.weight in the Vue widgets comes from this Algolia weight attribute. It is what drives the per-oz division in linePremium() and perOzTierMarkup().
Order Line Item Meta Fieldsโ
When a dealer adds a product to their cart and an order is created, these meta fields are snapshotted onto each order line item:
| Meta key | Type | What is stored |
|---|---|---|
_spot_price | float | Spot price per oz at the moment of cart add |
_premium | float | Premium value โ see rule below |
_metal_weight | float | Product weight in oz at time of order |
_markup_mode | string | Markup mode at time of order |
_unit_price | float | Final calculated unit price at time of order |
_premium Storage Ruleโ
_premium follows the same per-oz / per-piece rule as the display layer:
| Product weight | _premium stored as |
|---|---|
>= 1 oz and each_fixed | Per-oz premium (e.g. 2.05 for a 10 oz bar) |
< 1 oz and each_fixed | Per-piece premium total (e.g. 1.50 for a 0.5 oz coin) |
| Any other mode | Per-oz premium as calculated |
This is normalised in class-woocommerce-cart.php at reload/sync time, before the value is ever written to the order.
Order Recalculation Formulaโ
When staff trigger a price recalculation from the WP Admin order edit screen (class-woocommerce-order-edit.php), the formula used is:
For each_fixed with weight >= 1oz (per-oz _premium):
unit_price = (spot_per_oz + _premium) ร _metal_weight
Example: (75.524 + 2.05) ร 10 = $775.74
For each_fixed with weight < 1oz (per-piece _premium):
unit_price = (spot_per_oz ร _metal_weight) + _premium
For all other modes:
unit_price = (spot_per_oz + _premium) ร _metal_weight
Admin Order Edit โ Premium Columnโ
The Premium column visible in WP Admin โ Orders โ Edit Order โ line items reads _premium from the line item and displays it. Because _premium is now stored as per-oz for weight โฅ 1 oz products, no division is needed โ the stored value is shown directly.
The column is rendered by class-woocommerce-order-edit.php โ add_admin_order_item_values().
Vue Widget Premium Displayโ
order-view โ CatalogGridItem.vueโ
The .product-premium badge next to each product in the trade catalog:
// linePremium() in CatalogGridItem.vue
const weight = props.product.weight > 0 ? props.product.weight : 1;
return ( priceMode.value === 'Per Piece' && weight >= 1 )
? markup.value / weight
: markup.value;
priceMode is set by getPriceMode() in Store/index.js, which maps each_fixed โ 'Per Piece'.
cart-view โ CartItem.vueโ
The premium badge next to each line item in the cart:
// linePremium() in CartItem.vue
const weight = props.product.weight > 0 ? props.product.weight : 1;
return ( props.product.markup_mode === 'each_fixed' && weight >= 1 )
? markup.value / weight
: markup.value;
_shared โ ItemDetails.vue (Tier Chart)โ
The expandable tier pricing table shown under each product:
// perOzTierMarkup() in ItemDetails.vue
const weight = props.product.weight > 0 ? props.product.weight : 1;
return ( props.priceMode === 'Per Piece' && weight >= 1 )
? tier.markup / weight
: tier.markup;
All three contexts apply the same per-oz / per-piece rule.
class-metal-pricing.php โ Key Methodsโ
| Method | Returns | Description |
|---|---|---|
get_spot_price( $metal ) | float | Current spot price from WP options (cached per request) |
get_price_data( $product ) | array | Full pricing array: metal_type, weight, markup_mode, markup_rate, markup, base_price, tiers, spot_price |
get_unit_price( $product, $qty ) | float | Live unit price for a given quantity (applies tier logic) |
calculate_markup( $spot, $rate, $modifier, $mode ) | float | Computes markup value from raw fields |
calc_spot_price_from_product( $metal, $weight, $unit_price ) | float | Back-calculates spot from the current product price |
Data Flow: Add to Cart โ Order Line Itemโ
1. Dealer clicks "Add to Cart" in the Vue catalog widget
โโ CatalogGridItem.vue โ addCartItem() in Store/index.js
โ
โผ
2. POST to WooCommerce cart REST endpoint
โโ class-woocommerce-cart.php โ add_to_cart()
โ
โผ
3. Cart reload (class-woocommerce-cart.php โ reload())
โโ get_price_data() called for each cart item
โโ premium normalised to per-oz if each_fixed && weight >= 1
โโ _spot_price, _premium, _unit_price written to cart item session
โ
โผ
4. Order created at checkout
โโ Cart item meta copied to order line item meta
โโ _spot_price, _premium, _metal_weight, _markup_mode, _unit_price stored
โ
โผ
5. WP Admin โ Edit Order
โโ Premium column reads _premium (already per-oz for weight >= 1)
โโ Recalculation uses (spot + _premium) ร weight for each_fixed >= 1oz
Common Pitfallsโ
| Mistake | Correct approach |
|---|---|
Dividing _premium by weight in admin display | _premium is already per-oz for weight โฅ 1 โ display it as-is |
Storing per-oz premium directly in _markup_rate for each_fixed | _markup_rate is always per-piece โ only the display/storage normalisation layer converts |
| Assuming weight is always set | _metal_weight can be empty โ always default to 1 |
| Hardcoding spot prices | Always read from spot_price_<metal> WordPress options |
Editing dist/app.js directly | Always edit source files and rebuild with npx mix --production |