English source · 9 min
Modular B2B commerce
Studio setup and access: Channel wizard fixes, reusable roles, current grants, central B2B configuration and guided company creation.
VEYNDO supports company purchasing on the same catalog, currency calculation, cart, inventory transaction, payment-provider contract, order and document owners used by B2C and POS. A company is inside one merchant tenant. A sales channel is a shared selling context; an unrelated SaaS merchant remains a separate tenant.
UCP buyers: Discoverable offer negotiation connects the same RFQs, merchant inbox and purchasing approvals to UCP checkout. This is a versioned VEYNDO extension, not an official UCP price-negotiation capability.
Enter company purchasing as a buyer
Choose Business account in the standard storefront. It opens the existing customer account at company purchasing. If unsigned, sign in with your own customer account first; the dialog retains the destination after authentication. An existing session is reused. My account still opens the ordinary overview.
The entry does not log into a shared demonstration account, assign a company, select a unit or change prices. Merchants link the customer's existing identity to purchasing units under Companies & purchasing → Customers & employees. Without a current assignment the portal explains that company access must first be enabled. Unit selection, RFQs, approval, budgets and purchases continue through the same authorized native operations described below. Component regressions exercise both entry routes and real session transport; hosted buyer acceptance is recorded separately in the current status. The 17 October hosted follow-up accepts the unsigned entry without automatic login and a preserved native checkout. It does not newly accept a hosted signed-buyer RFQ/approval journey.
Build only the modules a buyer needs
| Module | Configuration / actual behavior |
|---|---|
| Organization | Companies, VAT/external IDs, purchasing-unit hierarchy and cost centers. Names use one content-language editor and shop-main-language inheritance. |
| Employees | Assign existing customer identities to units, enable/pause access, choose buying/approval/list/RFQ/unit-order rights and an individual approval threshold. |
| Budgets | Optional unit budget in exact minor currency units, UTC week/month/quarter/year period and unit currency. Parallel orders serialize against the same unit; retries allocate once. |
| Approval | Optional unit and/or employee threshold. Another currently authorized employee approves the exact cart, prices, methods and current policy. Changes invalidate it; self-approval is denied. |
| Catalog | Optional private product selection, searched/paged by native SQL before results leave the server. Parent selection admits native variants; the cart rechecks selection. |
| Checkout policy | Optional required purchase-order reference and allowed shipping/payment methods, selected by their translated native names. Native destination rules still apply. |
| Buying tools | SKU/quantity quick order, private or unit-shared repeat-order lists. Shared reading does not authorize overwriting another buyer's list. |
| Quotes | Request an offer on a current company cart, exchange messages, let the merchant offer integer unit prices and an expiry, accept on the original buyer/cart, release or reject. Native tax, stock, methods and approval still apply. |
| Company history | Explicit unit-wide order permission, shared customer order details, native status/tracking projections and already-issued receipt PDFs. No matching by email. |
| Extensions | Private company/unit admin sections, a read-only authenticated buyer slot, native core references, scoped callbacks, Flow events and MCP commands. |
Try it
- Sign in to Studio with
b2b.manage,customers.writeand the necessary PII/catalog rights. Open B2B → New company and enter the name in the shop's main language. - The guided create dialog creates the first purchasing unit atomically, with an optional existing customer. Open that unit, or add another purchasing unit. Choose its currency, cost center and optional budget/approval threshold. Amount inputs explicitly use minor units:
100000is EUR 1,000.00, not EUR 100,000. - Assign two existing customers: a buyer and a different approver. Keep independent rights explicit. Pause access instead of deleting historical identities.
- Sign in as the buyer in the storefront. Open Account → Company purchasing, select the unit and enter a purchase-order reference. Add products, use SKU lines or a saved list.
- Optionally request an offer; the merchant negotiates it in the quote inbox. For purchase approval, open the native checkout and first review addresses, delivery, payment and total. Then use Request purchase approval inside checkout. A different approver reviews the unit inbox and confirms the exact current quote.
- Use Refresh in checkout to read the confirmed approval without changing the cart revision, then place the order. Changed items, price, methods or policy require another review and approval. A pending approval or exceeded budget disables purchase, including direct form submission. The resulting order stores the company/unit/PO and consumes the approval/offer and budget atomically. Generate an invoice in the existing order detail; permitted unit colleagues can open the same PDF.
flowchart LR
Studio[Studio company policies] --> Membership[Current unit and employee grants]
Account[Authenticated buyer account] --> Cart[Native company cart]
EDI[Independent EDI app] --> List[Mapped private purchasing list]
List --> Cart
Membership --> Cart
Cart --> Offer[Optional original-cart quote]
Offer --> Approval[Optional separate approver]
Cart --> Approval
Approval --> Checkout[Shared currency / tax / stock / checkout]
Checkout --> Order[One immutable native order]
Order --> Budget[Same-transaction spending allocation]
Order --> Receipt[Existing invoice and PDF owner]
Order --> Outbox[Existing outbox / Flow / apps / MCP]
Browser walkthrough — 12 October 2026
These unretouched captures show a synthetic local merchant and buyer. A unit cost-center edit persisted with actor/revision history; the buyer selected that unit, entered a PO, submitted two SKU lines and requested an approval. No external payment, email or model call was made.
Archived capture from the preceding release: Native company and purchasing-unit workspace
A complete local Chrome buyer → independent approver → checkout run saved order RAC-f0646e6d, 134.83 EUR, and Bag 0. After reloading, the same order remained visible. PostgreSQL held exactly one unreleased 13483-minor-unit EUR budget allocation and the consumed approval. The shared final-order transport now forwards the current tenant-scoped customer session; a read-only approval refresh preserves checkout review, while changed price/revision invalidates it. These are synthetic products/contact details and a simulated payment.
Archived capture from the preceding release: Actual local order completion persisted after reload
Reopening the account preserves its current unit and purchase-order reference. The unit/PO restoration, independent employee grants, list ownership and one-field language inheritance have UI regressions; approval completion, native order, PDF and Flow effects have HTTP/PostgreSQL acceptance.
Hosted configuration acceptance
The hosted B2B source tree from PR #93 passed both full CI runs, its migration job completed, and the Core reached Running, 1/1. A synthetic company and unit were saved through Studio, reloaded and reopened with their original EUR budget, approval threshold, cost center and required PO. During this test a digit-led UUID exposed an invalid generated reference; the shared generator now guarantees the native identifier contract, with a regression covering both editors. The capture uses explicit valid references. It confirms hosted configuration persistence, not certified EDI transport or a real provider charge.
Archived capture from the preceding release: Public company and unit configuration after reload
HTTP, MCP and app integration
Merchant commands use POST /api/b2b/{operation}. Buyer commands use
POST /store-api/b2b/{operation} with the current customer token and, for cart
operations, sw-context-token. MCP discovers the same b2b.* capabilities;
calling a tool never substitutes for a current customer identity or merchant right.
b2b.configuration projects only the shared main/content languages, enabled
currencies and method names; it does not disclose provider credentials.
Examples: company.save, unit.save, member.save, catalog.save, contexts,
select, quick-order, list.save, list.apply, approval.request,
approval.decide, quote.request, quote.offer, quote.accept, unit.orders.
Writes require expected revisions. Errors retain current state; clients must reload
and review a conflicting edit instead of force-overwriting it.
Apps can reference b2bCompany / b2bUnit through native tenant composite foreign
keys and mount admin.b2b.company, admin.b2b.unit or account.b2b. The buyer
slot is read-only; editable employee administration remains a merchant operation.
The EDI app uses the approved b2b_requisition callback to create a private buyer
list, never to impersonate a buyer and purchase automatically.
Events include b2b.organization_changed, b2b.employee_changed,
b2b.catalog_changed, b2b.list_changed, b2b.approval_requested,
b2b.approval_decided, b2b.purchase_completed, b2b.quote_requested,
b2b.quote_changed, b2b.quote_selected. Flow actions use the existing dispatcher.
App subscriptions require events:b2b.* (or an exact event) and b2b.read;
the app projection strips customer identity, addresses and private quote/cart tokens.
Compared with established B2B suites
Shopware B2B Components and commercetools Associate APIs are useful references for organization, employee and purchase authorization. This is an independently implemented native slice, not copied proprietary code or a claim of one-to-one equivalence. VEYNDO implements organization units, explicit employee permissions, quick orders, repeat lists, quote negotiation, threshold approvals and unit budgets in the connected path described above.
Still open: delegated buyer-managed employee invitations, sequential/multi-level approval chains, aggregate parent budgets, negotiated contract catalogs, credit limits and payment terms/dunning, customer-company addresses, PunchOut, buyer-specific bulk import wizards, and partner-specific EDI/network certification. Parent hierarchy currently organizes units; it does not imply role inheritance. Budgets measure placed orders (including pending payment), not financial settlement; only admitted order cancellation releases their allocation. Currency/period changes are denied while unreleased allocations exist, preventing edits from erasing spending.
Evidence and limits
The registered b2b suite uses actual HTTP/PostgreSQL, synthetic customers and
simulated payments. It checks foreign/self denial, stale approvals and quote offers,
parallel budget contention, private search/detail/cart scope, unit-cycle races,
revocation, current MCP schemas and existing unit receipt PDF access. EDI's own
suite additionally checks import → private list → native checkout and immutable
outbound documents after a gateway restart. No real partner transport is implied.
Two extracted production policies have Lean properties: no budget admission above the remaining balance, and no unauthorized/self/stale approval admission. The surrounding database, async code, provider APIs and UI are tested, not formally proved. See EDI, source ownership, formal boundary and multi-currency.
The private-catalog check also filters variant siblings before detail pagination. A real B2B purchase emits b2b.purchase_completed; the isolated acceptance configures a standard Flow Builder action and verifies the resulting native order tag. Approval/RFQ snapshots exclude cart bearer tokens. Existing tenant business-price/Wasm policies remain active.
Configuration history and private publication
Company/unit editors use the shared entity history. merchant.history,
merchant.history.version and merchant.history.restore include b2bCompany,
b2bUnit, b2bQuote, b2bList and b2bApproval. Only company/unit configuration
is restorable: current employee grants, catalog selection, approvals and spending
are not rewound by history. Restore uses expected current revision and the same
hierarchy, currency/period allocation and settings validation as a normal write.
History reads require b2b.read plus customers.pii; restore adds b2b.manage.
Private environments clone company/unit configuration and catalog selections,
not employees, RFQs, lists, approvals or spending. Review b2b-company:ID and
b2b-unit:ID changes in the existing Staging & releases view. Publication adds
b2b.manage, read/PII rights and catalog read for units to the normal environment
management rights; unauthorized B2B diffs are hidden. Native company/unit/catalog
writers run in the release transaction, parents before children, after selected
products/settings. Unit policy and catalog writes each advance the unit revision.
Current live members and allocations remain; their period/currency guards can
reject a change that passed in the empty sandbox. Stale digests or live policy
changes reject the entire release. Existing generic deletion limitations remain.
B2B-referenced shipping/payment methods and currencies cannot be removed from
configuration without resolving dependencies. Full audit.
Concurrent checkout and customer preferences
The native budget allocation remains in the purchase transaction. The optional last-used payment-method preference is saved after commit, only when changed, and its failure cannot turn a committed purchase into an error. This avoids the customer-SHARE/unit-UPDATE inversion exposed by two parallel original-cart checkouts. scripts/testing/b2b_checkout_race.py holds the actual product row, observes both blocked native backends and then releases the gate; the original implementation returned HTTP 500. The corrected path must return one successful budgeted order and one native budget rejection, including with the separate signed tenant/system logins. It does not weaken active-customer checks or retry a financial effect.