V slovenskom a stredoeurópskom segmente B2B e-commerce stále prežíva mylná predstava, že internetový obchod pre firemných partnerov je len upravená verzia B2C maloobchodu s pridaným políčkom pre IČO a globálnou percentuálnou zľavou. Táto ilúzia rýchlo narazí na realitu v momente, keď sa pokúsite digitalizovať zmluvné vzťahy s veľkými odberateľmi.
Skutočný B2B predaj je definovaný vysokou variabilitou: každý partner nakupuje za individuálne vyjednané ceny, disponuje rôznou splatnosťou faktúr, má pridelené kreditné limity a vyžaduje schvaľovanie objednávok na viacerých úrovniach.
Keď sa tieto komplexné pravidlá pokúsite vtesnať do tradičných monolitických platforiem alebo krabicových SaaS riešení, výsledkom býva spomalenie databázy, problematická synchronizácia s podnikovými ERP systémami a frustrovaní odberatelia.
Tento technický článok analyzuje, ako v technologickom štúdiu nolimeo pristupujeme k riešeniu dvoch najťažších výziev B2B e-commerce: dynamickej individuálnej cenotvorbe (pricing matrix) a asynchrónnym split platbám s kreditnou kontrolou pomocou modularizovanej headless architektúry a modernej databázovej vrstvy.
1. Výzva: Explózia dopytov pri dynamických cenníkoch (Pricing Matrix)
V klasickom e-shope má produkt jednu cenu s DPH, prípadne jednu zľavnenú cenu. V B2B systéme má jeden produkt (napr. priemyselné čerpadlo) stovky rôznych cien v závislosti od toho, kto sa na web pozerá. Cenotvorba zvyčajne kombinuje:
- Základnú cenníkovú cenu pre anonymných návštevníkov.
- Skupinové zľavy (napr. cenník pre kategóriu "Stavebniny A", "Veľkoobchod B").
- Individuálne zmluvné ceny na úrovni konkrétneho IČO (napr. partner XY má na tento jeden kábel fixnú cenu 1,42 € bez ohľadu na kampane).
- Množstevné zľavy (volume/tiered pricing): cena klesá podľa odobraného počtu kusov v košíku (napr. 1-10 ks = 10 €/ks, 11-50 ks = 9 €/ks, 51+ ks = 8 €/ks).
Problém relačného preťaženia (JOIN Explosion)
Ak má e-shop 50 000 produktových variantov a 1 500 B2B odberateľov, pričom každý má špecifické zmluvné podmienky, databáza monolitického e-shopu sa ľahko stane úzkym hrdlom. Pri každom načítaní katalógu musí aplikačný server vykonať sériu komplikovaných dopytov s viacnásobnými JOIN operáciami na tabuľky cien, skupín, pravidiel a zliav.
Pri prezeraní kategórie s 24 produktmi tak databáza môže prepočítavať veľké množstvo relácií v reálnom čase, čo zvyšuje záťaž servera a predlžuje čas načítania stránky (TTFB).
Architektonické riešenie: Indexované ukladanie a oddelená cenová služba
V projektoch postavených na Medusa.js a Next.js tento problém riešime izoláciou cenovej logiky do dedikovanej štruktúry s dobre navrhnutými indexmi. Namiesto prepočítavania zliav za behu cez zložité pravidlá v PHP alebo SQL ukladáme predpočítané výsledné ceny priamo pre kombinácie zákazník_id + SKU + množstvo.
Tradičný monolit (pomalé synchrónne prepočty):
Užívateľ ──> [ Web Server ] ──> [ SQL JOINs: Zľavy, Cenníky, Skupiny, Dane, Produkty ] ──> Pomalé TTFB (>1.5s)
nolimeo headless stack (rýchlejšia odozva):
Užívateľ ──> [ Next.js Edge ] ──> [ Redis Cache / Predpočítaný Cenový Index ] ──> Rýchlejšie zobrazenie ceny
▲
(Asynchrónny zápis pri zmenách v ERP)
│
[ Node.js Middleware (Zod Validácia) ] ──> [ Medusa.js PostgreSQL ]
Implementácia databázového modelu v Drizzle ORM
Pre výkon, type-safety a jasnú štruktúru využívame TypeScript a Drizzle ORM. Nižšie je zjednodušená ukážka databázovej schémy, ktorá rieši individuálne ceny aj stupňovité množstevné zľavy:
// src/db/schema/pricing.ts
import { pgTable, uuid, varchar, integer, timestamp, index, foreignKey, check } from "drizzle-orm/pg-core";
import { sql } from "drizzle-orm";
// Zmluvné účty (Firmy)
export const b2bCompanies = pgTable("b2b_companies", {
id: uuid("id").primaryKey().defaultRandom(),
companyName: varchar("company_name", { length: 255 }).notNull(),
ico: varchar("ico", { length: 8 }).unique().notNull(),
dic: varchar("dic", { length: 12 }),
creditLimit: integer("credit_limit").default(0).notNull(), // Hodnota v centoch pre elimináciu float chýb
currentBalance: integer("current_balance").default(0).notNull(), // Aktuálne čerpaný kredit
createdAt: timestamp("created_at").defaultNow().notNull(),
});
// Cenníky (môžu byť skupinové alebo priradené ku konkrétnej firme)
export const b2bPriceLists = pgTable("b2b_price_lists", {
id: uuid("id").primaryKey().defaultRandom(),
name: varchar("name", { length: 255 }).notNull(),
companyId: uuid("company_id").references(() => b2bCompanies.id, { onDelete: "cascade" }),
priority: integer("priority").default(0).notNull(), // Vyššie číslo = vyššia priorita pri konflikte cien
validFrom: timestamp("valid_from"),
validTo: timestamp("valid_to"),
createdAt: timestamp("created_at").defaultNow().notNull(),
}, (table) => ({
companyIdx: index("price_list_company_idx").on(table.companyId),
}));
// Cenníkové položky s podporou množstevných úrovní (Tiered Pricing)
export const b2bPrices = pgTable("b2b_prices", {
id: uuid("id").primaryKey().defaultRandom(),
priceListId: uuid("price_list_id").references(() => b2bPriceLists.id, { onDelete: "cascade" }).notNull(),
variantSku: varchar("variant_sku", { length: 100 }).notNull(),
priceInCents: integer("price_in_cents").notNull(), // Cena v centoch bez DPH
minQuantity: integer("min_quantity").default(1).notNull(), // Hranica pre množstevnú zľavu (napr. od 10 ks)
createdAt: timestamp("created_at").defaultNow().notNull(),
}, (table) => ({
skuListIdx: index("sku_list_idx").on(table.variantSku, table.priceListId),
qtyCheck: check("min_qty_positive", sql`${table.minQuantity} >= 1`),
priceCheck: check("price_positive", sql`${table.priceInCents} >= 0`),
}));
S týmto dátovým modelom vieme napísať efektívny SQL dopyt, ktorý vytiahne najvýhodnejšiu platnú cenu pre konkrétneho klienta a počet kusov:
-- Dopyt na zistenie najnižšej platnej B2B ceny pre SKU 'PUMP-INDUSTRIAL-001' pri odbere 15 kusov
SELECT p.price_in_cents
FROM b2b_prices p
JOIN b2b_price_lists pl ON p.price_list_id = pl.id
WHERE p.variant_sku = 'PUMP-INDUSTRIAL-001'
AND p.min_quantity <= 15
AND (pl.company_id = 'c8b671a5-29e8-466d-8854-bd6b680373ab' OR pl.company_id IS NULL)
AND (pl.valid_from IS NULL OR pl.valid_from <= NOW())
AND (pl.valid_to IS NULL OR pl.valid_to >= NOW())
ORDER BY pl.priority DESC, p.price_in_cents ASC
LIMIT 1;
Tento prístup výrazne odľahčuje relačnú databázu. Pri správne navrhnutom cachovaní a asynchrónnom dotazovaní cien cez Next.js Server Components sa vizuálna štruktúra stránky načíta rýchlejšie a zložité ceny sa na pozadí plynule dosadia cez React Suspense stream.
2. Výzva: Kreditná kontrola a split platby na faktúru
Jedným z najväčších rozdielov oproti B2C je správanie nákupného košíka pri pokladni (checkout). B2B nákupcovia málokedy platia kartou cez platobnú bránu. Štandardom je platba na faktúru so splatnosťou (napr. 14, 30 alebo 60 dní).
Pre predajcu to však predstavuje úverové riziko. Každá firma má preto v internom ERP systéme (napr. Pohoda, KROS, SAP) stanovený kreditný limit (napr. odberateľ môže mať nezaplatené faktúry maximálne do výšky 10 000 €).
Ak sa odberateľ pokúsi odoslať objednávku, ktorá presahuje jeho voľný kreditný limit, systém musí reagovať flexibilne:
- Zablokovať objednávku a požiadať o okamžité uhradenie dlhu.
- Umožniť split platbu (rozdelenie platby): časť objednávky do výšky voľného limitu prejde na faktúru a zvyšok sumy nákupca doplatí kartou alebo zálohovou platbou cez integrovanú platobnú bránu.
Architektúra asynchrónneho spracovania split platieb
Pre zníženie rizika výpadkov a stabilnejšiu integráciu s lokálnymi ERP a platobnými API nepoužívame krehké synchrónne prepojenia. Ak by sme volali ERP systém synchrónne počas checkoutu a ERP by malo čo i len krátky výpadok, platba by mohla zlyhať a nákupca by videl chybovú obrazovku.
Náš asynchrónny checkout tok funguje nasledovne:
[ Next.js Frontend ] ──( 1. Validácia košíka )──> [ Node.js Middleware ]
│
┌───────────────────────────┴───────────────────────────┐
▼ (Validné dáta) ▼ (Zod Validácia)
[ PostgreSQL Transakcia ] [ Overenie limitov v Redis ]
│ │
├───────────────────────────────────────────────────────┘
▼
[ Rozdelenie sumy: Faktúra vs. Karta ]
│
┌──────────────┴──────────────┐
▼ (Upfront časť) ▼ (Fakturačná časť)
[ Platobná brána (Stripe/GP webpay) ] [ Rezervácia kreditu v DB ]
│ │
└──────────────┬──────────────┘
▼
[ Redis Queue / BullMQ ] ──( Asynchrónne doručenie )──> [ Podnikové ERP ]
Prísna validácia B2B objednávky cez Zod
Každá prichádzajúca checkout požiadavka musí prejsť prísnou štruktúrnou kontrolou (type-safety) na strane middleware. Znižujeme tým riziko manipulácie s cenou alebo prečerpania kreditných limitov neautorizovaným používateľom.
Nižšie je ukážka TypeScript kódu s využitím validačnej knižnice Zod:
// src/lib/validation/checkout.ts
import { z } from "zod";
export const B2BCheckoutSchema = z.object({
companyId: z.string().uuid("Neplatný identifikátor spoločnosti"),
cartId: z.string().uuid("Neplatný identifikátor košíku"),
items: z.array(z.object({
sku: z.string().min(1, "SKU nesmie byť prázdne"),
quantity: z.number().int().positive("Množstvo musí byť celé kladné číslo"),
unitPriceInCents: z.number().int().nonnegative(),
})).min(1, "Košík nemôže byť prázdny"),
paymentStrategy: z.enum(["invoice", "card", "split"]),
splitDetails: z.object({
invoiceAmountInCents: z.number().int().nonnegative().default(0),
cardAmountInCents: z.number().int().nonnegative().default(0),
}).optional(),
shippingAddressId: z.string().uuid("Neplatná adresa doručenia"),
});
export type B2BCheckoutInput = z.infer<typeof B2BCheckoutSchema>;
/**
* Validuje biznis logiku kreditného limitu firmy pred vykonaním platby.
*/
export function validateCreditAllocation(
requestedCredit: number,
allowedLimit: number,
currentBalance: number
): { allowed: boolean; remainingLimit: number } {
const availableCredit = allowedLimit - currentBalance;
if (requestedCredit > availableCredit) {
return {
allowed: false,
remainingLimit: Math.max(0, availableCredit),
};
}
return {
allowed: true,
remainingLimit: availableCredit - requestedCredit,
};
}
3. Riešenie zlyhaní a chybových stavov (hraničné stavy)
V reálnom IT svete systémy padajú. Ak navrhujete enterprise B2B platformu, musíte vopred počítať so zlyhaniami tretích strán. V nolimeu staviame systémy tak, aby boli odolné voči chybám (fault-tolerant by design). Rozoberme dva časté zlyhávacie scenáre pri B2B platbách:
Hraničný stav #1: Výpadok ERP počas aktualizácie čerpaného limitu
Problém: Zákazník úspešne dokončí objednávku, systém zarezervuje 4 000 € z jeho kreditného limitu a pošle aktualizáciu do interného ERP systému predajcu. Sieť ERP systému je však preťažená a vráti chybový kód 504 Gateway Timeout alebo úplne spadne. Ak by sme transakciu len tak ukončili, vznikne nesúlad: na e-shope bude svietiť iný zostatok kreditu ako v reálnom účtovníctve, čo môže viesť k dvojitému čerpaniu limitu.
Riešenie: Používame PostgreSQL transakcie a asynchrónne spracovanie frontu úloh pomocou Redis (BullMQ) s modelom doručenia typu at-least-once delivery.
Pri výpadku API sa objednávka a zmena kreditu uložia lokálne v stave pending_erp_sync. Úloha sa zaradí do frontu, ktorý sa automaticky pokúša o opätovný zápis s exponenciálnym predlžovaním času (exponential backoff). Pokiaľ ERP neodpovie úspešne, systém drží dáta v kontrolovanom stave. Ak zlyhanie pretrváva, presunie sa do Dead Letter Queue (DLQ) a tím dostane prioritnú notifikáciu na monitorovací SLA kanál.
Hraničný stav #2: Timeout platobnej brány pri asynchrónnom overení split platby
Problém: Pri split platbe zákazník zaplatí časť sumy cez platobnú bránu (napr. Stripe) a zvyšok sa pripíše na faktúru. Stripe strhne peniaze z karty, no pri návrate na e-shop zlyhá pripojenie klienta (napríklad prechodom cez tunel na mobilnom telefóne) a webhook od Stripe nedorazí včas na náš backend. Objednávka zostane visieť v stave "neplatená", hoci peniaze boli z karty reálne odoslané a kreditný limit bol čiastočne zablokovaný.
Riešenie: Systém implementuje asynchrónne párovanie pomocou unikátneho idempotency_key priradeného ku každej relácii košíka.
Keď webhook od Stripe dorazí alebo keď sa spustí periodický cron job na kontrolu nedokončených platieb, systém uzamkne riadok v databáze (SELECT ... FOR UPDATE), overí stav transakcie priamo cez API platobnej brány, skontroluje zarezervovaný B2B kredit a asynchrónne dokončí objednávku. Sporné prípady môže poslať na manuálnu validáciu.
4. Porovnanie: vlastný headless (Medusa.js) vs. tradičný B2C monolit
Prechod na headless e-commerce a oddelenie frontendu od backendu zásadne ovplyvňuje technické ukazovatele aj celkovú stabilitu podnikania:
| Parameter | Tradičný monolit / šablóna | Vlastný headless (nolimeo stack) |
|---|---|---|
| Odozva B2B cien (TTFB) | Často pomalšia kvôli ťažkým SQL dopytom pri každom zobrazení | Rýchlejšia pri správne navrhnutom cachovaní, RSC a Redis vrstve |
| Kreditný limit a split platby | Prakticky nemožné bez nestabilných a drahých externých pluginov | Natívna súčasť biznis logiky s prísnou Zod validáciou |
| Integrácia tradičných lokálnych ERP | Synchrónna a krehká (výpadok ERP môže zhodiť nákupný tok) | Asynchrónna s retry mechanizmami cez Redis fronty |
| Bezpečnosť zmluvných cien | Filtrovanie na aplikačnej úrovni (vyššie riziko úniku citlivých dát) | Izolácia na úrovni PostgreSQL databázy pomocou RLS a aplikačných pravidiel |
| Duševné vlastníctvo (IP) | Uzamknutie u poskytovateľa (kód často nevlastníte) | Vlastníctvo zdrojového kódu podľa zmluvného nastavenia projektu |
Záver: Budujte suverénne riešenie pre váš B2B rast
Prechod stredne veľkej či enterprise firmy na headless B2B e-commerce nie je lacný ani jednoduchý krok. Vyžaduje precíznu analýzu existujúcich interných procesov a koordinovanú prácu senior vývojárov.
Je to však často najrozumnejší spôsob, ako dostať technologický dlh pod kontrolu, získať lepší dosah na najdôležitejší predajný kanál a poskytnúť partnerom používateľskú skúsenosť, ktorá zodpovedá realite B2B predaja.
V technologickom štúdiu nolimeo sa vyhýbame prázdnym sľubom. Klientom dodávame funkčné riešenia reálnych problémov. Pri spolupráci s nami komunikujete priamo s ľuďmi, ktorí systém navrhujú a vyvíjajú, bez zbytočnej agentúrnej réžie.
Máte pochybnosti o stabilite súčasného B2B riešenia alebo hľadáte technologického partnera na migráciu z monolitickej platformy? Napíšte nám a prejdeme si cenotvorbu, ERP integrácie aj bezpečný technický smer.
