Skip to main content

Pricing Storage & Display Reference

This page is the authoritative reference for how product pricing is stored and displayed across every layer of the Dealers Site. Pricing is now calculated in the SDM Middleware using nFusion data and imported into the dealer site.

Pricing changes have ripple effects

Any pricing change must be reviewed across all dealer-facing and admin pricing surfaces:

  • Trade page line items and cart totals
  • Cart page line items and totals
  • POST /suma/v1/quote draft API endpoint
  • WP Admin order edit view
  • Order-update recalculation hooks and any downstream totals refresh logic

A change can look correct in one screen and still drift in another because these surfaces do not all read pricing from the same path.


Architecture Overview: Where Pricing Lives

nFusion API


SDM Middleware (PriceSyncService)
- Fetches nFusion data (ask, tiers, spot, markup)
- Calculates final prices (spot + markup + adjustments)
- Generates volume tier breaks

├──▶ POST /suma/v1/prices ──────────────────────┐
│ │
▼ ▼
Dealer Site: WooCommerce Product Meta
_price, _regular_price, _markup_rate, _markup_mode, _metal_weight, _volume_pricing

├──▶ class-metal-pricing.php ←── reads meta (display & volume tier application)
│ │
│ ├──▶ Algolia index (price, markup_rate, markup_mode, retail_tiers — for search)
│ │
│ ├──▶ Cart REST API (unit_price, premium, tier info passed to Vue)
│ │
│ └──▶ Order line item meta (_unit_price, _premium, _metal_weight, _markup_mode)

└──▶ Vue widgets (apply volume tiers, apply catalog overrides, display pricing)

Spot Prices (Legacy — Reference Only)

Deprecated

Spot prices are no longer used for dealer site pricing calculations. The Middleware Platform calculates all prices and imports them pre-calculated. These options are retained for reference/display only.

Spot prices are pulled from the nFusion feed via:

herd php artisan nfusion:sync

They are stored as WordPress options and are never used for pricing calculations — they remain for reference/reporting:

Option keyDescription
spot_price_goldGold spot price (USD per troy oz) — not used for pricing
spot_price_silverSilver spot price (USD per troy oz) — not used for pricing
spot_price_platinumPlatinum spot price (USD per troy oz) — not used for pricing
spot_price_palladiumPalladium spot price (USD per troy oz) — not used for pricing
spot_price_copperCopper spot price (USD per troy oz) — not used for pricing
spot_price_modifier_goldHistorical modifier (not used)
spot_price_modifier_silverHistorical modifier (not used)
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 (for reference), but they are not used in price calculations.


Product Meta Fields (Imported from Middleware)

All pricing data for a product is imported from the Middleware Platform and stored 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.
_pricefloatFinal calculated ask price (imported from middleware)
_regular_pricefloatFinal calculated price for WooCommerce (imported from middleware)
_markup_ratefloatMarkup value used in calculation (stored for reference/display). Meaning depends on _markup_mode.
_markup_modestringDetermines how _markup_rate was interpreted during middleware calculation. See Markup Modes below.
_markup_rate_2floatSecondary markup rate (if applicable).
_markup_sale_pricefloatOptional override sale price.
_volume_pricingJSONArray of tier breakpoints with calculated ask prices: [{"qty": 1, "ask": 28.50}, ...] (imported from middleware)
Import Only

These fields are imported via POST /suma/v1/prices from the Middleware Platform. Do not manually edit pricing meta fields on the dealer site — changes will be overwritten on the next sync.


Markup Modes (Reference — Calculated in Middleware)

The _markup_mode meta key reflects how the middleware calculated the price. It is stored for reference and used by frontend widgets to display pricing correctly.

Mode_markup_mode valueHow Middleware CalculatedFrontend Display
Weight Fixed (default)`` (empty)Per-oz dollar premiumPremium shown per oz
Per Piece Fixedeach_fixedPer-piece flat dollar premiumPremium shown per piece
Weight Percentweight_percentPercentage markup on spotPercentage shown
Spot OnlyspotNo premium — pure spot pricingSpot price only
Fixed Price(non-metal)Direct fixed priceFixed price displayed

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-viewCatalogGridItem.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-viewCartItem.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;

_sharedItemDetails.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