Skip to content

Pricing Engine

Prices that update in real time as your customers design their ring, calculated from actual 3D geometry, not lookup tables. When a customer widens the band by 0.5mm, the price changes because the volume changed. When they add diamonds, the cost reflects real carat weight derived from stone diameter.

The Pricing Engine supports both Western (markup based) and Indian/Middle Eastern (making-charge-based) pricing models used by jewellers worldwide. You set the metal prices, markup, and making charges. The engine handles the math.

Quick Overview

metalCost       = weight × pricePerGram         ← auto-selected per metal
makingCharge    = based on mode (none / % / per-gram)
finishSurcharge = flat fee per finish type
stoneCost       = totalCarats × pricePerCarat
settingCost     = stoneCount × costPerStone
─────────────────────────────────────────────
subtotal        = sum of all above
total           = subtotal × markupMultiplier
final           = round(total, roundingStep)

Two Pricing Models

WesternIndian / Middle Eastern
RegionsUS, Europe, luxuryIndia, Dubai, Gulf, Asia
Making chargeNone, baked into markupExplicit (% or per-gram)
Markup1.5×–2.5×1.0×
Setting costBundled in markupPer-stone, itemized
javascript
// Western - markup covers everything
api.setPricingParams({
  markupMultiplier: 2.0,
});

// Indian - making charge + setting cost, no markup
api.setPricingParams({
  makingChargeMode: 'percent',
  makingChargePercent: 18,
  settingCostPerStone: 25,
  markupMultiplier: 1.0,
});

Reading Prices

javascript
const price = api.getPrice('her');
// price.totalUsd       → final rounded price
// price.metalPrice     → { name, densityGcm3, pricePerGram, totalUsd }
// price.makingCharge   → { mode, percent, perGram, totalUsd }
// price.diamonds       → { count, totalCarats, pricePerCarat, totalUsd } | null
// price.settingCost    → { costPerStone, count, totalUsd } | null
// price.finishSurcharge → { name, totalUsd } | null

Listen for real time updates:

javascript
api.events.on('price:updated', ({ bandName, pricing }) => {
  document.getElementById('price').textContent = `$${pricing.totalUsd}`;
});

Configuring Pricing

All parameters are optional. Only provided values are updated.

javascript
api.setPricingParams({
  // Metal pricing (see Per-Metal Pricing below)
  metalPrices: {
    'White':  { density: 15.8, pricePerGram: 48.0 },
    'Yellow': { density: 15.5, pricePerGram: 45.0 },
    'Rose':   { density: 15.2, pricePerGram: 43.0 },
  },

  // Finish surcharges (see Finish Surcharges below)
  finishSurcharges: {
    'Polished': 0, 'Brush': 0, 'Hammered': 75, 'Nature': 120,
  },

  // Making charge
  makingChargeMode: 'percent',  // 'none' | 'percent' | 'per-gram'
  makingChargePercent: 18,

  // Diamonds & setting
  diamondPricePerCarat: 1500,
  settingCostPerStone: 25,

  // Markup & rounding
  markupMultiplier: 1.0,
  roundingEnabled: true,
  roundingStep: 5,
}, 'her');  // optional: per-band

Detail Sections

Per-Metal Pricing

Why Different Metals Have Different Prices

All 18K gold alloys contain 75% pure gold, but the remaining 25% differs:

MetalAlloy Composition (18K)Density (g/cm³)Relative Cost
Yellow Gold75% Au, 12.5% Ag, 12.5% Cu~15.5Base
White Gold75% Au, 25% Pd/Ni/Zn~15.8+5–15% (palladium)
Rose Gold75% Au, 22.5% Cu, 2.5% Ag~15.2-2–5% (copper)

Configuration

When metalPrices is set, the engine automatically looks up density and price based on the active metal (primary slot):

javascript
api.setPricingParams({
  metalPrices: {
    'White':  { density: 15.8, pricePerGram: 48.0 },
    'Yellow': { density: 15.5, pricePerGram: 45.0 },
    'Rose':   { density: 15.2, pricePerGram: 43.0 },
  },
});

Fallback behavior

PrioritySource
1stmetalPrices[activeMetal]
2ndFlat metalDensityGcm3 / metalPricePerGram
3rdDefaults (density 15.5, price $97/g)

The flat fields still work for jewellers who price all golds equally.

Multi color rings

For 2-color or 3-color rings, the primary metal (slot 1) is used for pricing. The alloy difference between slots is negligible at the volume level. This matches real world practice.

TIP

If your store offers different metals per slot in a multi color ring, make sure your UI communicates that pricing is based on the primary metal (slot 1) only. This avoids customer confusion when a rose gold accent doesn't change the price.

