B2B ecommerce has different rules than B2C. Your wholesale customers expect negotiated pricing, volume discounts, net payment terms, and quick reordering. Your ERP already manages all of this. The challenge is surfacing ERP-driven pricing logic in WooCommerce without duplicating business rules.

The B2B Pricing Problem

In your ERP, pricing looks like this:

Customer: ABC Corp (Tier: Gold)

├── Product: Widget A

│ ├── List Price: $100

│ ├── Gold Tier Price: $75

│ ├── Volume 10+: $70

│ ├── Volume 50+: $65

│ └── Special Contract Price: $62 (until Dec 2026)

├── Product: Widget B

│ ├── List Price: $200

│ └── Gold Tier Price: $160

WooCommerce has none of this natively. It supports one regular price and one sale price per product.

Architecture: ERP as Pricing Engine

Don’t replicate pricing logic in WooCommerce. Use your ERP as the pricing engine:

Customer logs into WooCommerce

→ Frontend requests prices for visible products

→ Middleware queries ERP with customer ID + product SKUs

→ ERP returns customer-specific prices

→ Prices displayed in WooCommerce

Customer adds to cart

→ Cart total calculated using ERP prices

→ At checkout, final prices verified against ERP

→ Order created with ERP-validated prices

Implementation: Real-Time Price Lookup

Custom REST Endpoint

// WordPress plugin: ERP Price Endpoint

add_action('rest_api_init', function() {

register_rest_route('b2b/v1', '/prices', [

'methods' => 'POST',

'callback' => 'get_erp_prices',

'permission_callback' => function() {

return is_user_logged_in();

}

]);

});

function get_erp_prices($request) {

$product_ids = $request->get_param('product_ids');

$customer_id = get_user_meta(get_current_user_id(), 'erp_customer_id', true);

if (!$customer_id) {

return new WP_Error('no_erp_account', 'No ERP account linked', ['status' => 400]);

}

// Call ERP API for customer-specific prices

$erp_prices = erp_get_customer_prices($customer_id, $product_ids);

return rest_ensure_response($erp_prices);

}

Middleware Price Service

// Price lookup service

app.post('/api/prices', authenticateCustomer, async (req, res) => {

const { productSkus } = req.body;

const erpCustomerId = req.customer.erpId;

// Cache key: customer + SKU combination

const cacheKey = prices:${erpCustomerId}:${productSkus.sort().join(',')};

const cached = await redis.get(cacheKey);

if (cached) return res.json(JSON.parse(cached));

// Fetch from ERP

const prices = await erp.getCustomerPrices(erpCustomerId, productSkus);

// Transform to WooCommerce format

const result = prices.map(p => ({

sku: p.itemCode,

price: p.customerPrice,

listPrice: p.listPrice,

discount: ((p.listPrice - p.customerPrice) / p.listPrice * 100).toFixed(0),

volumePricing: p.volumeBreaks?.map(vb => ({

minQty: vb.quantity,

price: vb.price

})) || [],

currency: p.currency

}));

// Cache for 15 minutes

await redis.setex(cacheKey, 900, JSON.stringify(result));

res.json(result);

});

Customer Tiers and Role-Based Pricing

Map ERP customer tiers to WooCommerce user roles:

// Sync ERP tiers to WooCommerce roles

function sync_customer_tier($user_id, $erp_tier) {

$tier_role_map = [

'standard' => 'customer',

'silver' => 'b2b_silver',

'gold' => 'b2b_gold',

'platinum' => 'b2b_platinum'

];

$role = $tier_role_map[$erp_tier] ?? 'customer';

$user = new WP_User($user_id);

$user->set_role($role);

}

// Create B2B roles on plugin activation

function create_b2b_roles() {

add_role('b2b_silver', 'B2B Silver', ['read' => true, 'b2b_access' => true]);

add_role('b2b_gold', 'B2B Gold', ['read' => true, 'b2b_access' => true]);

add_role('b2b_platinum', 'B2B Platinum', ['read' => true, 'b2b_access' => true]);

}

Volume Discounts Display

Show volume pricing tables on product pages:

// React component for volume pricing

