A B2B checkout is not a retail checkout with an invoice option bolted on. It has to carry a purchase order number, respect a credit limit the ERP owns, and route orders above a threshold to an approver — without becoming the slowest part of the buying process.
Retail checkout optimisation is a well-trodden field: remove fields, remove steps, remove friction. Applying that instinct directly to B2B produces a checkout that finance refuses to sign off, because in trade buying some of the friction is the control.
A wholesale order frequently involves three roles: the person who selects the goods, the person whose budget pays for them, and the finance function that decides whether this account can take on more exposure. A checkout that ignores those roles pushes the work into email, and the "digital" order ends up processed by hand anyway.
The four capabilities
Purchase order capture
A PO number field, validated to the customer’s format, carried through to the ERP and printed on the invoice. Often mandatory per account, not globally.
Payment terms
Net 30 / 60 / custom, offered only to accounts entitled to them, with everyone else falling back to card or bank transfer.
Credit limit enforcement
The available credit figure lives in the ERP. The checkout must read it at the moment of purchase, not trust a nightly copy.
Approval routing
Orders over a threshold become a pending request routed to a named approver, rather than an order.
The order lifecycle, with approvals in it
Order state flow for a B2B checkout with approvals. A buyer builds a cart, then the checkout validates the purchase order number format and calls the ERP for a live credit check. If credit is insufficient the order is blocked with a message and finance is notified. If the order value is under the account threshold it is placed directly. If it is over, it becomes a pending approval routed to a named approver, who can approve, reject or amend. Approved orders are placed and pushed to the ERP as a sales order with the PO number attached.
Credit limits must be checked live
This is the single most common shortcut, and it is the one that costs real money. Teams sync an available-credit figure overnight because a live ERP call at checkout feels risky. Then a customer places four orders in one morning, each individually within the stale limit, and collectively far beyond it.
Check at the moment of purchase. Give the call a tight timeout and an explicit, deliberate fallback — and make the fallback a business decision rather than an accident of implementation.
const CREDIT_TIMEOUT_MS = 1200;
async function resolveCreditDecision({ accountId, orderTotal }) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), CREDIT_TIMEOUT_MS);
try {
const res = await fetch(`${ERP_BASE}/accounts/${accountId}/credit`, {
signal: controller.signal,
headers: { Authorization: `Bearer ${erpToken()}` }
});
if (!res.ok) throw new Error(`ERP ${res.status}`);
const { availableCredit, termsCode } = await res.json();
return {
decision: orderTotal <= availableCredit ? 'allow' : 'block',
availableCredit,
termsCode,
source: 'live'
};
} catch (err) {
// Deliberate policy, agreed with finance and written down:
// fall back to the last known figure, but only below a stated ceiling.
const cached = await lastKnownCredit(accountId);
const withinFallbackCeiling = orderTotal <= Math.min(cached, FALLBACK_CEILING);
reportDegradedCreditCheck({ accountId, reason: err.message });
return {
decision: withinFallbackCeiling ? 'allow' : 'review',
availableCredit: cached,
source: 'cached',
degraded: true
};
} finally {
clearTimeout(timer);
}
}Enforcing rules inside the checkout
On Shopify Plus, the mechanisms are Checkout UI extensions for fields such as the PO number, and Shopify Functions for rules that must be enforced server-side — minimum order values, blocking a payment method the account is not entitled to, hiding delivery options that do not apply to a trade account.
The important distinction is that a UI extension is a presentation concern and a Function is an enforcement concern. Anything a determined buyer must not be able to bypass belongs in a Function. Validation that only exists in the UI is advice, not a control.
#[shopify_function]
fn run(input: input::ResponseData) -> Result<output::FunctionRunResult> {
let minimum = input
.cart
.buyer_identity
.as_ref()
.and_then(|b| b.purchasing_company.as_ref())
.and_then(|c| c.company.minimum_order.as_ref())
.and_then(|m| m.value.parse::<f64>().ok())
.unwrap_or(0.0);
let subtotal = input.cart.cost.subtotal_amount.amount;
let errors = if subtotal < minimum {
vec![output::FunctionError {
localized_message: format!(
"Orders on this account start at {:.2}. Add {:.2} to continue.",
minimum,
minimum - subtotal
),
target: "cart".to_owned(),
}]
} else {
vec![]
};
Ok(output::FunctionRunResult { errors })
}Approvals without the delay
An approval step adds latency to the buying process, and buyers notice. Three things keep it tolerable: notify the approver immediately through a channel they actually read, let them approve without logging into a portal they have never used, and show the requester exactly where the order is sitting and who has it.
Also allow the approver to amend rather than only approve or reject. In practice a great many approvals are "yes, but drop the second line", and forcing a reject-and-rebuild cycle for that is how a well-intentioned control becomes the reason a customer starts phoning orders through again.
Key takeaways
- B2B friction is not always waste — some of it is the financial control the account requires.
- Check credit live at checkout. Nightly figures let several same-day orders each pass a stale limit.
- Decide and document the ERP-unreachable failure mode before launch, and monitor the degraded path.
- UI validation is advice; enforcement belongs server-side, in a Function or its equivalent.
- Let approvers amend orders, not just approve or reject — it is what most approvals actually are.
360 Expert Solutions
E-commerce & ERP Integration Team
We build and scale B2B and DTC commerce on Shopify Plus, BigCommerce and WooCommerce, wired into NetSuite and Oracle. These articles are written by the engineers and architects who deliver those projects.
Building something like this?
We architect B2B commerce and ERP integrations end to end.