Skip to main content

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 keyDescription
spot_price_goldCurrent gold spot price (USD per troy oz)
spot_price_silverCurrent silver spot price (USD per troy oz)
spot_price_platinumCurrent platinum spot price (USD per troy oz)
spot_price_palladiumCurrent palladium spot price (USD per troy oz)
spot_price_copperCurrent copper spot price (USD per troy oz)
spot_price_modifier_goldDollar adjustment applied to gold spot before pricing
spot_price_modifier_silverDollar 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 keyTypeDescription
_metal_typestringgold, silver, platinum, palladium, copper. Empty = non-metal product.
_metal_weightfloatWeight in troy ounces. Defaults to 1 if empty.
_metal_weight_unitstringUsually oz.
_markup_modestringDetermines how _markup_rate is interpreted. See Markup Modes below.
_markup_ratefloatBase markup value. Meaning depends on _markup_mode.
_markup_rate_2floatSecondary markup rate (used for sale / volume tier overrides).
_markup_sale_pricefloatOptional override sale price.
_volume_pricingJSONArray 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 interpretationUnit price formula
Weight Fixed (default)`` (empty)Per-oz dollar premium(spot + modifier + markup_rate) ร— weight
Per Piece Fixedeach_fixedPer-piece flat dollar premium โ€” not per-ozSee below
Weight Percentweight_percentPercentage of spot (e.g. 5 = 5%)(spot ร— (1 + rate/100)) ร— weight
Spot OnlyspotNo premium โ€” pure spot pricingspot ร— 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
Important

_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 weightDisplay rule
>= 1 oz and each_fixedDivide markup_rate by weight โ†’ show per-oz premium
< 1 oz and each_fixedShow markup_rate as-is โ†’ per-piece total
Any other modeShow 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 attributeSourceNotes
weight_metal_weightDefaults to 1 if meta is empty
weight_unit_metal_weight_unitUsually oz
markup_rate_markup_rateAlways the raw per-piece value for each_fixed
markup_mode_markup_modeUsed by Vue to select the display formula
metal_metal_typegold, silver, etc.
priceCalculated live unit priceRecalculated on each nFusion sync
tiers_volume_pricingJSON 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 keyTypeWhat is stored
_spot_pricefloatSpot price per oz at the moment of cart add
_premiumfloatPremium value โ€” see rule below
_metal_weightfloatProduct weight in oz at time of order
_markup_modestringMarkup mode at time of order
_unit_pricefloatFinal 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_fixedPer-oz premium (e.g. 2.05 for a 10 oz bar)
< 1 oz and each_fixedPer-piece premium total (e.g. 1.50 for a 0.5 oz coin)
Any other modePer-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โ€‹

MethodReturnsDescription
get_spot_price( $metal )floatCurrent spot price from WP options (cached per request)
get_price_data( $product )arrayFull pricing array: metal_type, weight, markup_mode, markup_rate, markup, base_price, tiers, spot_price
get_unit_price( $product, $qty )floatLive unit price for a given quantity (applies tier logic)
calculate_markup( $spot, $rate, $modifier, $mode )floatComputes markup value from raw fields
calc_spot_price_from_product( $metal, $weight, $unit_price )floatBack-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โ€‹

MistakeCorrect 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 pricesAlways read from spot_price_<metal> WordPress options
Editing dist/app.js directlyAlways edit source files and rebuild with npx mix --production