Pricing & promotions
B2B & Wholesale Tiered Pricing
Indicative price, not an offer: $399–799/year
Generated from docs/plan/paid/b2b-wholesale-tiered-pricing/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.
Wholesale buyers need different prices, volume-based discounts, and credit-term orders with approval workflows. This paid feature adds a wholesale pricing layer on top of the free customer-groups foundation, enabling price overrides per customer group, volume breaks by quantity, and order-approval gates for credit terms.
The problem
Merchants sell to wholesale/reseller buyers who need tiered pricing based on order volume, but the cart has no wholesale pricing layer, no volume-break logic, and no approval workflow for credit-term orders. Standard pricing doesn’t account for bulk orders or customer-group-specific discounts.
What it does
- Define wholesale customer groups with discount tiers and credit-approval settings
- Create volume-break rules per product: min qty triggers price override (stored in integer minor units)
- Apply wholesale price overrides in cart calculation when customer is in wholesale group
- Support credit-term orders with mandatory approval workflow before payment
- Generate wholesale pricing quotes for customers
- Audit reporting: show which customers see which prices, when applied, and reconciliation of group assignments
- Admin approval queue for wholesale orders marked requires_approval=true
What it deliberately does NOT do
Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.
- Does not build a full wholesale/B2B order management system beyond pricing and approval—that’s a separate admin suite; this is pricing and gate only
- Does not create credit-risk assessment automation—merchants handle underwriting themselves; system only enforces approval gates
- Does not integrate payment processors’ credit extensions (e.g., Stripe billing)—customer and merchant arrange processor support outside AstroBaaS
- Does not provide wholesale-specific shipping logic (e.g., pallet shipping)—shipping is a separate feature; this is pricing only
- Does not support multi-level distributor tiers (A→B→C resale)—single tier per group keeps schema and reconciliation simple; merchants can use multiple groups instead
- Does not track customer credit balance or payment history—that’s order/payment accounting, not pricing domain; built into approval workflow only
Data model
New entity: WholesaleGroup with name, discount_tier_multiplier (float for price scaling), credit_limit_amount (integer minor units, nullable), requires_approval (boolean). New entity: VolumeBreak with product_id, group_id, min_qty (integer), price_override (integer minor units). New field on Customer: wholesale_group_id (nullable FK to WholesaleGroup). New field on Order: is_wholesale (boolean), applied_tier_id (nullable), requires_approval (boolean), approval_status (enum: pending/approved/rejected, default pending). Migration required: add columns to Customer and Order; set existing orders is_wholesale=false, approval_status=approved (backwards compat).
API
- POST /admin/api/wholesale-groups — create group with multiplier, credit limit, approval flag
- GET /admin/api/wholesale-groups — list all wholesale groups
- PUT /admin/api/wholesale-groups/:id — update group settings
- DELETE /admin/api/wholesale-groups/:id — delete group (fails if customers assigned)
- POST /admin/api/volume-breaks — create volume-break rule for product+group
- GET /admin/api/products/:id/volume-breaks — list all volume breaks for product
- PUT /admin/api/volume-breaks/:id — update volume-break qty or price
- DELETE /admin/api/volume-breaks/:id — delete volume-break rule
- PUT /admin/api/customers/:id/assign-group — assign customer to wholesale group
- GET /admin/api/wholesale-orders?status=pending — list orders awaiting approval
- POST /admin/api/wholesale-orders/:id/approve — approve wholesale order, unblock payment
- POST /admin/api/wholesale-orders/:id/reject — reject order with reason
- GET /admin/api/wholesale-pricing-audit?group=:id — report: customers in group, prices seen, date applied
Every route added here must also appear in src/pages/openapi.json.ts — a test
fails the build if it does not.
Admin
Wholesale groups management page: CRUD, test pricing rules against sample cart. Volume breaks editor: per-product interface to add/edit qty thresholds and price overrides. Bulk customer-to-group assignment via CSV upload with validation. Wholesale orders queue: filterable by status (pending/approved/rejected), shows customer, total, reason approval required (credit term). Pricing rule audit report: download CSV showing customer→group mapping, current price vs. standard, volume breaks in effect, reconciliation warnings.
The seam — why this is paid
Core owns: customer entity, order schema, cart price-calculation hook interface. Paid pack owns: WholesaleGroup entity, VolumeBreak rules, approval workflow, audit reporting. Why: every merchant needs to sell standard pricing to anyone; wholesale is an optional business process (some never use it), not a legal/compliance requirement. Merchants using wholesale need premium support for pricing-rule audits and customer-group reconciliation.
Operational complexity requiring customer segmentation, pricing-override workflows, quote generation, and credit-approval flows. Already designated as paid tier. Support commitment includes pricing-rule auditing and customer-group reconciliation.
Dependencies
- customer-groups-and-wholesale-pricing must exist (free feature): defines the CustomerGroup entity and group_id FK on Customer
- Order approval workflow must exist or be added: state machine for approval_status transitions
- Cart price-calculation hook must support merchant-defined overrides: cart must call pricing logic that checks for wholesale overrides
Acceptance checks
Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.
- Wholesale customer in group ‘reseller’ with volume break (min qty 10) orders qty=15; cart applies volume-break price (not standard)
- Same product, non-wholesale customer orders qty=15; cart applies standard catalog price (not wholesale override)
- Wholesale order with requires_approval=true does not transition to ‘paid’ status until approval_status is ‘approved’
- Bulk-assign 100 customers to ‘reseller’ group via CSV; audit report confirms all 100 mapped with assignment date
- Admin attempts to assign customer to non-existent group; request fails with FK constraint error
- Volume break with min_qty=50: customer orders qty=45; break does NOT apply (quantity below threshold)
- Volume break with min_qty=50: customer orders qty=50; break DOES apply
- Delete wholesale group with 0 customers assigned; succeeds. Delete group with 5 customers; fails with message
- Approve wholesale order; next workflow step (payment) executes without manual override
Risks
If cart calculation doesn’t hook the override check properly, wholesale prices won’t apply—audit all price-calculation paths across all three storage drivers. If approval workflow blocks payment permanently, customers get stuck in limbo; must provide admin override/manual-approval path. Concurrent pricing-rule changes could expose race conditions (e.g., customer views price, rule changes, customer checks out): add isolation test and explicit data-freshness rule. Merchants forget to assign customers to groups, then wonder why discounts don’t appear; add on-boarding checklist with ‘assign your first wholesale customer’ step.
Commercial context
| Suggested price | $399–799/year |
| Rival anchor | Magento ships customer-group pricing free as part of the tax and pricing architecture. |
The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.
Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full
catalogue and ../../AI-GUIDE.md for how to work on this repository.