B2B ERP, Pricing and Architecture
Shareable with BLACKROLL and project partners. Maintained by 22Bits. Written 2026-10-17, updated after the kickoff 2026-10-21 and after Sen's email 2026-10-23. Part of Project: B2B Migration.
What flows between the ERP and Shopify today, what the epics expect, where it is hard, and what the target looks like with Option A and Option B. The decisions that follow from it (E1 to E7, P1 to P5) are in B2B Decisions and Workshop Plan.
Working assumption (E1): Option B through the Xaidat gateway, if Xaidat can scope and deliver it in time, with Option A as the fallback for the CH wave. Xaidat is a third party, so the scope, estimate and timeline for the gateway extension have to come from Xaidat. To confirm with Kristy and Teoman Lam.
The systems
The ERP part is about customers, prices, money and orders. Product data for the B2B store is decided in E7: the D2C product data is the starting point (Sen, 2026-10-23).
MyFactory is the ERP: customer master, delivery addresses, price lists, invoices, open balances, order processing.
Xaidat is the gateway between MyFactory and Shopify. It is an external vendor, and every change on its side needs the ERP team. Teoman Lam is the main ERP contact for this project; Holger Welzel joins for specific ERP topics (Sen, 2026-10-23).
PIM holds product content, which reaches the stores through the gateway. A new PIM is planned for January (kickoff).
The wholesale app on pro.blackroll.com holds the prices dealers actually pay.
How the ERP touches the other areas
Almost every area needs something from MyFactory through Xaidat. That is why ERP and data ownership come first: until we know who owns customers, prices and payment status, the customer, pricing and payment decisions stay provisional. The dotted line is a weak link.
flowchart LR
erp[("ERP and gateway<br/>MyFactory, Xaidat")]:::ext
ci["Customer intake"]:::area
pr["Pricing"]:::area
pay["Payments and currencies"]:::area
pl["Platform"]:::area
mig["Migration and go-live"]:::area
th["Theming and content"]:::area
erp -->|"who creates customers,<br/>companies, locations"| ci
erp -->|"price lists, tiers,<br/>ADR-050 price model"| pr
erp -->|"invoices, payment status,<br/>credit limits"| pay
erp -->|"translator app or Xaidat work,<br/>feed into a second store"| pl
erp -->|"CH dealers into<br/>the ERP first?"| mig
erp -.->|"only product data<br/>through PIM"| th
classDef ext fill:#8c8c8c,stroke:#6b6b6b,color:#ffffff
classDef area fill:#438dd5,stroke:#2e6295,color:#ffffffToday on pro.blackroll.com
From the DEPT spike (section 5) and ADR-050:
A dealer applies through the Pipedrive form, is approved by hand, and the record is created in MyFactory.
Xaidat creates the Shopify customer with tags: MyFactory customer number, price tier (EK1, EK2, EK3 or variants like EK1-10P), one of 40+ segments,
NO_*restrictions,Auf Rechnung.Only the tier tag crosses from the ERP. The prices come from the wholesale app, maintained by hand, with no sync to the ERP price lists. A logged-in customer without a tier tag pays the full price.
NO_*tags become hidden products through Flows with product IDs hardcoded in the scripts.Orders go through Xaidat into MyFactory, and the ERP order ID is written back.
Payment status never comes back (invoice orders stay "payment pending"). Credit control is a Flow that removes
Auf Rechnungwhen a payment is overdue.
The wholesale app
It is Wholesale Pricing Discount B2B (WPD) by Wholesale Helper (App Store), identified from the live theme Blackroll-AG/PROSHOP-Ger (synced by Shopify, last update 2026-10-14): snippets wcp_* and the app proxy /apps/wpdapp/api/. How it prices, read from the theme code:
shop metafield
wcp_active_discounts: the active rules asTAG-PERCENT, for exampleEK1-10;shop metafield
wcp_discountgroup: the scope of each rule (whole store, collections or products);product metafield
wcp_set_prices: fixed prices per variant per tag, packed asTAG--variantId-price^variantId-price; a fixed price overrides the percentage rule, and the result is never above the normal price;checkout:
snippets/wcp_cart.liquidcatches the checkout button, posts the cart to/apps/wpdapp/api/order/draftand redirects to the draft order's invoice URL; a second path (order/net-draft) exists for the app's own net terms.
So the "app matrix" of the spike is two layers (percentage per tag, fixed price per variant per tag), and all of it sits in Shopify metafields. A one-off price import can read it through the Admin API, with the app's bulk export of individual variant prices as a cross-check. Nothing is needed from the app vendor. 22Bits has admin access to the app; what is configured beyond prices (volume discounts, net terms, minimums, wholesale shipping) is not checked yet (facts to collect).
What the epics expect
Data | Direction | Today | Expected in the new store | Source |
|---|---|---|---|---|
Customer | ERP → Shopify | Tagged customer | Company (= MyFactory customer number) with contacts | Spike 2.6, BLAC-6172 |
Delivery addresses | ERP → Shopify | Customer address book | Locations (= ERP delivery addresses) with their own VAT and payment settings | Spike 2.6 |
Segment, restrictions | ERP → Shopify | Tags plus Flow scripts | Catalog per company | Spike 2.2, BLAC-6173 |
Prices | ERP → Shopify (target) | WPD metafields, maintained by hand | Price lists "synced from ERP" | BLAC-6174, Spike 2.3 |
Payment permission | ERP → Shopify |
| Payment terms on the company | Spike 2.5 |
Credit limit, open balance | ERP → Shopify | Nothing (tag side effect) | Credit status the checkout can check | Spike 6, Sen's requirements |
Product content | PIM → gateway → Shopify | Works for D2C | Same, into a second store | BLAC-6177 |
Stock | ERP → Shopify | Via gateway | Per market and location | BLAC-6177 |
Orders | Shopify → ERP | Draft orders via Xaidat | Checkout orders via Xaidat | BLAC-6176 |
Status, invoice, paid | ERP → Shopify | ERP order ID only | Status, invoice and payment status back | BLAC-6176, Sen's requirements |
This table is the starting point for E2 (system of record per data object).
Where it is hard
Translation gap. Native B2B wants companies, catalogs, price lists and terms; the ERP sends tags. Option A: a custom app translates. Option B: Xaidat publishes native objects. The epics read like B, the spike says start with A, and BLACKROLL prefers B (kickoff).
Price truth. BLAC-6174 wants Shopify prices to match the ERP, but the prices dealers pay today come from WPD, not from the ERP. Where the two differ, someone decides which is right before migration (P3). ADR-050 is reworking the ERP price model (base lists plus factors, later maybe a new ERP), so a price sync built now targets a moving model. Decided on 2026-10-26 (P2): prices live only in Shopify for now, managed per catalog in the Shopify admin and by CSV, and price management in the ERP becomes part of the integration with the new ERP later. Neither Option A nor Option B carries prices any more.
ERP data is needed even with Option A. Credit status and payment status sync are custom in both options (spike section 6) and need Xaidat. Option A only avoids ERP work for customers, catalogs and prices.
Messy data. 31% of DE customers have several addresses; 33 restriction combinations; open whether dealers may enter one-off delivery addresses or logistics needs every address in the ERP (spike open point 5, CI4). The owners and meaning of many customer tags and metafields are unknown; an inventory is in progress.
Second store. Whether the gateway can feed products and stock into a new store in the organization is not written anywhere (question 8).
Switzerland is the easy case. CH dealers have no ERP record, no tiers and no tags, only one blanket discount of about 40% (ADR-060 4.3). Companies and CHF price lists can be created in Shopify directly. The ERP is only needed for orders going in (the old CH store already has a Xaidat order webhook) and for products and stock coming out. The full ERP problem starts with the DE wave. With Option B, CH dealers would need ERP records first (E3). Sen (2026-10-23) describes the CH process differently: customers are created in Shopify first and then in MyFactory, and prices are set per product group. This is checked with Teoman Lam before workshop 1a (question 26), because it changes how easy the CH case really is.
Ownership. BLAC-6176 names 22Bits as delivery, but the gateway is Xaidat's and only the ERP team approves changes.
Architecture views: today, Option A, Option B
Three container views in C4 style (level 2): who uses which system and which data moves where. They follow the sections above and the DEPT spike. Between A and B only one thing changes: who turns ERP data into native B2B objects. In A a custom app inside Shopify does it (orange), in B the Xaidat gateway does it (red). The domain of the new store is open (PL2), so the target views don't name one.
How to read them
Dark blue: people. Blue: Shopify stores and what runs inside them. Grey: existing systems outside Shopify.
Orange: new custom code that 22Bits builds and DEPT runs after January.
White box with red dashed border: change needed on the Xaidat or ERP side, not scoped yet.
Dashed frame: one Shopify store or domain. Dashed arrow: manual step or not scoped yet.
Two-way arrows between Shopify and the ERP side: the part before the slash flows toward the ERP, the part after it flows toward Shopify.
1. Today
Separate B2B setups. DE dealers order on pro.blackroll.com, where the WPD wholesale app sets prices per customer tag. Those prices are maintained by hand and are not synced with the MyFactory price lists. CH dealers order through the legacy Nuxt app on b2b.blackroll.com in front of the blackroll-switzerland store. They are created by Make and Klaviyo from Pipedrive and have no ERP record. The Pro BNL store is not drawn yet.
flowchart TB
dealerDE(["Dealer DE<br/>[Person]"]):::person
dealerCH(["Dealer CH<br/>[Person]"]):::person
sales(["B2B sales / back office<br/>[Person]"]):::person
subgraph CHB["CH B2B fallback: b2b.blackroll.com"]
direction TB
nuxt["Legacy Nuxt app<br/>[Vercel]<br/>Storefront, reads legacy Craft"]:::container
chstore[("Shopify store<br/>[blackroll-switzerland]<br/>CembraPay, voucher codes")]:::container
end
subgraph PRO["pro.blackroll.com: Shopify store blackroll-proshop-ger"]
direction TB
theme["Liquid theme<br/>[PROSHOP-Ger]<br/>Shop for tagged dealers"]:::container
wpd["WPD wholesale app<br/>[Shopify app]<br/>Tier prices per tag,<br/>draft order checkout"]:::container
flows["Flows + payment function<br/>[Shopify Flow, Function]<br/>NO_ tags hide products,<br/>Auf Rechnung"]:::container
core[("Store data<br/>[Shopify]<br/>Customers + tags,<br/>products, orders")]:::container
end
pipedrive["Pipedrive<br/>[CRM]<br/>Dealer applications"]:::ext
makek["Make + Klaviyo<br/>[Automation]<br/>CH: creates customer,<br/>sends codes"]:::ext
xaidat["Xaidat gateway<br/>[Vendor middleware]<br/>Moves data between<br/>ERP and Shopify"]:::ext
erp[("MyFactory<br/>[ERP]<br/>Customers, prices,<br/>invoices, orders")]:::ext
pim["PIM<br/>[Product data]"]:::ext
dealerCH -->|"orders"| nuxt
nuxt -->|"Storefront API"| chstore
dealerDE -->|"orders"| theme
theme -->|"tier prices"| wpd
wpd -->|"draft orders"| core
flows -->|"acts on tags"| core
sales -->|"approves"| pipedrive
pipedrive -->|"CH leads"| makek
makek -->|"customer + codes"| chstore
sales -.->|"DE: creates customer<br/>by hand"| erp
core <-->|"orders / tagged customers,<br/>products, stock"| xaidat
chstore <-->|"orders / products, stock"| xaidat
pim -->|"product data"| xaidat
xaidat <-->|"orders / customers, stock"| erp
classDef person fill:#08427b,stroke:#052e56,color:#ffffff
classDef container fill:#438dd5,stroke:#2e6295,color:#ffffff
classDef ext fill:#8c8c8c,stroke:#6b6b6b,color:#ffffff
style PRO fill:#f4f8fd,stroke:#438dd5,stroke-dasharray:5 5
style CHB fill:#f4f8fd,stroke:#438dd5,stroke-dasharray:5 52. Target with Option A: tag translator app
Xaidat stays as it is and keeps sending customers with tags. A custom app in the new store translates the tags into companies, locations, catalogs and payment terms. Price lists are seeded once by migration scripts from the WPD data on pro.blackroll.com (readable through the Admin API).
CH has no ERP record and no tags, so in wave 1 the CH companies and CHF price lists come from the migration scripts and the lead flow. The translator matters from the DE wave on.
Credit and payment status still need a Xaidat extension (spike section 6). Option A reduces ERP work, it does not remove it.
Open: can Xaidat feed a second store in the organization (question 8)? Who keeps prices in line with the ERP after the one-off import?
flowchart TB
dealer(["Dealer contact<br/>[Person]<br/>Wave 1 CH, later DE"]):::person
sales(["B2B sales / back office<br/>[Person]"]):::person
leadflow["Pipedrive + lead flow<br/>[CRM, Make or n8n]<br/>Applications, approval"]:::ext
migration["Migration scripts<br/>[One-off, 22Bits]<br/>WPD prices from pro.blackroll.com,<br/>CH dealers as companies"]:::newcode
subgraph NEW["New B2B store: Shopify Plus expansion store, domain open (PL2)"]
direction TB
theme["Horizon theme<br/>[Liquid]<br/>Gated shop, quick order"]:::container
b2b[("Native B2B<br/>[Shopify]<br/>Companies, locations, catalogs,<br/>price lists, payment terms")]:::container
translator["Tag translator app<br/>[Custom app, 22Bits builds, DEPT runs]<br/>ERP tags to companies,<br/>catalogs, terms"]:::newcode
end
status["Status sync<br/>[Xaidat extension, not scoped]<br/>Payment status, credit limit"]:::change
pim["PIM<br/>[Product data]"]:::ext
xaidat["Xaidat gateway<br/>[Unchanged]<br/>Tags, products, stock, orders"]:::ext
erp[("MyFactory<br/>[ERP]<br/>Customers, prices,<br/>invoices, orders")]:::ext
dealer --->|"logs in, orders"| theme
sales -->|"approves"| leadflow
theme -->|"B2B context"| b2b
leadflow --->|"company + contact"| b2b
migration --->|"price lists, CH companies"| b2b
b2b <-->|"tags in / B2B objects out"| translator
b2b <-.->|"paid, credit"| status
b2b <--->|"orders / tagged customers,<br/>products, stock (2nd store? Q8)"| xaidat
pim -->|"product data"| xaidat
xaidat <-->|"orders / customers, stock"| erp
status -.- erp
sales -.->|"DE: creates customer<br/>by hand"| erp
classDef person fill:#08427b,stroke:#052e56,color:#ffffff
classDef container fill:#438dd5,stroke:#2e6295,color:#ffffff
classDef ext fill:#8c8c8c,stroke:#6b6b6b,color:#ffffff
classDef newcode fill:#e07b00,stroke:#a85c00,color:#ffffff
classDef change fill:#ffffff,stroke:#b20000,stroke-width:2px,stroke-dasharray:6 4,color:#b20000
style NEW fill:#f4f8fd,stroke:#438dd5,stroke-dasharray:5 53. Target with Option B: Xaidat publishes native B2B objects (working assumption)
Xaidat is extended to create companies, locations, catalogs, price lists and payment terms directly from MyFactory. The ERP stays the single source for customers and prices, and no translator app is needed.
Needs Xaidat development (not scoped, no estimate yet) and sign-off from the ERP team.
CH dealers need ERP records first (ADR-060 4.3, E3). Price lists follow the new price model from ADR-050, which is still moving.
Less custom code for DEPT to run after January, more dependency on Xaidat's timeline.
flowchart TB
dealer(["Dealer contact<br/>[Person]<br/>Wave 1 CH, later DE"]):::person
sales(["B2B sales / back office<br/>[Person]"]):::person
subgraph NEW["New B2B store: Shopify Plus expansion store, domain open (PL2)"]
direction TB
theme["Horizon theme<br/>[Liquid]<br/>Gated shop, quick order"]:::container
b2b[("Native B2B<br/>[Shopify]<br/>Companies, locations, catalogs,<br/>price lists, payment terms")]:::container
end
leadflow["Pipedrive + lead flow<br/>[CRM, Make or n8n]<br/>Applications, approval"]:::ext
xaidat["Xaidat gateway<br/>[Extended, not scoped]<br/>Publishes native B2B objects"]:::change
erp[("MyFactory<br/>[ERP]<br/>Price model per ADR-050<br/>Customers, delivery addresses,<br/>price lists, credit, orders")]:::ext
pim["PIM<br/>[Product data]"]:::ext
dealer -->|"logs in, orders"| theme
theme -->|"B2B context"| b2b
sales -->|"approves"| leadflow
leadflow -->|"creates customer"| erp
xaidat <-->|"orders / customers, prices, credit"| erp
pim -->|"product data"| xaidat
xaidat -->|"companies, locations, catalogs,<br/>price lists, terms, products, stock"| b2b
b2b -->|"orders"| xaidat
xaidat -->|"status, invoice, paid"| b2b
classDef person fill:#08427b,stroke:#052e56,color:#ffffff
classDef container fill:#438dd5,stroke:#2e6295,color:#ffffff
classDef ext fill:#8c8c8c,stroke:#6b6b6b,color:#ffffff
classDef change fill:#ffffff,stroke:#b20000,stroke-width:2px,stroke-dasharray:6 4,color:#b20000
style NEW fill:#f4f8fd,stroke:#438dd5,stroke-dasharray:5 5Questions referenced here are numbered as in B2B Decisions and Workshop Plan.