# B2B ERP, Pricing and Architecture

:::info
**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](https://wiki.22bits.de/doc/project-b2b-migration-fVcY2IIWti).

:::

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](https://wiki.22bits.de/doc/b2b-decisions-and-workshop-plan-b29gSFrr9D).

**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.

```mermaidjs
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:#ffffff
```

## Today on pro.blackroll.com

From the DEPT spike (section 5) and ADR-050:


1. A dealer applies through the Pipedrive form, is approved by hand, and the record is created in MyFactory.
2. 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`.
3. 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.
4. `NO_*` tags become hidden products through Flows with product IDs hardcoded in the scripts.
5. Orders go through Xaidat into MyFactory, and the ERP order ID is written back.
6. Payment status never comes back (invoice orders stay "payment pending"). Credit control is a Flow that removes `Auf Rechnung` when a payment is overdue.

## The wholesale app

It is Wholesale Pricing Discount B2B (WPD) by Wholesale Helper ([App Store](https://apps.shopify.com/wholesale-pricing-discount)), 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 as `TAG-PERCENT`, for example `EK1-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 as `TAG--variantId-price^variantId-price`; a fixed price overrides the percentage rule, and the result is never above the normal price;
* checkout: `snippets/wcp_cart.liquid` catches the checkout button, posts the cart to `/apps/wpdapp/api/order/draft` and 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 | `Auf Rechnung` tag | 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.

```mermaidjs
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 5
```

### 2. 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?

```mermaidjs
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 5
```

### 3. 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.

```mermaidjs
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 5
```

Questions referenced here are numbered as in [B2B Decisions and Workshop Plan](https://wiki.22bits.de/doc/b2b-decisions-and-workshop-plan-b29gSFrr9D).

---

**Documents**

- [Workshop 0: Steering Pre-read (28 Oct)](https://wiki.22bits.de/s/6cf1c07b-6734-40db-a1e8-8542d7f0474c/doc/workshop-0-steering-pre-read-28-oct-JhVrYRXijD)
- [B2B Customer Groups](https://wiki.22bits.de/s/6cf1c07b-6734-40db-a1e8-8542d7f0474c/doc/b2b-customer-groups-gEqCKsjdkf)
- [B2B Decisions and Workshop Plan](https://wiki.22bits.de/s/6cf1c07b-6734-40db-a1e8-8542d7f0474c/doc/b2b-decisions-and-workshop-plan-b29gSFrr9D)
- [B2B Scope Matrix](https://wiki.22bits.de/s/6cf1c07b-6734-40db-a1e8-8542d7f0474c/doc/b2b-scope-matrix-ZZotcIoK84)
- [B2B ERP, Pricing and Architecture](https://wiki.22bits.de/s/6cf1c07b-6734-40db-a1e8-8542d7f0474c/doc/b2b-erp-pricing-and-architecture-3q14unmr2V)
- [B2B Content and Design in a Liquid Store](https://wiki.22bits.de/s/6cf1c07b-6734-40db-a1e8-8542d7f0474c/doc/b2b-content-and-design-in-a-liquid-store-SOHXeET9QJ)
- [B2B Risks and Dependencies](https://wiki.22bits.de/s/6cf1c07b-6734-40db-a1e8-8542d7f0474c/doc/b2b-risks-and-dependencies-rlQ4rP4ukg)
- [Epic Questions](https://wiki.22bits.de/s/6cf1c07b-6734-40db-a1e8-8542d7f0474c/doc/epic-questions-AOKIT9yuYL)