:::

Finish Surcharges

How Finishes Affect Price

Most jewellers charge a flat surcharge for specialty finishes over the baseline (polished):

FinishTypical SurchargeReason
Polished$0 (baseline)Standard buffing
Brushed$0–$25Simple, often included
Sand$25–$50Blasting equipment
Linear$25–$50Precise directional work
Ice$30–$75Specialized texturing
Hammered$50–$100Skilled hand labour
Nature$50–$150Artisan carving/casting

Configuration

javascript
api.setPricingParams({
  finishSurcharges: {
    'Polished': 0,
    'Brush': 0,
    'Sand': 35,
    'Linear': 40,
    'Ice': 50,
    'Hammered': 75,
    'Nature': 120,
  },
});

Unlisted finishes default to $0. The surcharge is a flat fee added to the subtotal before markup.

Making Charges

Three Modes

ModeFormulaTypical Use
'none'No chargeWestern model (profit in markup)
'percent'metalCost × (percent / 100)India (8–35%, typical 15–18%)
'per-gram'weight × perGramRateDubai/Gulf ($5–$50/gram)

Examples

javascript
// Indian jeweller - 18% of metal cost
api.setPricingParams({
  makingChargeMode: 'percent',
  makingChargePercent: 18,
  markupMultiplier: 1.0,
});

// Dubai jeweller - flat $12/gram
api.setPricingParams({
  makingChargeMode: 'per-gram',
  makingChargePerGram: 12,
  markupMultiplier: 1.0,
});

// Western jeweller - no making charge, 2× markup
api.setPricingParams({
  makingChargeMode: 'none',
  markupMultiplier: 2.0,
});
Diamond & Setting Costs

Stone Pricing

Diamond cost is calculated from total carat weight:

stoneCost = (caratPerStone × stoneCount) × pricePerCarat

The carat weight per stone is derived from the stoneSize parameter using the round brilliant approximation.

javascript
api.setPricingParams({
  diamondPricePerCarat: 1500,
});

Setting Labour

Setting cost covers physically mounting each stone. Rates vary by setting type:

Setting TypeTypical Range
Pave$10–$25/stone
Prong$15–$40/stone
Channel$20–$50/stone
Bezel$25–$60/stone
javascript
api.setPricingParams({
  settingCostPerStone: 30,
});

The setting cost is added to the subtotal before markup.

Rounding

Configuration

Rounding is enabled by default with a step of $5:

javascript
// Disable rounding (exact calculated price)
api.setPricingParams({ roundingEnabled: false });

// Round to nearest $10
api.setPricingParams({ roundingStep: 10 });

Examples

Raw PriceStep 1Step 5Step 10Step 50
$1,247.32$1,247$1,245$1,250$1,250
$2,998.50$2,999$3,000$3,000$3,000
$523.80$524$525$520$500
Weight Units

The metalPricePerGram field accepts prices in different units. Set weightUnit and the engine converts internally:

javascript
// Price in troy ounces (common for precious metals)
api.setPricingParams({
  metalPricePerGram: 1800,
  weightUnit: 'troy_ounce',  // divided by 31.1035 internally
});
UnitGrams per Unit
'gram'1 (default)
'ounce'28.3495
'troy_ounce'31.1035

WARNING

When using metalPrices (per-metal pricing), values are always in price-per-gram. The weightUnit conversion only applies to the flat metalPricePerGram field.

Per-Band Pricing

Each band can have independent pricing parameters:

javascript
// Her - diamond ring with setting cost
api.setPricingParams({
  diamondPricePerCarat: 1500,
  settingCostPerStone: 25,
}, 'her');

// His - plain band, no diamonds
api.setPricingParams({
  markupMultiplier: 1.8,
}, 'his');
Manifest-Based Defaults

When loading from the iJewel platform, pricing defaults come from the project manifest:

json
{
  "pricing": {
    "metals": {
      "Yellow": { "density": 15.5, "pricePerGram": 45.0 },
      "White": { "density": 15.8, "pricePerGram": 48.0 },
      "Rose": { "density": 15.2, "pricePerGram": 46.0 }
    }
  }
}

Manifest values are used as defaults and can be overridden at runtime with setPricingParams().

Server Side Validation

WARNING

The pricing engine runs in the browser for real time display. A user could modify the DOM or intercept API calls to change the price. Always recalculate the price on your server before processing payment. Never trust client-submitted prices for checkout.

The recommended flow:

  1. Customer configures ring → client shows live price
  2. Customer adds to cart → your backend receives the config JSON via getSnapshot()
  3. Your backend recalculates price using the config dimensions, metal prices, and diamond specs
  4. Payment is processed against the server-calculated price

