Skip to main content

Pricing Plans

This document describes the pricing plan system used by bbserver billing.

At a high level:

  • A price plan is selected by (market_id, item_code) at a given timestamp.
  • A plan contains a base unit_price_minor (and optional micro pricing fields).
  • A plan contains tiers that apply multiplier_bps based on device count.
  • Billing code computes an amount and records an issue_charges row, then debits the user wallet.

Where pricing plans live

Source config (JSON)

Pricing plans are authored in:

  • apps/bbserver/config_dir/pricing_plans.json

Format:

  • Root object contains plans: []
  • Each entry is a plan object, optionally with tiers: []

Markets config

Valid markets are configured in:

  • apps/bbserver/config_dir/markets.json

market_id in pricing plans should match one of the configured market IDs (e.g. US, CN).

Loaded storage (MySQL)

Plans and tiers are stored in MySQL tables:

  • price_plans
  • price_plan_tiers

The query used to resolve an active plan is implemented in:

  • libs/bbdb/src/billing_store_mysql.cpp

Resolution logic:

  • WHERE market_id = ? AND item_code = ? AND is_active = 1
  • Optional validity window filters: valid_from, valid_to
  • Choose best match: ORDER BY priority DESC, valid_from DESC LIMIT 1

Admin APIs

Plans can be listed/edited/reloaded via the admin pricing handler:

  • Routes: apps/bbserver/include/handler_route_builder.hpp
  • Handler: apps/bbserver/include/http_handlers/admin_pricing_handler.hpp

Endpoints (server-side):

  • GET /apiv1/admin/pricing/plans → list plans
  • GET /apiv1/admin/pricing/plans/:plan_id → get a plan
  • PUT /apiv1/admin/pricing/plans/:plan_id → update a plan + tiers
  • POST /apiv1/admin/pricing/plans/reload → reload plans from config (pricing_plans)
  • POST /apiv1/admin/pricing/markets/reload → reload markets from config (markets)

Reload implementation is in:

  • libs/bbdb/include/payment/price_plan_admin_service.cpp

Schema (plan fields)

Each plan entry supports (most common):

  • market_id (string, required)
    • Example: US, CN
    • Must be non-empty when resolving a plan (resolver returns HTTP 422 otherwise).
  • item_code (string, required for authoring; defaulted by some call sites)
    • Used by billing code to select the plan.
  • name (string)
    • Human-readable unique-ish identifier.
  • currency (string)
    • Example: USD, CNY
  • unit_price_minor (integer)
    • The base price per unit in minor currency units.
    • Example: USD cents.
  • priority (integer)
    • Higher wins when multiple active plans match.
  • is_active (boolean)
    • Only active plans are considered.
  • Optional validity window:
    • valid_from / valid_to (MySQL timestamps)

Optional micro-pricing (used for higher precision or alternative units):

  • unit_price_micro (integer)
  • micro_precision (integer, default = 6)
  • amount_micro (integer)

Notes:

  • During config reload, the service will default missing micro fields:
    • micro_precision = 6
    • unit_price_micro = 0
    • amount_micro = unit_price_micro

Schema (tier fields)

Tiers define how the plan scales with device count:

  • min_devices (integer, required)
  • max_devices (integer, optional)
    • If omitted, the tier is unbounded on the high end.
  • multiplier_bps (integer, optional; default = 10000)
    • Basis points multiplier. 10000 = 1.0x.
    • 20000 = 2.0x.
  • label (string, optional)

Tier validation (server-side) requires:

  • min_devices >= 1
  • Tiers must be ordered, non-overlapping, and contiguous-ish by increasing ranges
  • max_devices >= min_devices when present
  • Only one unbounded tier allowed

(See tier checks in libs/bbdb/include/payment/price_plan_admin_service.cpp.) (See tier checks in libs/bbdb/include/payment/price_plan_admin_service.cpp.)

How amounts are computed

1) Pick a plan

Given (market_id, item_code, at_epoch):

  • Find the best active plan in price_plans.
  • Load its tiers from price_plan_tiers.

Resolution service:

  • libs/bbdb/include/payment/price_plan_resolver_service.cpp

2) Pick a tier

The tier is selected by comparing a device count to tier ranges.

Typical patterns:

  • Issuance charge uses agents_count (device count for the certificate).
  • Device attach charge uses total_devices_after_attach.

3) Compute unit price and amount

Minor units:

  • unit_price_minor_effective = unit_price_minor * multiplier_bps / 10000
  • amount_minor = unit_price_minor_effective * quantity

Micro units (if used):

  • amount_micro = unit_price_micro * multiplier_bps / 10000 * quantity

Exact details differ slightly per billing call-site.

Canonical item_code catalog

These are the item_code values currently used by billing code and config.

certificate.issue

Meaning:

  • Charges for certificate issuance and/or master-wrapped device attachment.

Used by:

  • Issuance charge runner: apps/bbserver/src/issue_charge_runner.cpp
  • Default for device attach billing: libs/bbdb/include/billing/device_attach_billing_service.cpp

ca.distribution

Meaning:

  • Charges when a CA trust bundle is distributed/attached to a device.

Used by:

  • Device CA assignment billing: apps/bbserver/include/http_handlers/devices_handler.hpp

ip.issuance

Meaning:

  • Reserved for IP issuance pricing (currently present in config).

Used by:

  • Config only (as of current repo state): apps/bbserver/config_dir/pricing_plans.json

Legacy / test-only

  • cert.issue appears in some tests as a historical item code.
    • It should not be relied on for production configs unless the production billing code uses it.

Example config

A minimal plan:

{
"market_id": "US",
"item_code": "certificate.issue",
"name": "us-cert-issue",
"unit_price_minor": 100,
"currency": "USD",
"priority": 0,
"is_active": true,
"tiers": [
{"min_devices": 1, "max_devices": 5, "multiplier_bps": 10000, "label": "base"},
{"min_devices": 6, "max_devices": 15, "multiplier_bps": 20000, "label": "6-15"},
{"min_devices": 16, "multiplier_bps": 40000, "label": "15+"}
]
}

Troubleshooting

“No active price plan for device attachment”

This error comes from device attach billing when no plan is found for (market_id, item_code).

  • Code path: libs/bbdb/include/billing/device_attach_billing_service.cpp
  • Confirm the user has a market_id (preferred market) and the plan exists for that market.
  • Ensure the plan is active and valid for the current time window.

Reloading plans after editing config

After updating the config JSON, invoke the reload endpoint:

  • POST /apiv1/admin/pricing/plans/reload

If you changed markets:

  • POST /apiv1/admin/pricing/markets/reload