💰 Pricing Setup
Learn how to configure prices for your products correctly — including setting retail and wholesale tiers, handling multiple currencies, using quantity-break discounts, and understanding why a product might appear to have a price but still fail to add to cart.
- At least one product must exist in your catalog (see Catalog Management)
- You must have a B2B account with Seller or Admin role
- Know your selling currency — every price row carries its own currency, and your Company profile's default currency is used when the platform looks prices up for buyers from your own organisation
- Base price — for Agricultural Products and Arts & Crafts products this is the retail / minimum-quantity-1 row created automatically from the Price and Currency fields on the vertical product form. It shows an Auto-synced badge on the pricing panel, is rewritten every time the product is saved in the vertical app, and cannot be deleted from the panel. For General Commerce products the first price row comes from the Default Price field on the catalog form (or is added on the panel).
- Tier rows — wholesale, VIP and volume bands. Added, edited and deleted on the shared pricing panel for every product, whichever vertical it belongs to.
Part 1 — How Pricing Works
Each product can have multiple Price records. The platform picks the best matching price at checkout using a 5-step resolution process. Understanding this prevents the most common setup mistakes.
| Field | What it controls | Common mistake |
|---|---|---|
currency |
Which currency this price is expressed in (e.g. UGX, USD, KES) | Setting currency to USD but your company's default is UGX — the price is found but the label shows the wrong currency |
customer_tier |
retail (B2C buyers), wholesale (B2B buyers), or vip | Only setting a wholesale price — B2C buyers can't add to cart |
min_quantity |
Minimum order quantity for this price to apply | Setting min_quantity=10 on the only price — adding 1 unit fails because no price applies |
valid_from / valid_until |
Date window when this price is active (blank = always active). Not editable on the pricing panel — only a platform administrator can set these in the Django admin | Setting valid_until in the past — the price appears in admin but is invisible to buyers |
is_active |
Master on/off switch for the price record | Accidentally deactivating the only price for a product |
How the platform finds a price at checkout
When a buyer adds a product to cart, the platform tries these steps in order and stops at the first match:
Exact match — buyer's currency + buyer's tier
e.g. a UGX retail price for a retail buyer. Best case — always aim for this.
Same currency, retail tier (tier downgrade)
A B2B wholesale buyer gets the retail price when no wholesale price exists in their currency.
Any currency + buyer's tier
A US buyer (USD context) can still purchase a UGX-priced product. The price is shown in UGX with an automatic conversion displayed for reference.
Any currency + retail (last sensible fallback)
Wholesale buyer, wrong currency — still finds a retail price to fall back on.
Absolute last resort — any active price at all
VIP-only prices, exotic currencies — if anything active exists, it is used.
If no valid active price is found for the requested quantity, the platform raises a "has no price configured" error at add-to-cart. See Part 5 — Diagnosing Price Problems for how to fix this.
Part 2 — Adding Your First Price
Every product needs at least one active retail price before buyers can add it to cart.
Open the product's pricing panel
- General Commerce: go to Catalog → Products
(
/catalog/products/), click the product, then click Manage Pricing (/catalog/products/<id>/pricing/). - Agricultural Products / Arts & Crafts: open the product's edit page in the
vertical app (
/agriproducts/<id>/edit/or/arts_en_crafts/<id>/edit/) and switch to the Pricing tab. The base price is already there — it comes from the Price and Currency fields on the Details tab. If you see "Save the product first to enable tier pricing", save the Details tab once and come back.
Click Add Tier
Vertical products already have this row — it is the auto-synced base price, so skip to Part 3. For a General Commerce product without a price yet, fill in the Add Price Tier form with these values for a standard single retail price:
| Field | Value | Notes |
|---|---|---|
| Currency | UGX (or your local currency) | Must match your company's default currency for step-1 resolution |
| Customer Tier | Retail | Required so B2C buyers can purchase. Add a separate wholesale row later for B2B pricing. |
| Unit Price | e.g. 45000 | The price per single unit |
| Minimum Quantity | 1 | ⚠️ Must be 1 for a standard price. Higher values mean the price only activates for bulk orders. |
Click Save Tier. New rows are created active with no date window; the message "Price tier added." confirms it (or "Price tier updated." if a row with the same currency, tier and minimum quantity already existed — its unit price is overwritten). To change a row later, click its pencil Edit button: you can change the unit price, minimum quantity and the Active switch, then Save Changes.
Verify the price is live
Open your product in the shop (Browse Products in the sidebar, or
/catalog/shop/) and confirm the price is displayed. Try adding 1 unit to
cart to confirm end-to-end.
Part 3 — Adding Wholesale (B2B) Pricing
B2B buyers are automatically assigned the wholesale tier. Add a separate price row to give them a discounted rate. If no wholesale price exists, the platform falls back to the retail price — B2B buyers can always purchase.
Add a second price row with tier = wholesale
Click Add Tier on the same product's pricing panel and set:
| Field | Value |
|---|---|
| Currency | Same as your retail / base price (e.g. UGX) |
| Customer Tier | Wholesale |
| Unit Price | Your discounted B2B rate, e.g. 38000 |
| Minimum Quantity | 1 (or higher if the discount only applies for bulk) |
Click Save Tier. The new row appears in the Price Tiers table immediately; the base price card is unchanged. On a vertical edit page you are returned to the Pricing tab.
- B2C buyer adds 1 unit → pays UGX 45,000 (retail)
- B2B buyer adds 1 unit → pays UGX 38,000 (wholesale)
Part 4 — Quantity Break Discounts
Use multiple price rows with different min_quantity values to
automatically apply bulk discounts. The platform always selects the row with the
highest min_quantity that still fits the buyer's order quantity.
| Row | Tier | Min Qty | Unit Price | When applied |
|---|---|---|---|---|
| 1 | retail | 1 | UGX 45,000 | Orders of 1–9 units |
| 2 | retail | 10 | UGX 40,000 | Orders of 10–49 units |
| 3 | retail | 50 | UGX 35,000 | Orders of 50+ units |
⚠️ Always include a row with min_quantity=1. If your smallest row has min_quantity=10, a buyer adding 5 units will get a "no price configured" error even though prices clearly exist in the admin.
Part 5 — Diagnosing "No Price Configured" Errors
You see a price listed on the product page but the buyer gets an error when adding to cart. This happens when the price record exists but fails all five resolution steps. Here are the common causes and fixes:
| Symptom | Root cause | Fix |
|---|---|---|
| Error on add-to-cart for qty=1, price visible in admin | min_quantity is set to something greater than 1 on all price rows |
Add a new price row with min_quantity = 1 |
| Price visible on the pricing panel but not in shop | is_active = False on the price record (row shows an Inactive badge) |
Click the row's pencil Edit button, switch Active on, Save Changes |
| Price worked last week, fails now | valid_until date has passed (shown in the Valid Until column) |
Ask a platform administrator to update or clear valid_until in the Django admin, or add a fresh tier row |
| Price set up but not activating yet | valid_from is in the future |
Ask a platform administrator to clear or backdate valid_from in the Django admin |
| Vertical product shows "No base price" in Catalog → Products, or "No Price" on the vertical list | The product was saved with price 0 (or no currency), so no base price row was created | Edit the product in the vertical app, enter a non-zero Price and Currency, click Update Product |
| Delete button on the base price row is greyed out / "The base price cannot be deleted" error | The base price row is owned by the vertical app | Change the price on the product's Details tab and save — it is re-synced automatically |
| B2C buyer gets error, B2B buyer is fine | Only a wholesale tier price exists — no retail row | Add a separate price row with customer_tier = retail |
| Price shows correctly in shop but Order Summary shows wrong currency | Product price currency does not match company default currency | Check your Company profile currency matches the currency code on your price records |
Platform administrators can scan all products for price configuration issues from the Render shell:
python manage.py check_product_prices
To check a single product:
python manage.py check_product_prices --product-id 42
To automatically reactivate prices that are inactive but otherwise valid:
python manage.py check_product_prices --fix-inactiveThe tool reports BLOCKING problems (will fail add-to-cart), warnings (prices expiring soon), and an OK summary for healthy products.
Part 6 — Multi-Currency & International Buyers
As a seller you set prices in your local currency (e.g. UGX). International buyers automatically see a converted price for reference alongside your original price. You do not need to create separate price records for each currency.
- Exchange rates are refreshed daily from Open Exchange Rates.
- On the shop listing: UGX 45,000 with ≈ USD 12.20 shown below in smaller text.
- On the product detail page: an "Approx." line below the headline price with the buyer's currency and a conversion badge (e.g.
UGX → USD). - In the cart Order Summary: an indicative total in the buyer's currency with the note "actual charge in UGX".
- The actual transaction is always processed in the seller's currency. The conversion is informational only.
- If no FX rate is available for a pair (e.g. an obscure currency), the converted line is simply hidden — no error is shown.
Go to Settings → Company Profile → Default Currency and set it to the currency your prices are denominated in (e.g. UGX for Uganda, KES for Kenya, USD for USD-denominated businesses). This currency is used as the cart total label and the FX conversion base. Mismatched settings cause the Order Summary to show the wrong currency code on the correct number.
Part 7 — Promotional & Time-Limited Prices
valid_from and valid_until can schedule promotional pricing
without touching your standard price row. These two dates are not editable on the
pricing panel — a platform administrator must set them in the Django admin
(Catalog → Prices). Sellers without admin access should use the Active
switch approach in step 3 instead.
Keep your standard price row intact
Never edit the dates on your permanent price row. Instead, create a second price row with the promotional rate.
Add a promotional price row
| Field | Value |
|---|---|
| Currency | Same as standard price |
| Customer Tier | retail (or wholesale) |
| Unit Price | Your promotional rate, e.g. 35,000 |
| Min Quantity | 1 |
| Valid From | Promotion start date/time |
| Valid Until | Promotion end date/time |
| Active | ✓ ticked |
How the platform handles the overlap
During the promotional window, both rows are valid. The platform selects the row with the highest min_quantity that still applies — if both rows have min_quantity=1 the choice between them is not guaranteed, so do not rely on two overlapping rows.