
B2b Commerce
- 109 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Enable wholesale and B2B selling with company accounts, custom catalogs and pricing, quote-to-order workflows, purchase orders, and net payment terms.
About
A skill for adding B2B and wholesale capabilities to a store: company accounts, custom catalogs, quote workflows, and net terms. A developer uses it to build wholesale ordering on top of an existing ecommerce platform.
- Company accounts, custom per-account pricing and catalogs
- Quote-to-order workflow and Net 30/60/90 invoicing
B2b Commerce by the numbers
- 109 all-time installs (skills.sh)
- Ranked #2,942 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill b2b-commerceAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 109 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Enable wholesale and B2B selling with company accounts, custom catalogs and pricing, quote-to-order workflows, purchase orders, and net payment terms.
Files
B2B Commerce
Overview
B2B commerce adds wholesale capabilities to your store: company accounts where multiple employees order under one credit limit, custom pricing per account, a quote-to-order workflow for large or custom orders, and net payment terms (Net 30/60/90) with invoice generation. Most platforms have first-party B2B features or dedicated apps that handle these requirements without custom development.
When to Use This Skill
- When onboarding wholesale customers who require account-level pricing, catalogs, and credit terms
- When building a distributor portal where multiple employees at the same company can place orders under a shared credit limit
- When implementing a configure-price-quote (CPQ) flow for custom or large-volume orders
- When replacing a phone/email-based wholesale ordering process with a self-service portal
- When B2B customers require purchase order numbers on invoices for their accounts payable process
Core Instructions
Step 1: Determine your platform and choose the right B2B tool
| Platform | Recommended Tool | Why |
|---|---|---|
| Shopify Plus | Shopify B2B (built-in) | Shopify Plus includes Company accounts, custom catalogs, payment terms, and a dedicated B2B checkout — no extra app needed |
| Shopify (non-Plus) | Wholesale Club or Wholesale Gorilla | Both apps add customer-tag-based wholesale pricing, minimum order quantities, and hidden wholesale sections without requiring Plus |
| WooCommerce | WooCommerce B2B (WooCommerce.com extension) or B2BWoo | WooCommerce B2B handles company registration, role-based pricing, and net terms; B2BWoo is a premium alternative |
| BigCommerce | BigCommerce B2B Edition or the built-in Customer Groups | BigCommerce B2B Edition adds company accounts and quotes; Customer Groups (all plans) handle tiered wholesale pricing |
| Custom / Headless | Build company accounts, custom catalogs, and a quote workflow from scratch | Full control over credit limits, approval workflows, and invoice generation |
Step 2: Set up company accounts and wholesale pricing
Shopify Plus — B2B (built-in)
1. In Shopify admin, go to Customers → B2B → Companies → Create company 2. Enter the company name, billing address, payment terms (e.g., Net 30), and credit limit 3. Add company locations (shipping addresses) and assign buyer contacts to the company 4. Create a Price List for the company: go to Customers → B2B → Price Lists → Create price list and set custom prices per product or a percentage discount on all products 5. Assign the price list to the company — company buyers see these prices when logged in 6. Company buyers see a B2B-specific checkout with net terms payment options and a PO number field
Quote workflow on Shopify Plus:
- Shopify's built-in B2B includes a Draft Orders feature — sales reps create draft orders for buyers, the buyer reviews and approves, then the draft converts to an order
- Go to Orders → Create order to create a draft; share the link with the buyer for approval
Shopify (non-Plus) — Wholesale Club
1. Install Wholesale Club from the Shopify App Store 2. Create a "Wholesale" customer tag in Shopify 3. In Wholesale Club, create a discount tier for the "Wholesale" tag: e.g., 30% off everything for tagged customers 4. Apply the wholesale tag to customer accounts when you approve them for wholesale 5. Tagged customers see the discounted prices when logged in; non-tagged customers see retail prices 6. For minimum order quantities: Wholesale Club's paid tier supports MOQs per product or order
WooCommerce
WooCommerce B2B (official extension): 1. Install from WooCommerce.com 2. Go to WooCommerce → B2B → Registration to create a wholesale account registration form with business details (company name, tax ID, etc.) 3. Manually approve wholesale registrations in WooCommerce → B2B → Customers 4. Set pricing rules: apply a percentage discount to user role "Wholesale Customer" across all products or per category 5. For net terms: WooCommerce B2B adds a "Pay by Invoice" payment gateway — orders placed with net terms generate an invoice PDF and allow deferred payment
Alternative — WooCommerce Role-Based Pricing:
- Create a "Wholesale" user role (using the User Role Editor plugin)
- Use the WooCommerce Role-Based Pricing plugin to set per-role prices per product or per category
BigCommerce
Customer Groups (available on all plans): 1. Go to Customers → Customer Groups → Add 2. Create a "Wholesale" group with a percentage discount on all products or category-specific discounts 3. Assign wholesale customers to this group manually or via registration form rules 4. Customers in the Wholesale group see group-specific pricing at checkout
BigCommerce B2B Edition (add-on for growing wholesale operations): 1. Contact BigCommerce sales to add B2B Edition to your plan 2. B2B Edition adds: company accounts with multiple buyers, quote requests, corporate payment methods, and a buyer portal 3. The buyer portal lets company admins manage users, view order history, and request quotes from a self-service interface
Step 3: Set up a quote workflow
For large or custom orders, a quote workflow lets buyers request a price before committing.
Shopify Plus
1. Use Draft Orders — a sales rep creates a draft order with custom pricing 2. Share the draft order link with the buyer via email 3. The buyer reviews the order in Shopify's checkout and approves/places the order 4. For a more formal quote system: install QuoteIQ or CPQ Configurator apps from the Shopify App Store
WooCommerce
1. Install WooCommerce Request a Quote (free, by YITH or similar) 2. Add a "Request a Quote" button to product pages — buyers add items to a quote cart 3. Buyer submits the quote request; you receive an email with the items and quantities 4. Edit the quote (adjust pricing) and send back to the buyer with an approval link 5. Buyer approves → quote converts to a WooCommerce order automatically
BigCommerce B2B Edition
- B2B Edition includes a native quote module — buyers click "Request a Quote" on any product
- You receive the quote request in the BigCommerce admin, set pricing, and send it back
- Buyers approve quotes and place the order in one step
Step 4: Configure net payment terms and invoice generation
Net terms let B2B buyers pay after receiving goods (Net 30 = pay within 30 days of invoice).
Shopify Plus
1. On the company record: set Payment Terms to Net 15, Net 30, Net 60, or Net 90 2. When a B2B buyer places an order, Shopify creates a "due on" date automatically 3. Track outstanding invoices in Customers → B2B → Orders → Invoices 4. Send payment reminders: Shopify automatically emails buyers when invoices are due (configure in Settings → Notifications)
Accounting integration for net terms:
- Connect Shopify to QuickBooks Online or Xero via their Shopify connectors
- Net terms orders sync as unpaid invoices in QuickBooks/Xero
- Your accounting team manages the payment collection follow-up from QuickBooks/Xero
WooCommerce
- The WooCommerce B2B extension's "Pay by Invoice" gateway creates PDF invoices with due dates based on net terms
- Connect to QuickBooks via the QuickBooks Online WooCommerce extension for AR tracking
- For automated payment reminders: use AutomateWoo to send reminder emails 7 days before and on the invoice due date
BigCommerce
- B2B Edition's corporate payment methods include "Net terms" options
- Connect to QuickBooks or NetSuite via their BigCommerce connectors for invoice tracking
Step 5: Custom / Headless — core B2B data model
// Company account with credit management
interface Company {
id: string;
name: string;
taxId?: string; // EIN, VAT number
billingAddress: Address;
paymentTerms: 'prepay' | 'net15' | 'net30' | 'net60' | 'net90';
creditLimitCents: number | null; // null = no limit
creditUsedCents: number;
status: 'pending' | 'approved' | 'suspended';
}
// Check credit before placing an order
async function checkCreditAvailability(
companyId: string,
orderTotalCents: number
): Promise<{ approved: boolean; reason?: string }> {
const company = await db.companies.findById(companyId);
if (company.status !== 'approved') {
return { approved: false, reason: 'Company account not approved' };
}
if (company.creditLimitCents !== null) {
const availableCredit = company.creditLimitCents - company.creditUsedCents;
if (orderTotalCents > availableCredit) {
return {
approved: false,
reason: `Order exceeds available credit. Available: $${(availableCredit / 100).toFixed(2)}`,
};
}
}
return { approved: true };
}
// Record credit usage when order is placed (in same DB transaction as order creation)
async function consumeCredit(companyId: string, amountCents: number): Promise<void> {
// Atomic update prevents race condition when multiple buyers order simultaneously
await db.raw(
'UPDATE companies SET credit_used_cents = credit_used_cents + ? WHERE id = ? AND credit_used_cents + ? <= COALESCE(credit_limit_cents, 2147483647)',
[amountCents, companyId, amountCents]
);
}Best Practices
- Require company approval before enabling net terms — run a basic credit check or manual review before setting net terms; at minimum, verify the company's business registration
- Enforce credit limits atomically — check and update credit in the same database transaction that creates the order; race conditions can allow two buyers to exceed the limit simultaneously
- Always put the PO number on invoices — B2B buyers require their internal PO number on every invoice for their accounts payable workflow; missing this causes payment delays of 30–60 days
- Release credit when orders are paid or cancelled — decrement credit_used when you receive payment; this lets buyers place new orders without waiting for manual adjustments
- Set quote expiration dates — approved quotes with custom pricing should expire after 30 days to prevent buyers from ordering at stale prices after costs change
- Audit all quote price changes — log who changed which line item price and when; this is important for margin tracking and potential disputes
Common Pitfalls
| Problem | Solution |
|---|---|
| Individual buyer sees another company's pricing | Scope all catalog and pricing queries strictly by company ID; test by logging in as buyers from different companies |
| Credit limit exceeded due to concurrent orders | Use atomic SQL update with a WHERE clause checking credit headroom; Shopify Plus's built-in B2B handles this automatically |
| B2B checkout bypasses the approval flow | Enforce approval checks server-side in your order creation logic, not just in the UI |
| Wholesale prices show to non-wholesale customers | Customer tags on Shopify and user roles in WooCommerce must be verified server-side; never trust client-side state for pricing |
Related Skills
- @vendor-management
- @order-management-system
- @returns-refund-policy
- @multi-channel-selling
- @accounts-payable-management
{
"context": "Tests whether the agent correctly models the B2B data layer: the companies, company_users, and company_catalogs tables with the right column types, constraints, and defaults. Also checks that billing information is stored at the company level rather than on individual users, and that company status and credit fields follow the prescribed schema.",
"type": "weighted_checklist",
"checklist": [
{
"name": "companies.id is UUID",
"max_score": 5,
"description": "companies.id column uses UUID type with gen_random_uuid() as default"
},
{
"name": "payment_terms CHECK constraint",
"max_score": 10,
"description": "companies.payment_terms has a CHECK constraint that includes exactly the values: prepay, net15, net30, net60, net90"
},
{
"name": "payment_terms default net30",
"max_score": 5,
"description": "companies.payment_terms defaults to 'net30'"
},
{
"name": "credit stored in cents",
"max_score": 8,
"description": "Both credit_limit and credit_used on companies are INTEGER type (cents), not DECIMAL or FLOAT"
},
{
"name": "credit_limit nullable",
"max_score": 5,
"description": "companies.credit_limit allows NULL (no NOT NULL constraint), meaning no limit"
},
{
"name": "company status CHECK",
"max_score": 8,
"description": "companies.status has a CHECK constraint with values: pending, approved, suspended; defaults to 'pending'"
},
{
"name": "billing_address JSONB on company",
"max_score": 8,
"description": "billing_address is stored as JSONB on the companies table, NOT on a users table"
},
{
"name": "company_users role CHECK",
"max_score": 10,
"description": "company_users.role has a CHECK constraint with values: admin, buyer, approver, viewer; defaults to 'buyer'"
},
{
"name": "spending_limit per user",
"max_score": 8,
"description": "company_users has a spending_limit INTEGER column (cents per order) that allows NULL"
},
{
"name": "company_users UNIQUE",
"max_score": 5,
"description": "company_users has a UNIQUE constraint on (company_id, user_id)"
},
{
"name": "company_catalogs custom_price nullable",
"max_score": 8,
"description": "company_catalogs.custom_price is INTEGER allowing NULL (meaning use volume pricing when NULL)"
},
{
"name": "company_catalogs is_visible",
"max_score": 5,
"description": "company_catalogs has is_visible BOOLEAN NOT NULL DEFAULT true column"
},
{
"name": "company_catalogs UNIQUE",
"max_score": 5,
"description": "company_catalogs has a UNIQUE constraint on (company_id, product_id)"
},
{
"name": "credit_used default zero",
"max_score": 5,
"description": "companies.credit_used has NOT NULL DEFAULT 0"
},
{
"name": "tax_id column",
"max_score": 5,
"description": "companies table includes a tax_id column (VARCHAR type) for EIN/VAT numbers"
}
]
}
Wholesale Portal Database Migration
Problem/Feature Description
SupplyChain Direct is migrating from a legacy wholesale ordering platform to a modern PostgreSQL-backed system. The legacy system tracked company customers as a flat list with a single billing contact, but the new platform needs to support multiple buyers per company account, per-user purchase controls, and deferred payment arrangements (net terms).
The engineering team is starting with the database migration. They need SQL that models company accounts with proper payment term options and credit tracking, assigns users to company accounts with role-based controls, and maintains a per-company product catalog with optional custom pricing. The finance team has emphasized that monetary values must be stored consistently and that the schema must enforce valid values for status and payment term fields.
Output Specification
Write a PostgreSQL migration file named migration.sql containing the CREATE TABLE statements for the company accounts data model. The migration should be standalone and runnable against a PostgreSQL database that already has users and products tables.
Document any design decisions in a design_notes.md file — particularly how you chose to store monetary amounts, how you enforced field constraints, and how you separated company-level from user-level data.
{
"context": "Tests whether the agent correctly implements company-scoped catalog retrieval (with fallback to all products and custom price overlay), server-side approval enforcement (not just client-side), net-terms invoice generation with pdfkit including correct due date calculation and PO number, and credit release when orders are paid or cancelled.",
"type": "weighted_checklist",
"checklist": [
{
"name": "catalog empty fallback",
"max_score": 10,
"description": "getCatalogForCompany returns ALL active products (not an empty list or error) when the company has no custom catalog entries"
},
{
"name": "custom price overlay",
"max_score": 8,
"description": "getCatalogForCompany overrides the standard product price with custom_price from company_catalogs when a catalog entry exists"
},
{
"name": "catalog scoped by company_id",
"max_score": 8,
"description": "getCatalogForCompany queries company_catalogs with a company_id filter (not returning catalog data for all companies)"
},
{
"name": "is_visible filter",
"max_score": 5,
"description": "getCatalogForCompany filters catalog entries where is_visible = true"
},
{
"name": "approval: buyer role triggers approval",
"max_score": 8,
"description": "checkOrderApprovalRequired returns true when the user's role is 'buyer' (regardless of order total)"
},
{
"name": "approval: spending_limit check",
"max_score": 8,
"description": "checkOrderApprovalRequired returns true when orderTotal exceeds the user's spending_limit (when spending_limit is not null)"
},
{
"name": "approval enforced server-side",
"max_score": 8,
"description": "The checkout/order-placement API endpoint calls checkOrderApprovalRequired (or equivalent) on the server, not relying solely on a client-provided flag"
},
{
"name": "invoice due date calculation",
"max_score": 11,
"description": "generateInvoice calculates dueDate by parsing the numeric days from payment_terms (e.g. 'net30' → 30) and adding to order.created_at"
},
{
"name": "invoice number format",
"max_score": 6,
"description": "Invoice number follows the format INV-{order_number} (INV- prefix)"
},
{
"name": "invoice uses pdfkit",
"max_score": 8,
"description": "Invoice PDF is built using pdfkit (the package named 'pdfkit'), not a different PDF library"
},
{
"name": "PO number on invoice",
"max_score": 6,
"description": "Invoice includes the order's po_number field in the output"
},
{
"name": "credit release on payment",
"max_score": 7,
"description": "There is a handler (webhook or function) that decrements companies.credit_used when an order is paid"
},
{
"name": "credit release on cancellation",
"max_score": 7,
"description": "Order cancellation logic decrements companies.credit_used immediately when a credit-consuming order is cancelled"
}
]
}
B2B Portal: Catalog, Checkout Controls, and Invoice Features
Problem/Feature Description
MedEquip Wholesale operates a portal for hospitals and clinics purchasing medical equipment. Different hospital networks have negotiated distinct pricing agreements, and some products are only available to certain accounts. The platform already has a company_catalogs table that stores per-company product visibility and pricing overrides, but the catalog endpoint currently ignores these and returns the same list to everyone.
Three features need to be built out. First, the product listing API must return a company-specific view: showing only their contracted products with their negotiated prices, but falling back to the full product catalog for accounts that have no custom catalog configured. Second, the checkout flow has had incidents where junior purchasing staff approved large orders without the required internal sign-off — approval logic currently lives only in the frontend, and the team wants it enforced on the server regardless of what the client sends. Third, the finance team is manually generating invoices and needs them automated: each fulfilled order with net payment terms needs a PDF invoice with the correct payment due date and the hospital's internal purchase order number for their accounts payable system. Finally, the credit management team reports that cancelled and paid orders are not releasing the credit they consumed, causing companies to hit their limits prematurely.
Output Specification
Implement the following in TypeScript, organized into files as you see fit:
1. getCatalogForCompany(companyId) — returns the product list scoped to the company's catalog configuration 2. checkOrderApprovalRequired(companyId, buyerId, orderTotal) — returns whether an order needs approval before it can be placed 3. A checkout/place-order API handler that calls the approval check on the server side 4. generateInvoice(orderId) — returns a PDF buffer for the order's invoice 5. Credit release logic for order payment and cancellation events
Write all implementations to files and include a short summary.md describing your approach to each feature.
Assume the following are available: db (with tables: companies, company_users, company_catalogs, products, orders, order_lines), calculateTax(order), and a PDF generation library of your choice. Do not use any external API services — everything should work offline.
{
"context": "Tests whether the agent correctly implements the B2B quote lifecycle: submitting a quote (status check, email notification), approving a quote (transaction wrapping, 30-day validity, both price fields updated), converting to an order (credit check inside transaction, quote locked to 'ordered', price frozen from approval), and auditing quote changes.",
"type": "weighted_checklist",
"checklist": [
{
"name": "submitQuote status guard",
"max_score": 6,
"description": "submitQuote checks that quote.status === 'draft' and throws/returns an error if not"
},
{
"name": "submitQuote email template",
"max_score": 6,
"description": "submitQuote sends email using template named 'quote-submitted' with quoteId and companyName in the payload"
},
{
"name": "submitQuote notifies account manager",
"max_score": 5,
"description": "submitQuote sends the notification to the account manager (not the buyer) for the company"
},
{
"name": "approveQuote uses transaction",
"max_score": 8,
"description": "approveQuote wraps all database updates (quote lines and quote status) in a single database transaction"
},
{
"name": "approveQuote updates both price fields",
"max_score": 8,
"description": "approveQuote sets BOTH unit_price AND offered_price on each quote line (not just one of them)"
},
{
"name": "approveQuote 30-day validity",
"max_score": 8,
"description": "approveQuote sets valid_until to exactly 30 days from the current time (30 * 86400000 ms or equivalent)"
},
{
"name": "approveQuote buyer email",
"max_score": 6,
"description": "approveQuote sends email to the buyer using template 'quote-approved' with quoteId and validUntil"
},
{
"name": "placeOrderFromQuote approved check",
"max_score": 5,
"description": "placeOrderFromQuote checks quote.status === 'approved' and throws if not"
},
{
"name": "placeOrderFromQuote expiry check",
"max_score": 5,
"description": "placeOrderFromQuote checks that valid_until has not passed and throws an error if the quote is expired"
},
{
"name": "credit check inside transaction",
"max_score": 12,
"description": "placeOrderFromQuote checks credit_used + orderTotal against credit_limit INSIDE the same transaction as the order insert, not before entering the transaction"
},
{
"name": "PO number propagation",
"max_score": 5,
"description": "placeOrderFromQuote copies po_number from the function parameter (falling back to quote.po_number) onto the created order"
},
{
"name": "quote marked ordered",
"max_score": 6,
"description": "placeOrderFromQuote updates the quote's status to 'ordered' inside the transaction"
},
{
"name": "price not recalculated",
"max_score": 8,
"description": "placeOrderFromQuote uses unit_price from the quote_lines (frozen at approval time) without fetching current product prices"
},
{
"name": "audit logging",
"max_score": 12,
"description": "Implementation includes an audit log mechanism (table, event log, or similar) that records who changed a quote line price and when"
}
]
}
Quote Management System for Wholesale Orders
Problem/Feature Description
BuildRight Materials sells construction supplies to contractors and distributors. Their current process involves phone calls and spreadsheets to negotiate large custom orders. The sales team wants a self-service portal where business customers can request quotes for large orders, the sales team can review and counter-propose pricing, and the customer can then convert an accepted quote into an actual order.
The backend team needs TypeScript functions that handle the entire quote lifecycle. Price negotiations are sensitive — the system must keep a clear record of how prices changed during negotiation, and approved prices must remain locked in even if the underlying product catalog changes afterward. There have also been incidents where high-value orders were placed against exhausted credit lines, so the credit check must be robust against concurrent activity.
Output Specification
Implement the following TypeScript functions in a file named quote-workflow.ts:
submitQuote(quoteId, buyerNotes?)— transitions a quote from initial state to submitted and alerts the responsible sales contactapproveQuote(quoteId, approvedBy, finalPrices)— finalizes negotiated prices and marks the quote ready to convert; the approved quote should have a limited window before it expiresplaceOrderFromQuote(quoteId, buyerId, poNumber?)— converts an approved, non-expired quote into an order, consuming the company's available credit
Also write a brief implementation_notes.md explaining your approach to credit safety and price integrity.
Assume the following are already available as imports or globals: db (database client with tables: quotes, quoteLines, orders, orderLines, companies), emailService, getAccountManagerEmail(companyId), getBuyerEmail(userId).
{
"name": "finsi/b2b-commerce",
"version": "0.1.0",
"summary": "B2B features — company accounts, quote workflows, custom catalogs, net terms",
"skills": {
"b2b-commerce": {
"path": "SKILL.md"
}
}
}