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
| Western | Indian / Middle Eastern | |
|---|---|---|
| Regions | US, Europe, luxury | India, Dubai, Gulf, Asia |
| Making charge | None, baked into markup | Explicit (% or per-gram) |
| Markup | 1.5×–2.5× | 1.0× |
| Setting cost | Bundled in markup | Per-stone, itemized |
// 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
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 } | nullListen for real time updates:
api.events.on('price:updated', ({ bandName, pricing }) => {
document.getElementById('price').textContent = `$${pricing.totalUsd}`;
});Configuring Pricing
All parameters are optional. Only provided values are updated.
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-bandDetail Sections
Per-Metal Pricing
Why Different Metals Have Different Prices
All 18K gold alloys contain 75% pure gold, but the remaining 25% differs:
| Metal | Alloy Composition (18K) | Density (g/cm³) | Relative Cost |
|---|---|---|---|
| Yellow Gold | 75% Au, 12.5% Ag, 12.5% Cu | ~15.5 | Base |
| White Gold | 75% Au, 25% Pd/Ni/Zn | ~15.8 | +5–15% (palladium) |
| Rose Gold | 75% 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):
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
| Priority | Source |
|---|---|
| 1st | metalPrices[activeMetal] |
| 2nd | Flat metalDensityGcm3 / metalPricePerGram |
| 3rd | Defaults (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):
| Finish | Typical Surcharge | Reason |
|---|---|---|
| Polished | $0 (baseline) | Standard buffing |
| Brushed | $0–$25 | Simple, often included |
| Sand | $25–$50 | Blasting equipment |
| Linear | $25–$50 | Precise directional work |
| Ice | $30–$75 | Specialized texturing |
| Hammered | $50–$100 | Skilled hand labour |
| Nature | $50–$150 | Artisan carving/casting |
Configuration
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
| Mode | Formula | Typical Use |
|---|---|---|
'none' | No charge | Western model (profit in markup) |
'percent' | metalCost × (percent / 100) | India (8–35%, typical 15–18%) |
'per-gram' | weight × perGramRate | Dubai/Gulf ($5–$50/gram) |
Examples
// 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) × pricePerCaratThe carat weight per stone is derived from the stoneSize parameter using the round brilliant approximation.
api.setPricingParams({
diamondPricePerCarat: 1500,
});Setting Labour
Setting cost covers physically mounting each stone. Rates vary by setting type:
| Setting Type | Typical Range |
|---|---|
| Pave | $10–$25/stone |
| Prong | $15–$40/stone |
| Channel | $20–$50/stone |
| Bezel | $25–$60/stone |
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:
// Disable rounding (exact calculated price)
api.setPricingParams({ roundingEnabled: false });
// Round to nearest $10
api.setPricingParams({ roundingStep: 10 });Examples
| Raw Price | Step 1 | Step 5 | Step 10 | Step 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:
// Price in troy ounces (common for precious metals)
api.setPricingParams({
metalPricePerGram: 1800,
weightUnit: 'troy_ounce', // divided by 31.1035 internally
});| Unit | Grams 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:
// 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:
{
"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:
- Customer configures ring → client shows live price
- Customer adds to cart → your backend receives the config JSON via
getSnapshot() - Your backend recalculates price using the config dimensions, metal prices, and diamond specs
- 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.
// 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.
| Data | Source | Example |
|---|---|---|
| Ring volume (mm³) | getPrice().volumeMm3 | 142.7 |
| Ring weight (grams) | getPrice().weightGrams | 2.21 |
| Metal density (g/cm³) | getPrice().metalPrice.densityGcm3 | 15.5 |
| Active metal | getMaterials().slots[0].metal | 'White' |
| Active finish | getMaterials().slots[0].finish | 'Hammered' |
| Diamond count | getDiamonds().count | 12 |
| Diamond total carats | getPrice().diamonds.totalCarats | 0.36 |
| Stone diameter (mm) | getSpecSheetData().diamondSizeInfo.diameterMm | 1.5 |
| Setting type | getDiamonds().settingType | 'Channel' |
| Width, height, radius (mm) | getDimensions() | { widthMm: 4.0, heightMm: 1.8, radiusMm: 8.5 } |
| Profile name | getSnapshot().profile.name | 'D-Shape' |
| Partition count | getMaterials().partition | 2 |
| Band name | getActiveBand() | '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
// 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)}`;// 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