function VolumePricing({ pricing }) {

if (!pricing.volumePricing?.length) return null;

return (

{pricing.volumePricing.map(vp => (

))}

Quantity Price per Unit Savings
1 - {pricing.volumePricing[0].minQty - 1} ${pricing.price}
{vp.minQty}+ ${vp.price}

{((pricing.price - vp.price) / pricing.price * 100).toFixed(0)}% off

);

}

Quote/RFQ Workflow

For high-value or custom orders, B2B customers expect a quote workflow:

// Request for Quote endpoint

app.post('/api/rfq', authenticateCustomer, async (req, res) => {

const { items, notes } = req.body;

// Create RFQ in ERP

const rfq = await erp.createQuote({

customerId: req.customer.erpId,

items: items.map(i => ({

sku: i.sku,

quantity: i.quantity,

requestedPrice: i.requestedPrice || null

})),

notes,

validUntil: addDays(new Date(), 30)

});

// Notify sales team

await notify.salesTeam({

type: 'new_rfq',

customer: req.customer.name,

totalValue: rfq.estimatedTotal,

rfqId: rfq.id

});

// Create WooCommerce order with "quote" status

const wcOrder = await wooApi.post('orders', {

status: 'quote',

customer_id: req.customer.wcId,

line_items: items.map(i => ({

product_id: i.productId,

quantity: i.quantity,

total: '0' // Price TBD

})),

meta_data: [

{ key: '_rfq_id', value: rfq.id },

{ key: '_rfq_status', value: 'pending' }

]

});

res.json({ rfqId: rfq.id, orderId: wcOrder.data.id });

});

Payment Terms

B2B customers pay on net terms (Net 30, Net 60), not at checkout. Handle this through:

  • Custom payment gateway: “Purchase Order / Net Terms” that creates the order without payment
  • ERP invoice generation: After order ships, ERP generates the invoice with the agreed terms
  • Credit limit check: Before allowing PO payment, verify the customer’s credit limit in the ERP
// Custom payment gateway: Purchase Order

class WC_Gateway_Purchase_Order extends WC_Payment_Gateway {

public function __construct() {

$this->id = 'purchase_order';

$this->title = 'Purchase Order (Net 30)';

$this->method_title = 'Purchase Order';

$this->has_fields = true;

}

public function payment_fields() {

echo '

Enter your PO number. Payment is due within 30 days of shipment.

';

echo '';

}

public function process_payment($order_id) {

$order = wc_get_order($order_id);

$po_number = sanitize_text_field($_POST['po_number']);

// Verify credit limit with ERP

$customer_erp_id = get_user_meta($order->get_customer_id(), 'erp_customer_id', true);

$credit = erp_check_credit($customer_erp_id, $order->get_total());

if (!$credit['approved']) {

wc_add_notice('Credit limit exceeded. Please contact your account manager.', 'error');

return ['result' => 'failure'];

}

$order->update_meta_data('_po_number', $po_number);

$order->update_status('processing', 'PO received: ' . $po_number);

$order->save();

return ['result' => 'success', 'redirect' => $this->get_return_url($order)];

}

}

Catalog Visibility

B2B stores often need to hide prices or products from non-authenticated visitors:

// Hide prices for non-B2B users

add_filter('woocommerce_get_price_html', function($price, $product) {

if (!current_user_can('b2b_access')) {

return 'Login for pricing';

}

return $price;

}, 10, 2);

// Hide entire products from non-B2B users

add_action('pre_get_posts', function($query) {

if (!is_admin() && $query->is_main_query() && !current_user_can('b2b_access')) {

$query->set('meta_query', [[

'key' => '_b2b_only',

'compare' => 'NOT EXISTS'

]]);

}

});

Conclusion

WooCommerce B2B with ERP integration is about letting each system do what it’s best at. WooCommerce handles the shopping experience — product display, cart, user interface. The ERP handles the business logic — pricing, credit limits, payment terms, volume discounts. The middleware bridges the gap, translating ERP data into WooCommerce-friendly formats in real time. Don’t try to replicate ERP pricing logic in WooCommerce plugins. Query the ERP, cache the results, and let the source of truth remain the source of truth.

Leave a Reply

Your email address will not be published. Required fields are marked *

Close Search Window