The PriceBreakdown object includes all inputs (volume, weight, metal density, price per gram, carat weight, markup) so your backend can verify the math.

javascript
// Server-side price validation (Node.js)
function validatePrice(snapshot, pricingParams) {
  const { weightGrams, metal, finish } = snapshot;
  const metalInfo = pricingParams.metalPrices[metal];
  const metalCost = weightGrams * metalInfo.pricePerGram;

  // Making charge (percent mode shown; adapt for 'per-gram' or 'none')
  const makingCharge = pricingParams.makingChargeMode === 'percent'
    ? metalCost * (pricingParams.makingChargePercent / 100)
    : 0;

  const finishSurcharge = pricingParams.finishSurcharges?.[finish] ?? 0;

  // Diamonds (null when no stones are set)
  const diamonds = snapshot.diamonds;
  const stoneCost = diamonds
    ? diamonds.totalCarats * pricingParams.diamondPricePerCarat
    : 0;
  const settingCost = diamonds
    ? diamonds.count * (pricingParams.settingCostPerStone ?? 0)
    : 0;

  const subtotal = metalCost + makingCharge + finishSurcharge + stoneCost + settingCost;
  const total = subtotal * (pricingParams.markupMultiplier ?? 1);
  return total;
}

Custom Pricing (Bring Your Own Formula)

If you want to bypass the built in pricing engine and calculate prices entirely on your own, the API gives you all the physical measurements you need. Set markupMultiplier: 0 or ignore the PriceBreakdown output and read the raw data instead.

What the API gives you

These are the physical/geometric values computed from the actual 3D model. You cannot get these without the engine.

DataSourceExample
Ring volume (mm³)getPrice().volumeMm3142.7
Ring weight (grams)getPrice().weightGrams2.21
Metal density (g/cm³)getPrice().metalPrice.densityGcm315.5
Active metalgetMaterials().slots[0].metal'White'
Active finishgetMaterials().slots[0].finish'Hammered'
Diamond countgetDiamonds().count12
Diamond total caratsgetPrice().diamonds.totalCarats0.36
Stone diameter (mm)getSpecSheetData().diamondSizeInfo.diameterMm1.5
Setting typegetDiamonds().settingType'Channel'
Width, height, radius (mm)getDimensions(){ widthMm: 4.0, heightMm: 1.8, radiusMm: 8.5 }
Profile namegetSnapshot().profile.name'D-Shape'
Partition countgetMaterials().partition2
Band namegetActiveBand()'her'

What you supply yourself

Your own rates, margins, and business logic:

  • Metal price per gram (per alloy, per karat, per market)
  • Diamond price per carat (by size, cut, clarity, color)
  • Making charges or labor cost
  • Finish surcharges
  • Setting labor per stone
  • Markup or margin
  • Tax, shipping, rounding

Example

javascript
// Client side: extract physical data from the engine
const price = api.getPrice('her');
const materials = api.getMaterials('her');
const diamonds = api.getDiamonds('her');

const orderData = {
  volumeMm3: price.volumeMm3,
  weightGrams: price.weightGrams,
  metal: materials.slots[0].metal,
  finish: materials.slots[0].finish,
  diamondCount: diamonds?.count ?? 0,
  diamondCarats: price.diamonds?.totalCarats ?? 0,
  settingType: diamonds?.settingType ?? 'none',
  profile: api.getSnapshot('her').profile.name,
  dimensions: api.getDimensions('her'),
};

// Send to your backend
const response = await fetch('/api/price', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(orderData),
});
const { total } = await response.json();

// Display your own price
document.getElementById('price').textContent = `$${total.toFixed(2)}`;
javascript
// Server side: your own pricing logic (Node.js)
function calculatePrice(order, catalog) {
  const metal = catalog.metals[order.metal];
  const metalCost = order.weightGrams * metal.pricePerGram;
  const laborCost = order.weightGrams * metal.laborPerGram;
  const finishFee = catalog.finishFees[order.finish] ?? 0;
  const diamondCost = order.diamondCarats * catalog.diamondRate;
  const settingCost = order.diamondCount * catalog.settingFeePerStone;

  return metalCost + laborCost + finishFee + diamondCost + settingCost;
}

This approach gives you full control. The engine handles the 3D geometry and measurement. You handle the business logic.

Full PriceBreakdown Reference

See API Reference → PriceBreakdown for the complete type definition.

Coming Soon

  • Per-karat gold pricing (10K, 14K, 18K, 24K)
  • Tiered diamond pricing based on size and quality
  • Tax calculation hooks
  • Multi-currency support